linput

Listen to input events
git clone git://git.akobets.xyz/linput
Log | Files | Refs | README | LICENSE

linput.c (11488B)


      1 #include <fcntl.h>
      2 #include <glob.h>
      3 #include <errno.h>
      4 #include <limits.h>
      5 #include <linux/input.h>
      6 #include <poll.h>
      7 #include <signal.h>
      8 #include <stdarg.h>
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 #include <string.h>
     12 #include <sys/inotify.h>
     13 #include <sys/wait.h>
     14 #include <unistd.h>
     15 
     16 #include "config.h"
     17 
     18 #define LENGTH(x)        (sizeof(x) / sizeof(x[0]))
     19 
     20 typedef struct node {
     21   int fd;
     22   char *path;
     23   struct node *next;
     24 } fd_node;
     25 
     26 static int is_mod(int key);
     27 static int has_key(const struct HotkeyRule *hotkey, int key);
     28 static int is_key_ignored(int key);
     29 static int is_match_hotkey_event(const struct HotkeyRule *hotkey, int key);
     30 static int is_match_hotkey_mod(const struct HotkeyRule *hotkey);
     31 static int is_hotkey_active(const struct HotkeyRule *hotkey, int last_key);
     32 static void hotkey_reset();
     33 static void run(char **cmd);
     34 
     35 static void add_fd(int fd, const char *path);
     36 static void remove_fd(int fd);
     37 static void generate_poll_array();
     38 static int open_input_file(const char *path);
     39 static void close_input_file(int fd);
     40 
     41 static void init_input_files();
     42 static void init_inotify();
     43 static void handle_input(int fd);
     44 static void handle_inotify(int fd);
     45 
     46 static void sigchld(int unused);
     47 static void die(const char *fmt, ...);
     48 
     49 static int key_state[KEY_CNT] = { 0 };
     50 static fd_node *fd_head = NULL;
     51 static int inotify_fd = -1;
     52 static size_t nfds = 0;
     53 static struct pollfd *pfds = NULL;
     54 static int poll_change = 0;
     55 
     56 int
     57 is_mod(int key)
     58 {
     59   return key == KEY_LEFTSHIFT ||
     60     key == KEY_RIGHTSHIFT ||
     61     key == KEY_LEFTCTRL ||
     62     key == KEY_RIGHTCTRL ||
     63     key == KEY_LEFTMETA ||
     64     key == KEY_RIGHTMETA ||
     65     key == KEY_LEFTALT ||
     66     key == KEY_RIGHTALT;
     67 }
     68 
     69 int
     70 has_key(const struct HotkeyRule *hotkey, int key)
     71 {
     72   const int *p;
     73 
     74   for (p = hotkey->keys; *p != 0; p++)
     75     if (key == *p)
     76       return 1;
     77 
     78   return 0;
     79 }
     80 
     81 int
     82 is_key_ignored(int key)
     83 {
     84   size_t i;
     85 
     86   for (i = 0; i < LENGTH(ignored_keys); i++)
     87     if (ignored_keys[i] == key)
     88       return 1;
     89 
     90   return 0;
     91 }
     92 
     93 int
     94 is_match_hotkey_event(const struct HotkeyRule *hotkey, int key)
     95 {
     96   if ((hotkey->event_mask & ON_RELEASE) &&
     97       key_state[key] == 0)
     98     return 1;
     99   if ((hotkey->event_mask & ON_PRESS) &&
    100       key_state[key] == 1)
    101     return 1;
    102   if ((hotkey->event_mask & ON_HOLD) &&
    103       key_state[key] == 2)
    104     return 1;
    105 
    106   return 0;
    107 }
    108 
    109 int
    110 is_match_hotkey_mod(const struct HotkeyRule *hotkey)
    111 {
    112   int mod_mask;
    113 
    114   mod_mask = hotkey->mod_mask;
    115 
    116   if (mod_mask & MOD_ANY)
    117     return 1;
    118 
    119   if (mod_mask & MOD_SHIFT) {
    120     if (key_state[KEY_LEFTSHIFT] == 0 &&
    121       key_state[KEY_RIGHTSHIFT] == 0)
    122       return 0;
    123   } else {
    124     if ((mod_mask & MOD_LEFTSHIFT)
    125       ? key_state[KEY_LEFTSHIFT] == 0
    126       : key_state[KEY_LEFTSHIFT] != 0)
    127       return 0;
    128     if ((mod_mask & MOD_RIGHTSHIFT)
    129       ? key_state[KEY_RIGHTSHIFT] == 0
    130       : key_state[KEY_RIGHTSHIFT] != 0)
    131       return 0;
    132   }
    133 
    134   if (mod_mask & MOD_CTRL) {
    135     if (key_state[KEY_LEFTCTRL] == 0 &&
    136       key_state[KEY_RIGHTCTRL] == 0)
    137       return 0;
    138   } else {
    139     if ((mod_mask & MOD_LEFTCTRL)
    140       ? key_state[KEY_LEFTCTRL] == 0
    141       : key_state[KEY_LEFTCTRL] != 0)
    142       return 0;
    143     if ((mod_mask & MOD_RIGHTCTRL)
    144       ? key_state[KEY_RIGHTCTRL] == 0
    145       : key_state[KEY_RIGHTCTRL] != 0)
    146       return 0;
    147   }
    148 
    149   if (mod_mask & MOD_SUPER) {
    150     if (key_state[KEY_LEFTMETA] == 0 &&
    151       key_state[KEY_RIGHTMETA] == 0)
    152       return 0;
    153   } else {
    154     if ((mod_mask & MOD_LEFTSUPER)
    155       ? key_state[KEY_LEFTMETA] == 0
    156       : key_state[KEY_LEFTMETA] != 0)
    157       return 0;
    158     if ((mod_mask & MOD_RIGHTSUPER)
    159       ? key_state[KEY_RIGHTMETA] == 0
    160       : key_state[KEY_RIGHTMETA] != 0)
    161       return 0;
    162   }
    163 
    164   if (mod_mask & MOD_ALT) {
    165     if (key_state[KEY_LEFTALT] == 0 &&
    166       key_state[KEY_RIGHTALT] == 0)
    167       return 0;
    168   } else {
    169     if ((mod_mask & MOD_LEFTALT)
    170       ? key_state[KEY_LEFTALT] == 0
    171       : key_state[KEY_LEFTALT] != 0)
    172       return 0;
    173     if ((mod_mask & MOD_RIGHTALT)
    174       ? key_state[KEY_RIGHTALT] == 0
    175       : key_state[KEY_RIGHTALT] != 0)
    176       return 0;
    177   }
    178 
    179   return 1;
    180 }
    181 
    182 int
    183 is_hotkey_active(const struct HotkeyRule *hotkey, int last_key)
    184 {
    185   int k;
    186 
    187   if (!has_key(hotkey, last_key) && !is_mod(last_key))
    188     return 0;
    189   if (!is_match_hotkey_event(hotkey, last_key))
    190     return 0;
    191   if (!is_match_hotkey_mod(hotkey))
    192     return 0;
    193 
    194   for (k = 1; k < LENGTH(key_state); k++) {
    195     if (k == last_key || is_mod(k))
    196       /* skip, handled above */
    197       continue;
    198 
    199     if (has_key(hotkey, k)) {
    200       /* all other keys in hotkey should be pressed */
    201       if (key_state[k] == 0)
    202         return 0;
    203     } else {
    204       /* all keys not in hotkey should not be pressed */
    205       if (key_state[k] != 0 && !is_key_ignored(k))
    206         return 0;
    207     }
    208   }
    209 
    210   return 1;
    211 }
    212 
    213 void
    214 hotkey_reset()
    215 {
    216   memset(key_state, 0, sizeof(key_state));
    217 }
    218 
    219 void
    220 run(char **cmd)
    221 {
    222   if (cmd == NULL)
    223     return;
    224 
    225   switch (fork()) {
    226   case -1:
    227     fprintf(stderr, "fork: %s\n", strerror(errno));
    228     break;
    229   case 0:
    230     /* drop setuid/setgid privileges */
    231     if (setuid(getuid()) == -1) {
    232       fprintf(stderr, "setuid: %s\n", strerror(errno));
    233       _exit(EXIT_FAILURE);
    234     }
    235     if (setgid(getgid()) == -1) {
    236       fprintf(stderr, "setgid: %s\n", strerror(errno));
    237       _exit(EXIT_FAILURE);
    238     }
    239 
    240     execvp(cmd[0], cmd);
    241     fprintf(stderr, "execvp %s: %s\n", cmd[0], strerror(errno));
    242     _exit(EXIT_FAILURE);
    243     break;
    244   }
    245 }
    246 
    247 void
    248 add_fd(int fd, const char *path)
    249 {
    250   fd_node *new;
    251 
    252   new = (fd_node *) malloc(sizeof(fd_node));
    253   if (new == NULL)
    254     die("malloc: %s\n", strerror(errno));
    255   new->fd = fd;
    256   new->path = strdup(path);
    257   if (new->path == NULL)
    258     die("strdup: %s\n", strerror(errno));
    259   new->next = NULL;
    260 
    261   if (fd_head == NULL) {
    262     fd_head = new;
    263   } else {
    264     fd_node *last;
    265 
    266     last = fd_head;
    267     while (last->next != NULL)
    268       last = last->next;
    269     last->next = new;
    270   }
    271 }
    272 
    273 void
    274 remove_fd(int fd)
    275 {
    276   fd_node *current, *prev, *node;
    277 
    278   if (fd_head == NULL)
    279     return;
    280 
    281   current = fd_head;
    282   prev = NULL;
    283   node = NULL;
    284   while (current != NULL) {
    285     if (current->fd == fd) {
    286       node = current;
    287       break;
    288     }
    289     prev = current;
    290     current = current->next;
    291   }
    292 
    293   if (node != NULL) {
    294     if (node == fd_head)
    295       fd_head = node->next;
    296     else if (prev != NULL)
    297       prev->next = node->next;
    298     free(node->path);
    299     free(node);
    300   }
    301 }
    302 
    303 void
    304 generate_poll_array()
    305 {
    306   fd_node *current;
    307   size_t n, i;
    308 
    309   /* total fds - 1 inotify fd + all input file fds */
    310   n = 1;
    311 
    312   if (fd_head != NULL) {
    313     current = fd_head;
    314     while (current != NULL) {
    315       n++;
    316       current = current->next;
    317     }
    318   }
    319 
    320   if (n != nfds) {
    321     nfds = n;
    322     pfds = realloc(pfds, sizeof(struct pollfd) * n);
    323     if (pfds == NULL)
    324       die("realloc: %s\n", strerror(errno));
    325   }
    326 
    327   pfds[0].fd = inotify_fd;
    328   pfds[0].events = POLLIN;
    329   for (current = fd_head, i = 1; current != NULL; current = current->next, i++) {
    330     pfds[i].fd = current->fd;
    331     pfds[i].events = POLLIN;
    332   }
    333 
    334   poll_change = 0;
    335 }
    336 
    337 int
    338 open_input_file(const char *path) {
    339   int fd;
    340   int savedErrno;
    341 
    342   fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
    343   savedErrno = errno;
    344 
    345   if (fd != -1) {
    346     add_fd(fd, path);
    347     poll_change = 1;
    348   }
    349 
    350   errno = savedErrno;
    351   return fd;
    352 }
    353 
    354 void
    355 close_input_file(int fd) {
    356   close(fd);
    357   remove_fd(fd);
    358   poll_change = 1;
    359 }
    360 
    361 void
    362 init_input_files()
    363 {
    364   int res;
    365   glob_t pglob;
    366   size_t i;
    367   int success;
    368 
    369   success = 0;
    370 
    371   res = glob("/dev/input/event*", 0, NULL, &pglob);
    372   if (res != 0)
    373     die("glob: can't find files in /dev/input/event*", strerror(errno));
    374   for (i = 0; i < pglob.gl_pathc; i++)
    375     if (open_input_file(pglob.gl_pathv[i]) != -1)
    376       success = 1;
    377 
    378   if (!success)
    379     die("can't open any files in /dev/input/event*\n");
    380 
    381   globfree(&pglob);
    382 }
    383 
    384 void
    385 init_inotify()
    386 {
    387   inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
    388   poll_change = 1;
    389   if (inotify_fd == -1) {
    390     fprintf(stderr, "inotify init failed (inotify_init): %s\n", strerror(errno));
    391     return;
    392   }
    393 
    394   if (inotify_add_watch(inotify_fd, "/dev/input", IN_CREATE | IN_ATTRIB) == -1) {
    395     fprintf(stderr, "inotify init failed (inotify_add_watch): %s\n", strerror(errno));
    396     return;
    397   }
    398 }
    399 
    400 void
    401 handle_input(int fd)
    402 {
    403   ssize_t n;
    404   struct input_event buf;
    405 
    406   while (
    407     (
    408       (n = read(fd, &buf, sizeof(struct input_event))) ==
    409       sizeof(struct input_event)
    410     ) ||
    411     (n == -1 && errno == EINTR)
    412   ) {
    413     size_t i;
    414 
    415     for (i = 0; i < LENGTH(events); i++) {
    416       struct EventRule event = events[i];
    417 
    418       if (event.cmd == NULL) continue;
    419 
    420       if (
    421         event.type == buf.type &&
    422         event.code == buf.code &&
    423         event.value == buf.value
    424       )
    425         run(event.cmd);
    426     }
    427 
    428     if (buf.type == EV_KEY) {
    429       if (reset_key != 0 && buf.code == reset_key && buf.value == 1)
    430         hotkey_reset();
    431 
    432       key_state[buf.code] = buf.value;
    433 
    434       for (i = 0; i < LENGTH(hotkeys); i++) {
    435         struct HotkeyRule hotkey = hotkeys[i];
    436 
    437         if (hotkey.cmd == NULL) continue;
    438 
    439         if (is_hotkey_active(&hotkey, buf.code))
    440           run(hotkey.cmd);
    441       }
    442     }
    443   }
    444 }
    445 
    446 void
    447 handle_inotify(int fd) {
    448   char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
    449   char filename[NAME_MAX + 1];
    450   ssize_t n, processed_bytes;
    451 
    452   n = read(fd, &buf, sizeof(buf));
    453   if (n <= 0)
    454     return;
    455 
    456   processed_bytes = 0;
    457   while (processed_bytes < n) {
    458     struct inotify_event* event = (struct inotify_event *) &buf[processed_bytes];
    459 
    460     if (
    461       (event->mask & (IN_CREATE | IN_ATTRIB)) &&
    462       !(event->mask & IN_ISDIR) &&
    463       strncmp(event->name, "event", 5) == 0
    464     ) {
    465       fd_node *current;
    466       int already_opened;
    467 
    468       snprintf(filename, sizeof(filename), "/dev/input/%s", event->name);
    469 
    470       already_opened = 0;
    471       current = fd_head;
    472       while (current != NULL) {
    473         if (strcmp(current->path, filename) == 0) {
    474           already_opened = 1;
    475           break;
    476         }
    477         current = current->next;
    478       }
    479 
    480       if (!already_opened)
    481         open_input_file(filename);
    482     }
    483 
    484     processed_bytes += sizeof(struct inotify_event) + event->len;
    485   }
    486 }
    487 
    488 void
    489 sigchld(int unused)
    490 {
    491   while (waitpid(-1, NULL, WNOHANG) > 0);
    492 }
    493 
    494 void
    495 die(const char *fmt, ...)
    496 {
    497   va_list ap;
    498 
    499   va_start(ap, fmt);
    500   vfprintf(stderr, fmt, ap);
    501   va_end(ap);
    502 
    503   exit(EXIT_FAILURE);
    504 }
    505 
    506 int
    507 main(int argc, char **argv)
    508 {
    509   struct sigaction act;
    510   sigset_t sigmask;
    511   int res;
    512 
    513   if (argc == 2 && strcmp("-v", argv[1]) == 0) {
    514     die("linput %s\n", VERSION);
    515   } else if (argc != 1)
    516     die("usage: linput [-v]\n");
    517 
    518   act.sa_handler = sigchld;
    519   sigemptyset(&sigmask);
    520   act.sa_mask = sigmask;
    521   act.sa_flags = 0;
    522   sigaction(SIGCHLD, &act, NULL);
    523 
    524   init_input_files();
    525   init_inotify();
    526   generate_poll_array();
    527 
    528   /* loop infinitely, reading input events */
    529   while (
    530     (res = poll(pfds, nfds, -1)) > 0 ||
    531     (res == -1 && errno == EINTR)
    532   ) {
    533     size_t i;
    534 
    535     /* handle input event fds */
    536     for (i = 1; i < nfds; i++) {
    537       if (pfds[i].revents & POLLIN)
    538         handle_input(pfds[i].fd);
    539       if (pfds[i].revents & (POLLERR | POLLHUP))
    540         close_input_file(pfds[i].fd);
    541     }
    542 
    543     /* handle inotify fd */
    544     if (pfds[0].revents & POLLIN)
    545       handle_inotify(pfds[0].fd);
    546     if (pfds[0].revents & (POLLERR | POLLHUP)) {
    547       close(pfds[0].fd);
    548       init_inotify();
    549     }
    550 
    551     /* rebuild poll array if necessary */
    552     if (poll_change)
    553       generate_poll_array();
    554   }
    555 
    556   if (res == -1)
    557     die("poll: %s\n", strerror(errno));
    558 
    559   die("poll: unexpected error\n");
    560 }