83 lines
3.9 KiB
C++
83 lines
3.9 KiB
C++
#pragma once
|
|
#include "lan_input.h"
|
|
#include <map>
|
|
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<std::string,POINT> positions;
|
|
std::map<std::string,uint64_t> ready;
|
|
std::function<bool(const std::string &,InputFrame)> 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<peer);router.tick(true,now);
|
|
}
|
|
public:
|
|
InputMesh(std::string id,InputEnvironment environment,std::map<std::string,POINT> layout,
|
|
std::function<bool(const std::string &,InputFrame)> 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<std::string,uint64_t> 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);
|
|
}
|
|
};
|
|
}
|