#include "lan_input.h" #include namespace ks::lan { ULONG_PTR remoteInputMarker(){return inputMarker() ^ ULONG_PTR(0x4c414e49);} ULONG_PTR cursorWarpMarker(){return inputMarker() ^ ULONG_PTR(0x4c414e57);} namespace { LRESULT CALLBACK controlOverlay(HWND w,UINT message,WPARAM wp,LPARAM lp) { if(message==WM_SETCURSOR){SetCursor(nullptr);return TRUE;} if(message==WM_MOUSEACTIVATE)return MA_NOACTIVATE; if(message==WM_PAINT) { PAINTSTRUCT paint{};HDC dc=BeginPaint(w,&paint);RECT r{};GetClientRect(w,&r); FillRect(dc,&r,(HBRUSH)(COLOR_INFOBK+1));SetBkMode(dc,TRANSPARENT);SetTextColor(dc,GetSysColor(COLOR_INFOTEXT)); auto previous=SelectObject(dc,GetStockObject(DEFAULT_GUI_FONT)); DrawTextW(dc,L"KSwitcher: mouse + keyboard on the other PC\nCtrl+Alt+Esc returns control here",-1,&r,DT_CENTER|DT_WORDBREAK); SelectObject(dc,previous);EndPaint(w,&paint);return 0; } return DefWindowProcW(w,message,wp,lp); } struct ControlOverlay { HWND cover{},notice{}; ~ControlOverlay(){hide();} void hide(){if(notice)DestroyWindow(notice);if(cover)DestroyWindow(cover);notice=cover=nullptr;} void show() { if(cover)return; WNDCLASSW cls{};cls.hInstance=GetModuleHandleW(nullptr);cls.lpfnWndProc=controlOverlay;cls.lpszClassName=L"KSwitcher.RemoteControlOverlay"; RegisterClassW(&cls); auto style=WS_EX_TOPMOST|WS_EX_TOOLWINDOW|WS_EX_NOACTIVATE; POINT cursor{};GetCursorPos(&cursor);MONITORINFO monitor{sizeof(monitor)};GetMonitorInfoW(MonitorFromPoint(cursor,MONITOR_DEFAULTTONEAREST),&monitor); // Only cover the parked cursor, not a full-screen layered bitmap. int x=(monitor.rcMonitor.left+monitor.rcMonitor.right)/2-16,y=(monitor.rcMonitor.top+monitor.rcMonitor.bottom)/2-16; cover=CreateWindowExW(style|WS_EX_LAYERED,cls.lpszClassName,L"",WS_POPUP,x,y,32,32,nullptr,nullptr,cls.hInstance,nullptr); if(cover){SetLayeredWindowAttributes(cover,0,1,LWA_ALPHA);ShowWindow(cover,SW_SHOWNOACTIVATE);} notice=CreateWindowExW(style,cls.lpszClassName,L"KSwitcher remote control",WS_POPUP,monitor.rcMonitor.left+20,monitor.rcMonitor.top+20,400,48,nullptr,nullptr,cls.hInstance,nullptr); if(notice)ShowWindow(notice,SW_SHOWNOACTIVATE); SetCursor(nullptr); } }; bool ordinaryDesktop() { HDESK d=OpenInputDesktop(0,FALSE,DESKTOP_READOBJECTS);if(!d)return false; wchar_t name[256]{};DWORD count{};bool ok=GetUserObjectInformationW(d,UOI_NAME,name,sizeof(name),&count)!=0; CloseDesktop(d);return ok&&_wcsicmp(name,L"Default")==0; } INPUT button(unsigned index,bool down) { INPUT input{};input.type=INPUT_MOUSE; static constexpr DWORD flags[5][2]={{MOUSEEVENTF_LEFTUP,MOUSEEVENTF_LEFTDOWN},{MOUSEEVENTF_RIGHTUP,MOUSEEVENTF_RIGHTDOWN}, {MOUSEEVENTF_MIDDLEUP,MOUSEEVENTF_MIDDLEDOWN},{MOUSEEVENTF_XUP,MOUSEEVENTF_XDOWN},{MOUSEEVENTF_XUP,MOUSEEVENTF_XDOWN}}; input.mi.dwFlags=flags[index][down];if(index>=3)input.mi.mouseData=index==3?XBUTTON1:XBUTTON2;return input; } } InputEnvironment windowsInputEnvironment() { auto overlay=std::make_shared(); return { []{LONG x=GetSystemMetrics(SM_XVIRTUALSCREEN),y=GetSystemMetrics(SM_YVIRTUALSCREEN);return RECT{x,y,x+GetSystemMetrics(SM_CXVIRTUALSCREEN),y+GetSystemMetrics(SM_CYVIRTUALSCREEN)};}, []{POINT p{};GetCursorPos(&p);return p;}, [](const INPUT &i){INPUT input=i;return SendInput(1,&input,sizeof(input))==1;}, ordinaryDesktop, []{for(int key=1;key<256;++key)if(GetAsyncKeyState(key)&0x8000)return true;return false;}, []{POINT p{};GetCursorPos(&p);MONITORINFO info{sizeof(info)}; if(GetMonitorInfoW(MonitorFromPoint(p,MONITOR_DEFAULTTONEAREST),&info))p={(info.rcMonitor.left+info.rcMonitor.right)/2,(info.rcMonitor.top+info.rcMonitor.bottom)/2}; return p;}, [overlay](ControlMode mode){if(mode==ControlMode::Sending)overlay->show();else overlay->hide();} }; } bool atEdge(POINT p,RECT r,int side) {return side==0?p.x<=r.left:side==1?p.x>=r.right-1:side==2?p.y<=r.top:p.y>=r.bottom-1;} int edgePosition(POINT p,RECT r,int side) { auto length=side<2?r.bottom-r.top:r.right-r.left; auto offset=side<2?p.y-r.top:p.x-r.left; return int(std::clamp(int64_t(offset)*65535/std::max(1,length-1),0,65535)); } POINT edgePoint(RECT r,int side,int position,int inset) { position=std::clamp(position,0,65535); LONG x=r.left+LONG(int64_t(position)*std::max(0,r.right-r.left-1)/65535); LONG y=r.top+LONG(int64_t(position)*std::max(0,r.bottom-r.top-1)/65535); POINT p=side==0?POINT{r.left+inset,y}:side==1?POINT{r.right-1-inset,y}:side==2?POINT{x,r.top+inset}:POINT{x,r.bottom-1-inset}; p.x=std::clamp(p.x,r.left,r.right-1);p.y=std::clamp(p.y,r.top,r.bottom-1);return p; } InputRouter::InputRouter(InputEnvironment e,std::function s,int side,bool prefer) :env(std::move(e)),send(std::move(s)),edge(side),preferLocal(prefer),nextToken(prefer?1:2) {if(side<0||side>3)throw std::runtime_error("Invalid screen position.");} InputRouter::~InputRouter(){cancel(GetTickCount64());release();} void InputRouter::mode(ControlMode value){if(mode_!=value){mode_=value;++revision_;if(env.modeChanged)env.modeChanged(value);}} bool InputRouter::emit(INPUT i) { if(i.type==INPUT_KEYBOARD)i.ki.dwExtraInfo=remoteInputMarker(); else i.mi.dwExtraInfo=remoteInputMarker(); return env.desktop()&&env.inject(i); } bool InputRouter::move(POINT p,bool warp) { auto r=env.bounds();if(r.right<=r.left||r.bottom<=r.top)return false; INPUT input{};input.type=INPUT_MOUSE;input.mi.dwFlags=MOUSEEVENTF_MOVE|MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_VIRTUALDESK; // Target pixel centres, avoiding a one-pixel bias on every relative mouse event. input.mi.dx=LONG((2*int64_t(std::clamp(p.x,r.left,r.right-1)-r.left)+1)*32768/(r.right-r.left)); input.mi.dy=LONG((2*int64_t(std::clamp(p.y,r.top,r.bottom-1)-r.top)+1)*32768/(r.bottom-r.top)); input.mi.dwExtraInfo=warp?cursorWarpMarker():remoteInputMarker();return env.desktop()&&env.inject(input); } bool InputRouter::post(InputFrame f){return connected&&send(f);} bool InputRouter::held()const { return std::any_of(physical.begin(),physical.end(),[](bool b){return b;})||env.held(); } void InputRouter::release() { for(size_t i=0;i1000))cancel(now); if(mode_==ControlMode::Local)release(); // Retry releases blocked by a temporarily secure desktop. } void InputRouter::receive(const InputFrame &f,uint64_t now) { if(!connected||!env.desktop()){cancel(now);return;} if(f.type==InputType::Enter) { if(!f.token||f.a!=(edge^1)||f.b<0||f.b>65535||held()|| mode_==ControlMode::Sending||mode_==ControlMode::Receiving||(mode_==ControlMode::Requesting&&preferLocal)) {post({InputType::Return,f.token,32768});return;} if(mode_==ControlMode::Requesting)cancel(now); token=f.token;mode(ControlMode::Receiving); if(!move(edgePoint(env.bounds(),edge,f.b))||!post({InputType::Accept,token}))cancel(now); return; } if(f.type==InputType::Accept) { if(mode_!=ControlMode::Requesting||f.token!=token||held()||!atEdge(env.cursor(),env.bounds(),edge)){post({InputType::Return,f.token,32768});if(f.token==token)cancel(now);return;} auto r=env.bounds();anchor=env.anchor?env.anchor():POINT{(r.left+r.right)/2,(r.top+r.bottom)/2};mode(ControlMode::Sending); if(!move(anchor,true))cancel(now); return; } if(f.token!=token||!token)return; if(f.type==InputType::Return){local(now,std::clamp(f.a,0,65535));return;} if(mode_!=ControlMode::Receiving)return; bool ok=true; if(f.type==InputType::Move) { if(std::abs(int64_t(f.a))>32768||std::abs(int64_t(f.b))>32768){cancel(now);return;} auto p=env.cursor();auto r=env.bounds();p.x=LONG(std::clamp(int64_t(p.x)+f.a,r.left,r.right-1));p.y=LONG(std::clamp(int64_t(p.y)+f.b,r.top,r.bottom-1)); if(atEdge(p,r,edge)&&!std::any_of(remoteKeys.begin(),remoteKeys.end(),[](bool b){return b;})&& !std::any_of(remoteButtons.begin(),remoteButtons.end(),[](bool b){return b;})) {post({InputType::Return,token,edgePosition(p,r,edge)});local(now);return;} ok=move(p); } else if(f.type==InputType::Key) { if(f.a<1||f.a>255||f.b<0||f.b>255||f.c<0||f.c>3){cancel(now);return;} INPUT input{};input.type=INPUT_KEYBOARD;input.ki.wVk=WORD(f.a);input.ki.wScan=WORD(f.b); input.ki.dwFlags=(f.c&1?0:KEYEVENTF_KEYUP)|(f.c&2?KEYEVENTF_EXTENDEDKEY:0); if(f.b&&f.a!=VK_PAUSE)input.ki.dwFlags|=KEYEVENTF_SCANCODE; ok=emit(input);if(ok){remoteKeys[size_t(f.a)]=(f.c&1)!=0;input.ki.dwFlags|=KEYEVENTF_KEYUP;releases[size_t(f.a)]=input;} } else if(f.type==InputType::Button) { if(f.a<0||f.a>4||f.b<0||f.b>1){cancel(now);return;} ok=emit(button(unsigned(f.a),f.b!=0));if(ok)remoteButtons[size_t(f.a)]=f.b!=0; } else if(f.type==InputType::Wheel) { if(f.a<-32768||f.a>32767||f.b<0||f.b>1){cancel(now);return;} INPUT input{};input.type=INPUT_MOUSE;input.mi.dwFlags=f.b?MOUSEEVENTF_HWHEEL:MOUSEEVENTF_WHEEL;input.mi.mouseData=DWORD(f.a);ok=emit(input); } if(!ok)cancel(now); } bool InputRouter::keyboard(const KBDLLHOOKSTRUCT &k,bool down,uint64_t now) { if(k.flags&LLKHF_INJECTED||k.vkCode>255||!k.vkCode)return false; auto key=size_t(k.vkCode);physical[key]=down; if(mode_!=ControlMode::Local&&down&&key==VK_ESCAPE&&(physical[VK_LCONTROL]||physical[VK_RCONTROL]||physical[VK_CONTROL])&& (physical[VK_LMENU]||physical[VK_RMENU]||physical[VK_MENU])) {outKeys[key]=true;cancel(now);return true;} if(mode_==ControlMode::Receiving||mode_==ControlMode::Requesting)cancel(now); if(mode_==ControlMode::Sending) { outKeys[key]=down; if(!post({InputType::Key,token,int32_t(k.vkCode),int32_t(k.scanCode),int32_t((down?1:0)|((k.flags&LLKHF_EXTENDED)?2:0))}))cancel(now); return true; } if(outKeys[key]){if(!down)outKeys[key]=false;return true;} return false; } bool InputRouter::mouse(UINT message,const MSLLHOOKSTRUCT &m,uint64_t now,bool allowEnter) { if(m.flags&LLMHF_INJECTED)return false; int index=-1;bool down=false; switch(message){case WM_LBUTTONDOWN:down=true;[[fallthrough]];case WM_LBUTTONUP:index=0;break; case WM_RBUTTONDOWN:down=true;[[fallthrough]];case WM_RBUTTONUP:index=1;break; case WM_MBUTTONDOWN:down=true;[[fallthrough]];case WM_MBUTTONUP:index=2;break; case WM_XBUTTONDOWN:down=true;[[fallthrough]];case WM_XBUTTONUP:index=HIWORD(m.mouseData)==XBUTTON1?3:4;break;} if(mode_==ControlMode::Receiving||(mode_==ControlMode::Requesting&&message!=WM_MOUSEMOVE))cancel(now); if(mode_==ControlMode::Requesting&&message==WM_MOUSEMOVE&&!atEdge(m.pt,env.bounds(),edge))cancel(now); if(mode_==ControlMode::Sending) { bool ok=true; if(message==WM_MOUSEMOVE) { auto dx=m.pt.x-anchor.x,dy=m.pt.y-anchor.y; if(dx||dy)ok=post({InputType::Move,token,dx,dy}); ok=move(anchor,true)&&ok; } else if(index>=0){outButtons[size_t(index)]=down;ok=post({InputType::Button,token,index,down?1:0});} else if(message==WM_MOUSEWHEEL||message==WM_MOUSEHWHEEL)ok=post({InputType::Wheel,token,int16_t(HIWORD(m.mouseData)),message==WM_MOUSEHWHEEL?1:0}); if(!ok)cancel(now); return true; } if(index>=0&&outButtons[size_t(index)]){if(!down)outButtons[size_t(index)]=false;return true;} if(mode_==ControlMode::Local&&connected&&allowEnter&&message==WM_MOUSEMOVE&&now>=cooldown&& env.desktop()&&!held()&&atEdge(m.pt,env.bounds(),edge)) { token=nextToken;nextToken+=2;if(!nextToken)nextToken=preferLocal?1:2; returnPoint=m.pt;requested=now;mode(ControlMode::Requesting); if(!post({InputType::Enter,token,edge,edgePosition(m.pt,env.bounds(),edge)}))cancel(now); } return false; } }