Created
June 13, 2017 19:39
-
-
Save DataBeaver/37e3b457b849bb5c01e6bc49d27c97b9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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