Base window size and position is not remembered

Windows 10, SDL3

I’m having some trouble understanding the base window size and handling window minimization and full screen mode.

A window, when displayed in its basic mode (let’s call it windowed), i.e. smaller than the screen area, has its own position and size. When a window is maximized, its base size is remembered, so restoring the window causes its position and size to be what the window had before it was maximized. Everything works intuitively here.

If I do the same with the window, but instead of maximizing it, I enable fullscreen mode (e.g. desktop fullscreen), the base size and position will also be remembered, so exiting fullscreen mode will restore its position and size to the one the window had before enabling fullscreen. Everything is correct.

The problem occurs when I enable fullscreen while the window is maximized. In this case, the base size and position of the window are lost — overwritten by those of the maximized window. If I disable fullscreen, the window is messed up — it has the size of the maximized window, the position is almost the same as the maximized one (a few pixels shifted to the side). Restoring the window only disables maximization, but the window is not restored to the size before maximization and fullscreen — these dimensions and position are lost.

I noticed this already in SDL2, but didn’t report it because I thought that was the way the OS managed windows and didn’t store the size and position of the base, maximized, and full-screen windows separately — that it only stored data for the previous mode, so changing the mode from windowed to maximized and then to full-screen caused the windowed mode data to be overwritten by the maximized mode.

This problem also got worse when I implemented a borderless window with my own decoration — minimizing a maximized window causes problems with restoring maximization. I’ll investigate this further, because it may be my fault.

That’s why in my projects I simply store the base window size and position data myself, so that I could work around issues with changing window display modes.


The question is: should the lack of storing the base position and size of the window be treated as a bug and reported so that it can be added to the SDL, or is that just how Windows works and does it need to be programmed by yourself, in the application itself?

Maybe I’m doing something wrong in my project, so here’s a list of steps to reproduce this problem:

  1. Create a resizable window and set its initial size to e.g. 640x480.
  2. Maximize window.
  3. Enable fullscreen mode (can be fullscreen desktop),
  4. Disable fullscreen.

The result should be that the window may not be restored to its maximized form, and even if it is, trying to restore the window to its windowed form (e.g. using the restore button on the window title bar), i.e. to 640x480 size, will not be possible — the window will not resize.

However, in the case of a borderless window, the problem can be encountered with the following steps:

  1. Create a borderless window and set its initial size to e.g. 640x480.
  2. Maximize the window by calling SDL_MaximizeWindow,
  3. Minimize the window by clicking the application button on the taskbar.
  4. Restore the window by clicking the application button on the taskbar.

The window may not be properly restored to its maximized state.

Here is a test demonstrating the issue with a resizable window having a standard decoration and entering fullscreen mode when the window is maximized. For testing I use these headers (they are fine) and fresh SDL 3.1.10.

The sources look like this:

uses
  SDL3;
label
  Cleanup,
  FailedWindow,
  FailedRenderer;
var
  Window:   PSDL_Window;
  Renderer: PSDL_Renderer;
  Event:    TSDL_Event;
begin
  Window := SDL_CreateWindow('Window test', 640, 480, SDL_WINDOW_RESIZABLE);

  if Window = nil then
  begin
    WriteLn('SDL_CreateWindow failed: "', SDL_GetError(), '".');
    goto FailedWindow;
  end;

  Renderer := SDL_CreateRenderer(Window, 'opengl');

  if Renderer = nil then
  begin
    WriteLn('SDL_CreateRenderer failed: "', SDL_GetError(), '".');
    goto FailedRenderer;
  end;

  while True do
  begin
    while SDL_PollEvent(@Event) do
    case Event._Type of
      SDL_EVENT_QUIT: goto Cleanup;

      SDL_EVENT_KEY_DOWN:
      case Event.Key.Scancode of
        SDL_SCANCODE_F: SDL_SetWindowFullscreen(Window, True);
        SDL_SCANCODE_W: SDL_SetWindowFullscreen(Window, False);
      end;

      SDL_EVENT_WINDOW_MINIMIZED:        WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_MINIMIZED');
      SDL_EVENT_WINDOW_MAXIMIZED:        WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_MAXIMIZED');
      SDL_EVENT_WINDOW_RESTORED:         WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_RESTORED');
      SDL_EVENT_WINDOW_ENTER_FULLSCREEN: WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_ENTER_FULLSCREEN');
      SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_LEAVE_FULLSCREEN');

      SDL_EVENT_WINDOW_MOVED:
        WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_MOVED: ', Event.Window.Data1, ',', Event.Window.Data2);

      SDL_EVENT_WINDOW_RESIZED:
        WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_RESIZED: ', Event.Window.Data1, ',', Event.Window.Data2);

      SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
        WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: ', Event.Window.Data1, ',', Event.Window.Data2);
    end;

    SDL_SetRenderDrawColor (Renderer, 225, 225, 225, 255);
    SDL_RenderClear        (Renderer);
    SDL_RenderPresent      (Renderer);
    SDL_Delay(20);
  end;

Cleanup:
  SDL_DestroyRenderer(Renderer);

FailedRenderer:
  SDL_DestroyWindow(Window);

FailedWindow:
  WriteLn('Press ENTER to finish...');
  ReadLn();
end.

Now I do the following steps after the window appears on the screen:

  1. I maximize the window using the system maximize button.
  2. I press the F key to enable fullscreen.
  3. I press the W key to disable fullscreen.
  4. I restore the window using the system restore button.

This is what the console logs look like for each step:

  1. When the window shows on the screen:
   3010  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: 640,480
  1. When I maximize the window using the system maximize button.
  67656  SDL_EVENT_WINDOW_MAXIMIZED
  67657  SDL_EVENT_WINDOW_MOVED: 0,63
  67657  SDL_EVENT_WINDOW_RESIZED: 1280,961
  67657  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: 1280,961
  1. When I press the F key to enable fullscreen.
  85419  SDL_EVENT_WINDOW_ENTER_FULLSCREEN
  85419  SDL_EVENT_WINDOW_RESTORED
  85419  SDL_EVENT_WINDOW_MOVED: 0,0
  85420  SDL_EVENT_WINDOW_RESIZED: 1280,1024
  85420  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: 1280,1024
  1. When I press the W key to disable fullscreen.
  91758  SDL_EVENT_WINDOW_LEAVE_FULLSCREEN
  91758  SDL_EVENT_WINDOW_MAXIMIZED
  91759  SDL_EVENT_WINDOW_MOVED: 0,63
  91759  SDL_EVENT_WINDOW_RESIZED: 1280,961
  91759  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: 1280,961
  1. When I restore the window using the system restore button.
 124537  SDL_EVENT_WINDOW_RESTORED
 124537  SDL_EVENT_WINDOW_MOVED: 8,31
 124537  SDL_EVENT_WINDOW_RESIZED: 1264,985
 124537  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: 1264,985

Point 5, i.e. restoring the window from a maximized state, works incorrectly, because instead of setting the window size to 640,480, it sets it to 1264,985, which is almost the same as for the maximized window.

WindowTestBordered.zip (1.4 MB) — exe with dll to test, source code.

And here is a second test, demonstrating the problem with restoring a maximized borderless window, both from a minimized form and from fullscreen. Same headers and SDL library. The source code contains a fix that enables hit-testing of borderless windows and their mouse stretching. It works until the first maximize and minimize of the window.

Source code:

uses
  SDL3,
  Windows;

  function CallbackHitTest(AWindow: PSDL_Window; AArea: PSDL_Point; AData: Pointer): TSDL_HitTestResult; cdecl;
  var
    WindowW:      Int32;
    WindowH:      Int32;
    HandleLeft:   Boolean;
    HandleTop:    Boolean;
    HandleRight:  Boolean;
    HandleBottom: Boolean;
  begin
    Result := SDL_HITTEST_NORMAL;

    if not SDL_GetWindowSize(AWindow, @WindowW, @WindowH) then exit;

    HandleLeft   := AArea^.X < 16;
    HandleTop    := AArea^.Y < 16;
    HandleRight  := AArea^.X > WindowW - 16;
    HandleBottom := AArea^.Y > WindowH - 16;

    if HandleLeft  and HandleTop    then exit(SDL_HITTEST_RESIZE_TOPLEFT);
    if HandleRight and HandleTop    then exit(SDL_HITTEST_RESIZE_TOPRIGHT);
    if HandleLeft  and HandleBottom then exit(SDL_HITTEST_RESIZE_BOTTOMLEFT);
    if HandleRight and HandleBottom then exit(SDL_HITTEST_RESIZE_BOTTOMRIGHT);

    if HandleLeft   then exit(SDL_HITTEST_RESIZE_LEFT);
    if HandleTop    then exit(SDL_HITTEST_RESIZE_TOP);
    if HandleRight  then exit(SDL_HITTEST_RESIZE_RIGHT);
    if HandleBottom then exit(SDL_HITTEST_RESIZE_BOTTOM);

    if AArea^.y < 100 then exit(SDL_HITTEST_DRAGGABLE);
  end;

label
  Cleanup,
  FailedWindow,
  FailedRenderer;
var
  Win32Props:  UInt32;
  Win32Handle: HWND;
  Win32Long:   LONG;
var
  Window:   PSDL_Window;
  Renderer: PSDL_Renderer;
  Event:    TSDL_Event;
begin
  Window := SDL_CreateWindow('Window test', 640, 480, SDL_WINDOW_RESIZABLE or SDL_WINDOW_BORDERLESS);

  if Window = nil then
  begin
    WriteLn('SDL_CreateWindow failed: "', SDL_GetError(), '".');
    goto FailedWindow;
  end;

  Renderer := SDL_CreateRenderer(Window, 'opengl');

  if Renderer = nil then
  begin
    WriteLn('SDL_CreateRenderer failed: "', SDL_GetError(), '".');
    goto FailedRenderer;
  end;

  // Fix to enable resizing borderless window.
  Win32Props  := SDL_GetWindowProperties(Window);
  Win32Handle := HWND(SDL_GetPointerProperty(Win32Props, 'SDL.window.win32.hwnd', nil));

  if Win32Handle = 0 then
    WriteLn('SDL_GetPointerProperty failed: "', SDL_GetError(), '".')
  else
  begin
    Win32Long := GetWindowLong(Win32Handle, GWL_STYLE);

    if Win32Long = 0 then
      WriteLn('GetWindowLong failed: "', GetLastError(), '".')
    else
      if SetWindowLong(Win32Handle, GWL_STYLE, Win32Long or WS_THICKFRAME) <> Win32Long then
        WriteLn('SetWindowLong failed: "', GetLastError(), '".');
  end;
  // End of fix.

  if not SDL_SetWindowHitTest(Window, @CallbackHitTest, nil) then
    WriteLn('SDL_SetWindowHitTest failed: "', SDL_GetError(), '".');

  while True do
  begin
    while SDL_PollEvent(@Event) do
    case Event._Type of
      SDL_EVENT_QUIT: goto Cleanup;

      SDL_EVENT_KEY_DOWN:
      case Event.Key.Scancode of
        SDL_SCANCODE_M: SDL_MaximizeWindow(Window);
        SDL_SCANCODE_N: SDL_MinimizeWindow(Window);
        SDL_SCANCODE_R: SDL_RestoreWindow(Window);
        SDL_SCANCODE_F: SDL_SetWindowFullscreen(Window, True);
        SDL_SCANCODE_W: SDL_SetWindowFullscreen(Window, False);
      end;

      SDL_EVENT_WINDOW_RESTORED:         WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_RESTORED');
      SDL_EVENT_WINDOW_MINIMIZED:        WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_MINIMIZED');
      SDL_EVENT_WINDOW_MAXIMIZED:        WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_MAXIMIZED');
      SDL_EVENT_WINDOW_ENTER_FULLSCREEN: WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_ENTER_FULLSCREEN');
      SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_LEAVE_FULLSCREEN');

      SDL_EVENT_WINDOW_MOVED:
        WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_MOVED: ', Event.Window.Data1, ',', Event.Window.Data2);

      SDL_EVENT_WINDOW_RESIZED:
        WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_RESIZED: ', Event.Window.Data1, ',', Event.Window.Data2);

      SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
        WriteLn(SDL_GetTicks():7, '  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: ', Event.Window.Data1, ',', Event.Window.Data2);
    end;

    SDL_SetRenderDrawColor (Renderer, 225, 225, 225, 255);
    SDL_RenderClear        (Renderer);
    SDL_RenderPresent      (Renderer);
    SDL_Delay(20);
  end;

Cleanup:
  SDL_DestroyRenderer(Renderer);

FailedRenderer:
  SDL_DestroyWindow(Window);

FailedWindow:
  WriteLn('Press ENTER to finish...');
  ReadLn();
end.

List of steps for the first test, i.e. the problem of minimizing a maximized borderless window:

  1. I press the M key to maximize the window.
  2. I click on the application button on the taskbar to minimize the window.
  3. I click on the application button on the taskbar to restore the window.

The console output for all steps:

  1. When the window shows on the screen:
   2070  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: 640,480
   2070  SDL_EVENT_WINDOW_MOVED: 320,292
  1. When I press the M key to maximize the window.
  18524  SDL_EVENT_WINDOW_MAXIMIZED
  18524  SDL_EVENT_WINDOW_MOVED: 0,40
  18524  SDL_EVENT_WINDOW_RESIZED: 1280,984
  18524  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: 1280,984
  1. When I click on the application button on the taskbar to minimize the window.
  31711  SDL_EVENT_WINDOW_MINIMIZED
  1. When I click on the application button on the taskbar to restore the window.
  40402  SDL_EVENT_WINDOW_MOVED: -1280,40

Point 4, i.e. restoring a minimized window works incorrectly, because the window is not restored to its maximized form, and secondly, SDL sends the event SDL_EVENT_WINDOW_MOVED with coordinates -1280,40, which are completely incorrect (these are the coordinates of my second screen, located to the left of the primary, 40 is the height of the taskbar located at the top of the screen).

This is what the maximized window looks like (before minimizing):

And this is how it looks like after it is minimized and restored:

It is not aligned to the top and right edge of the primary display (on the right).

WindowTestBorderless.zip (1.4 MB) — exe with dll to test, source code.

Now the second test of the borderless window, i.e. enabling fullscreen when the borderless window is maximized. The same code as the post above, and the list of steps looks like this:

  1. I press the M key to maximize the window.
  2. I press the F key to enable fullscreen.
  3. I press the W key to disable fullscreen.
  4. I press the R key to restore the window.

The console output for all steps:

  1. When the window shows on the screen:
   2910  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: 640,480
   2911  SDL_EVENT_WINDOW_MOVED: 320,292
  1. When I press the M key to maximize the window.
   8116  SDL_EVENT_WINDOW_MAXIMIZED
   8116  SDL_EVENT_WINDOW_MOVED: 0,40
   8116  SDL_EVENT_WINDOW_RESIZED: 1280,984
   8116  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: 1280,984
  1. When I press the F key to enable fullscreen.
  14179  SDL_EVENT_WINDOW_ENTER_FULLSCREEN
  14179  SDL_EVENT_WINDOW_RESTORED
  14179  SDL_EVENT_WINDOW_MOVED: 0,0
  14179  SDL_EVENT_WINDOW_RESIZED: 1280,1024
  14179  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: 1280,1024
  1. When I press the W key to disable fullscreen.
  21121  SDL_EVENT_WINDOW_LEAVE_FULLSCREEN
  21121  SDL_EVENT_WINDOW_MAXIMIZED
  21122  SDL_EVENT_WINDOW_MOVED: 0,40
  21122  SDL_EVENT_WINDOW_RESIZED: 1280,984
  21122  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: 1280,984
  1. When I press the R key to restore the window.
  27259  SDL_EVENT_WINDOW_RESTORED
  27259  SDL_EVENT_WINDOW_MOVED: 0,0
  27259  SDL_EVENT_WINDOW_RESIZED: 1280,1024
  27259  SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: 1280,1024

Point 5, i.e. restoring the maximized window (after exiting fullscreen) works wrong because instead of restoring the window size to 640x480, it changes the window size to 1280x1024 (the same as the resolution of the primary display), which also turns on the desktop fullscreen again (because the window is the size of the display).

WindowTestBorderless.zip (1.4 MB) — exe with dll to test, source code.

More information on this topic can be found here: