TDE base libraries and programs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

518 lines
15KB

  1. /*
  2. Copyright 2011-2013 Timothy Pearson <kb9vqf@pearsoncomputing.net>
  3. This file is part of tdekbdledsync, the TDE Keyboard LED Synchronization Daemon
  4. tdekbdledsync is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as
  6. published by the Free Software Foundation, either version 3
  7. of the License, or (at your option) any later version.
  8. tdekbdledsync is distributed in the hope that it will be useful, but
  9. WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public
  13. License along with tdekbdledsync. If not, see http://www.gnu.org/licenses/.
  14. */
  15. // The idea here is to periodically read the Xorg core keyboard state, and then forcibly set the physical LED states on all attached keyboards to match (via the event interface)
  16. // Once every half second should work well enough on most systems
  17. #include <stdio.h>
  18. #include <stdlib.h>
  19. #include <exception>
  20. #include <string.h>
  21. #include <unistd.h>
  22. #include <errno.h>
  23. #include <fcntl.h>
  24. #include <limits.h>
  25. #include <dirent.h>
  26. #include <linux/vt.h>
  27. #include <linux/input.h>
  28. #include <linux/uinput.h>
  29. #include <sys/file.h>
  30. #include <sys/types.h>
  31. #include <sys/stat.h>
  32. #include <sys/select.h>
  33. #include <sys/time.h>
  34. #include <termios.h>
  35. #include <signal.h>
  36. #include <stdint.h>
  37. extern "C" {
  38. #include <libudev.h>
  39. #include "getfd.h"
  40. }
  41. #include <libgen.h>
  42. #include <X11/Xlib.h>
  43. #include <X11/Xatom.h>
  44. #include <X11/XKBlib.h>
  45. #include <X11/extensions/XTest.h>
  46. #include <X11/keysym.h>
  47. using namespace std;
  48. // WARNING
  49. // MAX_KEYBOARDS must be greater than or equal to MAX_INPUT_NODE
  50. #define MAX_KEYBOARDS 128
  51. #define MAX_INPUT_NODE 128
  52. #define TestBit(bit, array) (array[(bit) / 8] & (1 << ((bit) % 8)))
  53. typedef unsigned char byte;
  54. char filename[32];
  55. char key_bitmask[(KEY_MAX + 7) / 8];
  56. int keyboard_fd_num;
  57. int keyboard_fds[MAX_KEYBOARDS];
  58. Display* display = NULL;
  59. int lockfd = -1;
  60. char lockFileName[256];
  61. // --------------------------------------------------------------------------------------
  62. // Useful function from Stack Overflow
  63. // http://stackoverflow.com/questions/874134/find-if-string-endswith-another-string-in-c
  64. // --------------------------------------------------------------------------------------
  65. /* returns 1 iff str ends with suffix */
  66. int str_ends_with(const char * str, const char * suffix) {
  67. if( str == NULL || suffix == NULL )
  68. return 0;
  69. size_t str_len = strlen(str);
  70. size_t suffix_len = strlen(suffix);
  71. if(suffix_len > str_len)
  72. return 0;
  73. return 0 == strncmp( str + str_len - suffix_len, suffix, suffix_len );
  74. }
  75. // --------------------------------------------------------------------------------------
  76. // --------------------------------------------------------------------------------------
  77. // Useful function from Stack Overflow
  78. // http://stackoverflow.com/questions/1599459/optimal-lock-file-method
  79. // --------------------------------------------------------------------------------------
  80. int tryGetLock(char const *lockName) {
  81. mode_t m = umask( 0 );
  82. int fd = open( lockName, O_RDWR|O_CREAT, 0666 );
  83. umask( m );
  84. if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 ) {
  85. close( fd );
  86. fd = -1;
  87. }
  88. return fd;
  89. }
  90. // --------------------------------------------------------------------------------------
  91. // Useful function from Stack Overflow
  92. // http://stackoverflow.com/questions/1599459/optimal-lock-file-method
  93. // --------------------------------------------------------------------------------------
  94. void releaseLock(int fd, char const *lockName) {
  95. if( fd < 0 ) {
  96. return;
  97. }
  98. remove( lockName );
  99. close( fd );
  100. }
  101. // --------------------------------------------------------------------------------------
  102. // Get the VT number X is running on
  103. // (code taken from GDM, daemon/getvt.c, GPLv2+)
  104. // --------------------------------------------------------------------------------------
  105. int get_x_vtnum(Display *dpy)
  106. {
  107. Atom prop;
  108. Atom actualtype;
  109. int actualformat;
  110. unsigned long nitems;
  111. unsigned long bytes_after;
  112. unsigned char *buf;
  113. int num;
  114. prop = XInternAtom (dpy, "XFree86_VT", False);
  115. if (prop == None)
  116. return -1;
  117. if (XGetWindowProperty (dpy, DefaultRootWindow (dpy), prop, 0, 1,
  118. False, AnyPropertyType, &actualtype, &actualformat,
  119. &nitems, &bytes_after, &buf)) {
  120. return -1;
  121. }
  122. if (nitems != 1) {
  123. XFree (buf);
  124. return -1;
  125. }
  126. switch (actualtype) {
  127. case XA_CARDINAL:
  128. case XA_INTEGER:
  129. case XA_WINDOW:
  130. switch (actualformat) {
  131. case 8:
  132. num = (*(uint8_t *)(void *)buf);
  133. break;
  134. case 16:
  135. num = (*(uint16_t *)(void *)buf);
  136. break;
  137. case 32:
  138. num = (*(uint32_t *)(void *)buf);
  139. break;
  140. default:
  141. XFree (buf);
  142. return -1;
  143. }
  144. break;
  145. default:
  146. XFree (buf);
  147. return -1;
  148. }
  149. XFree (buf);
  150. return num;
  151. }
  152. // --------------------------------------------------------------------------------------
  153. // --------------------------------------------------------------------------------------
  154. // Get the specified xkb mask modifier
  155. // (code taken from numlockx)
  156. // --------------------------------------------------------------------------------------
  157. unsigned int xkb_mask_modifier(XkbDescPtr xkb, const char *name) {
  158. int i;
  159. if( !xkb || !xkb->names ) {
  160. return 0;
  161. }
  162. for( i = 0; i < XkbNumVirtualMods; i++ ) {
  163. char* modStr = XGetAtomName( xkb->dpy, xkb->names->vmods[i] );
  164. if( modStr == NULL ) {
  165. continue;
  166. }
  167. if( strcmp(name, modStr) == 0 ) {
  168. unsigned int mask;
  169. XkbVirtualModsToReal( xkb, 1 << i, &mask );
  170. XFree(modStr);
  171. return mask;
  172. }
  173. XFree(modStr);
  174. }
  175. return 0;
  176. }
  177. // --------------------------------------------------------------------------------------
  178. // --------------------------------------------------------------------------------------
  179. // Get the capslock xkb mask modifier
  180. // --------------------------------------------------------------------------------------
  181. unsigned int xkb_capslock_mask() {
  182. return LockMask;
  183. }
  184. // --------------------------------------------------------------------------------------
  185. // --------------------------------------------------------------------------------------
  186. // Get the numlock xkb mask modifier
  187. // (code taken from numlockx)
  188. // --------------------------------------------------------------------------------------
  189. unsigned int xkb_numlock_mask() {
  190. XkbDescPtr xkb;
  191. if(( xkb = XkbGetKeyboard(display, XkbAllComponentsMask, XkbUseCoreKbd )) != NULL ) {
  192. unsigned int mask = xkb_mask_modifier( xkb, "NumLock" );
  193. XkbFreeKeyboard( xkb, 0, True );
  194. return mask;
  195. }
  196. return 0;
  197. }
  198. // --------------------------------------------------------------------------------------
  199. // --------------------------------------------------------------------------------------
  200. // Get the scroll lock xkb mask modifier
  201. // (code taken from numlockx and modified)
  202. // --------------------------------------------------------------------------------------
  203. unsigned int xkb_scrolllock_mask() {
  204. XkbDescPtr xkb;
  205. if(( xkb = XkbGetKeyboard(display, XkbAllComponentsMask, XkbUseCoreKbd )) != NULL ) {
  206. unsigned int mask = xkb_mask_modifier( xkb, "ScrollLock" );
  207. XkbFreeKeyboard( xkb, 0, True );
  208. return mask;
  209. }
  210. return 0;
  211. }
  212. // --------------------------------------------------------------------------------------
  213. int find_keyboards() {
  214. int i, j;
  215. int fd;
  216. char name[256] = "Unknown";
  217. keyboard_fd_num = 0;
  218. for (i=0; i<MAX_KEYBOARDS; i++) {
  219. keyboard_fds[i] = 0;
  220. }
  221. for (i=0; i<MAX_INPUT_NODE; i++) {
  222. snprintf(filename, sizeof(filename), "/dev/input/event%d", i);
  223. fd = open(filename, O_RDWR|O_SYNC);
  224. if (fd >= 0) {
  225. ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask);
  226. // Ensure that we do not detect tsak faked keyboards
  227. ioctl (fd, EVIOCGNAME(sizeof(name)), name);
  228. if (str_ends_with(name, "+tsak") == 0) {
  229. struct input_id input_info;
  230. ioctl (fd, EVIOCGID, &input_info);
  231. if ((input_info.vendor != 0) && (input_info.product != 0)) {
  232. /* We assume that anything that has an alphabetic key in the
  233. QWERTYUIOP range in it is the main keyboard. */
  234. for (j = KEY_Q; j <= KEY_P; j++) {
  235. if (TestBit(j, key_bitmask)) {
  236. keyboard_fds[keyboard_fd_num] = fd;
  237. }
  238. }
  239. }
  240. }
  241. if (keyboard_fds[keyboard_fd_num] == 0) {
  242. close(fd);
  243. }
  244. else {
  245. keyboard_fd_num++;
  246. }
  247. }
  248. }
  249. return 0;
  250. }
  251. void handle_sigterm(int signum) {
  252. if (lockfd >= 0) {
  253. releaseLock(lockfd, lockFileName);
  254. }
  255. exit(0);
  256. }
  257. int main() {
  258. int current_keyboard;
  259. char name[256] = "Unknown";
  260. unsigned int states;
  261. struct input_event ev;
  262. struct vt_stat vtstat;
  263. int vt_fd;
  264. int x11_vt_num = -1;
  265. // XEvent xev;
  266. XkbStateRec state;
  267. bool num_lock_set = false;
  268. bool caps_lock_set = false;
  269. bool scroll_lock_set = false;
  270. int num_lock_mask;
  271. int caps_lock_mask;
  272. int scroll_lock_mask;
  273. int evBase;
  274. int errBase;
  275. // Register cleanup handlers
  276. struct sigaction action;
  277. memset(&action, 0, sizeof(struct sigaction));
  278. action.sa_handler = handle_sigterm;
  279. sigaction(SIGTERM, &action, NULL);
  280. // Open X11 display
  281. display = XOpenDisplay(NULL);
  282. if (!display) {
  283. printf ("[tdekbdledsync] Unable to open X11 display!\n");
  284. return -1;
  285. }
  286. // Ensure only one process is running on a given display
  287. sprintf(lockFileName, "/var/lock/tdekbdledsync-%s.lock", XDisplayString(display));
  288. lockfd = tryGetLock(lockFileName);
  289. if (lockfd < 0) {
  290. printf ("[tdekbdledsync] Another instance of this program is already running on this X11 display!\n[tdekbdledsync] Lockfile detected at '%s'\n", lockFileName);
  291. return -2;
  292. }
  293. // Set up Xkb extension
  294. int i1, mn, mj;
  295. mj = XkbMajorVersion;
  296. mn = XkbMinorVersion;
  297. if (!XkbQueryExtension(display, &i1, &evBase, &errBase, &mj, &mn)) {
  298. printf("[tdekbdledsync] Server doesn't support a compatible XKB\n");
  299. releaseLock(lockfd, lockFileName);
  300. return -3;
  301. }
  302. XkbSelectEvents(display, XkbUseCoreKbd, XkbStateNotifyMask, XkbStateNotifyMask);
  303. // Get X server VT number
  304. x11_vt_num = get_x_vtnum(display);
  305. // Open console socket
  306. vt_fd = getfd(NULL);
  307. // Monitor for hotplugged keyboards
  308. struct udev *udev;
  309. struct udev_device *dev;
  310. struct udev_monitor *mon;
  311. struct timeval tv;
  312. // Create the udev object
  313. udev = udev_new();
  314. if (!udev) {
  315. printf("[tdekbdledsync] Cannot connect to udev interface\n");
  316. releaseLock(lockfd, lockFileName);
  317. return -4;
  318. }
  319. // Set up a udev monitor to monitor input devices
  320. mon = udev_monitor_new_from_netlink(udev, "udev");
  321. udev_monitor_filter_add_match_subsystem_devtype(mon, "input", NULL);
  322. udev_monitor_enable_receiving(mon);
  323. while (1) {
  324. // Get masks
  325. num_lock_mask = xkb_numlock_mask();
  326. caps_lock_mask = xkb_capslock_mask();
  327. scroll_lock_mask = xkb_scrolllock_mask();
  328. // Find keyboards
  329. find_keyboards();
  330. if (keyboard_fd_num == 0) {
  331. printf ("[tdekbdledsync] Could not find any usable keyboard(s)!\n");
  332. releaseLock(lockfd, lockFileName);
  333. return -5;
  334. }
  335. else {
  336. fprintf(stderr, "[tdekbdledsync] Found %d keyboard(s)\n", keyboard_fd_num);
  337. for (current_keyboard=0;current_keyboard<keyboard_fd_num;current_keyboard++) {
  338. // Print device name
  339. ioctl(keyboard_fds[current_keyboard], EVIOCGNAME (sizeof (name)), name);
  340. fprintf(stderr, "[tdekbdledsync] Syncing keyboard: (%s)\n", name);
  341. }
  342. while (1) {
  343. // Get current active VT
  344. if (ioctl(vt_fd, VT_GETSTATE, &vtstat)) {
  345. fprintf(stderr, "[tdekbdledsync] Unable to get current VT!\n");
  346. releaseLock(lockfd, lockFileName);
  347. return -6;
  348. }
  349. if (x11_vt_num == vtstat.v_active) {
  350. // Get Virtual Core keyboard status
  351. if (XkbGetState(display, XkbUseCoreKbd, &state) != Success) {
  352. fprintf(stderr, "[tdekbdledsync] Unable to query X11 Virtual Core keyboard!\n");
  353. releaseLock(lockfd, lockFileName);
  354. return -7;
  355. }
  356. caps_lock_set = (state.mods & caps_lock_mask);
  357. num_lock_set = (state.mods & num_lock_mask);
  358. scroll_lock_set = (state.mods & scroll_lock_mask);
  359. for (current_keyboard=0;current_keyboard<keyboard_fd_num;current_keyboard++) {
  360. // Set LEDs
  361. ev.type = EV_LED;
  362. ev.code = LED_CAPSL;
  363. ev.value = caps_lock_set;
  364. if (write(keyboard_fds[current_keyboard], &ev, sizeof(ev)) < 0) {
  365. fprintf(stderr, "[tdekbdledsync] Unable to set LED state\n");
  366. }
  367. ev.type = EV_LED;
  368. ev.code = LED_NUML;
  369. ev.value = num_lock_set;
  370. if (write(keyboard_fds[current_keyboard], &ev, sizeof(ev)) < 0) {
  371. fprintf(stderr, "[tdekbdledsync] Unable to set LED state\n");
  372. }
  373. ev.type = EV_LED;
  374. ev.code = LED_SCROLLL;
  375. ev.value = scroll_lock_set;
  376. if (write(keyboard_fds[current_keyboard], &ev, sizeof(ev)) < 0) {
  377. fprintf(stderr, "[tdekbdledsync] Unable to set LED state\n");
  378. }
  379. }
  380. }
  381. else {
  382. // Ensure the X server is still alive
  383. // If the X server has terminated, this will fail and the program will terminate
  384. XSync(display, False);
  385. }
  386. // Check the hotplug monitoring process to see if any keyboards were added or removed
  387. fd_set readfds;
  388. FD_ZERO(&readfds);
  389. FD_SET(udev_monitor_get_fd(mon), &readfds);
  390. tv.tv_sec = 0;
  391. tv.tv_usec = 0;
  392. int fdcount = select(udev_monitor_get_fd(mon)+1, &readfds, NULL, NULL, &tv);
  393. if (fdcount < 0) {
  394. if (errno == EINTR) {
  395. fprintf(stderr, "[tdekbdledsync] Signal caught in hotplug monitoring process; ignoring\n");
  396. }
  397. else {
  398. fprintf(stderr, "[tdekbdledsync] Select failed on udev file descriptor in hotplug monitoring process\n");
  399. }
  400. }
  401. else {
  402. dev = udev_monitor_receive_device(mon);
  403. if (dev) {
  404. int reload_keyboards = 0;
  405. if (strcmp(udev_device_get_action(dev), "add") == 0) {
  406. reload_keyboards = 1;
  407. }
  408. if (strcmp(udev_device_get_action(dev), "remove") == 0) {
  409. reload_keyboards = 1;
  410. }
  411. udev_device_unref(dev);
  412. if( reload_keyboards ) {
  413. // Reload keyboards
  414. break;
  415. }
  416. }
  417. }
  418. // Poll
  419. usleep(250*1000);
  420. // // Wait for an Xkb event
  421. // // FIXME
  422. // // This prevents the udev hotplug monitor from working, as XNextEvent does not stop blocking when a keyboard hotplug occurs
  423. // while (1) {
  424. // XNextEvent(display, &xev);
  425. // if (xev.type == evBase + XkbEventCode) {
  426. // XkbEvent *xkb_event = reinterpret_cast<XkbEvent*>(&xev);
  427. // if (xkb_event->any.xkb_type & XkbStateNotify) {
  428. // if ((xkb_event->state.changed & XkbModifierStateMask) || (xkb_event->state.changed & XkbModifierBaseMask)) {
  429. // // Modifier state has changed
  430. // // Synchronize keyboard indicators
  431. // break;
  432. // }
  433. // }
  434. // }
  435. // }
  436. }
  437. }
  438. // Close all keyboard file descriptors
  439. for (int current_keyboard=0;current_keyboard<keyboard_fd_num;current_keyboard++) {
  440. close(keyboard_fds[current_keyboard]);
  441. }
  442. }
  443. releaseLock(lockfd, lockFileName);
  444. udev_monitor_unref(mon);
  445. udev_unref(udev);
  446. return 0;
  447. }