[PATCH 0/1] MR9999: winex11.drv: Fix window stacking order with insert_after parameter.
When calling the SetWindowPos function to change the window Z-order with an insert_after parameter as a real window handle, the expected result is that the insert_after window appears above the specified window hwnd. For example, calling SetWindowPos(g_hwndB, g_hwndA, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE) should result in window A appearing above window B. Below is a demo showing test results on Windows versus Wine: The initial z-order of WindowA and WindowB is: WindowB is above WindowA: {width=781 height=399} Next, click the "Place WindowA above WindowB" button on Windows and Wine respectively. The test results are as follows: On Windows, After clicking the button, WindowA is placed above WindowB: {width=801 height=439} \ On wine, After clicking the button, the Z-order of WindowA and WindowB remains unchanged: {width=900 height=444} demo: [demo.cpp](/uploads/6e3a44e8b223e54d28c2d65e66856d40/demo.cpp) Signed-off-by: Zhao Yi zhaoyi@uniontech.com -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9999
From: Zhao Yi <zhaoyi@uniontech.com> Signed-off-by: Zhao Yi <zhaoyi@uniontech.com> --- dlls/winex11.drv/window.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index a9f6dd08276..4087ee00584 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -2122,19 +2122,20 @@ void make_window_embedded( struct x11drv_win_data *data ) * * Synchronize the X window position with the Windows one */ -static void sync_window_position( struct x11drv_win_data *data, UINT swp_flags, const struct window_rects *old_rects ) +static HWND sync_window_position( struct x11drv_win_data *data, UINT swp_flags, const struct window_rects *old_rects ) { DWORD style = NtUserGetWindowLongW( data->hwnd, GWL_STYLE ); DWORD ex_style = NtUserGetWindowLongW( data->hwnd, GWL_EXSTYLE ); RECT new_rect, window_rect; BOOL above = FALSE; + HWND prev = 0; - if (data->managed && ((style & WS_MINIMIZE) || data->desired_state.wm_state == IconicState)) return; + if (data->managed && ((style & WS_MINIMIZE) || data->desired_state.wm_state == IconicState)) return prev; if (!(swp_flags & SWP_NOZORDER) || (swp_flags & SWP_SHOWWINDOW)) { /* find window that this one must be after */ - HWND prev = NtUserGetWindowRelative( data->hwnd, GW_HWNDPREV ); + prev = NtUserGetWindowRelative( data->hwnd, GW_HWNDPREV ); while (prev && !(NtUserGetWindowLongW( prev, GWL_STYLE ) & WS_VISIBLE)) prev = NtUserGetWindowRelative( prev, GW_HWNDPREV ); if (!prev) above = TRUE; /* top child */ @@ -2154,6 +2155,7 @@ static void sync_window_position( struct x11drv_win_data *data, UINT swp_flags, window_rect.top - old_rects->window.top ); window_set_config( data, new_rect, above ); + return prev; } @@ -3255,6 +3257,7 @@ void X11DRV_WindowPosChanged( HWND hwnd, HWND insert_after, HWND owner_hint, UIN UINT ex_style = NtUserGetWindowLongW( hwnd, GWL_EXSTYLE ), new_style = NtUserGetWindowLongW( hwnd, GWL_STYLE ); struct window_rects old_rects; BOOL is_managed, was_fullscreen, activate = !(swp_flags & SWP_NOACTIVATE), fullscreen = !!(swp_flags & WINE_SWP_FULLSCREEN); + HWND prev = 0; if ((is_managed = is_window_managed( hwnd, swp_flags, fullscreen ))) make_owner_managed( hwnd ); @@ -3297,7 +3300,7 @@ void X11DRV_WindowPosChanged( HWND hwnd, HWND insert_after, HWND owner_hint, UIN /* don't change position if we are about to minimize or maximize a managed window */ if (!(data->managed && (swp_flags & SWP_STATECHANGED) && (new_style & (WS_MINIMIZE|WS_MAXIMIZE)))) { - sync_window_position( data, swp_flags, &old_rects ); + prev = sync_window_position( data, swp_flags, &old_rects ); #ifdef HAVE_LIBXSHAPE if (IsRectEmpty( &old_rects.window ) != IsRectEmpty( &new_rects->window )) sync_empty_window_shape( data, surface ); @@ -3324,6 +3327,19 @@ void X11DRV_WindowPosChanged( HWND hwnd, HWND insert_after, HWND owner_hint, UIN /* if window was fullscreen and is being hidden, release cursor clipping */ was_fullscreen &= data->desired_state.wm_state != NormalState; + if (prev && prev == insert_after) + { + Window prev_window = X11DRV_get_whole_window(prev); + if (prev_window) + { + XWindowChanges changes; + changes.stack_mode = Above; + XConfigureWindow( data->display, prev_window, CWStackMode, &changes ); + } + /* should use changes.stack_mode = Above; changes.sibling = data->whole_window */ + /* but Above with a sibling doesn't work so well , so we ignore it */ + } + XFlush( data->display ); /* make sure changes are done before we start painting again */ release_win_data( data ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9999
Associated with MR !9181 -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9999#note_128370
hi, i don't know enough to review this MR, but i feel it would make sense to have a test case in wine validating this behavior. i think it's possible to get the window stacking order with `GetTopWindow`/`GetNextWindow`. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9999#note_128595
On Tue Feb 3 03:52:16 2026 +0000, Yuxuan Shui wrote:
hi, i don't know enough to review this MR, but i feel it would make sense to have a test case in wine validating this behavior. i think it's possible to get the window stacking order with `GetTopWindow`/`GetNextWindow`. Thank you for your review. The window Z-order logic in Wine works as follows: when **SetWindowPos** is called, a window Z-order linked list is constructed on the **wineserver** side. When retrieving a window at a specified Z-order position—such as calling the **GetTopWindow/GetNextWindow** functions to obtain the top window or the next window—it is fetched from the window Z-order linked list built on the **wineserver** side. Therefore, the correct window at the specified Z-order position can always be retrieved. In the demo results running on Wine mentioned above, calling **GetNextWindow(hwnd, GW_HWNDNEXT)** to get the next window after window A returns window B, which aligns with the expected outcome: {width=900 height=447}
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9999#note_128604
On Tue Feb 3 03:52:16 2026 +0000, Zhao Yi wrote:
Thank you for your review. The window Z-order logic in Wine works as follows: when **SetWindowPos** is called, a window Z-order linked list is constructed on the **wineserver** side. When retrieving a window at a specified Z-order position—such as calling the **GetTopWindow/GetNextWindow** functions to obtain the top window or the next window—it is fetched from the window Z-order linked list built on the **wineserver** side. Therefore, the correct window at the specified Z-order position can always be retrieved. In the demo results running on Wine mentioned above, calling **GetNextWindow(hwnd, GW_HWNDNEXT)** to get the next window after window A returns window B, which aligns with the expected outcome: {width=900 height=447} After applying this MR !9999, running the above demo on Wine and clicking the "Place WindowA above WindowB" button results in WindowA being placed above WindowB, which matches the behavior observed when running the same demo on Windows: {width=877 height=447}
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9999#note_128608
On Tue Feb 3 06:13:00 2026 +0000, Zhao Yi wrote:
After applying this MR !9999, running the above demo on Wine and clicking the "Place WindowA above WindowB" button results in WindowA being placed above WindowB, which matches the behavior observed when running the same demo on Windows: {width=877 height=447} Yes, that is right. Your fix works, and that's great! What I am saying is, we want to make sure:
1. in the future, we won't accidentally break this again. 2. what happens under wine is indeed the same as what happens under Windows. So I am suggesting, can you add a test case to programmatically verify, the result of calling `SetWindowPos` is what you would expect? You can have a look at `dlls/user32/tests/win.c`, in function `test_SetWindowPos`, you can find how this kind of stuff are being tested. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9999#note_128625
That will only work for two windows, otherwise it will mess up the Z-order. I don't think we should be bringing the insert_after window to the front. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9999#note_128644
Fwiw any extra X request will likely need to be properly integrated in the window state tracker to reduce race conditions. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9999#note_128646
On Wed Feb 4 03:38:46 2026 +0000, Alexandre Julliard wrote:
That will only work for two windows, otherwise it will mess up the Z-order. I don't think we should be bringing the insert_after window to the front. You are correct that when there are more than two windows, window Z-order issues can arise. For example, with three windows in the order: **hwndA, hwndB, hwndC**, if we call **SetWindowPos(hwndB, hwndA, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE)** to place **hwndA** above **hwndB**, based on my MR modification, **hwndA** would actually be placed at the top, resulting in the window Z-order: **hwndB, hwndC, hwndA**. However, the expected window Z-order should be: **hwndB, hwndA, hwndC**.
Actually, I think the correct modification for my MR should be: `XWindowChanges changes;` `changes.stack_mode = Below;` `changes.sibling = prev_window;` `XConfigureWindow(data->display, data->whole_window, CWStackMode | CWSibling, &changes);` However, the current X server does not support the **CWSibling** mode very well, so I abandoned this modification approach. In my own testing, calling the **XSetTransientForHint** function to establish a relationship between two windows can successfully place **hwndA** above **hwndB** without affecting the Z-order of other windows. For example, calling **XSetTransientForHint(data->display, prev_window, data->whole_window)**; achieves this. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9999#note_128743
participants (5)
-
Alexandre Julliard (@julliard) -
Rémi Bernon (@rbernon) -
Yuxuan Shui (@yshui) -
Zhao Yi -
Zhao Yi (@Zhaoyi)