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 <stefan11111@shitposting.expert>
This commit is contained in:
stefan11111
2026-01-11 21:29:37 +02:00
committed by Enrico Weigelt
parent a2c58b9934
commit cc152cf964
4 changed files with 372 additions and 21 deletions

View File

@@ -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);

View File

@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: MIT OR X11
*
* Copyright © 2026 stefan11111 <stefan11111@shitposting.expert>
*/
#ifndef __EVDEH_H__
#define __EVDEH_H__
char* EvdevDefaultPtr(void);
char* EvdevDefaultKbd(void);
#endif /* __EVDEH_H__ */

View File

@@ -0,0 +1,355 @@
/* SPDX-License-Identifier: MIT OR X11
*
* Copyright © 2026 stefan11111 <stefan11111@shitposting.expert>
*/
#include <kdrive-config.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#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);
}

View File

@@ -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