main.cpp 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. #include <cstring>
  2. #include <cassert>
  3. #include <cstdio>
  4. #include <map>
  5. #include <X11/Xlib.h>
  6. #define XK_MISCELLANY
  7. #define XK_LATIN1
  8. #include <X11/keysymdef.h>
  9. extern "C" {
  10. #include <xdo.h>
  11. }
  12. struct KeyboardMapping {
  13. const char* keyseq;
  14. int window_index;
  15. static const int ALL_WINDOWS = -1;
  16. KeyboardMapping(const char* keyseq, int window_index)
  17. : keyseq(keyseq), window_index(window_index)
  18. {
  19. }
  20. };
  21. const std::map<KeySym, const KeyboardMapping> kbd_map = {
  22. // 0
  23. {XK_w, KeyboardMapping("Up", 0)},
  24. {XK_a, KeyboardMapping("Left", 0)},
  25. {XK_s, KeyboardMapping("Down", 0)},
  26. {XK_d, KeyboardMapping("Right", 0)},
  27. {XK_Control_L, KeyboardMapping("Control_L", 0)},
  28. {XK_v, KeyboardMapping("Delete", 0)},
  29. // 1
  30. {XK_Up, KeyboardMapping("Up", 1)},
  31. {XK_Left, KeyboardMapping("Left", 1)},
  32. {XK_Down, KeyboardMapping("Down", 1)},
  33. {XK_Right, KeyboardMapping("Right", 1)},
  34. {XK_Control_R, KeyboardMapping("Control_R", 1)},
  35. {XK_Delete, KeyboardMapping("Delete", 1)},
  36. // all
  37. {XK_space, KeyboardMapping("Control_R", KeyboardMapping::ALL_WINDOWS)},
  38. };
  39. const useconds_t KEY_DELAY = 12000; // microseconds
  40. void search_windows_by_name(const xdo_t* xdo, const char* name_regex, Window** windowlist_ret, unsigned int* nwindows_ret) {
  41. xdo_search_t search;
  42. memset(&search, 0, sizeof(xdo_search_t));
  43. search.max_depth = -1;
  44. search.require = xdo_search::SEARCH_ALL;
  45. search.searchmask |= SEARCH_NAME;
  46. search.winname = name_regex;
  47. xdo_search_windows(xdo, &search, windowlist_ret, nwindows_ret);
  48. }
  49. class ToontownKeyboardManager {
  50. Display* display;
  51. int screen;
  52. Window main_window;
  53. xdo_t* xdo;
  54. void search_toontown_windows(Window** windowlist_ret, unsigned int* nwindows_ret) {
  55. search_windows_by_name(xdo, "^Toontown", windowlist_ret, nwindows_ret);
  56. }
  57. void send_keyseq(const KeyboardMapping* mapping, int type) {
  58. Window* windows;
  59. unsigned int nwindows;
  60. search_toontown_windows(&windows, &nwindows);
  61. for(unsigned int i=0; i<nwindows; i++) {
  62. if(mapping->window_index >= nwindows
  63. || mapping->window_index == KeyboardMapping::ALL_WINDOWS
  64. || mapping->window_index == i) {
  65. // file:///usr/share/doc/libxdo-dev/html/xdo_8h.html
  66. // http://www.cl.cam.ac.uk/~mgk25/ucs/keysymdef.h
  67. if(type == KeyPress) {
  68. xdo_send_keysequence_window_down(xdo, windows[i], mapping->keyseq, KEY_DELAY);
  69. } else if(type == KeyRelease) {
  70. xdo_send_keysequence_window_up(xdo, windows[i], mapping->keyseq, KEY_DELAY);
  71. }
  72. }
  73. }
  74. if(type == KeyPress) {
  75. printf("> '%s' down\n", mapping->keyseq);
  76. } else if(type == KeyRelease) {
  77. printf("> '%s' up\n", mapping->keyseq);
  78. }
  79. }
  80. bool handle_key(XKeyEvent* key_event) {
  81. // https://tronche.com/gui/x/xlib/utilities/keyboard/XLookupKeysym.html
  82. KeySym key_symbol = XLookupKeysym(key_event, 0);
  83. if(key_event->state == ControlMask && key_symbol == XK_c) {
  84. return false; // Ctrl-c
  85. } else {
  86. auto kbd_map_it = kbd_map.find(key_symbol);
  87. if(kbd_map_it != kbd_map.end()) {
  88. send_keyseq(&kbd_map_it->second, key_event->type);
  89. } else if(key_event->type == KeyPress) {
  90. printf(
  91. "no mapping for '%s' (%#lx)\n",
  92. XKeysymToString(key_symbol),
  93. key_symbol
  94. );
  95. }
  96. return true;
  97. }
  98. }
  99. static Bool key_autorepeat_predicate(Display* display, XEvent* event, XPointer arg) {
  100. XKeyEvent* release_event = (XKeyEvent*)arg;
  101. return event->type == KeyPress
  102. && event->xkey.keycode == release_event->keycode
  103. && event->xkey.time == release_event->time;
  104. }
  105. bool key_event(XKeyEvent* key_event) {
  106. // https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html#XKeyEvent
  107. XEvent repeat_event;
  108. if(!XCheckIfEvent(display, &repeat_event, key_autorepeat_predicate, (XPointer)key_event)) {
  109. return handle_key(key_event);
  110. } else {
  111. // ignore auto release event
  112. // xcheckifevent() removed the associated auto press event
  113. return true;
  114. }
  115. }
  116. bool window_event(XEvent* event) {
  117. if(event->type == KeyPress || event->type == KeyRelease) {
  118. return key_event(&event->xkey);
  119. } else {
  120. printf("window event %d", event->type);
  121. return true;
  122. }
  123. }
  124. public:
  125. ToontownKeyboardManager() {
  126. display = XOpenDisplay(NULL);
  127. assert(display);
  128. screen = XDefaultScreen(display);
  129. xdo = xdo_new(NULL);
  130. assert(xdo);
  131. // https://tronche.com/gui/x/xlib/window/XCreateWindow.html
  132. main_window = XCreateSimpleWindow(
  133. display,
  134. RootWindow(display, screen), // parent
  135. 0, // x
  136. 0, // y
  137. 50, // width
  138. 50, // height
  139. 1, // border width
  140. 0, // border
  141. 0 // background
  142. );
  143. XSelectInput(display, main_window, KeyPressMask | KeyReleaseMask);
  144. XMapWindow(display, main_window);
  145. }
  146. void loop() {
  147. XEvent event;
  148. do {
  149. XNextEvent(display, &event);
  150. } while(window_event(&event));
  151. }
  152. ~ToontownKeyboardManager() {
  153. XCloseDisplay(display);
  154. }
  155. };
  156. int main() {
  157. // http://stackoverflow.com/questions/2100654/ignore-auto-repeat-in-x11-applications
  158. // XAutoRepeatOn(display);
  159. ToontownKeyboardManager app;
  160. app.loop();
  161. return 0;
  162. }