mirror of
https://github.com/SteamFramelyHomebrew/framely.git
synced 2026-10-19 13:01:32 +02:00
55 lines
3.6 KiB
C++
55 lines
3.6 KiB
C++
#pragma once
|
|
// Steam's already-configured evdev output, without SDL or a second uinput pad.
|
|
#include "gamepad_steam.h"
|
|
#include <filesystem>
|
|
#include <sys/stat.h>
|
|
#include <chrono>
|
|
#include <poll.h>
|
|
#include "gamepad_direct_policy.h"
|
|
inline int direct_slot(const input_id& id,const char* name,const std::vector<SteamPadInfo>& rows){
|
|
if(id.vendor!=0x28de||id.product!=0x11ff)return -1;
|
|
const char* at=std::strstr(name,"pad ");int slot=-1;char extra;
|
|
if(!at||std::sscanf(at+4,"%d%c",&slot,&extra)!=1||!allowed_steam_slot(rows,slot))return -1;
|
|
return slot;
|
|
}
|
|
inline int steam_direct(const char* infoPath,const char* directory){
|
|
if(!infoPath||!directory)return 1;
|
|
std::ifstream info(infoPath);auto rows=read_steam_pad_info(info);
|
|
std::string node;int slot=64;struct stat identity{};
|
|
std::error_code ec;
|
|
for(auto& entry:std::filesystem::directory_iterator("/dev/input",ec)){
|
|
const auto name=entry.path().filename().string();if(name.rfind("event",0)!=0)continue;
|
|
int fd=open(entry.path().c_str(),O_RDWR|O_NONBLOCK|O_CLOEXEC);if(fd<0)continue;
|
|
input_id id{};char label[128]{};int next=-1;struct stat st{};
|
|
if(ioctl(fd,EVIOCGID,&id)==0&&ioctl(fd,EVIOCGNAME(sizeof label),label)>0&&fstat(fd,&st)==0&&S_ISCHR(st.st_mode))next=direct_slot(id,label,rows);
|
|
close(fd);if(next>=0&&next<slot){node=entry.path();slot=next;identity=st;}
|
|
}
|
|
if(node.empty()){fprintf(stderr,"No writable Steam Input output is available; wake the controller and retry, or select the bridge.\n");return 1;}
|
|
const auto source=*std::find_if(rows.begin(),rows.end(),[&](auto p){return p.slot==slot;});
|
|
const auto control=std::string(directory)+"/control";
|
|
int ctl=open(control.c_str(),O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC,0644);if(ctl<0)return 1;
|
|
if(fchmod(ctl,0644)!=0){close(ctl);return 1;}
|
|
auto now=[](){return uint64_t(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count());};
|
|
bool sourceValid=true;unsigned missing=0;bool enabled=false,rumble=!getenv("FRAMELY_GAMEPAD_RUMBLE")||std::string(getenv("FRAMELY_GAMEPAD_RUMBLE"))!="0";
|
|
auto publish=[&](){FramelyDirectControl c{now()+2000000000ULL,uint32_t(enabled&&sourceValid),uint32_t(rumble)};return pwrite(ctl,&c,sizeof c,0)==sizeof c;};
|
|
if(!publish()){close(ctl);return 1;}
|
|
printf("%s\n",node.c_str());fflush(stdout);
|
|
std::string commands;const auto parent=getppid();uint64_t scanned=0;
|
|
while(!stopped&&getppid()==parent){
|
|
pollfd p{STDIN_FILENO,POLLIN,0};int result=poll(&p,1,50);if(result<0&&errno!=EINTR)break;
|
|
if(result>0&&(p.revents&(POLLIN|POLLHUP|POLLERR))){char buffer[128];auto n=read(0,buffer,sizeof buffer);if(n<=0)break;commands.append(buffer,n);size_t at;
|
|
while((at=commands.find('\n'))!=std::string::npos){auto cmd=commands.substr(0,at);commands.erase(0,at+1);if(cmd=="enable")enabled=true;else if(cmd=="disable")enabled=false;else if(cmd=="rumble-on")rumble=true;else if(cmd=="rumble-off")rumble=false;}
|
|
if(commands.size()>128)break;
|
|
}
|
|
if(now()-scanned>=500000000ULL){scanned=now();struct stat current{};std::ifstream file(infoPath);auto latest=read_steam_pad_info(file);
|
|
if(stat(node.c_str(),¤t)!=0||current.st_ino!=identity.st_ino||current.st_rdev!=identity.st_rdev){
|
|
fprintf(stderr,"Steam Input output changed/disconnected; restart this container to reconnect.\n");break;
|
|
}
|
|
sourceValid=std::any_of(latest.begin(),latest.end(),[&](auto p){return p.slot==source.slot&&p.identified&&p.vendor==source.vendor&&p.product==source.product;});
|
|
if(sourceValid)missing=0;else if(++missing>=6){fprintf(stderr,"Steam Input controller ownership changed; restart this container to reconnect.\n");break;}
|
|
}
|
|
if(!publish())break;
|
|
}
|
|
enabled=false;rumble=false;publish();close(ctl);return 0;
|
|
}
|