From 75a65e05e1a5d6b2199eefc3edc1f9ed9bd684fa Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Wed, 15 Apr 2026 11:24:20 -0400 Subject: [PATCH] x11: Use XInput2 events to pass through the keyboard ID to core key events XInput2 keyboard handling has limitations: system keys that shouldn't be passed through when the keyboard isn't grabbed can be seen, and the text input system needs key events to flow through the X server to function properly (passing synthesized events through the filter function is not sufficient and doesn't work with non-Latin character sets). The primary bit of information missing from the core X key events that XInput2 provides is the source device, so use the XInput2 slave keyboard device events to store that value, and apply it to core X key events with the same serial. XInput2 events always arrive before core events so this works universally. --- src/video/x11/SDL_x11events.c | 8 ++++++-- src/video/x11/SDL_x11video.h | 2 ++ src/video/x11/SDL_x11xinput2.c | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 15af66124f..961c8b08e2 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -1834,12 +1834,16 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) case KeyPress: case KeyRelease: { + SDL_KeyboardID keyboardID = SDL_GLOBAL_KEYBOARD_ID; if (data->xinput2_keyboard_enabled) { - // This input is being handled by XInput2 + // This input is being handled by XInput2. break; + } else if (xevent->xkey.serial == videodata->xinput_last_key_serial) { + // Use the device ID from the XInput2 event if the serials match. + keyboardID = videodata->xinput_last_keyboard_device; } - X11_HandleKeyEvent(_this, data, SDL_GLOBAL_KEYBOARD_ID, xevent); + X11_HandleKeyEvent(_this, data, keyboardID, xevent); } break; case MotionNotify: diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h index 01c2e5b9fb..7bc858d98b 100644 --- a/src/video/x11/SDL_x11video.h +++ b/src/video/x11/SDL_x11video.h @@ -140,6 +140,8 @@ struct SDL_VideoData SDL_XInput2DeviceInfo *mouse_device_info; unsigned long xinput_last_button_serial; + unsigned long xinput_last_key_serial; + int xinput_last_keyboard_device; int xinput_master_pointer_device; bool xinput_hierarchy_changed; diff --git a/src/video/x11/SDL_x11xinput2.c b/src/video/x11/SDL_x11xinput2.c index d6c704e3f6..3a94c6a1f0 100644 --- a/src/video/x11/SDL_x11xinput2.c +++ b/src/video/x11/SDL_x11xinput2.c @@ -303,6 +303,12 @@ bool X11_InitXinput2(SDL_VideoDevice *_this) eventmask.mask_len = sizeof(mask); eventmask.mask = mask; +#ifndef USE_XINPUT2_KEYBOARD + // If not using the full keyboard handling, register for keypresses to get the event source devices. + XISetMask(mask, XI_KeyPress); + XISetMask(mask, XI_KeyRelease); +#endif + XISetMask(mask, XI_HierarchyChanged); X11_XISelectEvents(data->display, DefaultRootWindow(data->display), &eventmask, 1); @@ -535,6 +541,8 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) case XI_KeyRelease: { const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + +#ifdef XINPUT2_USE_KEYBOARD SDL_WindowData *windowdata = X11_FindWindow(videodata, xev->event); XEvent xevent; @@ -564,6 +572,13 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) xevent.xkey.same_screen = 1; X11_HandleKeyEvent(_this, windowdata, (SDL_KeyboardID)xev->sourceid, &xevent); +#else + /* Keys are handled through core X events, however, note the device ID and + * associated serial, so that the source device ID can be passed through. + */ + videodata->xinput_last_key_serial = xev->serial; + videodata->xinput_last_keyboard_device = xev->sourceid; +#endif } break; case XI_RawButtonPress: