From cc4992ac07dd1118b4fcaab3adcc4ff46a2a3ff4 Mon Sep 17 00:00:00 2001 From: Raheman Vaiya Date: Wed, 2 Feb 2022 17:42:41 -0500 Subject: [PATCH] Use inotify for hotplug detection (eliminate udev) The end result should (hopefully) be a little more robust (and systemd independent). I no longer remember what possessed me to switch away from the original inotify implementation, but udev detection seems to have caused enough grief to warrant switching back. This will probably break FreeBSD support without some kind of inotify glue and/or a kqueue implementation. If any FreeBSD users see this feel free to open an issue or submit a PR :P. --- Makefile | 2 +- src/evdev.c | 128 ++++++++++++++++++++++++++++++ src/keyd.c | 220 +++++++++++++--------------------------------------- src/keyd.h | 19 +++-- 4 files changed, 198 insertions(+), 171 deletions(-) create mode 100644 src/evdev.c diff --git a/Makefile b/Makefile index e909220..42c329d 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ CFLAGS+=-DVERSION=\"$(VERSION)\" \ all: vkbd-uinput vkbd-%: mkdir -p bin - $(CC) $(CFLAGS) -O3 src/*.c src/vkbd/$(@:vkbd-%=%).c -o bin/keyd -ludev + $(CC) $(CFLAGS) -O3 src/*.c src/vkbd/$(@:vkbd-%=%).c -o bin/keyd -lpthread debug: CFLAGS+="-pedantic -Wall -Wextra -g" $(MAKE) man: diff --git a/src/evdev.c b/src/evdev.c new file mode 100644 index 0000000..7c91cd8 --- /dev/null +++ b/src/evdev.c @@ -0,0 +1,128 @@ +#include "keyd.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int evdev_is_keyboard(const char *devnode) +{ + int fd = open(devnode, O_RDONLY); + if (fd < 0) { + perror("open"); + exit(-1); + } + + uint8_t keymask[(KEY_CNT+7)/8]; + + if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof keymask), keymask) < 0) { + perror("ioctl"); + fprintf(stderr, "WARNING: Failed to retrieve key bit for %s\n", devnode); + return 0; + } + + int has_a = keymask[KEY_A/8] & (0x01 << (KEY_A%8)); + int has_d = keymask[KEY_D/8] & (0x01 << (KEY_D%8)); + + close(fd); + + return has_a && has_d; +} + + +struct worker { + pthread_t tid; + char path[1024]; + int result; +} workers[256]; + +static void *is_keyboard_worker(void *worker) +{ + struct worker *w = (struct worker*) worker; + w->result = evdev_is_keyboard(w->path); +} + +/* + * Note: the returned array is owned by the function and + * should not be freed by the caller. Successive invocations + * invalidate devs. + */ + +int evdev_get_keyboard_nodes(char **devs, int *ndevs) +{ + struct dirent *ent; + int nkbds = 0; + int n = 0; + + DIR *dh = opendir("/dev/input/"); + if (!dh) { + perror("opendir /dev/input"); + exit(-1); + } + + n = 0; + + while((ent = readdir(dh))) { + char path[1024]; + if (strstr(ent->d_name, "event") == ent->d_name) { + assert(n < sizeof(workers)/sizeof(workers[0])); + struct worker *w = &workers[n++]; + snprintf(w->path, sizeof(w->path), "/dev/input/%s", ent->d_name); + pthread_create(&w->tid, NULL, is_keyboard_worker, w); + } + } + + closedir(dh); + + *ndevs = 0; + while(n--) { + pthread_join(workers[n].tid, NULL); + + if(workers[n].result) + devs[(*ndevs)++] = workers[n].path; + } +} + +const char *evdev_device_name(const char *devnode) +{ + static char name[256]; + + int fd = open(devnode, O_RDONLY); + if (fd < 0) { + perror("open"); + exit(-1); + } + + if (ioctl(fd, EVIOCGNAME(sizeof(name)), &name) == -1) + return NULL; + + close(fd); + return name; +} + +uint32_t evdev_device_id(const char *devnode) +{ + struct input_id info; + + int fd = open(devnode, O_RDONLY); + if (fd < 0) { + perror("open"); + exit(-1); + } + + if (ioctl(fd, EVIOCGID, &info) == -1) { + perror("ioctl"); + exit(-1); + } + + close(fd); + return info.vendor << 16 | info.product; +} + diff --git a/src/keyd.c b/src/keyd.c index 93f156a..ff8825b 100644 --- a/src/keyd.c +++ b/src/keyd.c @@ -41,13 +41,13 @@ #include #include #include -#include #include #include #include #include #include #include +#include #include "keys.h" #include "config.h" @@ -58,12 +58,9 @@ #include "error.h" #define VIRTUAL_KEYBOARD_NAME "keyd virtual keyboard" -#define MAX_KEYBOARDS 256 static struct vkbd *vkbd = NULL; -static struct udev *udev; -static struct udev_monitor *udevmon; uint8_t keystate[KEY_CNT] = { 0 }; static int sigfds[2]; struct keyboard *active_keyboard = NULL; @@ -95,124 +92,6 @@ static void info(char *fmt, ...) fprintf(stderr, "\n"); } -static void udev_type(struct udev_device *dev, int *iskbd, int *ismouse) -{ - if (iskbd) - *iskbd = 0; - if (ismouse) - *ismouse = 0; - - const char *path = udev_device_get_devnode(dev); - - if (!path || !strstr(path, "event")) /* Filter out non evdev devices. */ - return; - - struct udev_list_entry *prop; - udev_list_entry_foreach(prop, - udev_device_get_properties_list_entry(dev)) - { - if (!strcmp - (udev_list_entry_get_name(prop), "ID_INPUT_KEYBOARD") - && !strcmp(udev_list_entry_get_value(prop), "1")) { - if (iskbd) - *iskbd = 1; - } - - if (!strcmp - (udev_list_entry_get_name(prop), "ID_INPUT_MOUSE") - && !strcmp(udev_list_entry_get_value(prop), "1")) { - if (ismouse) - *ismouse = 1; - } - } -} - -static uint32_t evdev_device_id(const char *devnode) -{ - struct input_id info; - - int fd = open(devnode, O_RDONLY); - if (fd < 0) { - perror("open"); - exit(-1); - } - - if (ioctl(fd, EVIOCGID, &info) == -1) { - perror("ioctl"); - exit(-1); - } - - close(fd); - return info.vendor << 16 | info.product; -} - -static const char *evdev_device_name(const char *devnode) -{ - static char name[256]; - - int fd = open(devnode, O_RDONLY); - if (fd < 0) { - perror("open"); - exit(-1); - } - - if (ioctl(fd, EVIOCGNAME(sizeof(name)), &name) == -1) - return NULL; - - close(fd); - return name; -} - -static void get_keyboard_nodes(char *nodes[MAX_KEYBOARDS], int *sz) -{ - struct udev *udev; - struct udev_enumerate *enumerate; - struct udev_list_entry *devices, *ent; - - udev = udev_new(); - if (!udev) - die("Cannot create udev context."); - - enumerate = udev_enumerate_new(udev); - if (!enumerate) - die("Cannot create enumerate context."); - - udev_enumerate_add_match_subsystem(enumerate, "input"); - udev_enumerate_scan_devices(enumerate); - - devices = udev_enumerate_get_list_entry(enumerate); - if (!devices) - die("Failed to get device list."); - - *sz = 0; - udev_list_entry_foreach(ent, devices) { - int iskbd, ismouse; - - const char *name = udev_list_entry_get_name(ent);; - struct udev_device *dev = udev_device_new_from_syspath(udev, name); - const char *path = udev_device_get_devnode(dev); - - udev_type(dev, &iskbd, &ismouse); - - if (iskbd) { - dbg("Detected keyboard node %s (%s) ismouse: %d", - name, evdev_device_name(path), ismouse); - - nodes[*sz] = malloc(strlen(path) + 1); - strcpy(nodes[*sz], path); - (*sz)++; - assert(*sz <= MAX_KEYBOARDS); - } else if (path) { - dbg("Ignoring %s (%s)", evdev_device_name(path), path); - } - - udev_device_unref(dev); - } - - udev_enumerate_unref(enumerate); - udev_unref(udev); -} - static void send_repetitions() { size_t i; @@ -404,14 +283,12 @@ static void scan_keyboards(int wait) int i, n; char *devs[MAX_KEYBOARDS]; - get_keyboard_nodes(devs, &n); + evdev_get_keyboard_nodes(devs, &n); if (wait) await_keyboard_neutrality(devs, n); - for (i = 0; i < n; i++) { + for (i = 0; i < n; i++) manage_keyboard(devs[i]); - free(devs[i]); - } } /* TODO: optimize */ @@ -601,7 +478,7 @@ static int monitor_loop() signal(SIGTERM, exit); atexit(monitor_cleanup); - get_keyboard_nodes(devnodes, &sz); + evdev_get_keyboard_nodes(devnodes, &sz); for (i = 0; i < sz; i++) { fd = open(devnodes[i], O_RDONLY | O_NONBLOCK); @@ -609,7 +486,6 @@ static int monitor_loop() perror("open"); exit(-1); } - free(devnodes[i]); fds[nfds++] = fd; } @@ -632,10 +508,55 @@ long get_time() return (tv.tv_sec*1E9)+tv.tv_nsec; } +static int create_inotify_fd() +{ + int fd = inotify_init1(IN_NONBLOCK); + if (fd < 0) { + perror("inotify"); + exit(-1); + } + + int wd = inotify_add_watch(fd, "/dev/input", IN_CREATE | IN_DELETE); + if (wd < 0) { + perror("inotify"); + exit(-1); + } + + return fd; +} + +static void process_inotify_events(int fd) +{ + int len; + char buf[4096]; + + while (1) { + struct inotify_event *ev; + if ((len = read(fd, buf, sizeof buf)) <= 0) + return; + + for (char *ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + ev->len) { + char path[1024]; + ev = (struct inotify_event*) ptr; + + if (strstr(ev->name, "ev") != ev->name) + continue; + + sprintf(path, "/dev/input/%s", ev->name); + + if (ev->mask & IN_CREATE && evdev_is_keyboard(path)) + manage_keyboard(path); + else if (ev->mask & IN_DELETE) + destroy_keyboard(path); + } + } +} + + static void main_loop() { struct keyboard *kbd; - int monfd; + int inotifyfd; int sd; long timeout = 0; /* in ns */ @@ -646,16 +567,7 @@ static void main_loop() scan_keyboards(1); - udev = udev_new(); - udevmon = udev_monitor_new_from_netlink(udev, "udev"); - - if (!udev) - die("Can't create udev."); - - udev_monitor_filter_add_match_subsystem_devtype(udevmon, "input", NULL); - udev_monitor_enable_receiving(udevmon); - - monfd = udev_monitor_get_fd(udevmon); + inotifyfd = create_inotify_fd(); sd = create_server_socket(); pipe(sigfds); @@ -664,17 +576,16 @@ static void main_loop() while (1) { int maxfd; fd_set fds; - struct udev_device *dev; int ret; struct timeval tv; FD_ZERO(&fds); - FD_SET(monfd, &fds); + FD_SET(inotifyfd, &fds); FD_SET(sd, &fds); FD_SET(sigfds[0], &fds); - maxfd = monfd > sigfds[0] ? monfd : sigfds[0]; + maxfd = inotifyfd > sigfds[0] ? inotifyfd : sigfds[0]; maxfd = sd > maxfd ? sd : maxfd; for (kbd = keyboards; kbd; kbd = kbd->next) { @@ -713,27 +624,8 @@ static void main_loop() reload_config(); } - if (FD_ISSET(monfd, &fds)) { - int iskbd; - dev = udev_monitor_receive_device(udevmon); - const char *devnode = udev_device_get_devnode(dev); - udev_type(dev, &iskbd, NULL); - - if (devnode && iskbd) { - const char *action = - udev_device_get_action(dev); - - if (!strcmp(action, "add")) - manage_keyboard(devnode); - else if (!strcmp(action, "remove")) - destroy_keyboard(devnode); - else - dbg("udev: action %s %s", - action, devnode); - } - udev_device_unref(dev); - } - + if (FD_ISSET(inotifyfd, &fds)) + process_inotify_events(inotifyfd); for (kbd = keyboards; kbd; kbd = kbd->next) { int fd = kbd->fd; @@ -794,8 +686,6 @@ static void cleanup() } free_vkbd(vkbd); - udev_unref(udev); - udev_monitor_unref(udevmon); unlink(SOCKET); } diff --git a/src/keyd.h b/src/keyd.h index 1a81301..b0d1d27 100644 --- a/src/keyd.h +++ b/src/keyd.h @@ -1,16 +1,25 @@ #ifndef KEYD_H #define KEYD_H +#include +#include + +#define MAX_KEYBOARDS 256 extern uint8_t keystate[KEY_CNT]; extern int debug; void dbg(const char *fmt, ...); -void set_mods(uint16_t mods); -void send_key(int code, int pressed); -void reload_config(); -void reset_keyboards(); -void reset_vkbd(); +void set_mods(uint16_t mods); +void send_key(int code, int pressed); +void reload_config(); +void reset_keyboards(); +void reset_vkbd(); + +int evdev_get_keyboard_nodes(char **devs, int *ndevs); +int evdev_is_keyboard(const char *devnode); +uint32_t evdev_device_id(const char *devnode); +const char *evdev_device_name(const char *devnode); #endif