Skip to content

Instantly share code, notes, and snippets.

@DataBeaver
Created June 13, 2017 19:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DataBeaver/37e3b457b849bb5c01e6bc49d27c97b9 to your computer and use it in GitHub Desktop.
Save DataBeaver/37e3b457b849bb5c01e6bc49d27c97b9 to your computer and use it in GitHub Desktop.
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/poll.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <set>
#include <vector>
#include <msp/core/application.h>
#include <msp/core/getopt.h>
#include <msp/fs/dir.h>
#include <msp/io/print.h>
#include <msp/strings/utils.h>
using namespace std;
using namespace Msp;
class JsMapper: public RegisteredApplication<JsMapper>
{
private:
struct InputKey
{
int type;
int code;
InputKey();
InputKey(int, int);
bool operator<(const InputKey &) const;
};
struct MappedInput
{
InputKey target;
int sign;
int value;
MappedInput *next;
struct input_absinfo abs_info;
MappedInput() { }
MappedInput(const InputKey &, int = 1);
};
bool list_devices_flag;
string dev_fn;
int dev_fd;
struct input_id dev_id;
string dev_name;
int uinput_fd;
map<InputKey, MappedInput> input_map;
public:
JsMapper(int, char **);
virtual int main();
private:
void list_devices();
void open_input();
void create_device();
virtual void tick();
void parse_map(const string &, int);
static InputKey get_input(const string &, int);
static int get_button(const string &);
static int get_axis(const string &);
virtual void sighandler(int);
};
JsMapper::JsMapper(int argc, char **argv):
list_devices_flag(false),
dev_fd(-1),
uinput_fd(-1)
{
bool combine_triggers = false;
bool triggers_as_buttons = false;
string map_buttons;
string map_axes;
GetOpt getopt;
getopt.add_option('l', "list", list_devices_flag, GetOpt::NO_ARG);
getopt.add_option('b', "map-buttons", map_buttons, GetOpt::REQUIRED_ARG);
getopt.add_option('a', "map-axes", map_axes, GetOpt::REQUIRED_ARG);
getopt.add_option('c', "combine-triggers", combine_triggers, GetOpt::NO_ARG);
getopt.add_option('t', "triggers-as-buttons", triggers_as_buttons, GetOpt::NO_ARG);
getopt.add_argument("device", dev_fn, GetOpt::OPTIONAL_ARG);
getopt(argc, argv);
if(combine_triggers)
{
input_map[InputKey(EV_ABS, ABS_Z)] = MappedInput(InputKey(EV_ABS, ABS_Z), 1);
input_map[InputKey(EV_ABS, ABS_RZ)] = MappedInput(InputKey(EV_ABS, ABS_Z), -1);
}
if(triggers_as_buttons)
{
input_map[InputKey(EV_ABS, ABS_Z)] = InputKey(EV_KEY, BTN_TL2);
input_map[InputKey(EV_ABS, ABS_RZ)] = InputKey(EV_KEY, BTN_TR2);
}
parse_map(map_buttons, EV_KEY);
parse_map(map_axes, EV_ABS);
if(!list_devices_flag && dev_fn.empty())
throw usage_error("No device given", getopt.generate_usage(argv[0]));
}
int JsMapper::main()
{
if(list_devices_flag)
{
list_devices();
return 0;
}
open_input();
create_device();
catch_signal(SIGINT);
catch_signal(SIGTERM);
Application::main();
ioctl(uinput_fd, UI_DEV_DESTROY);
return exit_code;
}
void JsMapper::list_devices()
{
FS::Path devdir("/dev/input");
list<string> names = FS::list_filtered(devdir, "^event");
for(const auto &n: names)
{
FS::Path fn = devdir/n;
int fd = open(fn.c_str(), O_RDONLY);
if(fd<0)
{
IO::print("Couldn't open %s\n", fn.str());
continue;
}
char namebuf[256];
ioctl(fd, EVIOCGNAME(sizeof(namebuf)), namebuf);
IO::print("%s: %s\n", n, namebuf);
close(fd);
}
}
void JsMapper::open_input()
{
dev_fd = open((FS::Path("/dev/input")/dev_fn).c_str(), O_RDONLY);
char namebuf[256];
int len = ioctl(dev_fd, EVIOCGNAME(sizeof(namebuf)), namebuf);
dev_name.assign(namebuf, len);
ioctl(dev_fd, EVIOCGID, &dev_id);
IO::print("Using %04X:%04X %s\n", dev_id.vendor, dev_id.product, dev_name);
UInt32 key_bits[(KEY_CNT+31)/32];
ioctl(dev_fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), key_bits);
for(unsigned i=0; i<KEY_CNT; ++i)
if((key_bits[i/32]>>(i%32))&1)
{
InputKey ik(EV_KEY, i);
if(!input_map.count(ik))
input_map[ik] = ik;
}
UInt32 abs_bits[(ABS_CNT+31)/32];
ioctl(dev_fd, EVIOCGBIT(EV_ABS, sizeof(abs_bits)), abs_bits);
for(unsigned i=0; i<ABS_CNT; ++i)
if((abs_bits[i/32]>>(i%32))&1)
{
InputKey ik(EV_ABS, i);
if(!input_map.count(ik))
input_map[ik] = ik;
ioctl(dev_fd, EVIOCGABS(i), &input_map[ik].abs_info);
}
map<InputKey, MappedInput *> prev_inputs;
for(auto &i: input_map)
{
MappedInput &mapped = i.second;
MappedInput *&prev = prev_inputs[mapped.target];
if(prev)
{
mapped.next = (prev->next ? prev->next : prev);
prev->next = &mapped;
}
prev = &mapped;
}
}
void JsMapper::create_device()
{
uinput_fd = open("/dev/uinput", O_RDWR);
struct uinput_setup setup;
setup.id = dev_id;
snprintf(setup.name, UINPUT_MAX_NAME_SIZE, "JsMapper: %s", dev_name.c_str());
setup.ff_effects_max = 0;
ioctl(uinput_fd, UI_DEV_SETUP, &setup);
ioctl(uinput_fd, UI_SET_EVBIT, EV_KEY);
ioctl(uinput_fd, UI_SET_EVBIT, EV_ABS);
set<InputKey> seen;
for(const auto &i: input_map)
{
const MappedInput &mapped = i.second;
if(seen.count(mapped.target))
continue;
seen.insert(mapped.target);
if(mapped.target.type==EV_KEY)
ioctl(uinput_fd, UI_SET_KEYBIT, mapped.target.code);
else if(mapped.target.type==EV_ABS)
{
ioctl(uinput_fd, UI_SET_ABSBIT, mapped.target.code);
struct uinput_abs_setup abs_setup;
abs_setup.code = mapped.target.code;
abs_setup.absinfo = mapped.abs_info;
abs_setup.absinfo.minimum = 0;
abs_setup.absinfo.maximum = 0;
for(const MappedInput *ptr=&mapped; ptr; )
{
if(ptr->sign>0)
{
abs_setup.absinfo.minimum += ptr->abs_info.minimum;
abs_setup.absinfo.maximum += ptr->abs_info.maximum;
}
else if(ptr->sign<0)
{
abs_setup.absinfo.minimum -= ptr->abs_info.maximum;
abs_setup.absinfo.maximum -= ptr->abs_info.minimum;
}
ptr = ptr->next;
if(ptr==&mapped)
break;
}
ioctl(uinput_fd, UI_ABS_SETUP, &abs_setup);
}
}
ioctl(uinput_fd, UI_DEV_CREATE);
}
void JsMapper::tick()
{
struct pollfd pfd = { dev_fd, POLLIN, 0 };
int result = poll(&pfd, 1, -1);
if(result<=0)
return;
struct input_event event;
result = read(dev_fd, reinterpret_cast<char *>(&event), sizeof(event));
if(result<static_cast<int>(sizeof(event)))
return;
auto i = input_map.find(InputKey(event.type, event.code));
if(i==input_map.end())
return;
i->second.value = event.value;
event.type = i->second.target.type;
event.code = i->second.target.code;
int value = 0;
for(MappedInput *ptr=&i->second; ptr; )
{
value += ptr->value*ptr->sign;
ptr = ptr->next;
if(ptr==&i->second)
break;
}
if(i->first.type==EV_ABS && event.type==EV_KEY)
event.value = (value>i->second.abs_info.maximum/2);
else
event.value = value;
write(uinput_fd, reinterpret_cast<char *>(&event), sizeof(event));
event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(uinput_fd, reinterpret_cast<char *>(&event), sizeof(event));
}
void JsMapper::parse_map(const string &str, int default_type)
{
vector<string> parts = split(str, ',');
for(const auto &m: parts)
{
string::size_type equals = m.find('=');
if(equals==string::npos)
continue;
InputKey left = get_input(m.substr(0, equals), default_type);
InputKey right = get_input(m.substr(equals+1), default_type);
input_map[left] = right;
}
}
JsMapper::InputKey JsMapper::get_input(const string &descr, int default_type)
{
string::size_type colon = descr.find(':');
int type = default_type;
string name;
if(colon!=string::npos)
{
string type_str = descr.substr(0, colon);
if(type_str=="button")
type = EV_KEY;
else if(type_str=="axis")
type = EV_ABS;
else
throw invalid_argument("get_input");
name = descr.substr(colon+1);
}
else
name = descr;
if(type<0)
throw invalid_argument("get_input");
if(type==EV_KEY)
return InputKey(EV_KEY, get_button(name));
else if(type==EV_ABS)
return InputKey(EV_ABS, get_axis(name));
else
throw invalid_argument("get_input");
}
int JsMapper::get_button(const string &name)
{
if(name=="A")
return BTN_A;
else if(name=="B")
return BTN_B;
else if(name=="X")
return BTN_X;
else if(name=="Y")
return BTN_Y;
else if(name=="TL")
return BTN_TL;
else if(name=="TR")
return BTN_TR;
else if(name=="TL2")
return BTN_TL2;
else if(name=="TR2")
return BTN_TR2;
else if(name=="SELECT")
return BTN_SELECT;
else if(name=="START")
return BTN_START;
else if(name=="MODE")
return BTN_MODE;
else if(name=="THUMBL")
return BTN_THUMBL;
else if(name=="THUMBR")
return BTN_THUMBR;
else if(isnumrc(name))
return lexical_cast<int>(name);
else
throw invalid_argument("get_button");
}
int JsMapper::get_axis(const string &name)
{
if(name=="X")
return ABS_X;
else if(name=="Y")
return ABS_Y;
else if(name=="Z")
return ABS_Z;
else if(name=="RX")
return ABS_RX;
else if(name=="RY")
return ABS_RY;
else if(name=="RZ")
return ABS_RZ;
else if(name=="THROTTLE")
return ABS_THROTTLE;
else if(name=="RUDDER")
return ABS_RUDDER;
else if(isnumrc(name))
return lexical_cast<int>(name);
else
throw invalid_argument("get_axis");
}
void JsMapper::sighandler(int)
{
exit(0);
}
JsMapper::InputKey::InputKey():
type(-1),
code(-1)
{ }
JsMapper::InputKey::InputKey(int t, int c):
type(t),
code(c)
{ }
bool JsMapper::InputKey::operator<(const InputKey &other) const
{
if(type!=other.type)
return type<other.type;
return code<other.code;
}
JsMapper::MappedInput::MappedInput(const InputKey &t, int s):
target(t),
sign(s),
value(0),
next(0)
{ }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment