From f31ca02723f57f84dc03fd19b2f5a3e4b8a15bda Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Sun, 17 May 2026 10:45:32 -0400 Subject: [PATCH] video: Windows keep any position set when in fullscreen after leaving fullscreen Adds an automated test for the behavior as well. --- src/video/SDL_video.c | 8 +++ src/video/cocoa/SDL_cocoawindow.m | 12 +++-- src/video/x11/SDL_x11window.c | 3 ++ src/video/x11/SDL_x11window.h | 1 + test/testautomation_video.c | 90 +++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 4 deletions(-) diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 318f05b1df..36f3dc89f5 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -3051,6 +3051,14 @@ bool SDL_SetWindowPosition(SDL_Window *window, int x, int y) window->pending.x = x; window->pending.y = y; + + /* Windows are placed at the coordinates received while in fullscreen after leaving fullscreen. + * Asynchronous backends need special handling in this case. + */ + if (!_this->SyncWindow && (window->flags & SDL_WINDOW_FULLSCREEN)) { + window->floating.x = window->windowed.x = x; + window->floating.y = window->windowed.y = y; + } window->undefined_x = false; window->undefined_y = false; window->last_position_pending = true; diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index 79c17713eb..5b8ffcdc96 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -1461,7 +1461,6 @@ static NSCursor *Cocoa_GetDesiredCursor(void) } SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); - _data.pending_position = NO; _data.pending_size = NO; /* Force the size change event in case it was delivered earlier @@ -2605,7 +2604,7 @@ bool Cocoa_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) BOOL fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN) ? YES : NO; int x, y; - if ([windata.listener isInFullscreenSpaceTransition]) { + if (fullscreen || [windata.listener isInFullscreenSpaceTransition]) { windata.pending_position = YES; return true; } @@ -3030,8 +3029,13 @@ SDL_FullscreenResult Cocoa_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Windo SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); - rect.origin.x = data.was_zoomed ? window->windowed.x : window->floating.x; - rect.origin.y = data.was_zoomed ? window->windowed.y : window->floating.y; + if (data.pending_position) { + rect.origin.x = window->pending.x; + rect.origin.y = window->pending.y; + } else { + rect.origin.x = data.was_zoomed ? window->windowed.x : window->floating.x; + rect.origin.y = data.was_zoomed ? window->windowed.y : window->floating.y; + } rect.size.width = data.was_zoomed ? window->windowed.w : window->floating.w; rect.size.height = data.was_zoomed ? window->windowed.h : window->floating.h; diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c index b1f982a32e..a0864d71b4 100644 --- a/src/video/x11/SDL_x11window.c +++ b/src/video/x11/SDL_x11window.c @@ -1200,6 +1200,7 @@ bool X11_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) } X11_UpdateWindowPosition(window, false); } else { + window->internal->fs_repositioned = true; SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_UPDATE, true); } return true; @@ -1921,6 +1922,8 @@ static SDL_FullscreenResult X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, } } else { SDL_zero(data->requested_fullscreen_mode); + data->pending_position = data->fs_repositioned; + data->fs_repositioned = false; /* Fullscreen windows sometimes end up being marked maximized by * window managers. Force it back to how we expect it to be. diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h index 5335ae8001..1ba725b9bd 100644 --- a/src/video/x11/SDL_x11window.h +++ b/src/video/x11/SDL_x11window.h @@ -111,6 +111,7 @@ struct SDL_WindowData bool pending_size; bool pending_position; + bool fs_repositioned; bool window_was_maximized; bool previous_borders_nonzero; bool toggle_borders; diff --git a/test/testautomation_video.c b/test/testautomation_video.c index ad1e3b1c8d..8a784353cf 100644 --- a/test/testautomation_video.c +++ b/test/testautomation_video.c @@ -843,6 +843,7 @@ static int SDLCALL video_getSetWindowPosition(void *arg) { const char *title = "video_getSetWindowPosition Test Window"; SDL_Window *window; + SDL_WindowFlags flags; int result; int maxxVariation, maxyVariation; int xVariation, yVariation; @@ -974,6 +975,95 @@ static int SDLCALL video_getSetWindowPosition(void *arg) } } + /* Fullscreen test */ + desiredX = 100; + desiredY = 100; + SDL_SetWindowPosition(window, desiredX, desiredY); + SDLTest_AssertPass("Call to SDL_SetWindowPosition(...,%d,%d)", desiredX, desiredY); + + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == true, "Verify return value; expected: true, got: %d", result); + + /* Get position */ + currentX = desiredX + 1; + currentY = desiredY + 1; + SDL_GetWindowPosition(window, ¤tX, ¤tY); + SDLTest_AssertPass("Call to SDL_GetWindowPosition()"); + + if (desiredX == currentX && desiredY == currentY) { + SDLTest_AssertCheck(desiredX == currentX, "Verify returned X position; expected: %d, got: %d", desiredX, currentX); + SDLTest_AssertCheck(desiredY == currentY, "Verify returned Y position; expected: %d, got: %d", desiredY, currentY); + } else { + bool hasEvent; + /* SDL_SetWindowPosition() and SDL_SetWindowSize() will make requests of the window manager and set the internal position and size, + * and then we get events signaling what actually happened, and they get passed on to the application if they're not what we expect. */ + currentX = desiredX + 1; + currentY = desiredY + 1; + hasEvent = getPositionFromEvent(¤tX, ¤tY); + SDLTest_AssertCheck(hasEvent == true, "Changing position was not honored by WM, checking present of SDL_EVENT_WINDOW_MOVED"); + if (hasEvent) { + SDLTest_AssertCheck(desiredX == currentX, "Verify returned X position is the position from SDL event; expected: %d, got: %d", desiredX, currentX); + SDLTest_AssertCheck(desiredY == currentY, "Verify returned Y position is the position from SDL event; expected: %d, got: %d", desiredY, currentY); + } + } + + /* Test setting position while fullscreen */ + result = SDL_SetWindowFullscreen(window, true); + SDLTest_AssertPass("SDL_SetWindowFullscreen()"); + SDLTest_AssertCheck(result == true, "Verify return value; expected: true, got: %d", result); + + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == true, "Verify return value; expected: true, got: %d", result); + + /* Verify that window is in fullscreen */ + flags = SDL_GetWindowFlags(window); + SDLTest_AssertPass("SDL_GetWindowFlags()"); + SDLTest_AssertCheck(flags & SDL_WINDOW_FULLSCREEN, "Verify the `SDL_WINDOW_FULLSCREEN` flag is set: %s", (flags & SDL_WINDOW_FULLSCREEN) ? "true" : "false"); + + /* Set the fullscreen window position */ + desiredX = desiredX + 10; + desiredY = desiredY + 10; + SDL_SetWindowPosition(window, desiredX, desiredY); + SDLTest_AssertPass("Call to SDL_SetWindowPosition(...,%d,%d)", desiredX, desiredY); + + result = SDL_SetWindowFullscreen(window, false); + SDLTest_AssertPass("SDL_SetWindowFullscreen()"); + SDLTest_AssertCheck(result == true, "Verify return value; expected: true, got: %d", result); + + result = SDL_SyncWindow(window); + SDLTest_AssertPass("SDL_SyncWindow()"); + SDLTest_AssertCheck(result == true, "Verify return value; expected: true, got: %d", result); + + /* Verify that window left fullscreen */ + flags = SDL_GetWindowFlags(window); + SDLTest_AssertPass("SDL_GetWindowFlags()"); + SDLTest_AssertCheck(!(flags & SDL_WINDOW_FULLSCREEN), "Verify the `SDL_WINDOW_FULLSCREEN` flag is not set: %s", !(flags & SDL_WINDOW_FULLSCREEN) ? "true" : "false"); + + /* Get position */ + currentX = desiredX + 1; + currentY = desiredY + 1; + SDL_GetWindowPosition(window, ¤tX, ¤tY); + SDLTest_AssertPass("Call to SDL_GetWindowPosition()"); + + if (desiredX == currentX && desiredY == currentY) { + SDLTest_AssertCheck(desiredX == currentX, "Verify returned X position; expected: %d, got: %d", desiredX, currentX); + SDLTest_AssertCheck(desiredY == currentY, "Verify returned Y position; expected: %d, got: %d", desiredY, currentY); + } else { + bool hasEvent; + /* SDL_SetWindowPosition() and SDL_SetWindowSize() will make requests of the window manager and set the internal position and size, + * and then we get events signaling what actually happened, and they get passed on to the application if they're not what we expect. */ + currentX = desiredX + 1; + currentY = desiredY + 1; + hasEvent = getPositionFromEvent(¤tX, ¤tY); + SDLTest_AssertCheck(hasEvent == true, "Changing position was not honored by WM, checking present of SDL_EVENT_WINDOW_MOVED"); + if (hasEvent) { + SDLTest_AssertCheck(desiredX == currentX, "Verify returned X position is the position from SDL event; expected: %d, got: %d", desiredX, currentX); + SDLTest_AssertCheck(desiredY == currentY, "Verify returned Y position is the position from SDL event; expected: %d, got: %d", desiredY, currentY); + } + } + null_tests: /* Dummy call with both pointers NULL */