#include #include #include #include #include #define XK_MISCELLANY #define XK_LATIN1 #include extern "C" { #include } struct KeyboardMapping { const char* keyseq; int window_index; static const int ALL_WINDOWS = -1; KeyboardMapping(const char* keyseq, int window_index) : keyseq(keyseq), window_index(window_index) { } }; const std::map kbd_map = { // 0 {XK_w, KeyboardMapping("Up", 0)}, {XK_a, KeyboardMapping("Left", 0)}, {XK_s, KeyboardMapping("Down", 0)}, {XK_d, KeyboardMapping("Right", 0)}, {XK_Control_L, KeyboardMapping("Control_L", 0)}, {XK_v, KeyboardMapping("Delete", 0)}, // 1 {XK_Up, KeyboardMapping("Up", 1)}, {XK_Left, KeyboardMapping("Left", 1)}, {XK_Down, KeyboardMapping("Down", 1)}, {XK_Right, KeyboardMapping("Right", 1)}, {XK_Control_R, KeyboardMapping("Control_R", 1)}, {XK_Delete, KeyboardMapping("Delete", 1)}, // 2 {XK_i, KeyboardMapping("Up", 2)}, {XK_j, KeyboardMapping("Left", 2)}, {XK_k, KeyboardMapping("Down", 2)}, {XK_l, KeyboardMapping("Right", 2)}, {XK_n, KeyboardMapping("Control_L", 2)}, {XK_semicolon, KeyboardMapping("Delete", 2)}, // all {XK_space, KeyboardMapping("Control_R", KeyboardMapping::ALL_WINDOWS)}, }; const useconds_t KEY_DELAY = 12000; // microseconds void search_windows_by_name(const xdo_t* xdo, const char* name_regex, Window** windowlist_ret, unsigned int* nwindows_ret) { xdo_search_t search; memset(&search, 0, sizeof(xdo_search_t)); search.max_depth = -1; search.require = xdo_search::SEARCH_ALL; search.searchmask |= SEARCH_NAME; search.winname = name_regex; xdo_search_windows(xdo, &search, windowlist_ret, nwindows_ret); } class ToontownKeyboardManager { Display* display; int screen; Window main_window; xdo_t* xdo; void search_toontown_windows(Window** windowlist_ret, unsigned int* nwindows_ret) { search_windows_by_name(xdo, "^Toontown", windowlist_ret, nwindows_ret); } void send_keyseq(const KeyboardMapping* mapping, int type) { Window* windows; unsigned int nwindows; search_toontown_windows(&windows, &nwindows); for(unsigned int i=0; iwindow_index >= nwindows || mapping->window_index == KeyboardMapping::ALL_WINDOWS || mapping->window_index == i) { // file:///usr/share/doc/libxdo-dev/html/xdo_8h.html // http://www.cl.cam.ac.uk/~mgk25/ucs/keysymdef.h if(type == KeyPress) { xdo_send_keysequence_window_down(xdo, windows[i], mapping->keyseq, KEY_DELAY); } else if(type == KeyRelease) { xdo_send_keysequence_window_up(xdo, windows[i], mapping->keyseq, KEY_DELAY); } } } if(type == KeyPress) { printf("> '%s' down\n", mapping->keyseq); } else if(type == KeyRelease) { printf("> '%s' up\n", mapping->keyseq); } } bool handle_key(XKeyEvent* key_event) { // https://tronche.com/gui/x/xlib/utilities/keyboard/XLookupKeysym.html KeySym key_symbol = XLookupKeysym(key_event, 0); if(key_event->state == ControlMask && key_symbol == XK_c) { return false; // Ctrl-c } else { auto kbd_map_it = kbd_map.find(key_symbol); if(kbd_map_it != kbd_map.end()) { send_keyseq(&kbd_map_it->second, key_event->type); } else if(key_event->type == KeyPress) { printf( "no mapping for '%s' (%#lx)\n", XKeysymToString(key_symbol), key_symbol ); } return true; } } static Bool key_autorepeat_predicate(Display* display, XEvent* event, XPointer arg) { XKeyEvent* release_event = (XKeyEvent*)arg; return event->type == KeyPress && event->xkey.keycode == release_event->keycode && event->xkey.time == release_event->time; } bool key_event(XKeyEvent* key_event) { // https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html#XKeyEvent XEvent repeat_event; if(!XCheckIfEvent(display, &repeat_event, key_autorepeat_predicate, (XPointer)key_event)) { return handle_key(key_event); } else { // ignore auto release event // xcheckifevent() removed the associated auto press event return true; } } bool window_event(XEvent* event) { if(event->type == KeyPress || event->type == KeyRelease) { return key_event(&event->xkey); } else { printf("window event %d", event->type); return true; } } public: ToontownKeyboardManager() { display = XOpenDisplay(NULL); assert(display); screen = XDefaultScreen(display); xdo = xdo_new(NULL); assert(xdo); // https://tronche.com/gui/x/xlib/window/XCreateWindow.html main_window = XCreateSimpleWindow( display, RootWindow(display, screen), // parent 0, // x 0, // y 50, // width 50, // height 1, // border width 0, // border 0 // background ); XSelectInput(display, main_window, KeyPressMask | KeyReleaseMask); XMapWindow(display, main_window); } void loop() { XEvent event; do { XNextEvent(display, &event); } while(window_event(&event)); } ~ToontownKeyboardManager() { XCloseDisplay(display); } }; int main() { // http://stackoverflow.com/questions/2100654/ignore-auto-repeat-in-x11-applications // XAutoRepeatOn(display); ToontownKeyboardManager app; app.loop(); return 0; }