From 733335e27222775076ea0f0d531218fac2437583 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Mon, 22 Sep 2025 16:27:01 -0400 Subject: [PATCH] x11: Apply remapping to XInput2 mouse button events from slave devices Slave pointer devices report raw button values, while the master pointer device reports button values with remapping applied. Manually apply the remapping table to slave device buttons to eliminate multiple button events from one press, and allow button remapping to function when relative mode is active. --- src/video/x11/SDL_x11events.c | 13 +++++++---- src/video/x11/SDL_x11sym.h | 1 + src/video/x11/SDL_x11xinput2.c | 41 +++++++++++++++++++++++++++++++++- src/video/x11/SDL_x11xinput2.h | 1 + 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index e0254fbfbe..1299be7368 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -1400,14 +1400,19 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) X11_ReconcileKeyboardState(_this); } else if (xevent->type == MappingNotify) { - if (!videodata->keyboard.xkb_enabled) { - // Has the keyboard layout changed? - const int request = xevent->xmapping.request; + const int request = xevent->xmapping.request; + if (request == MappingPointer) { #ifdef DEBUG_XEVENTS SDL_Log("window 0x%lx: MappingNotify!", xevent->xany.window); #endif - if ((request == MappingKeyboard) || (request == MappingModifier)) { + X11_Xinput2UpdatePointerMapping(_this); + } else if (!videodata->keyboard.xkb_enabled) { + // Has the keyboard layout changed? +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: MappingNotify!", xevent->xany.window); +#endif + if (request == MappingKeyboard || request == MappingModifier) { X11_XRefreshKeyboardMapping(&xevent->xmapping); } diff --git a/src/video/x11/SDL_x11sym.h b/src/video/x11/SDL_x11sym.h index 9ee239c3f1..ea0b42aae6 100644 --- a/src/video/x11/SDL_x11sym.h +++ b/src/video/x11/SDL_x11sym.h @@ -171,6 +171,7 @@ SDL_X11_SYM(char*,XResourceManagerString,(Display *display)) SDL_X11_SYM(XrmDatabase,XrmGetStringDatabase,(char *data)) SDL_X11_SYM(void,XrmDestroyDatabase,(XrmDatabase db)) SDL_X11_SYM(Bool,XrmGetResource,(XrmDatabase db, char* str_name, char* str_class, char **str_type_return, XrmValue *)) +SDL_X11_SYM(int,XGetPointerMapping,(Display *a, unsigned char *b, unsigned int c)) #ifdef SDL_VIDEO_DRIVER_X11_XFIXES SDL_X11_MODULE(XFIXES) diff --git a/src/video/x11/SDL_x11xinput2.c b/src/video/x11/SDL_x11xinput2.c index b251d0e9cc..4a5e3aca4e 100644 --- a/src/video/x11/SDL_x11xinput2.c +++ b/src/video/x11/SDL_x11xinput2.c @@ -50,6 +50,10 @@ static Atom xinput2_rel_y_atom; static Atom xinput2_abs_x_atom; static Atom xinput2_abs_y_atom; +// Pointer button remapping table +static unsigned char *xinput2_pointer_button_map; +static int xinput2_pointer_button_map_size; + #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO typedef struct { @@ -328,6 +332,7 @@ bool X11_InitXinput2(SDL_VideoDevice *_this) X11_XISelectEvents(data->display, DefaultRootWindow(data->display), &eventmask, 1); X11_Xinput2UpdateDevices(_this); + X11_Xinput2UpdatePointerMapping(_this); return true; #else @@ -337,6 +342,10 @@ bool X11_InitXinput2(SDL_VideoDevice *_this) void X11_QuitXinput2(SDL_VideoDevice *_this) { + SDL_free(xinput2_pointer_button_map); + xinput2_pointer_button_map = NULL; + xinput2_pointer_button_map_size = 0; + #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO for (int i = 0; i < scrollable_device_count; ++i) { SDL_free(scrollable_devices[i].scroll_info); @@ -347,6 +356,29 @@ void X11_QuitXinput2(SDL_VideoDevice *_this) #endif } +void X11_Xinput2UpdatePointerMapping(SDL_VideoDevice *_this) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + if (X11_Xinput2IsInitialized()) { + SDL_VideoData *vid = _this->internal; + + SDL_free(xinput2_pointer_button_map); + xinput2_pointer_button_map = NULL; + xinput2_pointer_button_map_size = 0; + + xinput2_pointer_button_map_size = X11_XGetPointerMapping(vid->display, NULL, 0); + if (xinput2_pointer_button_map_size) { + xinput2_pointer_button_map = SDL_calloc(xinput2_pointer_button_map_size, sizeof(unsigned char)); + if (xinput2_pointer_button_map) { + xinput2_pointer_button_map_size = X11_XGetPointerMapping(vid->display, xinput2_pointer_button_map, xinput2_pointer_button_map_size); + } else { + xinput2_pointer_button_map_size = 0; + } + } + } +#endif +} + #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 // xi2 device went away? take it out of the list. static void xinput2_remove_device_info(SDL_VideoData *videodata, const int device_id) @@ -562,7 +594,7 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) { const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; X11_PenHandle *pen = X11_FindPenByDeviceID(xev->sourceid); - const int button = xev->detail; + int button = xev->detail; const bool down = (cookie->evtype == XI_ButtonPress); #if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH) bool pointer_emulated = (xev->flags & XIPointerEmulated) != 0; @@ -587,6 +619,13 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, xev->event); int x_ticks = 0, y_ticks = 0; + // Slave pointer devices don't have button remapping applied automatically, so do it manually. + if (xev->deviceid != videodata->xinput_master_pointer_device) { + if (button <= xinput2_pointer_button_map_size) { + button = xinput2_pointer_button_map[button - 1]; + } + } + /* Discard wheel events from "Master" devices to avoid duplicates, * as coarse wheel events are stateless and can't be deduplicated. * diff --git a/src/video/x11/SDL_x11xinput2.h b/src/video/x11/SDL_x11xinput2.h index 91c33aa654..f05e96cd1e 100644 --- a/src/video/x11/SDL_x11xinput2.h +++ b/src/video/x11/SDL_x11xinput2.h @@ -40,5 +40,6 @@ extern void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window); extern void X11_Xinput2UngrabTouch(SDL_VideoDevice *_this, SDL_Window *window); extern bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *window); extern void X11_Xinput2UpdateDevices(SDL_VideoDevice *_this); +extern void X11_Xinput2UpdatePointerMapping(SDL_VideoDevice *_this); #endif // SDL_x11xinput2_h_