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.
This commit is contained in:
Frank Praznik
2025-09-22 16:27:01 -04:00
parent 4363582e6d
commit 733335e272
4 changed files with 51 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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