Browse Source

x11: Use XInput2 to handle pointer input while grabbed and touches are active

Core X11 events are used for absolute motion while the mouse is grabbed, as XInput doesn't receive motion events from the master pointer, and the position from the slave devices lags one report behind. However, pointer events emulated from multitouch events can't be filtered out when using core events, as the XInput flags are not passed through.

Use the absolute position from slave devices in XInput motion events while the mouse is grabbed and touch events are active to allow filtering out pointer events emulated from touch events. Pointer events in this case may be a report behind the master device, but receiving input from both the pointer and touches simultaneously is not likely to be a common occurrence.
Frank Praznik 6 days ago
parent
commit
5fe69e73ba
3 changed files with 31 additions and 6 deletions
  1. 1 1
      src/video/x11/SDL_x11events.c
  2. 29 5
      src/video/x11/SDL_x11xinput2.c
  3. 1 0
      src/video/x11/SDL_x11xinput2.h

+ 1 - 1
src/video/x11/SDL_x11events.c

@@ -1836,7 +1836,7 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
 
     case MotionNotify:
     {
-        if (data->xinput2_mouse_enabled && !data->mouse_grabbed) {
+        if (X11_Xinput2HandlesMotionForWindow(data)) {
             // This input is being handled by XInput2
             break;
         }

+ 29 - 5
src/video/x11/SDL_x11xinput2.c

@@ -38,6 +38,8 @@ static bool xinput2_initialized;
 #if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH)
 static bool xinput2_scrolling_supported;
 static bool xinput2_multitouch_supported;
+static bool xinput2_grabbed_touch_raised;
+static int xinput2_active_touch_count;
 #endif
 #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE
 static bool xinput2_gesture_supported;
@@ -673,15 +675,17 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
             }
 #endif
 
-            if (xev->deviceid == videodata->xinput_master_pointer_device) {
-                // Use the master device for non-relative motion, as the slave devices can seemingly lag behind.
+            /* Use the master device for non-relative motion, as the slave devices can seemingly lag behind,
+             * except when the mouse is grabbed and touches are active, as core input events are used for
+             * absolute motion while the mouse is grabbed, and core events don't have the XIPointerEmulated
+             * flag to filter out pointer events emulated from touch events.
+             */
+            SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
+            if (window && (xev->deviceid == videodata->xinput_master_pointer_device || (xinput2_active_touch_count && window->internal->mouse_grabbed))) {
                 SDL_Mouse *mouse = SDL_GetMouse();
                 if (!mouse->relative_mode) {
-                    SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
-                    if (window) {
                         X11_ProcessHitTest(_this, window->internal, (float)xev->event_x, (float)xev->event_y, false);
                         SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, (float)xev->event_x, (float)xev->event_y);
-                    }
                 }
             }
         }
@@ -692,6 +696,7 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
     {
         const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
         float x, y;
+        ++xinput2_active_touch_count;
         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_EVENT_FINGER_DOWN, x, y, 1.0);
@@ -702,6 +707,9 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
         const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
         float x, y;
         SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
+        if (!--xinput2_active_touch_count && window && window->internal->mouse_grabbed) {
+            xinput2_grabbed_touch_raised = true;
+        }
         xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y);
         SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_EVENT_FINGER_UP, x, y, 1.0);
     } break;
@@ -743,6 +751,20 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
 
 void X11_InitXinput2Multitouch(SDL_VideoDevice *_this)
 {
+    xinput2_grabbed_touch_raised = false;
+    xinput2_active_touch_count = 0;
+}
+
+bool X11_Xinput2HandlesMotionForWindow(SDL_WindowData *window_data)
+{
+    /* Send the active flag once more after the touch count is zero, to suppress the
+     * emulated motion event when the last touch is raised.
+     */
+    const bool ret = window_data->xinput2_mouse_enabled &&
+                     (!window_data->mouse_grabbed || xinput2_active_touch_count || xinput2_grabbed_touch_raised);
+    xinput2_grabbed_touch_raised = false;
+
+    return ret;
 }
 
 void X11_Xinput2Select(SDL_VideoDevice *_this, SDL_Window *window)
@@ -888,6 +910,8 @@ void X11_Xinput2UngrabTouch(SDL_VideoDevice *_this, SDL_Window *window)
         return;
     }
 
+    xinput2_grabbed_touch_raised = false;
+
     mods.modifiers = XIAnyModifier;
     mods.status = 0;
 

+ 1 - 0
src/video/x11/SDL_x11xinput2.h

@@ -41,5 +41,6 @@ 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);
+extern bool X11_Xinput2HandlesMotionForWindow(SDL_WindowData *window_data);
 
 #endif // SDL_x11xinput2_h_