[PATCH 0/2] MR9853: Draft: winebus: Add support for `EV_BTN` to udev
Adds support for new EV_BTN event that'll be used in place of EV_KEY for joysticks and maybe gamepads. This event doesn't require any mapping as it simply passes through button number as the code (starting from 1 just like HID) and it's value. It was needed to cleanly support joysticks and simracing/simflight hardware that defines more than 80 buttons. Some HW used hacks in the drivers that assigned random usages found lower then TRIGGER_HAPPPY range. WIP work dependent on upstream patches to LKML which I'll link later. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9853
From: Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com> --- dlls/winebus.sys/bus_udev.c | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/dlls/winebus.sys/bus_udev.c b/dlls/winebus.sys/bus_udev.c index 1ad4653fd8b..ecb610fa3fb 100644 --- a/dlls/winebus.sys/bus_udev.c +++ b/dlls/winebus.sys/bus_udev.c @@ -54,6 +54,10 @@ # ifndef SYN_DROPPED # define SYN_DROPPED 3 # endif +# ifndef EV_BTN +# define EV_BTN 0x06 +# define EVIOCGBTNCNT 0 +# endif #endif #ifndef BUS_BLUETOOTH @@ -179,10 +183,12 @@ struct lnxev_info struct input_id id; char name[MAX_PATH]; char uniq[MAX_PATH]; + BYTE ev[(EV_CNT) + 7 / 8]; BYTE abs[(ABS_CNT + 7) / 8]; BYTE rel[(REL_CNT + 7) / 8]; BYTE key[(KEY_CNT + 7) / 8]; BYTE ff[(FF_CNT + 7) / 8]; + ULONG num_button; }; struct lnxev_device @@ -198,6 +204,7 @@ struct lnxev_device int hat_count; int button_count; BOOL is_gamepad; + BOOL ev_btn; pthread_cond_t haptics_cond; pthread_t haptics_thread; @@ -662,7 +669,13 @@ static BOOL set_report_from_event(struct unix_device *iface, struct input_event case EV_MSC: return FALSE; #endif + case EV_BTN: + if (impl->ev_btn) + hid_device_set_button(iface, ie->code - 1, ie->value); + return FALSE; case EV_KEY: + if (impl->ev_btn) + return FALSE; if (!(button = impl->button_map[ie->code])) return FALSE; if (impl->is_gamepad && !impl->hat_count) { @@ -1207,13 +1220,14 @@ static NTSTATUS lnxev_device_create(struct udev_device *dev, int fd, const char #ifdef HAS_PROPER_INPUT_HEADER static const WCHAR evdev[] = {'e','v','d','e','v',0}; static const WCHAR zeros[] = {'0','0','0','0',0}; - int axis_count = 0, button_count = 0; + unsigned int axis_count = 0, button_count = 0; struct lnxev_info info = {0}; struct lnxev_device *impl; if (ioctl(fd, EVIOCGID, &info.id) == -1) memset(&info.id, 0, sizeof(info.id)); if (ioctl(fd, EVIOCGNAME(sizeof(info.name) - 1), info.name) == -1) memset(info.name, 0, sizeof(info.name)); if (ioctl(fd, EVIOCGUNIQ(sizeof(info.uniq) - 1), info.uniq) == -1) memset(info.uniq, 0, sizeof(info.uniq)); + if (ioctl(fd, EVIOCGBIT(0, sizeof(info.ev)), info.ev) == -1) memset(info.ev, 0, sizeof(info.ev)); if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(info.abs)), info.abs) == -1) memset(info.abs, 0, sizeof(info.abs)); if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(info.rel)), info.rel) == -1) memset(info.rel, 0, sizeof(info.rel)); if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(info.key)), info.key) == -1) memset(info.key, 0, sizeof(info.key)); @@ -1262,10 +1276,23 @@ static NTSTATUS lnxev_device_create(struct udev_device *dev, int fd, const char impl->hat_map[i - ABS_HAT0X + 1] = impl->hat_count; } - for (int i = BTN_MISC; i < KEY_MAX; i++) + if (test_bit(info.ev, EV_BTN)) { - if (!test_bit(info.key, i)) continue; - impl->button_map[i] = ++impl->button_count; + if (ioctl(fd, EVIOCGBTNCNT, &button_count)) button_count = 0; + else + { + impl->button_count = button_count; + impl->ev_btn = TRUE; + } + } + + if (!impl->ev_btn) + { + for (int i = BTN_MISC; i < KEY_MAX; i++) + { + if (!test_bit(info.key, i)) continue; + impl->button_map[i] = ++impl->button_count; + } } if (is_xbox_gamepad(desc.vid, desc.pid)) desc.is_gamepad = TRUE; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9853
From: Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com> --- dlls/winebus.sys/bus_udev.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dlls/winebus.sys/bus_udev.c b/dlls/winebus.sys/bus_udev.c index ecb610fa3fb..6b8762db9b4 100644 --- a/dlls/winebus.sys/bus_udev.c +++ b/dlls/winebus.sys/bus_udev.c @@ -1296,7 +1296,8 @@ static NTSTATUS lnxev_device_create(struct udev_device *dev, int fd, const char } if (is_xbox_gamepad(desc.vid, desc.pid)) desc.is_gamepad = TRUE; - else if (axis_count == 6 && button_count >= (impl->hat_count ? 10 : 14)) desc.is_gamepad = TRUE; + else if (test_bit(info.key, BTN_GAMEPAD) && axis_count == 6 && button_count >= (impl->hat_count ? 10 : 14)) + desc.is_gamepad = TRUE; if ((impl->is_gamepad = desc.is_gamepad)) { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9853
I thought that maybe instead of redefining missing usages, a better way would be to make `#ifndef` gates that would remove the code related to this? Without `EV_BTN` `EVIOCGBTNCNT` would never fire BUT maybe defining it as a random `0` isn't the best idea. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9853#note_126408
Rémi Bernon (@rbernon) commented about dlls/winebus.sys/bus_udev.c:
impl->hat_map[i - ABS_HAT0X + 1] = impl->hat_count; }
+ if (test_bit(info.ev, EV_BTN)) + { + if (ioctl(fd, EVIOCGBTNCNT, &button_count)) button_count = 0; + else + { + impl->button_count = button_count; + impl->ev_btn = TRUE; + } + } + + if (!impl->ev_btn) + {
Could we just fill button_map sequentially if EV_BTN/EVIOCGBTNCNT are there? I think this would make the ev_btn flag unnecessary and allow to squash the set_report_from_event cases together. We could/should probably make sure we're not considering the device as a gamepad at the same time, though this might be guaranteed with the next change, which should be reordered first maybe. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9853#note_126441
Rémi Bernon (@rbernon) commented about dlls/winebus.sys/bus_udev.c:
struct input_id id; char name[MAX_PATH]; char uniq[MAX_PATH]; + BYTE ev[(EV_CNT) + 7 / 8]; BYTE abs[(ABS_CNT + 7) / 8]; BYTE rel[(REL_CNT + 7) / 8]; BYTE key[(KEY_CNT + 7) / 8]; BYTE ff[(FF_CNT + 7) / 8]; + ULONG num_button;
This isn't used. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9853#note_126440
On Tue Jan 6 10:47:42 2026 +0000, Rémi Bernon wrote:
Could we just fill button_map sequentially if EV_BTN/EVIOCGBTNCNT are there? I think this would make the ev_btn flag unnecessary and allow to squash the set_report_from_event cases together. We could/should probably make sure we're not considering the device as a gamepad at the same time, though this might be guaranteed with the next change, which should be reordered first maybe. Thing is, we don't need the button map at all. Ideally, it would be removed some time down the line. The notion of named usages for generic buttons was wrong that 20 or so years ago and we're still paying the price.
That's why I made it so the button map is completely circumvented. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9853#note_126446
On Tue Jan 6 14:47:25 2026 +0000, Tomasz Pakuła wrote:
Thing is, we don't need the button map at all. Ideally, it would be removed some time down the line. The notion of named usages for generic buttons was wrong that 20 or so years ago and we're still paying the price. That's why I made it so the button map is completely circumvented. I don't want the code to still rely on EV_KEY at all if EV_BTN is fired plus the usages could run out and the map, again, would be useless. It IS possible to create a device with, say, 1024 buttons and KEY_MAX is lower than that (0x2ff = 767). Possible but I don't see it being removed anytime soon, it would require every device to switch to the new EV_BTN, and we would lose compatibility with older kernels.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9853#note_126910
participants (3)
-
Rémi Bernon (@rbernon) -
Tomasz Pakuła -
Tomasz Pakuła (@Lawstorant)