SDL: Fix clicking on the titlebar causing mouse input to freeze until esc is pressed

From 610e798406fbe82fb3c4d25f4cf592c55ebad02d Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 6 Mar 2024 13:33:23 -0800
Subject: [PATCH] Fix clicking on the titlebar causing mouse input to freeze
 until esc is pressed

When the titlebar drage region is clicked two actions are triggered:
  * The WM transfers focus to the application
  * The application starts a drag operation because the drag region was clicked

When the drag operation starts before input has been transferred to the application the
window manager gets in a bad state where mouse clicks don't work and the system isn't
actually dragging.

In this CL we delay drag operations until after the application has acquired active focus.
This fixes the problems outlined above.
---
 src/video/x11/SDL_x11events.c | 25 ++++++++++++++++++++++---
 src/video/x11/SDL_x11events.h |  2 +-
 src/video/x11/SDL_x11window.h |  2 ++
 3 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index 56a78c7fc332b..533178086f2dc 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -506,7 +506,7 @@ static void X11_DispatchUnmapNotify(SDL_WindowData *data)
     SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0);
 }
 
-static void InitiateWindowMove(SDL_VideoDevice *_this, const SDL_WindowData *data, const SDL_Point *point)
+static void DispatchWindowMove(SDL_VideoDevice *_this, const SDL_WindowData *data, const SDL_Point *point)
 {
     SDL_VideoData *viddata = _this->driverdata;
     SDL_Window *window = data->window;
@@ -531,6 +531,12 @@ static void InitiateWindowMove(SDL_VideoDevice *_this, const SDL_WindowData *dat
     X11_XSync(display, 0);
 }
 
+static void ScheduleWindowMove(SDL_VideoDevice *_this, SDL_WindowData *data, const SDL_Point *point)
+{
+    data->pending_move = SDL_TRUE;
+    data->pending_move_point = *point;
+}
+
 static void InitiateWindowResize(SDL_VideoDevice *_this, const SDL_WindowData *data, const SDL_Point *point, int direction)
 {
     SDL_VideoData *viddata = _this->driverdata;
@@ -574,7 +580,7 @@ SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, SDL_WindowData *data, const
     return SDL_TRUE;
 }
 
-SDL_bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, const SDL_WindowData *data, const float x, const float y)
+SDL_bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y)
 {
     SDL_Window *window = data->window;
 
@@ -589,7 +595,14 @@ SDL_bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, const SDL_WindowData *
 
         switch (data->hit_test_result) {
         case SDL_HITTEST_DRAGGABLE:
-            InitiateWindowMove(_this, data, &point);
+            /* Some window managers get in a bad state when a move event starts while input is transitioning
+               to the SDL window. This can happen when clicking on a drag region of an unfocused window
+               where the same mouse down event will trigger a drag event and a window activate. */
+            if (data->window->flags & SDL_WINDOW_INPUT_FOCUS) {
+                DispatchWindowMove(_this, data, &point);
+            } else {
+                ScheduleWindowMove(_this, data, &point);
+            }
             return SDL_TRUE;
 
         case SDL_HITTEST_RESIZE_TOPLEFT:
@@ -1719,6 +1732,12 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
                         X11_XResizeWindow(display, data->xwindow, data->window->floating.w, data->window->floating.h);
                     }
                 }
+                if ((flags & SDL_WINDOW_INPUT_FOCUS)) {
+                    if (data->pending_move) {
+                        DispatchWindowMove(_this, data, &data->pending_move_point);
+                        data->pending_move = SDL_FALSE;
+                    }
+                }
             }
             if (changed & SDL_WINDOW_OCCLUDED) {
                 SDL_SendWindowEvent(data->window, (flags & SDL_WINDOW_OCCLUDED) ? SDL_EVENT_WINDOW_OCCLUDED : SDL_EVENT_WINDOW_EXPOSED, 0, 0);
diff --git a/src/video/x11/SDL_x11events.h b/src/video/x11/SDL_x11events.h
index 4863a2435aa9b..3e34064e05abc 100644
--- a/src/video/x11/SDL_x11events.h
+++ b/src/video/x11/SDL_x11events.h
@@ -33,6 +33,6 @@ extern void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *wdata,
 extern void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *wdata, int button);
 extern SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window);
 extern SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y, SDL_bool force_new_result);
-extern SDL_bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, const SDL_WindowData *data, const float x, const float y);
+extern SDL_bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y);
 
 #endif /* SDL_x11events_h_ */
diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h
index e0f7549b94d58..454e92a56a935 100644
--- a/src/video/x11/SDL_x11window.h
+++ b/src/video/x11/SDL_x11window.h
@@ -63,6 +63,8 @@ struct SDL_WindowData
     Uint64 last_focus_event_time;
     PendingFocusEnum pending_focus;
     Uint64 pending_focus_time;
+    SDL_bool pending_move;
+    SDL_Point pending_move_point;
     XConfigureEvent last_xconfigure;
     struct SDL_VideoData *videodata;
     unsigned long user_time;