[PATCH 0/2] MR10725: win32u: Check config again before setting win32 window position.
For example, Homeworld Remastered Collection (244160) Homeworld 2 Classic changes the display mode after NtUserSetActiveWindow(), then the following NtUserSetInternalWindowPos() call sets the window rect to the window rect before the display mode change. Thus, the game window can end up having an unexpected rect. There is a similar problem when LOWORD(state_cmd) == 0. Fix a regression from be60f77909fc9ce2c9b6f1abd005e3b4aabaafec. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10725
From: Zhiyi Zhang <zzhang@codeweavers.com> For example, Homeworld Remastered Collection (244160) Homeworld 2 Classic changes the display mode after NtUserSetActiveWindow(), then the following NtUserSetInternalWindowPos() call sets the window rect to the window rect before the display mode change. Thus, the game window can end up having an unexpected rect. There is a similar problem when LOWORD(state_cmd) == 0. Fix a regression from be60f77909fc9ce2c9b6f1abd005e3b4aabaafec. --- dlls/win32u/message.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index 81e7fde1212..43c2855eff4 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -2235,12 +2235,26 @@ static LRESULT handle_internal_message( HWND hwnd, UINT msg, WPARAM wparam, LPAR { case SC_RESTORE: if (HIWORD(state_cmd)) NtUserSetActiveWindow( hwnd ); + + /* window config might have been changed by set_foreground_window() or NtUserSetActiveWindow(), check again */ + if (foreground || HIWORD(state_cmd)) + { + user_driver->pGetWindowStateUpdates( hwnd, &state_cmd, &swp_flags, &window_rect, &foreground ); + window_rect = map_rect_raw_to_virt( window_rect, get_thread_dpi() ); + } + NtUserSetInternalWindowPos( hwnd, SW_SHOW, &window_rect, NULL ); /* fallthrough */ default: send_message( hwnd, WM_SYSCOMMAND, LOWORD(state_cmd), 0 ); break; case 0: + /* window config might have been changed by set_foreground_window(), check again */ + if (foreground) + { + user_driver->pGetWindowStateUpdates( hwnd, &state_cmd, &swp_flags, &window_rect, &foreground ); + window_rect = map_rect_raw_to_virt( window_rect, get_thread_dpi() ); + } if (!swp_flags) break; NtUserSetWindowPos( hwnd, 0, window_rect.left, window_rect.top, window_rect.right - window_rect.left, window_rect.bottom - window_rect.top, swp_flags ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10725
From: Zhiyi Zhang <zzhang@codeweavers.com> There are many cases in window_update_client_config() that return 0. In those cases, we shouldn't change the window config. Fix a regression from be60f77909fc9ce2c9b6f1abd005e3b4aabaafec. Fix Homeworld Remastered Collection (244160) Homeworld 2 Classic black screen after restoring from the minimized state in fullscreen mode due to an unexpected window size. --- dlls/win32u/message.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index 43c2855eff4..3e7a2623f71 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -2243,7 +2243,7 @@ static LRESULT handle_internal_message( HWND hwnd, UINT msg, WPARAM wparam, LPAR window_rect = map_rect_raw_to_virt( window_rect, get_thread_dpi() ); } - NtUserSetInternalWindowPos( hwnd, SW_SHOW, &window_rect, NULL ); + if (swp_flags) NtUserSetInternalWindowPos( hwnd, SW_SHOW, &window_rect, NULL ); /* fallthrough */ default: send_message( hwnd, WM_SYSCOMMAND, LOWORD(state_cmd), 0 ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10725
This causes WineHQ bug 58804 to reappear. I will look into that. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10725#note_137424
Rémi Bernon (@rbernon) commented about dlls/win32u/message.c:
window_rect = map_rect_raw_to_virt( window_rect, get_thread_dpi() ); }
- NtUserSetInternalWindowPos( hwnd, SW_SHOW, &window_rect, NULL ); + if (swp_flags) NtUserSetInternalWindowPos( hwnd, SW_SHOW, &window_rect, NULL );
As we're about to restore the window on the Win32 side to match the host side the idea was to make sure that we will do so while keeping the config the window manager has decided the window to be in. Otherwise it'll make the Win32 side disagree, and either end up with both out of sync or cause another round-trip of window reconfiguration right after restore, which I believe was racing with extra window re-configuration from the WM that could be pending after restoration (see https://bugs.winehq.org/show_bug.cgi?id=58804). -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10725#note_137426
On Thu Apr 23 09:21:56 2026 +0000, Rémi Bernon wrote:
As we're about to restore the window on the Win32 side to match the host side the idea was to make sure that we will do so while keeping the config the window manager has decided the window to be in. Otherwise it'll make the Win32 side disagree, and either end up with both out of sync or cause another round-trip of window reconfiguration right after restore, which I believe was racing with extra window re-configuration from the WM that could be pending after restoration (see https://bugs.winehq.org/show_bug.cgi?id=58804). Ah sorry, spoke too soon and I see this is for cases where we would delay the Win32 side configuration otherwise... let me think about it. Maybe we should delay the state changes too in similar cases.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10725#note_137427
Rémi Bernon (@rbernon) commented about dlls/win32u/message.c:
{ case SC_RESTORE: if (HIWORD(state_cmd)) NtUserSetActiveWindow( hwnd ); + + /* window config might have been changed by set_foreground_window() or NtUserSetActiveWindow(), check again */ + if (foreground || HIWORD(state_cmd)) + { + user_driver->pGetWindowStateUpdates( hwnd, &state_cmd, &swp_flags, &window_rect, &foreground ); + window_rect = map_rect_raw_to_virt( window_rect, get_thread_dpi() ); + }
I think this then depends on whether events have been received already or not, and that's maybe not very deterministic? It is rather a regression from d56f36c1aa0927a19d74bef66586572ad57322de as I believe the NtSetForegroundWindow call above can only be made by the current foreground window, to give foreground to a different window, so it won't trigger extra requests. The requests are caused by the now restored `NtUserSetActiveWindow( hwnd );` call. If we need to avoid doing more operations after changing foreground / focus, which is very much possible, maybe unconditionally skipping the SC_RESTORE message if we called NtUserSetActiveWindow, and wait until we get another state update message is better? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10725#note_137428
I think this then depends on whether events have been received already or not, and that's maybe not very deterministic?
It's pretty stable. The game changes the size of its main window after the NtUserSetActiveWindow() call.
It is rather a regression from d56f36c1 as I believe the NtSetForegroundWindow call above can only be made by the current foreground window, to give foreground to a different window, so it won't trigger extra requests. The requests are caused by the now restored `NtUserSetActiveWindow( hwnd );` call.
In this case, the requests are indeed from the NtUserSetActiveWindow() call. However, I think it's also possible that an application might respond to the WM_ACTIVATEAPP/WM_ACTIVATE message from NtSetForegroundWindow() and call things like SetWindowPos().
maybe unconditionally skipping the SC_RESTORE message if we called NtUserSetActiveWindow, and wait until we get another state update message is better?
I am not sure. Could it be possible that there is no more WM_WINE_WINDOW_STATE_CHANGED message. Maybe we can just post another WM_WINE_WINDOW_STATE_CHANGED. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10725#note_137438
On Thu Apr 23 09:23:49 2026 +0000, Rémi Bernon wrote:
Ah sorry, spoke too soon and I see this is for cases where we would delay the Win32 side configuration otherwise... let me think about it. Maybe we should delay the state changes too in similar cases. Maybe we can modify WND->normal_rect without going through `NtUserSetInternalWindowPos`. That should avoid unwanted side effects.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10725#note_137439
I think this then depends on whether events have been received already or not, and that's maybe not very deterministic?
Oh, you meant if the data->current_state.rect hasn't been updated yet, for example, if the WM hasn't processed the request, the window rect can still be wrong. Yes, this is a problem. Let me think about how to fix this. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10725#note_137575
participants (3)
-
Rémi Bernon (@rbernon) -
Zhiyi Zhang -
Zhiyi Zhang (@zhiyi)