From cc152cf96446b61541964f9f8e095cc73aec7d2f Mon Sep 17 00:00:00 2001 From: stefan11111 Date: Sun, 11 Jan 2026 21:29:37 +0200 Subject: [PATCH] kdrive/linux: Improve evdev driver autodetection It should now work out of the box on most setups with one mouse and one keyboard. It probably still breaks on some single mouse and keyboard setups, and on setups with more than one mouse or keyboard. Signed-off-by: stefan11111 --- hw/kdrive/linux/{ => evdev}/evdev.c | 23 +- hw/kdrive/linux/evdev/evdev.h | 12 + hw/kdrive/linux/evdev/evdev_autodetect.c | 355 +++++++++++++++++++++++ hw/kdrive/linux/meson.build | 3 +- 4 files changed, 372 insertions(+), 21 deletions(-) rename hw/kdrive/linux/{ => evdev}/evdev.c (94%) create mode 100644 hw/kdrive/linux/evdev/evdev.h create mode 100644 hw/kdrive/linux/evdev/evdev_autodetect.c diff --git a/hw/kdrive/linux/evdev.c b/hw/kdrive/linux/evdev/evdev.c similarity index 94% rename from hw/kdrive/linux/evdev.c rename to hw/kdrive/linux/evdev/evdev.c index b265367ad4..0d3cbf4791 100644 --- a/hw/kdrive/linux/evdev.c +++ b/hw/kdrive/linux/evdev/evdev.c @@ -28,6 +28,7 @@ #include "inputstr.h" #include "scrnintstr.h" #include "kdrive.h" +#include "evdev.h" #define NUM_EVENTS 128 #define ABS_UNSET -65535 @@ -183,21 +184,11 @@ EvdevPtrRead(int evdevPort, void *closure) } } -#define NUM_DEFAULT_EVDEV 32 - static Status EvdevPtrInit(KdPointerInfo * pi) { if (!pi->path) { - char default_device[] = "/dev/input/eventxx"; - for (int i = 0; i < NUM_DEFAULT_EVDEV; i++) { - sprintf(default_device, "/dev/input/event%d", i); - fd = open(default_device, O_RDWR); - if (fd >= 0) { - pi->path = strdup(default_device); - break; - } - } + pi->path = EvdevDefaultPtr(); } else { int fd = open(pi->path, O_RDWR); @@ -364,15 +355,7 @@ static Status EvdevKbdInit(KdKeyboardInfo * ki) { if (!ki->path) { - char default_device[] = "/dev/input/eventxx"; - for (int i = 0; i < NUM_DEFAULT_EVDEV; i++) { - sprintf(default_device, "/dev/input/event%d", i); - fd = open(default_device, O_RDWR); - if (fd >= 0) { - ki->path = strdup(default_device); - break; - } - } + ki->path = EvdevDefaultKbd(); } else { int fd = open(ki->path, O_RDWR); diff --git a/hw/kdrive/linux/evdev/evdev.h b/hw/kdrive/linux/evdev/evdev.h new file mode 100644 index 0000000000..bdfbfd364c --- /dev/null +++ b/hw/kdrive/linux/evdev/evdev.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: MIT OR X11 + * + * Copyright © 2026 stefan11111 + */ + +#ifndef __EVDEH_H__ +#define __EVDEH_H__ + +char* EvdevDefaultPtr(void); +char* EvdevDefaultKbd(void); + +#endif /* __EVDEH_H__ */ diff --git a/hw/kdrive/linux/evdev/evdev_autodetect.c b/hw/kdrive/linux/evdev/evdev_autodetect.c new file mode 100644 index 0000000000..42b1ad072d --- /dev/null +++ b/hw/kdrive/linux/evdev/evdev_autodetect.c @@ -0,0 +1,355 @@ +/* SPDX-License-Identifier: MIT OR X11 + * + * Copyright © 2026 stefan11111 + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "kdrive.h" +#include "evdev.h" + +#define EVDEV_FMT "/dev/input/event%d" + +#define PROC_DEVICES "/proc/bus/input/devices" + +#define PHYS_MAX 64 /* Busid + device id */ + +#define MOUSE_EV (1 << 2) +#define KBD_EV 0x120013 + +enum { + EVDEV_KEYBOARD = 0, + EVDEV_MOUSE = 1, +}; + +#define NUM_FALLBACK_EVDEV 32 + +/* Simple fallback that was already here */ +static char* +FallbackEvdevCheck(void) +{ + char fallback_dev[] = "/dev/input/eventxx"; + + for (int i = 0; i < NUM_FALLBACK_EVDEV; i++) { + sprintf(fallback_dev, EVDEV_FMT, i); + int fd = open(fallback_dev, O_RDWR); + if (fd >= 0) { + close(fd); + return strdup(fallback_dev); + } + } + + return NULL; +} + +/* All numbers read are in base 16 */ +static inline uint64_t +read_val(const char *val) +{ + return strtol(val, NULL, 16); +} + +typedef struct { + uint32_t Bus; /* Bus= */ + uint32_t Vendor; /* Vendor= */ + uint32_t Product; /* Product= */ + uint32_t Version; /* Version= */ +} EvdevOptionalInfo; + +typedef struct { +/** + * Info that should be unique across physical devices, + * but not across logical devices. + */ + EvdevOptionalInfo info; /* I: */ + /* char *Name; */ /* N: Name = */ + char Phys[PHYS_MAX]; /* P: Phys = */ + /* char *Sysfs; */ /* S: Sysfs= */ + uint64_t Uniq; /* U: Uniq= */ + + int EventNo; /* H: Handlers=... eventxx ... */ + + /* If checking for these 2 ever causes problems, remove them */ + int is_mouse; /* H: Handlers=... mousexx ... */ + int is_kbd; /* H: handlers=... kbd ... */ + + uint64_t EV; /* B: EV= */ + int is_read; +} EventDevice; + +static EventDevice DefaultPtr = {0}; +static EventDevice DefaultKbd = {0}; + +static inline void +ReadOptInfo(EvdevOptionalInfo *dst, const char* data) +{ + const char *val = NULL; + + val = strstr(data, "Bus="); + if (val) { + val += sizeof("Bus=") - 1; + dst->Bus = read_val(val); + } + + val = strstr(data, "Vendor="); + if (val) { + val += sizeof("Vendor=") - 1; + dst->Vendor = read_val(val); + } + + val = strstr(data, "Product="); + if (val) { + val += sizeof("Product=") - 1; + dst->Product = read_val(val); + } + + val = strstr(data, "Version="); + if (val) { + val += sizeof("Version=") - 1; + dst->Version = read_val(val); + } +} + +static inline void +ReadPhys(char *dst, const char* data) +{ + char *p = dst; + + data = strstr(data, "Phys="); + if (!data) { + return; + } + + data += sizeof("Phys=") - 1; + while (*data && *data != '/' && p - dst < PHYS_MAX - 1) { + *p = *data; + p++; + data++; + } + *p = '\0'; +} + +static inline void +ReadUniq(uint64_t *dst, const char* data) +{ + data = strstr(data, "Uniq="); + if (!data) { + return; + } + + data += sizeof("Uniq=") - 1; + *dst = read_val(data); +} + +static inline void +ReadHandlers(EventDevice *dst, const char* data) +{ + dst->is_mouse = !!strstr(data, "mouse"); + dst->is_kbd = !!strstr(data, "kbd"); + + data = strstr(data, "event"); + if (!data) { + /* If this one is missing, we really can't do anything */ + dst->EventNo = -1; + } + + data += sizeof("event") - 1; + + /* This one is base10 */ + dst->EventNo = strtol(data, NULL, 10); +} + +static inline void +ReadEV(uint64_t *EV, const char* data) +{ + data = strstr(data, "EV="); + if (!data) { + return; + } + + data += sizeof("EV=") - 1; + *EV = read_val(data); +} + +static Bool +ReadEvdev(EventDevice *dst, FILE *f) +{ + for (;;) { + char *line = NULL; + char *end = NULL; + char *data = NULL; + size_t unused = 0; + + if (getline(&line, &unused, f) < 0) { + free(line); + return FALSE; + } + end = strchr(line, '\n'); + if (end) { + *end = '\0'; + } + + if (line[0] == '\0') { + free(line); + dst->is_read = TRUE; + return TRUE; + } + + if (line[1] != ':' || + line[2] == '\0' || line[3] == '\0') { + /* Skip this line */ + free(line); + continue; + } + + data = line + 3; + + switch (line[0]) { + case 'I': /* Optional info I: */ + ReadOptInfo(&dst->info, data); + break; + case 'P': /* P: Phys= */ + ReadPhys(dst->Phys, data); + break; + case 'U': /* U; Uniq= */ + ReadUniq(&dst->Uniq, data); + break; + case 'H': /* H: Handlers= */ + ReadHandlers(dst, data); + break; + case 'B': /* B: ... */ + ReadEV(&dst->EV, data); + break; + } + + free(line); + } +} + +static inline Bool +EvdevIsKbd(EventDevice *dev) +{ + return dev->is_kbd && ((dev->EV & KBD_EV) == KBD_EV); +} + +static inline Bool +EvdevIsPtr(EventDevice *dev) +{ + return dev->is_mouse && ((dev->EV & MOUSE_EV) == MOUSE_EV); +} + +static Bool +EvdevDifferentDevices(EventDevice *a, EventDevice *b) +{ +#define IS_DIFFERENT(x, y) ((x) && (y) && (x) != (y)) +#define IS_DIFF(f) if (IS_DIFFERENT(a->f, b->f)) { return TRUE; } + + if (!a->is_read || !b->is_read) { + return TRUE; + } + + IS_DIFF(Uniq); + + IS_DIFF(info.Bus); + IS_DIFF(info.Vendor); + IS_DIFF(info.Product); + IS_DIFF(info.Version); + + if (a->Phys[0] && b->Phys[0] && strcmp(a->Phys, b->Phys)) { + return TRUE; + } + + return FALSE; +} + +static char* +EvdevDefaultDevice(int type) +{ + char *ret = NULL; + FILE *f = NULL; + EventDevice read_dev = {0}; + + EventDevice *desired = (type == EVDEV_KEYBOARD) ? + &DefaultKbd : &DefaultPtr; + + EventDevice *other = (type == EVDEV_KEYBOARD) ? + &DefaultPtr : &DefaultKbd; + + if (desired->is_read) { + if (asprintf(&ret, EVDEV_FMT, desired->EventNo) < 0) { + return FallbackEvdevCheck(); + } + return ret; + } + + f = fopen(PROC_DEVICES, "r"); + if (!f) { + return FallbackEvdevCheck(); + } + + for (;;) { + if (feof(f)) { + fclose(f); + return FallbackEvdevCheck(); + } + + memset(&read_dev, 0, sizeof(read_dev)); + + if (!ReadEvdev(&read_dev, f)) { + fclose(f); + return FallbackEvdevCheck(); + } + + if (read_dev.EventNo == -1) { + continue; + } + + if (type == EVDEV_KEYBOARD && !EvdevIsKbd(&read_dev)) { + continue; + } + + if (type == EVDEV_MOUSE && !EvdevIsPtr(&read_dev)) { + continue; + } + + /** + * Sometimes, modern mice advertise themselved as keyboards. + * As such, we have to check that the mouse and keyboard + * are separate physical devices. + * + * Keyboards rarely advertise themselves as mice, + * but it doesn't hurt to check them too. + */ + if (EvdevDifferentDevices(&read_dev, other)) { + memcpy(desired, &read_dev, sizeof(read_dev)); + fclose(f); + if (asprintf(&ret, EVDEV_FMT, desired->EventNo) < 0) { + return FallbackEvdevCheck(); + } + return ret; + } + } + + /* Unreachable */ + fclose(f); + return FallbackEvdevCheck(); +} + +char* +EvdevDefaultKbd(void) +{ + return EvdevDefaultDevice(EVDEV_KEYBOARD); +} + +char* +EvdevDefaultPtr(void) +{ + return EvdevDefaultDevice(EVDEV_MOUSE); +} diff --git a/hw/kdrive/linux/meson.build b/hw/kdrive/linux/meson.build index 53085036c1..5b4f49538a 100644 --- a/hw/kdrive/linux/meson.build +++ b/hw/kdrive/linux/meson.build @@ -15,7 +15,8 @@ if build_kdrive_mouse endif if build_kdrive_evdev - srcs_linux += 'evdev.c' + srcs_linux += 'evdev/evdev.c' + srcs_linux += 'evdev/evdev_autodetect.c' endif if build_kdrive_tslib