SDL: x11: Ignore slave button presses on non-focused windows

From 0fc9db9b82c318b44562778cd4709e9638a57087 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Fri, 10 Apr 2026 11:23:49 -0400
Subject: [PATCH] x11: Ignore slave button presses on non-focused windows

When a window has the pointer grabbed, the X server will grab all master device events, and XInput2 will continue to deliver slave events to the window immediately under the pointer, regardless of grab status. Only send slave pointer events to the focused window, and fall back to the core X events to catch button presses missed when the pointer is over another window.
---
 src/video/x11/SDL_x11events.c  |  8 ++++----
 src/video/x11/SDL_x11video.h   |  1 +
 src/video/x11/SDL_x11xinput2.c | 10 +++++++++-
 3 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index 6bfb9142739b6..15af66124faad 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -1862,8 +1862,8 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
 
     case ButtonPress:
     {
-        if (data->xinput2_mouse_enabled) {
-            // This input is being handled by XInput2
+        if (data->xinput2_mouse_enabled && xevent->xbutton.serial == videodata->xinput_last_button_serial) {
+            // This input event was handled by XInput2.
             break;
         }
 
@@ -1873,8 +1873,8 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
 
     case ButtonRelease:
     {
-        if (data->xinput2_mouse_enabled) {
-            // This input is being handled by XInput2
+        if (data->xinput2_mouse_enabled && xevent->xbutton.serial == videodata->xinput_last_button_serial) {
+            // This input event was handled by XInput2.
             break;
         }
 
diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h
index 32c372278499c..01c2e5b9fb1ff 100644
--- a/src/video/x11/SDL_x11video.h
+++ b/src/video/x11/SDL_x11video.h
@@ -139,6 +139,7 @@ struct SDL_VideoData
     Uint32 global_mouse_buttons;
 
     SDL_XInput2DeviceInfo *mouse_device_info;
+    unsigned long xinput_last_button_serial;
     int xinput_master_pointer_device;
     bool xinput_hierarchy_changed;
 
diff --git a/src/video/x11/SDL_x11xinput2.c b/src/video/x11/SDL_x11xinput2.c
index 69b7e21546899..4ebba6e0f374e 100644
--- a/src/video/x11/SDL_x11xinput2.c
+++ b/src/video/x11/SDL_x11xinput2.c
@@ -607,8 +607,16 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
             SDL_WindowData *windowdata = X11_FindWindow(videodata, xev->event);
             int x_ticks = 0, y_ticks = 0;
 
-            // Slave pointer devices don't have button remapping applied automatically, so do it manually.
+            // Store the button serial to filter out redundant core button events.
+            videodata->xinput_last_button_serial = xev->serial;
+
             if (xev->deviceid != videodata->xinput_master_pointer_device) {
+                // Ignore slave button events on non-focused windows, or focus can be incorrectly set while a grab is active.
+                if (SDL_GetMouseFocus() != windowdata->window) {
+                    break;
+                }
+
+                // Slave pointer devices don't have button remapping applied automatically, so do it manually.
                 if (button <= xinput2_pointer_button_map_size) {
                     button = xinput2_pointer_button_map[button - 1];
                 }