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
-- v2: win32u/window: Ignore SetWindowPos() for message-only windows. user32/tests: Check that a message window brought topmost does not defocus other windows.
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 | 53 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+)
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 4c2af09d0c6..ed3ec88043e 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -8898,6 +8898,58 @@ static void test_hwnd_message(void) DestroyWindow(hwnd); }
+static BOOL message_window_topmost_destroying = FALSE; + +static LRESULT WINAPI message_window_topmost_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (msg == WM_KILLFOCUS && !message_window_topmost_destroying) + todo_wine ok(msg != WM_KILLFOCUS, "WM_KILLFOCUS message was delivered\n"); + + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +static void test_message_window_topmost(void) +{ + WNDCLASSW cls = { 0 }; + ATOM atom; + HWND hwnd, hwnd_msg; + 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"); + + SetLastError(0xdeadbeef); + ret = SetWindowPos(hwnd_msg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + ok(ret, "Unexpected failure when calling SetWindowPos()\n"); + ok(GetLastError() == 0xdeadbeef, "Last error unexpectedly set to %#lx\n", GetLastError()); + + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + message_window_topmost_destroying = TRUE; + + 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 +13140,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 | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index ed3ec88043e..b2362d3922e 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -8902,8 +8902,8 @@ static BOOL message_window_topmost_destroying = FALSE;
static LRESULT WINAPI message_window_topmost_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - if (msg == WM_KILLFOCUS && !message_window_topmost_destroying) - todo_wine ok(msg != WM_KILLFOCUS, "WM_KILLFOCUS message was delivered\n"); + if (!message_window_topmost_destroying) + ok(msg != WM_KILLFOCUS, "WM_KILLFOCUS message was delivered\n");
return DefWindowProcW(hwnd, msg, wparam, lparam); } diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c index e7ccbf7a928..ba73ee4a528 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -3490,6 +3490,7 @@ done: BOOL WINAPI NtUserSetWindowPos( HWND hwnd, HWND after, INT x, INT y, INT cx, INT cy, UINT flags ) { WINDOWPOS winpos; + HWND root;
TRACE( "hwnd %p, after %p, %d,%d (%dx%d), flags %08x\n", hwnd, after, x, y, cx, cy, flags ); if(TRACE_ON(win)) dump_winpos_flags(flags); @@ -3500,6 +3501,11 @@ BOOL WINAPI NtUserSetWindowPos( HWND hwnd, HWND after, INT x, INT y, INT cx, INT return FALSE; }
+ root = NtUserGetAncestor(hwnd, GA_ROOT); + root = NtUserGetAncestor(root, GA_PARENT); + if (root == get_hwnd_message_parent()) + return TRUE; + winpos.hwnd = get_full_window_handle( hwnd ); winpos.hwndInsertAfter = get_full_window_handle( after ); winpos.x = x;
Jinoh Kang (@iamahuman) commented about dlls/user32/tests/win.c:
DestroyWindow(hwnd);
}
+static BOOL message_window_topmost_destroying = FALSE;
+static LRESULT WINAPI message_window_topmost_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{
- if (!message_window_topmost_destroying)
ok(msg != WM_KILLFOCUS, "WM_KILLFOCUS message was delivered\n");
We could also test for the delivery of `WM_WINDOWPOSCHANGING`, `WM_WINDOWPOSCHANGED`, `WM_MOVE`, `WM_SIZE`, and etc.
Jinoh Kang (@iamahuman) commented about dlls/win32u/window.c:
return FALSE; }
- root = NtUserGetAncestor(hwnd, GA_ROOT);
- root = NtUserGetAncestor(root, GA_PARENT);
- if (root == get_hwnd_message_parent())
return TRUE;
`NtUserSetWindowPos` isn't the only function that changes the window position (c.f. DeferWindowPos, MoveWindow). Perhaps you meant to patch `set_window_pos` instead? I suppose this needs extra tests.
Jinoh Kang (@iamahuman) commented about dlls/user32/tests/win.c:
- 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");
- SetLastError(0xdeadbeef);
- ret = SetWindowPos(hwnd_msg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
Maybe extend the test to touching every aspect of the window position / z-order? After all, this MR introduces a drastic change of SetWindowPos semantics w.r.t message windows.