-- v4: win32u/window: Ignore changing WS_EX_TOPMOST for message-only windows. user32/tests: Check that message-only windows ignore WS_EX_TOPMOST.
From: Giovanni Mascellani gmascellani@codeweavers.com
libOVR brings a message window topmost claiming that this way it receives WM_DEVICECHANGE faster. Currently, in Wine, this causes a WM_KILLFOCUS to be sent to the actual topmost window, which in turn causes "Shantae: Risky's Revenge" to minimize. The effect is that the game automatically minimizes as soon as it is launched, which is incorrect.
Signed-off-by: Giovanni Mascellani gmascellani@codeweavers.com --- dlls/user32/tests/win.c | 101 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+)
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 4c2af09d0c6..599bcf17c40 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -8898,6 +8898,106 @@ static void test_hwnd_message(void) DestroyWindow(hwnd); }
+static BOOL message_window_topmost_event_delivered = FALSE; +static HWND message_window_topmost_hwnd_msg; + +static LRESULT WINAPI message_window_topmost_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + switch (msg) + { + case WM_KILLFOCUS: + case WM_SETFOCUS: + case WM_ACTIVATE: + case WM_ACTIVATEAPP: + case WM_NCACTIVATE: + message_window_topmost_event_delivered = TRUE; + break; + + case WM_MOVE: + case WM_SIZE: + case WM_WINDOWPOSCHANGING: + case WM_WINDOWPOSCHANGED: + /* On Windows no message is sent to either the regular or + * the message-only window. However on X11 events could + * arrive at any time, so the regular window could receive + * something. */ + if (hwnd == message_window_topmost_hwnd_msg) + message_window_topmost_event_delivered = TRUE; + break; + } + + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +static void test_message_window_topmost(void) +{ + WNDCLASSW cls = { 0 }; + HWND hwnd, hwnd_msg; + HDWP hdwp; + ATOM atom; + BOOL ret; + MSG msg; + + cls.lpfnWndProc = message_window_topmost_proc; + cls.hInstance = GetModuleHandleW(NULL); + cls.lpszClassName = L"test_message_window_topmost"; + atom = RegisterClassW(&cls); + ok(!!atom, "Cannot register window class\n"); + + hwnd = CreateWindowW(L"test_message_window_topmost", L"main window", WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, GetModuleHandleW(NULL), NULL); + ok(!!hwnd, "Cannot create main window\n"); + + hwnd_msg = CreateWindowW(L"test_message_window_topmost", L"message window", 0, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_MESSAGE, NULL, GetModuleHandleW(NULL), NULL); + ok(!!hwnd_msg, "Cannot create message window\n"); + + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + message_window_topmost_event_delivered = FALSE; + message_window_topmost_hwnd_msg = hwnd_msg; + + SetLastError(0xdeadbeef); + + ret = SetWindowPos(hwnd_msg, HWND_TOPMOST, 100, 100, 100, 100, 0); + ok(ret, "Unexpected failure when calling SetWindowPos()\n"); + + ret = SetWindowPos(hwnd_msg, HWND_NOTOPMOST, 100, 100, 100, 100, 0); + ok(ret, "Unexpected failure when calling SetWindowPos()\n"); + + hdwp = BeginDeferWindowPos(2); + ok(!!hdwp, "Unexpected failure when calling BeginDeferWindowPos()\n"); + + hdwp = DeferWindowPos(hdwp, hwnd_msg, HWND_TOPMOST, 100, 100, 100, 100, 0); + ok(!!hdwp, "Unexpected failure when calling DeferWindowPos()\n"); + + hdwp = DeferWindowPos(hdwp, hwnd_msg, HWND_NOTOPMOST, 100, 100, 100, 100, 0); + ok(!!hdwp, "Unexpected failure when calling DeferWindowPos()\n"); + + ret = EndDeferWindowPos(hdwp); + ok(ret, "Unexpected failure when calling EndDeferWindowPos()\n"); + + todo_wine ok(GetLastError() == 0xdeadbeef, "Last error unexpectedly set to %#lx\n", GetLastError()); + + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + todo_wine ok(!message_window_topmost_event_delivered, "Unexpected event was delivered\n"); + + ok(DestroyWindow(hwnd_msg), "Cannot destroy main window\n"); + ok(DestroyWindow(hwnd), "Cannot destroy message window\n"); + + ok(UnregisterClassW(L"test_message_window_topmost", GetModuleHandleW(NULL)), "Cannot unregister class\n"); +} + + static void test_layered_window(void) { HWND hwnd, child; @@ -13088,6 +13188,7 @@ START_TEST(win) test_thick_child_size(hwndMain); test_fullscreen(); test_hwnd_message(); + test_message_window_topmost(); test_nonclient_area(hwndMain); test_params(); test_GetWindowModuleFileName();
From: Giovanni Mascellani gmascellani@codeweavers.com
Signed-off-by: Giovanni Mascellani gmascellani@codeweavers.com --- dlls/user32/tests/win.c | 4 ++-- dlls/win32u/window.c | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 599bcf17c40..c12e02c28f8 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -8981,7 +8981,7 @@ static void test_message_window_topmost(void) ret = EndDeferWindowPos(hdwp); ok(ret, "Unexpected failure when calling EndDeferWindowPos()\n");
- todo_wine ok(GetLastError() == 0xdeadbeef, "Last error unexpectedly set to %#lx\n", GetLastError()); + ok(GetLastError() == 0xdeadbeef, "Last error unexpectedly set to %#lx\n", GetLastError());
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { @@ -8989,7 +8989,7 @@ static void test_message_window_topmost(void) DispatchMessageW(&msg); }
- todo_wine ok(!message_window_topmost_event_delivered, "Unexpected event was delivered\n"); + ok(!message_window_topmost_event_delivered, "Unexpected event was delivered\n");
ok(DestroyWindow(hwnd_msg), "Cannot destroy main window\n"); ok(DestroyWindow(hwnd), "Cannot destroy message window\n"); diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c index fee1617ef10..559693f83a4 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -3366,6 +3366,16 @@ BOOL set_window_pos( WINDOWPOS *winpos, int parent_x, int parent_y ) BOOL ret = FALSE; DPI_AWARENESS_CONTEXT context;
+ if (winpos->hwndInsertAfter == HWND_TOPMOST || winpos->hwndInsertAfter == HWND_NOTOPMOST) + { + HWND root; + + root = NtUserGetAncestor(winpos->hwnd, GA_ROOT); + root = NtUserGetAncestor(root, GA_PARENT); + if (root == get_hwnd_message_parent()) + return TRUE; + } + orig_flags = winpos->flags;
/* First, check z-order arguments. */
On Thu Jul 14 15:39:49 2022 +0000, Giovanni Mascellani wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/419/diffs?diff_id=5055&start_sha=3f96369515e4d1247bcee16bcd837736fa01b8ed#c04d10b2c8324a18e1cae53e0a4ce4c8dc36a903_8906_8905)
Right, I added a few more messages to watch for.
On Thu Jul 14 15:39:50 2022 +0000, Giovanni Mascellani wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/419/diffs?diff_id=5055&start_sha=3f96369515e4d1247bcee16bcd837736fa01b8ed#65bc214e6d025c53e466128853d07366b287855b_3508_3513)
After some additional experimenting, I noticed that `SetWindowPos()` calls are dropped only when they change `WS_EX_TOPMOST` (i.e., when called with `HWND_TOPMOST` or `HWND_NOTOPMOST`). Since that's the case I am interested into, I gated this check under that condition. I also moved the gate to `set_window_pos()`, as you suggest.
On Thu Jul 14 15:39:51 2022 +0000, Giovanni Mascellani wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/419/diffs?diff_id=5055&start_sha=3f96369515e4d1247bcee16bcd837736fa01b8ed#c04d10b2c8324a18e1cae53e0a4ce4c8dc36a903_8934_8942)
Since I restricted the change to the calls that touch `WS_EX_TOPMOST`, I don't think it is necessary to test other changes. I added calls to `DeferWindowPos()`, though.