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