Add encrypted LAN mouse and keyboard sharing with four-edge handoff
This commit is contained in:
1 parent
9b565dd55e
commit
e0bfdd9a90
15 files changed
+1093
-104
No files matched your search
@@ -0,0 +1,242 @@
|
||||
#include "lan_input.h"
|
||||
#include <algorithm>
|
||||
|
||||
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<ControlOverlay>();
|
||||
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>(int64_t(offset)*65535/std::max<LONG>(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<LONG>(0,r.right-r.left-1)/65535);
|
||||
LONG y=r.top+LONG(int64_t(position)*std::max<LONG>(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<bool(InputFrame)> 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;i<remoteKeys.size();++i)if(remoteKeys[i]&&emit(releases[i]))remoteKeys[i]=false;
|
||||
for(unsigned i=0;i<5;++i)if(remoteButtons[i]&&emit(button(i,false)))remoteButtons[i]=false;
|
||||
}
|
||||
void InputRouter::local(uint64_t now,int position)
|
||||
{
|
||||
bool sending=mode_==ControlMode::Sending;
|
||||
if(position<0)position=edgePosition(returnPoint,env.bounds(),edge);
|
||||
release();mode(ControlMode::Local);cooldown=now+600;token=0;
|
||||
if(sending)move(edgePoint(env.bounds(),edge,position),true);
|
||||
}
|
||||
void InputRouter::cancel(uint64_t now)
|
||||
{
|
||||
if(mode_!=ControlMode::Local)post({InputType::Return,token,edgePosition(env.cursor(),env.bounds(),edge)});
|
||||
local(now);
|
||||
}
|
||||
void InputRouter::tick(bool ready,uint64_t now)
|
||||
{
|
||||
connected=ready;
|
||||
if(!ready||!env.desktop()||(mode_==ControlMode::Requesting&&now-requested>1000))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>(int64_t(p.x)+f.a,r.left,r.right-1));p.y=LONG(std::clamp<int64_t>(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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user