SDL: x11: fixed mouse and keyboard input when XInput2 isn't available

From c47f9b9b547cc521985a7b33cdbb34e7cb8a9339 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 26 Mar 2024 13:01:00 -0700
Subject: [PATCH] x11: fixed mouse and keyboard input when XInput2 isn't
 available

---
 src/video/x11/SDL_x11events.c  |  22 +++++-
 src/video/x11/SDL_x11mouse.c   |   5 +-
 src/video/x11/SDL_x11window.h  |   1 +
 src/video/x11/SDL_x11xinput2.c | 123 +++++++++++++++++----------------
 src/video/x11/SDL_x11xinput2.h |   2 +-
 5 files changed, 89 insertions(+), 64 deletions(-)

diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index 02b7822cf54be..246f565e3000a 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -1525,15 +1525,24 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
        - ButtonPress
        - ButtonRelease
        XInput2 has more precise information, e.g., to distinguish different input devices. */
-#ifndef SDL_VIDEO_DRIVER_X11_XINPUT2
     case KeyPress:
     case KeyRelease:
     {
+        if (data->using_xinput2) {
+            // This input is being handled by XInput2
+            break;
+        }
+
         X11_HandleKeyEvent(_this, data, SDL_GLOBAL_KEYBOARD_ID, xevent);
     } break;
 
     case MotionNotify:
     {
+        if (data->using_xinput2) {
+            // This input is being handled by XInput2
+            break;
+        }
+
         SDL_Mouse *mouse = SDL_GetMouse();
         if (!mouse->relative_mode || mouse->relative_mode_warp) {
 #ifdef DEBUG_MOTION
@@ -1547,15 +1556,24 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
 
     case ButtonPress:
     {
+        if (data->using_xinput2) {
+            // This input is being handled by XInput2
+            break;
+        }
+
         X11_HandleButtonPress(_this, data, SDL_GLOBAL_MOUSE_ID, xevent->xbutton.button,
                               xevent->xbutton.x, xevent->xbutton.y, xevent->xbutton.time);
     } break;
 
     case ButtonRelease:
     {
+        if (data->using_xinput2) {
+            // This input is being handled by XInput2
+            break;
+        }
+
         X11_HandleButtonRelease(_this, data, SDL_GLOBAL_MOUSE_ID, xevent->xbutton.button);
     } break;
-#endif /* !SDL_VIDEO_DRIVER_X11_XINPUT2 */
 
     case PropertyNotify:
     {
diff --git a/src/video/x11/SDL_x11mouse.c b/src/video/x11/SDL_x11mouse.c
index b9876a703bc77..079332ad7fe73 100644
--- a/src/video/x11/SDL_x11mouse.c
+++ b/src/video/x11/SDL_x11mouse.c
@@ -363,7 +363,10 @@ static int X11_WarpMouseGlobal(float x, float y)
 
 static int X11_SetRelativeMouseMode(SDL_bool enabled)
 {
-    return X11_Xinput2IsInitialized() ? 0 : SDL_Unsupported();
+    if (!X11_Xinput2IsInitialized()) {
+        return SDL_Unsupported();
+    }
+    return 0;
 }
 
 static int X11_CaptureMouse(SDL_Window *window)
diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h
index 454e92a56a935..7f5c0c682d5ea 100644
--- a/src/video/x11/SDL_x11window.h
+++ b/src/video/x11/SDL_x11window.h
@@ -59,6 +59,7 @@ struct SDL_WindowData
     int border_right;
     int border_top;
     int border_bottom;
+    SDL_bool using_xinput2;
     SDL_bool mouse_grabbed;
     Uint64 last_focus_event_time;
     PendingFocusEnum pending_focus;
diff --git a/src/video/x11/SDL_x11xinput2.c b/src/video/x11/SDL_x11xinput2.c
index cdc741f143cbb..a8160f824eed7 100644
--- a/src/video/x11/SDL_x11xinput2.c
+++ b/src/video/x11/SDL_x11xinput2.c
@@ -288,18 +288,32 @@ static void xinput2_pen_ensure_window(SDL_VideoDevice *_this, const SDL_Pen *pen
 }
 #endif
 
-int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
+void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
 {
 #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
     SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
 
     if (cookie->extension != xinput2_opcode) {
-        return 0;
+        return;
     }
+
     switch (cookie->evtype) {
+    case XI_HierarchyChanged:
+    {
+        const XIHierarchyEvent *hierev = (const XIHierarchyEvent *)cookie->data;
+        int i;
+        for (i = 0; i < hierev->num_info; i++) {
+            if (hierev->info[i].flags & XISlaveRemoved) {
+                xinput2_remove_device_info(videodata, hierev->info[i].deviceid);
+            }
+        }
+        videodata->xinput_hierarchy_changed = SDL_TRUE;
+    } break;
+
     case XI_PropertyEvent:
     case XI_DeviceChanged:
     {
+        // FIXME: We shouldn't rescan all devices for pen changes every time a property or active slave changes
         X11_InitPen(_this);
     } break;
 
@@ -327,18 +341,18 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
 
         videodata->global_mouse_changed = SDL_TRUE;
         if (is_pen) {
-            return 0; /* Pens check for XI_Motion instead */
+            break; /* Pens check for XI_Motion instead */
         }
 
         /* Non-pen: */
 
         if (!mouse->relative_mode || mouse->relative_mode_warp) {
-            return 0;
+            break;
         }
 
         devinfo = xinput2_get_device_info(videodata, rawev->deviceid);
         if (!devinfo) {
-            return 0; /* oh well. */
+            break; /* oh well. */
         }
 
         parse_valuators(rawev->raw_values, rawev->valuators.mask,
@@ -355,19 +369,6 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
         SDL_SendMouseMotion(0, mouse->focus, (SDL_MouseID)rawev->sourceid, SDL_TRUE, (float)processed_coords[0], (float)processed_coords[1]);
         devinfo->prev_coords[0] = coords[0];
         devinfo->prev_coords[1] = coords[1];
-        return 1;
-    } break;
-
-    case XI_HierarchyChanged:
-    {
-        const XIHierarchyEvent *hierev = (const XIHierarchyEvent *)cookie->data;
-        int i;
-        for (i = 0; i < hierev->num_info; i++) {
-            if (hierev->info[i].flags & XISlaveRemoved) {
-                xinput2_remove_device_info(videodata, hierev->info[i].deviceid);
-            }
-        }
-        videodata->xinput_hierarchy_changed = SDL_TRUE;
     } break;
 
     case XI_KeyPress:
@@ -379,7 +380,7 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
 
         if (xev->deviceid != xev->sourceid) {
             /* Discard events from "Master" devices to avoid duplicates. */
-            return 1;
+            break;
         }
 
         if (cookie->evtype == XI_KeyPress) {
@@ -436,7 +437,7 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
                     SDL_WindowData *windowdata = X11_FindWindow(_this, xev->event);
                     if (windowdata && X11_TriggerHitTestAction(_this, windowdata, pen->last.x, pen->last.y)) {
                         SDL_SendWindowEvent(windowdata->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
-                        return 1; /* Don't pass on this event */
+                        break; /* Don't pass on this event */
                     }
                 }
                 SDL_SendPenTipEvent(0, pen->header.id,
@@ -446,14 +447,13 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
                                   pressed ? SDL_PRESSED : SDL_RELEASED,
                                   button - 1);
             }
-            return 1;
         } else {
             /* Otherwise assume a regular mouse */
             SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, xev->event);
 
             if (xev->deviceid != xev->sourceid) {
                 /* Discard events from "Master" devices to avoid duplicates. */
-                return 1;
+                break;
             }
 
             if (pressed) {
@@ -465,21 +465,23 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
         }
     } break;
 
-        /* Register to receive XI_Motion (which deactivates MotionNotify), so that we can distinguish
-           real mouse motions from synthetic ones, for multitouch and pen support. */
+    /* Register to receive XI_Motion (which deactivates MotionNotify), so that we can distinguish
+       real mouse motions from synthetic ones, for multitouch and pen support. */
     case XI_Motion:
     {
         const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
         const SDL_Pen *pen = SDL_GetPenPtr(X11_PenIDFromDeviceID(xev->deviceid));
 #if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
-        int pointer_emulated = (xev->flags & XIPointerEmulated);
-#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH */
+        SDL_bool pointer_emulated = ((xev->flags & XIPointerEmulated) != 0);
+#else
+        SDL_bool pointer_emulated = SDL_FALSE;
+#endif
 
         videodata->global_mouse_changed = SDL_TRUE;
 
         if (xev->deviceid != xev->sourceid) {
             /* Discard events from "Master" devices to avoid duplicates. */
-            return 1;
+            break;
         }
 
         if (pen) {
@@ -497,10 +499,9 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
             SDL_SendPenMotion(0, pen->header.id,
                               SDL_TRUE,
                               &pen_status);
-            return 1;
+            break;
         }
 
-#if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
         if (!pointer_emulated) {
             SDL_Mouse *mouse = SDL_GetMouse();
             if (!mouse->relative_mode || mouse->relative_mode_warp) {
@@ -511,8 +512,6 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
                 }
             }
         }
-        return 1;
-#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH */
     } break;
 
 #if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
@@ -523,8 +522,8 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
         SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
         xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y);
         SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_TRUE, x, y, 1.0);
-        return 1;
     } break;
+
     case XI_TouchEnd:
     {
         const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
@@ -532,8 +531,8 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
         SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
         xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y);
         SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_FALSE, x, y, 1.0);
-        return 1;
     } break;
+
     case XI_TouchUpdate:
     {
         const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
@@ -541,12 +540,10 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
         SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
         xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y);
         SDL_SendTouchMotion(0, xev->sourceid, xev->detail, window, x, y, 1.0);
-        return 1;
     } break;
 #endif /* SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH */
     }
 #endif /* SDL_VIDEO_DRIVER_X11_XINPUT2 */
-    return 0;
 }
 
 void X11_InitXinput2Multitouch(SDL_VideoDevice *_this)
@@ -592,36 +589,40 @@ int X11_Xinput2IsInitialized(void)
 
 SDL_bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
 {
+    SDL_WindowData *windowdata = (SDL_WindowData *)window->driverdata;
+
 #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
     const SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
-    XIEventMask eventmask;
-    unsigned char mask[4] = { 0, 0, 0, 0 };
-    SDL_WindowData *window_data = (SDL_WindowData *)window->driverdata;
 
-    eventmask.mask_len = sizeof(mask);
-    eventmask.mask = mask;
-    eventmask.deviceid = XIAllDevices;
-
-    XISetMask(mask, XI_KeyPress);
-    XISetMask(mask, XI_KeyRelease);
-    XISetMask(mask, XI_ButtonPress);
-    XISetMask(mask, XI_ButtonRelease);
-    XISetMask(mask, XI_Motion);
-    XISetMask(mask, XI_Enter);
-    XISetMask(mask, XI_Leave);
-    /* Hotplugging: */
-    XISetMask(mask, XI_DeviceChanged);
-    XISetMask(mask, XI_HierarchyChanged);
-    XISetMask(mask, XI_PropertyEvent); /* E.g., when swapping tablet pens */
-
-    if (X11_XISelectEvents(data->display,
-                           window_data->xwindow,
-                           &eventmask, 1) == Success) {
-        return SDL_TRUE;
+    if (X11_Xinput2IsInitialized()) {
+        XIEventMask eventmask;
+        unsigned char mask[4] = { 0, 0, 0, 0 };
+
+        eventmask.mask_len = sizeof(mask);
+        eventmask.mask = mask;
+        eventmask.deviceid = XIAllDevices;
+
+        XISetMask(mask, XI_KeyPress);
+        XISetMask(mask, XI_KeyRelease);
+        XISetMask(mask, XI_ButtonPress);
+        XISetMask(mask, XI_ButtonRelease);
+        XISetMask(mask, XI_Motion);
+        XISetMask(mask, XI_Enter);
+        XISetMask(mask, XI_Leave);
+        /* Hotplugging: */
+        XISetMask(mask, XI_DeviceChanged);
+        XISetMask(mask, XI_HierarchyChanged);
+        XISetMask(mask, XI_PropertyEvent); /* E.g., when swapping tablet pens */
+
+        if (X11_XISelectEvents(data->display, windowdata->xwindow, &eventmask, 1) == Success) {
+            windowdata->using_xinput2 = SDL_TRUE;
+        } else {
+            SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Could not enable XInput2 event handling\n");
+            windowdata->using_xinput2 = SDL_FALSE;
+        }
     }
-    SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Could not enable XInput2 mouse event handling\n");
 #endif
-    return SDL_FALSE;
+    return windowdata->using_xinput2;
 }
 
 int X11_Xinput2IsMultitouchSupported(void)
@@ -731,6 +732,8 @@ void X11_Xinput2UpdateDevices(SDL_VideoDevice *_this, SDL_bool initial_check)
     Uint32 *new_touch_devices = NULL;
     SDL_bool send_event = !initial_check;
 
+    SDL_assert(X11_Xinput2IsInitialized());
+
     info = X11_XIQueryDevice(data->display, XIAllDevices, &ndevices);
 
     old_keyboards = SDL_GetKeyboards(&old_keyboard_count);
diff --git a/src/video/x11/SDL_x11xinput2.h b/src/video/x11/SDL_x11xinput2.h
index e30f814552851..71a0976a92ffa 100644
--- a/src/video/x11/SDL_x11xinput2.h
+++ b/src/video/x11/SDL_x11xinput2.h
@@ -32,7 +32,7 @@ typedef struct XGenericEventCookie XGenericEventCookie;
 
 extern SDL_bool X11_InitXinput2(SDL_VideoDevice *_this);
 extern void X11_InitXinput2Multitouch(SDL_VideoDevice *_this);
-extern int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie);
+extern void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie);
 extern int X11_Xinput2IsInitialized(void);
 extern int X11_Xinput2IsMultitouchSupported(void);
 extern void X11_Xinput2SelectTouch(SDL_VideoDevice *_this, SDL_Window *window);