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.
This commit is contained in:
Frank Praznik
2026-04-15 11:24:20 -04:00
parent 63901401ca
commit 75a65e05e1
3 changed files with 23 additions and 2 deletions

View File

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

View File

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

View File

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