#pragma once #include "lan_input.h" #include namespace ks::lan { // The physical input source routes directly to the current destination. A // destination only reports edge crossings; it never relays injected key events. class InputMesh { std::string local,active; InputEnvironment env; std::map positions; std::map ready; std::function send; uint64_t activeGeneration{},routeRevision{}; InputRouter router; std::string neighbor(const std::string &from,int side)const { POINT p{};if(from!=local){auto found=positions.find(from);if(found==positions.end())return {};p=found->second;} if(side==0)--p.x;else if(side==1)++p.x;else if(side==2)--p.y;else if(side==3)++p.y;else return {}; if(!p.x&&!p.y)return local; for(auto &[id,cell]:positions)if(cell.x==p.x&&cell.y==p.y)return id; return {}; } void select(const std::string &peer,int side,uint64_t now) { if(active!=peer){active=peer;++routeRevision;} activeGeneration=ready.at(peer);router.selectEdge(side,local layout, std::function sender) :local(std::move(id)),env(std::move(environment)),positions(std::move(layout)),send(std::move(sender)), router(env,[this](InputFrame f){return !active.empty()&&ready.contains(active)&&send(active,f);},1,true,true){} ControlMode mode()const{return router.mode();} uint64_t revision()const{return router.revision()+routeRevision;} const std::string &peer()const{return active;} void cancel(uint64_t now){router.cancel(now);} void tick(std::map connections,uint64_t now) { ready=std::move(connections); if(!active.empty()){ bool connected=ready.contains(active); if(connected&&activeGeneration!=ready.at(active)){router.cancel(now,"peer reconnected");activeGeneration=ready.at(active);} router.tick(connected,now); } } void receive(const std::string &peer,const InputFrame &f,uint64_t now,bool allowEnter=true) { if(!ready.contains(peer)||!positions.contains(peer))return; if(f.type==InputType::Enter){ if(!allowEnter||f.a<0||f.a>3||(router.mode()!=ControlMode::Local&&active!=peer)){ send(peer,{InputType::Return,f.token,32768});return; } if(router.mode()==ControlMode::Local)select(peer,f.a^1,now); } if(active!=peer)return; if(f.type==InputType::Cross){ if(f.token!=router.sessionToken()||!router.canHandoff()||f.a<0||f.a>3||f.b<0||f.b>65535)return; auto target=neighbor(peer,f.a); if(target.empty()||(target!=local&&!ready.contains(target)))return; if(!send(peer,{InputType::Return,f.token,f.b})){router.cancel(now,"cannot release previous computer");return;} if(target==local){router.returnAt(f.a^1,f.b,now);++routeRevision;return;} active=target;activeGeneration=ready.at(target);++routeRevision; router.handoff(f.a,f.b,now);return; } router.receive(f,now); } bool keyboard(const KBDLLHOOKSTRUCT &key,bool down,uint64_t now){return router.keyboard(key,down,now);} bool mouse(UINT message,const MSLLHOOKSTRUCT &mouse,uint64_t now,bool allowEnter=true) { bool enter=false; if(router.mode()==ControlMode::Local&&allowEnter&&message==WM_MOUSEMOVE&&!(mouse.flags&LLMHF_INJECTED)){ for(int side=0;side<4;++side)if(atEdge(mouse.pt,env.bounds(),side)){ auto target=neighbor(local,side); if(!target.empty()&&ready.contains(target)){select(target,side,now);enter=true;break;} } } return router.mouse(message,mouse,now,enter); } }; }