SDL: x11: Don't move the window when restoring and ensure that resize and position events are sent when entering or leaving...

From 1745289b1b059dd1d3fe83a533249f4244025a67 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Sat, 16 Dec 2023 19:13:31 -0500
Subject: [PATCH] x11: Don't move the window when restoring and ensure that
 resize and position events are sent when entering or leaving fullscreen

Account for the border sizes when restoring the window and only turn off resize events when entering or leaving fullscreen until the frame extents are changed, and only if they are, or previously were, non-zero.

This necessitated further refinement to the sync algorithm as well, but as a result, the sync function no longer occasionally times out if creating a window and immediately recreating it when initializing a renderer, and some rare, spurious size and position failures in the centered window and state automated tests seem to be fixed.
---
 src/video/x11/SDL_x11events.c | 74 +++++++++++++++++++++++------------
 src/video/x11/SDL_x11window.c | 51 ++++++++++--------------
 src/video/x11/SDL_x11window.h |  4 +-
 3 files changed, 72 insertions(+), 57 deletions(-)

diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index 764bfa8e9738..26f5e060a519 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -1326,36 +1326,36 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
 
         if (xevent->xconfigure.x != data->last_xconfigure.x ||
             xevent->xconfigure.y != data->last_xconfigure.y) {
-            SDL_Window *w;
-            int x = xevent->xconfigure.x;
-            int y = xevent->xconfigure.y;
+            if (!data->disable_size_position_events) {
+                SDL_Window *w;
+                int x = xevent->xconfigure.x;
+                int y = xevent->xconfigure.y;
 
-            data->pending_operation &= ~X11_PENDING_OP_MOVE;
-            SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y);
-            SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y);
+                data->pending_operation &= ~X11_PENDING_OP_MOVE;
+                SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y);
+                SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y);
 
 #ifdef SDL_USE_IME
-            if (SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) {
-                /* Update IME candidate list position */
-                SDL_IME_UpdateTextRect(NULL);
-            }
+                if (SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) {
+                    /* Update IME candidate list position */
+                    SDL_IME_UpdateTextRect(NULL);
+                }
 #endif
-            for (w = data->window->first_child; w; w = w->next_sibling) {
-                /* Don't update hidden child windows, their relative position doesn't change */
-                if (!(w->flags & SDL_WINDOW_HIDDEN)) {
-                    X11_UpdateWindowPosition(w, SDL_TRUE);
+                for (w = data->window->first_child; w; w = w->next_sibling) {
+                    /* Don't update hidden child windows, their relative position doesn't change */
+                    if (!(w->flags & SDL_WINDOW_HIDDEN)) {
+                        X11_UpdateWindowPosition(w, SDL_TRUE);
+                    }
                 }
             }
         }
         if (xevent->xconfigure.width != data->last_xconfigure.width ||
             xevent->xconfigure.height != data->last_xconfigure.height) {
-            if (!data->skip_size_count) {
+            if (!data->disable_size_position_events) {
                 data->pending_operation &= ~X11_PENDING_OP_RESIZE;
                 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED,
                                     xevent->xconfigure.width,
                                     xevent->xconfigure.height);
-            } else {
-                data->skip_size_count--;
             }
         }
 
@@ -1653,9 +1653,28 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
                         SDL_UpdateFullscreenMode(data->window, SDL_FALSE, SDL_TRUE);
                     }
 
-                    if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) {
-                        /* Skip the first resize event if the borders are being turned on/off. */
-                        data->skip_size_count = 1;
+                    if ((flags & SDL_WINDOW_FULLSCREEN) &&
+                        (data->border_top || data->border_left || data->border_bottom || data->border_right)) {
+                        /* If the window is entering fullscreen and the borders are
+                         * non-zero sized, turn off size events until the borders are
+                         * shut off to avoid bogus window sizes and positions, and
+                         * note that the old borders were non-zero for restoration.
+                         */
+                        data->disable_size_position_events = SDL_TRUE;
+                        data->previous_borders_nonzero = SDL_TRUE;
+                    } else if (!(flags & SDL_WINDOW_FULLSCREEN) &&
+                               data->previous_borders_nonzero &&
+                               (!data->border_top && !data->border_left && !data->border_bottom && !data->border_right)) {
+                        /* If the window is leaving fullscreen and the current borders
+                         * are zero sized, but weren't when entering fullscreen, turn
+                         * off size events until the borders come back to avoid bogus
+                         * window sizes and positions.
+                         */
+                        data->disable_size_position_events = SDL_TRUE;
+                        data->previous_borders_nonzero = SDL_FALSE;
+                    } else {
+                        data->disable_size_position_events = SDL_FALSE;
+                        data->previous_borders_nonzero = SDL_FALSE;
                     }
                 }
                 if ((changed & SDL_WINDOW_MAXIMIZED) && ((flags & SDL_WINDOW_MAXIMIZED) && !(flags & SDL_WINDOW_MINIMIZED))) {
@@ -1678,11 +1697,11 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
                     /* Restore the last known floating state if leaving maximized mode */
                     if (!(flags & SDL_WINDOW_FULLSCREEN)) {
                         data->pending_operation |= X11_PENDING_OP_MOVE | X11_PENDING_OP_RESIZE;
-                        data->expected.x = data->window->floating.x;
-                        data->expected.y = data->window->floating.y;
+                        data->expected.x = data->window->floating.x - data->border_left;
+                        data->expected.y = data->window->floating.y - data->border_top;
                         data->expected.w = data->window->floating.w;
                         data->expected.h = data->window->floating.h;
-                        X11_XMoveWindow(display, data->xwindow, data->window->floating.x, data->window->floating.y);
+                        X11_XMoveWindow(display, data->xwindow, data->window->floating.x - data->border_left, data->window->floating.y - data->border_top);
                         X11_XResizeWindow(display, data->xwindow, data->window->floating.w, data->window->floating.h);
                     }
                 }
@@ -1699,18 +1718,23 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
                right approach, but it seems to work. */
             X11_UpdateKeymap(_this, SDL_TRUE);
         } else if (xevent->xproperty.atom == videodata->_NET_FRAME_EXTENTS) {
+            /* Re-enable size events if they were turned off waiting for the borders to come back
+             * when leaving fullscreen.
+             */
+            data->disable_size_position_events = SDL_FALSE;
             X11_GetBorderValues(data);
             if (data->border_top != 0 || data->border_left != 0 || data->border_right != 0 || data->border_bottom != 0) {
-                /* Adjust if the window size changed to accommodate the borders. */
+                /* Adjust if the window size/position changed to accommodate the borders. */
                 if (data->window->flags & SDL_WINDOW_MAXIMIZED) {
                     data->pending_operation |= X11_PENDING_OP_RESIZE;
                     data->expected.w = data->window->windowed.w;
                     data->expected.h = data->window->windowed.h;
                     X11_XResizeWindow(display, data->xwindow, data->window->windowed.w, data->window->windowed.h);
                 } else {
-                    data->pending_operation |= X11_PENDING_OP_RESIZE;
+                    data->pending_operation |= X11_PENDING_OP_RESIZE | X11_PENDING_OP_MOVE;
                     data->expected.w = data->window->floating.w;
                     data->expected.h = data->window->floating.h;
+                    X11_XMoveWindow(display, data->xwindow, data->window->floating.x - data->border_left, data->window->floating.y - data->border_top);
                     X11_XResizeWindow(display, data->xwindow, data->window->floating.w, data->window->floating.h);
                 }
             }
diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c
index 5a2de89d94cb..24aa08c0904d 100644
--- a/src/video/x11/SDL_x11window.c
+++ b/src/video/x11/SDL_x11window.c
@@ -856,6 +856,7 @@ static int X11_SyncWindowTimeout(SDL_VideoDevice *_this, SDL_Window *window, int
     int (*prev_handler)(Display *, XErrorEvent *);
     Uint64 timeout = 0;
     int ret = 0;
+    SDL_bool force_exit = SDL_FALSE;
 
     X11_XSync(display, False);
     prev_handler = X11_XSetErrorHandler(X11_CatchAnyError);
@@ -868,11 +869,25 @@ static int X11_SyncWindowTimeout(SDL_VideoDevice *_this, SDL_Window *window, int
         X11_XSync(display, False);
         X11_PumpEvents(_this);
 
-        if ((!(data->pending_operation & X11_PENDING_OP_MOVE) || (window->x == data->expected.x && window->y == data->expected.y)) &&
-            (!(data->pending_operation & X11_PENDING_OP_RESIZE) || (window->w == data->expected.w && window->h == data->expected.h)) &&
-            (data->pending_operation & ~(X11_PENDING_OP_MOVE | X11_PENDING_OP_RESIZE)) == X11_PENDING_OP_NONE) {
-            /* The window is where it is wanted and nothing is pending. Done. */
-            break;
+        if ((data->pending_operation & X11_PENDING_OP_MOVE) && (window->x == data->expected.x && window->y == data->expected.y)) {
+            data->pending_operation &= ~X11_PENDING_OP_MOVE;
+        }
+        if ((data->pending_operation & X11_PENDING_OP_RESIZE) && (window->w == data->expected.w && window->h == data->expected.h)) {
+            data->pending_operation &= ~X11_PENDING_OP_RESIZE;
+        }
+
+        if (data->pending_operation == X11_PENDING_OP_NONE) {
+            if (force_exit ||
+                (window->x == data->expected.x && window->y == data->expected.y &&
+                 window->w == data->expected.w && window->h == data->expected.h)) {
+                /* The window is in the expected state and nothing is pending. Done. */
+                break;
+            }
+
+            /* No operations are pending, but the window still isn't in the expected state.
+             * Try one more time before exiting.
+             */
+            force_exit = SDL_TRUE;
         }
 
         if (SDL_GetTicks() >= timeout) {
@@ -1310,15 +1325,9 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
         }
     }
 
-    /* Get some valid border values, if we haven't them yet */
+    /* Get some valid border values, if we haven't received them yet */
     if (data->border_left == 0 && data->border_right == 0 && data->border_top == 0 && data->border_bottom == 0) {
         X11_GetBorderValues(data);
-
-        if (!data->initial_border_adjustment) {
-            data->expected.x += data->border_left;
-            data->expected.y += data->border_top;
-            data->initial_border_adjustment = SDL_TRUE;
-        }
     }
 }
 
@@ -1591,24 +1600,6 @@ static int X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, SDL_Window *wind
             e.xclient.data.l[3] = 0l;
             X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
                            SubstructureNotifyMask | SubstructureRedirectMask, &e);
-
-            if (!data->window_was_maximized) {
-                /* Attempt to move the window back to where it was. */
-                SDL_RelativeToGlobalForWindow(window,
-                                              window->floating.x - data->border_left, window->floating.y - data->border_top,
-                                              &data->expected.x, &data->expected.y);
-
-                data->expected.w = window->floating.w;
-                data->expected.h = window->floating.h;
-                data->pending_operation |= X11_PENDING_OP_MOVE;
-                X11_XMoveWindow(display, data->xwindow, data->expected.x, data->expected.y);
-
-                /* If the window is bordered, the size will be set when the borders turn themselves back on. */
-                if (window->flags & SDL_WINDOW_BORDERLESS) {
-                    data->pending_operation |= X11_PENDING_OP_RESIZE;
-                    X11_XResizeWindow(display, data->xwindow, data->expected.w, data->expected.h);
-                }
-            }
         }
     } else {
         Uint32 flags;
diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h
index 11977a57b957..01fd049995a9 100644
--- a/src/video/x11/SDL_x11window.h
+++ b/src/video/x11/SDL_x11window.h
@@ -93,9 +93,9 @@ struct SDL_WindowData
         X11_PENDING_OP_RESIZE = 0x20
     } pending_operation;
 
-    SDL_bool initial_border_adjustment;
     SDL_bool window_was_maximized;
-    int skip_size_count;
+    SDL_bool disable_size_position_events;
+    SDL_bool previous_borders_nonzero;
     SDL_HitTestResult hit_test_result;
 };