Pegasus Mail posts a WM_USER + 8991 message to its own window when it loses focus, then calls SetFocus on its window when processing this message. Several other applications have been seen calling SetWindowPos during WM_ACTIVATE message, which might also attempt to reactivate the window.
That processing happens shortly after we have changed the foreground window to the desktop window, when focus is lost, then SetFocus tries to change the foreground window again.
This SetFocus behavior is tested, and should work like this, but would only activate the window if the process is allowed to do so. Windows has various rules around this, and it seems to boil down to something like:
* Allow taking focus if the process never was foreground, ie: when process is starting and gets initial focus on its windows.
* Allow taking focus if the process was foreground but lost it recently because of a window being destroyed.
* Forbid taking focus back if the process had foreground and called SetForegroundWindow explicitly to give it to another process.
This doesn't implement all this rules, but rather keep rely on the host window management with some additional heuristics to avoid activating windows which lost foreground recently. This is mostly about keeping track of user input time, updating it on user input and on focus change.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58167
---
I think this should be enough to prevent spurious window reactivation, either from calls to SetWindowPos during WM_ACTIVATE messages like in https://gitlab.winehq.org/wine/wine/-/merge_requests/9398 or from calls to SetFocus from posted messages right after window deactivation as described above.
Fwiw we could have tests for that, and show that this MR is fixing them but fvwm doesn't implement window minimization properly, and doesn't change focus when window is minimized, so the tests would be meaningless.
-- v5: server: Forbid background process window reactivation. server: Set NULL foreground input when switching to desktop window. win32u: Introduce a NtUserSetForegroundWindowInternal call. user32/tests: Use a dedicated window instead of the desktop window. ddraw/tests: Use a dedicated window instead of the desktop window.
From: Rémi Bernon rbernon@codeweavers.com
We cannot safely switch to the desktop window, and we also cannot safely destroy and re-create windows without risking to lose focus in favor of a foreign window. Lets create a dedicated window to which foreground will be returned whenever the test windows lose it. --- dlls/ddraw/tests/ddraw1.c | 87 +++++++++++++++++++++++++++++++++------ dlls/ddraw/tests/ddraw2.c | 87 +++++++++++++++++++++++++++++++++------ dlls/ddraw/tests/ddraw4.c | 87 +++++++++++++++++++++++++++++++++------ dlls/ddraw/tests/ddraw7.c | 87 +++++++++++++++++++++++++++++++++------ 4 files changed, 300 insertions(+), 48 deletions(-)
diff --git a/dlls/ddraw/tests/ddraw1.c b/dlls/ddraw/tests/ddraw1.c index fb5201f66ba..ab7ddf3be15 100644 --- a/dlls/ddraw/tests/ddraw1.c +++ b/dlls/ddraw/tests/ddraw1.c @@ -30,6 +30,7 @@
static BOOL is_ddraw64 = sizeof(DWORD) != sizeof(DWORD *); static DEVMODEA registry_mode; +static HWND foreground;
static HRESULT (WINAPI *pDwmIsCompositionEnabled)(BOOL *);
@@ -113,6 +114,64 @@ static void flush_events(void) } }
+static HWND create_foreground_window(void) +{ + for (UINT retries = 5; retries; retries--) + { + HWND hwnd; + BOOL ret; + + hwnd = CreateWindowW(L"static", NULL, WS_POPUP | WS_VISIBLE, 0, 0, 5, 5, NULL, NULL, NULL, NULL); + ok(hwnd != NULL, "CreateWindowW failed, error %lu\n", GetLastError()); + flush_events(); + + if (GetForegroundWindow() == hwnd) + return hwnd; + ret = DestroyWindow(hwnd); + ok(ret, "DestroyWindow failed, error %lu\n", GetLastError()); + flush_events(); + } + + ok(0, "Failed to create foreground window\n"); + return NULL; +} + +static LRESULT CALLBACK foreground_window_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (msg == WM_USER) + SetForegroundWindow((HWND)wparam); + if (msg == WM_CLOSE) + PostQuitMessage(0); + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +static DWORD WINAPI foreground_window_thread(void *arg) +{ + HANDLE event = arg; + MSG msg; + + foreground = create_foreground_window(); + SetWindowLongPtrW(foreground, GWLP_WNDPROC, (LONG_PTR)foreground_window_wndproc); + SetEvent(event); + + while (GetMessageW(&msg, NULL, 0, 0)) + { + if (msg.message == WM_QUIT) break; + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + return 0; +} + +static void start_foreground_window_thread(void) +{ + HANDLE event = CreateEventA(NULL, FALSE, FALSE, NULL); + CloseHandle(CreateThread(NULL, 0, foreground_window_thread, event, 0, NULL)); + WaitForSingleObject(event, 10000); + CloseHandle(event); +} + static BOOL ddraw_get_identifier(IDirectDraw *ddraw, DDDEVICEIDENTIFIER *identifier) { IDirectDraw4 *ddraw4; @@ -1053,6 +1112,8 @@ static void test_clipper_blt(void) window = CreateWindowA("static", "ddraw_test", WS_OVERLAPPEDWINDOW, 10, 10, 640, 480, 0, 0, 0, 0); ShowWindow(window, SW_SHOW); + flush_events(); + ddraw = create_ddraw(); ok(!!ddraw, "Failed to create a ddraw object.\n");
@@ -2632,7 +2693,7 @@ static void test_window_style(void) GetClientRect(window, &r); todo_wine ok(!EqualRect(&r, &fullscreen_rect), "Client rect and window rect are equal.\n");
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n");
tmp = GetWindowLongA(window, GWL_STYLE); @@ -2720,7 +2781,7 @@ static void test_window_style(void) ok(tmp == exstyle, "Expected window extended style %#lx, got %#lx.\n", exstyle, tmp);
ShowWindow(window, SW_SHOW); - ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); SetActiveWindow(window); ok(GetActiveWindow() == window, "Unexpected active window.\n"); @@ -2792,7 +2853,7 @@ static void test_window_style(void) expected_style = exstyle | WS_EX_TOPMOST; todo_wine ok(tmp == expected_style, "Expected window extended style %#lx, got %#lx.\n", expected_style, tmp);
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); tmp = GetWindowLongA(window, GWL_STYLE); expected_style = style | WS_VISIBLE | WS_MINIMIZE; @@ -3186,7 +3247,7 @@ static void test_coop_level_mode_set(void) wine_dbgstr_rect(&r));
expect_messages = exclusive_focus_loss_messages; - ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); ok(!expect_messages->message, "Expected message %#x, but didn't receive it.\n", expect_messages->message); memset(&devmode, 0, sizeof(devmode)); @@ -4461,7 +4522,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Exclusive with window not active. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; hr = IDirectDraw_SetCooperativeLevel(ddraw, window, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr); @@ -4470,7 +4531,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Normal with window not active, then exclusive with the same window. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; hr = IDirectDraw_SetCooperativeLevel(ddraw, window, DDSCL_NORMAL); ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr); @@ -4482,7 +4543,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Recursive set of DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; activateapp_testdata.ddraw = ddraw; activateapp_testdata.window = window; @@ -4504,7 +4565,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Setting DDSCL_NORMAL with recursive invocation. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; activateapp_testdata.ddraw = ddraw; activateapp_testdata.window = window; @@ -8217,7 +8278,7 @@ static void test_lost_device(void) ok(hr == DD_OK, "Got unexpected hr %#lx.\n", hr); }
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); hr = IDirectDrawSurface_IsLost(surface); ok(hr == DDERR_SURFACELOST, "Got unexpected hr %#lx.\n", hr); @@ -8301,7 +8362,7 @@ static void test_lost_device(void) hr = IDirectDrawSurface_IsLost(surface); ok(hr == DD_OK, "Got unexpected hr %#lx.\n", hr);
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); hr = IDirectDrawSurface_IsLost(surface); ok(hr == DD_OK, "Got unexpected hr %#lx.\n", hr); @@ -13835,7 +13896,7 @@ static void test_killfocus(void) hr = IDirectDraw_CreateSurface(killfocus_ddraw, &surface_desc, &killfocus_surface, NULL); ok(SUCCEEDED(hr), "Failed to create surface, hr %#lx.\n", hr);
- SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); ok(!killfocus_ddraw, "WM_KILLFOCUS was not received.\n");
DestroyWindow(window); @@ -14794,7 +14855,7 @@ static BOOL CALLBACK test_window_position_cb(HMONITOR monitor, HDC hdc, RECT *mo ret = SetWindowPos(window, 0, monitor_rect->left, monitor_rect->top, 0, 0, SWP_NOZORDER | SWP_NOSIZE); ok(ret, "SetWindowPos failed, error %lu.\n", GetLastError()); - ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); flush_events(); ret = ShowWindow(window, SW_RESTORE); @@ -16343,6 +16404,8 @@ START_TEST(ddraw1) if ((dwmapi = LoadLibraryA("dwmapi.dll"))) pDwmIsCompositionEnabled = (void *)GetProcAddress(dwmapi, "DwmIsCompositionEnabled");
+ start_foreground_window_thread(); + test_coop_level_create_device_window(); test_clipper_blt(); test_coop_level_d3d_state(); diff --git a/dlls/ddraw/tests/ddraw2.c b/dlls/ddraw/tests/ddraw2.c index f163ec80bed..36b7c8eeea8 100644 --- a/dlls/ddraw/tests/ddraw2.c +++ b/dlls/ddraw/tests/ddraw2.c @@ -31,6 +31,7 @@
static BOOL is_ddraw64 = sizeof(DWORD) != sizeof(DWORD *); static DEVMODEA registry_mode; +static HWND foreground;
static HRESULT (WINAPI *pDwmIsCompositionEnabled)(BOOL *);
@@ -114,6 +115,64 @@ static void flush_events(void) } }
+static HWND create_foreground_window(void) +{ + for (UINT retries = 5; retries; retries--) + { + HWND hwnd; + BOOL ret; + + hwnd = CreateWindowW(L"static", NULL, WS_POPUP | WS_VISIBLE, 0, 0, 5, 5, NULL, NULL, NULL, NULL); + ok(hwnd != NULL, "CreateWindowW failed, error %lu\n", GetLastError()); + flush_events(); + + if (GetForegroundWindow() == hwnd) + return hwnd; + ret = DestroyWindow(hwnd); + ok(ret, "DestroyWindow failed, error %lu\n", GetLastError()); + flush_events(); + } + + ok(0, "Failed to create foreground window\n"); + return NULL; +} + +static LRESULT CALLBACK foreground_window_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (msg == WM_USER) + SetForegroundWindow((HWND)wparam); + if (msg == WM_CLOSE) + PostQuitMessage(0); + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +static DWORD WINAPI foreground_window_thread(void *arg) +{ + HANDLE event = arg; + MSG msg; + + foreground = create_foreground_window(); + SetWindowLongPtrW(foreground, GWLP_WNDPROC, (LONG_PTR)foreground_window_wndproc); + SetEvent(event); + + while (GetMessageW(&msg, NULL, 0, 0)) + { + if (msg.message == WM_QUIT) break; + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + return 0; +} + +static void start_foreground_window_thread(void) +{ + HANDLE event = CreateEventA(NULL, FALSE, FALSE, NULL); + CloseHandle(CreateThread(NULL, 0, foreground_window_thread, event, 0, NULL)); + WaitForSingleObject(event, 10000); + CloseHandle(event); +} + static BOOL ddraw_get_identifier(IDirectDraw2 *ddraw, DDDEVICEIDENTIFIER *identifier) { IDirectDraw4 *ddraw4; @@ -901,6 +960,8 @@ static void test_clipper_blt(void) window = CreateWindowA("static", "ddraw_test", WS_OVERLAPPEDWINDOW, 10, 10, 640, 480, 0, 0, 0, 0); ShowWindow(window, SW_SHOW); + flush_events(); + ddraw = create_ddraw(); ok(!!ddraw, "Failed to create a ddraw object.\n");
@@ -2713,7 +2774,7 @@ static void test_window_style(void) GetClientRect(window, &r); todo_wine ok(!EqualRect(&r, &fullscreen_rect), "Client rect and window rect are equal.\n");
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n");
tmp = GetWindowLongA(window, GWL_STYLE); @@ -2801,7 +2862,7 @@ static void test_window_style(void) ok(tmp == exstyle, "Expected window extended style %#lx, got %#lx.\n", exstyle, tmp);
ShowWindow(window, SW_SHOW); - ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); SetActiveWindow(window); ok(GetActiveWindow() == window, "Unexpected active window.\n"); @@ -2873,7 +2934,7 @@ static void test_window_style(void) expected_style = exstyle | WS_EX_TOPMOST; todo_wine ok(tmp == expected_style, "Expected window extended style %#lx, got %#lx.\n", expected_style, tmp);
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); tmp = GetWindowLongA(window, GWL_STYLE); expected_style = style | WS_VISIBLE | WS_MINIMIZE; @@ -3283,7 +3344,7 @@ static void test_coop_level_mode_set(void) wine_dbgstr_rect(&r));
expect_messages = exclusive_focus_loss_messages; - ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); ok(!expect_messages->message, "Expected message %#x, but didn't receive it.\n", expect_messages->message); memset(&devmode, 0, sizeof(devmode)); @@ -4926,7 +4987,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Exclusive with window not active. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; hr = IDirectDraw2_SetCooperativeLevel(ddraw, window, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr); @@ -4935,7 +4996,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Normal with window not active, then exclusive with the same window. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; hr = IDirectDraw2_SetCooperativeLevel(ddraw, window, DDSCL_NORMAL); ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr); @@ -4947,7 +5008,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Recursive set of DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; activateapp_testdata.ddraw = ddraw; activateapp_testdata.window = window; @@ -4969,7 +5030,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Setting DDSCL_NORMAL with recursive invocation. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; activateapp_testdata.ddraw = ddraw; activateapp_testdata.window = window; @@ -9258,7 +9319,7 @@ static void test_lost_device(void) ok(hr == DD_OK, "Got unexpected hr %#lx.\n", hr); }
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); hr = IDirectDrawSurface_IsLost(surface); ok(hr == DDERR_SURFACELOST, "Got unexpected hr %#lx.\n", hr); @@ -9341,7 +9402,7 @@ static void test_lost_device(void) hr = IDirectDrawSurface_IsLost(surface); ok(hr == DD_OK, "Got unexpected hr %#lx.\n", hr);
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); hr = IDirectDrawSurface_IsLost(surface); ok(hr == DD_OK, "Got unexpected hr %#lx.\n", hr); @@ -14885,7 +14946,7 @@ static void test_killfocus(void) hr = IDirectDraw2_CreateSurface(killfocus_ddraw, &surface_desc, &killfocus_surface, NULL); ok(SUCCEEDED(hr), "Failed to create surface, hr %#lx.\n", hr);
- SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); ok(!killfocus_ddraw, "WM_KILLFOCUS was not received.\n");
DestroyWindow(window); @@ -15752,7 +15813,7 @@ static BOOL CALLBACK test_window_position_cb(HMONITOR monitor, HDC hdc, RECT *mo ret = SetWindowPos(window, 0, monitor_rect->left, monitor_rect->top, 0, 0, SWP_NOZORDER | SWP_NOSIZE); ok(ret, "SetWindowPos failed, error %lu.\n", GetLastError()); - ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); flush_events(); ret = ShowWindow(window, SW_RESTORE); @@ -17414,6 +17475,8 @@ START_TEST(ddraw2) if ((dwmapi = LoadLibraryA("dwmapi.dll"))) pDwmIsCompositionEnabled = (void *)GetProcAddress(dwmapi, "DwmIsCompositionEnabled");
+ start_foreground_window_thread(); + test_coop_level_create_device_window(); test_clipper_blt(); test_coop_level_d3d_state(); diff --git a/dlls/ddraw/tests/ddraw4.c b/dlls/ddraw/tests/ddraw4.c index 60eaa3ec8d0..1e9a964d86d 100644 --- a/dlls/ddraw/tests/ddraw4.c +++ b/dlls/ddraw/tests/ddraw4.c @@ -32,6 +32,7 @@ HRESULT WINAPI GetSurfaceFromDC(HDC dc, struct IDirectDrawSurface **surface, HDC
static BOOL is_ddraw64 = sizeof(DWORD) != sizeof(DWORD *); static DEVMODEA registry_mode; +static HWND foreground;
static HRESULT (WINAPI *pDwmIsCompositionEnabled)(BOOL *);
@@ -120,6 +121,64 @@ static void flush_events(void) } }
+static HWND create_foreground_window(void) +{ + for (UINT retries = 5; retries; retries--) + { + HWND hwnd; + BOOL ret; + + hwnd = CreateWindowW(L"static", NULL, WS_POPUP | WS_VISIBLE, 0, 0, 5, 5, NULL, NULL, NULL, NULL); + ok(hwnd != NULL, "CreateWindowW failed, error %lu\n", GetLastError()); + flush_events(); + + if (GetForegroundWindow() == hwnd) + return hwnd; + ret = DestroyWindow(hwnd); + ok(ret, "DestroyWindow failed, error %lu\n", GetLastError()); + flush_events(); + } + + ok(0, "Failed to create foreground window\n"); + return NULL; +} + +static LRESULT CALLBACK foreground_window_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (msg == WM_USER) + SetForegroundWindow((HWND)wparam); + if (msg == WM_CLOSE) + PostQuitMessage(0); + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +static DWORD WINAPI foreground_window_thread(void *arg) +{ + HANDLE event = arg; + MSG msg; + + foreground = create_foreground_window(); + SetWindowLongPtrW(foreground, GWLP_WNDPROC, (LONG_PTR)foreground_window_wndproc); + SetEvent(event); + + while (GetMessageW(&msg, NULL, 0, 0)) + { + if (msg.message == WM_QUIT) break; + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + return 0; +} + +static void start_foreground_window_thread(void) +{ + HANDLE event = CreateEventA(NULL, FALSE, FALSE, NULL); + CloseHandle(CreateThread(NULL, 0, foreground_window_thread, event, 0, NULL)); + WaitForSingleObject(event, 10000); + CloseHandle(event); +} + static BOOL ddraw_get_identifier(IDirectDraw4 *ddraw, DDDEVICEIDENTIFIER *identifier) { HRESULT hr; @@ -1093,6 +1152,8 @@ static void test_clipper_blt(void) window = CreateWindowA("static", "ddraw_test", WS_OVERLAPPEDWINDOW, 10, 10, 640, 480, 0, 0, 0, 0); ShowWindow(window, SW_SHOW); + flush_events(); + ddraw = create_ddraw(); ok(!!ddraw, "Failed to create a ddraw object.\n");
@@ -2953,7 +3014,7 @@ static void test_window_style(void) GetClientRect(window, &r); todo_wine ok(!EqualRect(&r, &fullscreen_rect), "Client rect and window rect are equal.\n");
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n");
tmp = GetWindowLongA(window, GWL_STYLE); @@ -3041,7 +3102,7 @@ static void test_window_style(void) ok(tmp == exstyle, "Expected window extended style %#lx, got %#lx.\n", exstyle, tmp);
ShowWindow(window, SW_SHOW); - ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); SetActiveWindow(window); ok(GetActiveWindow() == window, "Unexpected active window.\n"); @@ -3113,7 +3174,7 @@ static void test_window_style(void) expected_style = exstyle | WS_EX_TOPMOST; todo_wine ok(tmp == expected_style, "Expected window extended style %#lx, got %#lx.\n", expected_style, tmp);
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); tmp = GetWindowLongA(window, GWL_STYLE); expected_style = style | WS_VISIBLE | WS_MINIMIZE; @@ -3524,7 +3585,7 @@ static void test_coop_level_mode_set(void)
expect_messages = exclusive_focus_loss_messages; focus_test_ddraw = ddraw; - ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); ok(!expect_messages->message, "Expected message %#x, but didn't receive it.\n", expect_messages->message); focus_test_ddraw = NULL; @@ -6119,7 +6180,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Exclusive with window not active. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; hr = IDirectDraw4_SetCooperativeLevel(ddraw, window, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr); @@ -6128,7 +6189,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Normal with window not active, then exclusive with the same window. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; hr = IDirectDraw4_SetCooperativeLevel(ddraw, window, DDSCL_NORMAL); ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr); @@ -6140,7 +6201,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Recursive set of DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; activateapp_testdata.ddraw = ddraw; activateapp_testdata.window = window; @@ -6162,7 +6223,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Setting DDSCL_NORMAL with recursive invocation. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; activateapp_testdata.ddraw = ddraw; activateapp_testdata.window = window; @@ -10632,7 +10693,7 @@ static void test_lost_device(void) ok(hr == DD_OK, "Got unexpected hr %#lx.\n", hr); }
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); hr = IDirectDraw4_TestCooperativeLevel(ddraw); ok(hr == DDERR_NOEXCLUSIVEMODE, "Got unexpected hr %#lx.\n", hr); @@ -10732,7 +10793,7 @@ static void test_lost_device(void) ok(hr == DD_OK, "Got unexpected hr %#lx.\n", hr); }
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); hr = IDirectDraw4_TestCooperativeLevel(ddraw); ok(hr == DD_OK, "Got unexpected hr %#lx.\n", hr); @@ -17493,7 +17554,7 @@ static void test_killfocus(void) hr = IDirectDraw4_CreateSurface(killfocus_ddraw, &surface_desc, &killfocus_surface, NULL); ok(SUCCEEDED(hr), "Failed to create surface, hr %#lx.\n", hr);
- SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); ok(!killfocus_ddraw, "WM_KILLFOCUS was not received.\n");
DestroyWindow(window); @@ -18824,7 +18885,7 @@ static BOOL CALLBACK test_window_position_cb(HMONITOR monitor, HDC hdc, RECT *mo ret = SetWindowPos(window, 0, monitor_rect->left, monitor_rect->top, 0, 0, SWP_NOZORDER | SWP_NOSIZE); ok(ret, "SetWindowPos failed, error %lu.\n", GetLastError()); - ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); flush_events(); ret = ShowWindow(window, SW_RESTORE); @@ -20495,6 +20556,8 @@ START_TEST(ddraw4) if ((dwmapi = LoadLibraryA("dwmapi.dll"))) pDwmIsCompositionEnabled = (void *)GetProcAddress(dwmapi, "DwmIsCompositionEnabled");
+ start_foreground_window_thread(); + test_process_vertices(); test_coop_level_create_device_window(); test_clipper_blt(); diff --git a/dlls/ddraw/tests/ddraw7.c b/dlls/ddraw/tests/ddraw7.c index a3df2fa659f..7a3c96fa315 100644 --- a/dlls/ddraw/tests/ddraw7.c +++ b/dlls/ddraw/tests/ddraw7.c @@ -32,6 +32,7 @@ HRESULT WINAPI GetSurfaceFromDC(HDC dc, struct IDirectDrawSurface **surface, HDC static HRESULT (WINAPI *pDirectDrawCreateEx)(GUID *guid, void **ddraw, REFIID iid, IUnknown *outer_unknown); static BOOL is_ddraw64 = sizeof(DWORD) != sizeof(DWORD *); static DEVMODEA registry_mode; +static HWND foreground; static const GUID *hw_device_guid = &IID_IDirect3DHALDevice;
static HRESULT (WINAPI *pDwmIsCompositionEnabled)(BOOL *); @@ -161,6 +162,64 @@ static void flush_events(void) } }
+static HWND create_foreground_window(void) +{ + for (UINT retries = 5; retries; retries--) + { + HWND hwnd; + BOOL ret; + + hwnd = CreateWindowW(L"static", NULL, WS_POPUP | WS_VISIBLE, 0, 0, 5, 5, NULL, NULL, NULL, NULL); + ok(hwnd != NULL, "CreateWindowW failed, error %lu\n", GetLastError()); + flush_events(); + + if (GetForegroundWindow() == hwnd) + return hwnd; + ret = DestroyWindow(hwnd); + ok(ret, "DestroyWindow failed, error %lu\n", GetLastError()); + flush_events(); + } + + ok(0, "Failed to create foreground window\n"); + return NULL; +} + +static LRESULT CALLBACK foreground_window_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (msg == WM_USER) + SetForegroundWindow((HWND)wparam); + if (msg == WM_CLOSE) + PostQuitMessage(0); + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +static DWORD WINAPI foreground_window_thread(void *arg) +{ + HANDLE event = arg; + MSG msg; + + foreground = create_foreground_window(); + SetWindowLongPtrW(foreground, GWLP_WNDPROC, (LONG_PTR)foreground_window_wndproc); + SetEvent(event); + + while (GetMessageW(&msg, NULL, 0, 0)) + { + if (msg.message == WM_QUIT) break; + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + return 0; +} + +static void start_foreground_window_thread(void) +{ + HANDLE event = CreateEventA(NULL, FALSE, FALSE, NULL); + CloseHandle(CreateThread(NULL, 0, foreground_window_thread, event, 0, NULL)); + WaitForSingleObject(event, 10000); + CloseHandle(event); +} + static BOOL ddraw_get_identifier(IDirectDraw7 *ddraw, DDDEVICEIDENTIFIER2 *identifier) { HRESULT hr; @@ -1178,6 +1237,8 @@ static void test_clipper_blt(void) window = CreateWindowA("static", "ddraw_test", WS_OVERLAPPEDWINDOW, 10, 10, 640, 480, 0, 0, 0, 0); ShowWindow(window, SW_SHOW); + flush_events(); + ddraw = create_ddraw(); ok(!!ddraw, "Failed to create a ddraw object.\n");
@@ -2715,7 +2776,7 @@ static void test_window_style(void) GetClientRect(window, &r); todo_wine ok(!EqualRect(&r, &fullscreen_rect), "Client rect and window rect are equal.\n");
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n");
tmp = GetWindowLongA(window, GWL_STYLE); @@ -2803,7 +2864,7 @@ static void test_window_style(void) ok(tmp == exstyle, "Expected window extended style %#lx, got %#lx.\n", exstyle, tmp);
ShowWindow(window, SW_SHOW); - ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); SetActiveWindow(window); ok(GetActiveWindow() == window, "Unexpected active window.\n"); @@ -2875,7 +2936,7 @@ static void test_window_style(void) expected_style = exstyle | WS_EX_TOPMOST; todo_wine ok(tmp == expected_style, "Expected window extended style %#lx, got %#lx.\n", expected_style, tmp);
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); tmp = GetWindowLongA(window, GWL_STYLE); expected_style = style | WS_VISIBLE | WS_MINIMIZE; @@ -3285,7 +3346,7 @@ static void test_coop_level_mode_set(void)
expect_messages = exclusive_focus_loss_messages; focus_test_ddraw = ddraw; - ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); ok(!expect_messages->message, "Expected message %#x, but didn't receive it.\n", expect_messages->message); focus_test_ddraw = NULL; @@ -5960,7 +6021,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Exclusive with window not active. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; hr = IDirectDraw7_SetCooperativeLevel(ddraw, window, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr); @@ -5969,7 +6030,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Normal with window not active, then exclusive with the same window. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; hr = IDirectDraw7_SetCooperativeLevel(ddraw, window, DDSCL_NORMAL); ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr); @@ -5981,7 +6042,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Recursive set of DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; activateapp_testdata.ddraw = ddraw; activateapp_testdata.window = window; @@ -6003,7 +6064,7 @@ static void test_coop_level_activateapp(void) ok(SUCCEEDED(hr), "Failed to set cooperative level, hr %#lx.\n", hr);
/* Setting DDSCL_NORMAL with recursive invocation. */ - SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); activateapp_testdata.received = FALSE; activateapp_testdata.ddraw = ddraw; activateapp_testdata.window = window; @@ -10498,7 +10559,7 @@ static void test_lost_device(void) ok(hr == DD_OK, "Got unexpected hr %#lx.\n", hr); }
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); hr = IDirectDraw7_TestCooperativeLevel(ddraw); ok(hr == DDERR_NOEXCLUSIVEMODE, "Got unexpected hr %#lx.\n", hr); @@ -10600,7 +10661,7 @@ static void test_lost_device(void) ok(hr == DD_OK, "Got unexpected hr %#lx.\n", hr); }
- ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); hr = IDirectDraw7_TestCooperativeLevel(ddraw); ok(hr == DD_OK, "Got unexpected hr %#lx.\n", hr); @@ -17485,7 +17546,7 @@ static void test_killfocus(void) hr = IDirectDraw7_CreateSurface(killfocus_ddraw, &surface_desc, &killfocus_surface, NULL); ok(SUCCEEDED(hr), "Failed to create surface, hr %#lx.\n", hr);
- SetForegroundWindow(GetDesktopWindow()); + SetForegroundWindow(foreground); ok(!killfocus_ddraw, "WM_KILLFOCUS was not received.\n");
DestroyWindow(window); @@ -19204,7 +19265,7 @@ static BOOL CALLBACK test_window_position_cb(HMONITOR monitor, HDC hdc, RECT *mo ret = SetWindowPos(window, 0, monitor_rect->left, monitor_rect->top, 0, 0, SWP_NOZORDER | SWP_NOSIZE); ok(ret, "SetWindowPos failed, error %lu.\n", GetLastError()); - ret = SetForegroundWindow(GetDesktopWindow()); + ret = SetForegroundWindow(foreground); ok(ret, "Failed to set foreground window.\n"); flush_events(); ret = ShowWindow(window, SW_RESTORE); @@ -20919,6 +20980,8 @@ START_TEST(ddraw7) if ((dwmapi = LoadLibraryA("dwmapi.dll"))) pDwmIsCompositionEnabled = (void *)GetProcAddress(dwmapi, "DwmIsCompositionEnabled");
+ start_foreground_window_thread(); + test_process_vertices(); test_coop_level_create_device_window(); test_clipper_blt();
From: Rémi Bernon rbernon@codeweavers.com
We cannot safely switch to the desktop window, and we also cannot safely destroy and re-create windows without risking to lose focus in favor of a foreign window. Lets create a dedicated window to which foreground will be returned whenever the test windows lose it. --- dlls/user32/tests/input.c | 2 +- dlls/user32/tests/msg.c | 73 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-)
diff --git a/dlls/user32/tests/input.c b/dlls/user32/tests/input.c index ab7dd6b5605..7915a8416d3 100644 --- a/dlls/user32/tests/input.c +++ b/dlls/user32/tests/input.c @@ -561,7 +561,7 @@ static inline BOOL is_mouse_message( UINT message ) }
#define create_foreground_window( a ) create_foreground_window_( __FILE__, __LINE__, a, 5 ) -HWND create_foreground_window_( const char *file, int line, BOOL fullscreen, UINT retries ) +static HWND create_foreground_window_( const char *file, int line, BOOL fullscreen, UINT retries ) { for (;;) { diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c index a09d752786b..6fd76d28533 100644 --- a/dlls/user32/tests/msg.c +++ b/dlls/user32/tests/msg.c @@ -117,6 +117,7 @@ static HHOOK hKBD_hook; static HHOOK hCBT_hook; static DWORD cbt_hook_thread_id; static DWORD winevent_hook_thread_id; +static HWND foreground;
static const WCHAR testWindowClassW[] = { 'T','e','s','t','W','i','n','d','o','w','C','l','a','s','s','W',0 }; @@ -2695,6 +2696,76 @@ static void flush_sequence(void) LeaveCriticalSection( &sequence_cs ); }
+#define create_foreground_window( a ) create_foreground_window_( __FILE__, __LINE__, a, 5 ) +static HWND create_foreground_window_( const char *file, int line, BOOL fullscreen, UINT retries ) +{ + for (;;) + { + HWND hwnd; + BOOL ret; + + hwnd = CreateWindowW( L"static", NULL, WS_POPUP | (fullscreen ? 0 : WS_VISIBLE), + 100, 100, 5, 5, NULL, NULL, NULL, NULL ); + ok_(file, line)( hwnd != NULL, "CreateWindowW failed, error %lu\n", GetLastError() ); + + if (fullscreen) + { + HMONITOR hmonitor = MonitorFromWindow( hwnd, MONITOR_DEFAULTTOPRIMARY ); + MONITORINFO mi = {.cbSize = sizeof(MONITORINFO)}; + + ok_(file, line)( hmonitor != NULL, "MonitorFromWindow failed, error %lu\n", GetLastError() ); + ret = GetMonitorInfoW( hmonitor, &mi ); + ok_(file, line)( ret, "GetMonitorInfoW failed, error %lu\n", GetLastError() ); + ret = SetWindowPos( hwnd, 0, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right - mi.rcMonitor.left, + mi.rcMonitor.bottom - mi.rcMonitor.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW ); + ok_(file, line)( ret, "SetWindowPos failed, error %lu\n", GetLastError() ); + } + flush_events(); + + if (GetForegroundWindow() == hwnd) return hwnd; + ok_(file, line)( retries > 0, "failed to create foreground window\n" ); + if (!retries--) return hwnd; + + ret = DestroyWindow( hwnd ); + ok_(file, line)( ret, "DestroyWindow failed, error %lu\n", GetLastError() ); + flush_events(); + } +} + +static LRESULT CALLBACK foreground_window_wndproc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) +{ + if (msg == WM_USER) SetForegroundWindow( (HWND)wparam ); + if (msg == WM_CLOSE) PostQuitMessage( 0 ); + return DefWindowProcW( hwnd, msg, wparam, lparam ); +} + +static DWORD WINAPI foreground_window_thread( void *arg ) +{ + HANDLE event = arg; + MSG msg; + + foreground = create_foreground_window( FALSE ); + SetWindowLongPtrW( foreground, GWLP_WNDPROC, (LONG_PTR)foreground_window_wndproc ); + SetEvent( event ); + + while (GetMessageW( &msg, NULL, 0, 0 )) + { + if (msg.message == WM_QUIT) break; + TranslateMessage( &msg ); + DispatchMessageW( &msg ); + } + + return 0; +} + +static void start_foreground_window_thread(void) +{ + HANDLE event = CreateEventA( NULL, FALSE, FALSE, NULL ); + CloseHandle( CreateThread( NULL, 0, foreground_window_thread, event, 0, NULL ) ); + WaitForSingleObject( event, 10000 ); + CloseHandle( event ); +} + static const char* message_type_name(int flags) { if (flags & hook) return "hook"; if (flags & kbd_hook) return "kbd_hook"; @@ -21399,6 +21470,8 @@ START_TEST(msg) hCBT_hook = SetWindowsHookExA(WH_CBT, cbt_hook_proc, 0, GetCurrentThreadId()); if (!hCBT_hook) win_skip( "cannot set global hook, will skip hook tests\n" );
+ start_foreground_window_thread(); + test_winevents(); test_SendMessage_other_thread(); test_setparent_status();
From: Rémi Bernon rbernon@codeweavers.com
For user drivers to forcefully change foreground window, regardless of the restriction which will be introduced next.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58167 --- dlls/win32u/defwnd.c | 2 +- dlls/win32u/input.c | 7 ++++--- dlls/win32u/message.c | 4 ++-- dlls/win32u/win32u_private.h | 2 +- dlls/win32u/window.c | 7 +++++-- dlls/winemac.drv/window.c | 8 ++++---- dlls/winewayland.drv/window.c | 2 +- dlls/winex11.drv/event.c | 6 +++--- include/ntuser.h | 6 ++++++ server/protocol.def | 1 + 10 files changed, 28 insertions(+), 17 deletions(-)
diff --git a/dlls/win32u/defwnd.c b/dlls/win32u/defwnd.c index 870ef376e85..03662963db3 100644 --- a/dlls/win32u/defwnd.c +++ b/dlls/win32u/defwnd.c @@ -2206,7 +2206,7 @@ static LRESULT handle_nc_lbutton_down( HWND hwnd, WPARAM wparam, LPARAM lparam ) top = parent; }
- if (set_foreground_window( top, TRUE ) || (get_active_window() == top)) + if (set_foreground_window( top, TRUE, FALSE ) || (get_active_window() == top)) send_message( hwnd, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, lparam ); break; } diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index f431ab98a9f..57d054a9df6 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -2168,7 +2168,7 @@ HWND WINAPI NtUserSetFocus( HWND hwnd ) if (call_hooks( WH_CBT, HCBT_SETFOCUS, (WPARAM)hwnd, (LPARAM)previous, 0 )) return 0;
/* activate hwndTop if needed. */ - if (!(active = get_active_window()) && !set_foreground_window( hwndTop, FALSE )) return 0; + if (!(active = get_active_window()) && !set_foreground_window( hwndTop, FALSE, FALSE )) return 0; if (hwndTop != active) { if (!set_active_window( hwndTop, NULL, FALSE, FALSE, 0 )) return 0; @@ -2193,13 +2193,13 @@ HWND WINAPI NtUserSetFocus( HWND hwnd ) */ BOOL WINAPI NtUserSetForegroundWindow( HWND hwnd ) { - return set_foreground_window( hwnd, FALSE ); + return set_foreground_window( hwnd, FALSE, FALSE ); }
/******************************************************************* * set_foreground_window */ -BOOL set_foreground_window( HWND hwnd, BOOL mouse ) +BOOL set_foreground_window( HWND hwnd, BOOL mouse, BOOL internal ) { BOOL ret, send_msg_old = FALSE, send_msg_new = FALSE; DWORD new_thread_id; @@ -2211,6 +2211,7 @@ BOOL set_foreground_window( HWND hwnd, BOOL mouse ) SERVER_START_REQ( set_foreground_window ) { req->handle = wine_server_user_handle( hwnd ); + req->internal = internal; if ((ret = !wine_server_call_err( req ))) { previous = wine_server_ptr_handle( reply->previous ); diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index 7da66fd1494..b28ce272099 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -2231,7 +2231,7 @@ static LRESULT handle_internal_message( HWND hwnd, UINT msg, WPARAM wparam, LPAR if (!user_driver->pGetWindowStateUpdates( hwnd, &state_cmd, &swp_flags, &window_rect, &foreground )) return 0; window_rect = map_rect_raw_to_virt( window_rect, get_thread_dpi() );
- if (foreground) NtUserSetForegroundWindow( foreground ); + if (foreground) set_foreground_window( foreground, FALSE, TRUE ); switch (state_cmd) { case SC_RESTORE: @@ -2744,7 +2744,7 @@ static BOOL process_mouse_message( MSG *msg, UINT hw_id, ULONG_PTR extra_info, H /* fall through */ case MA_ACTIVATE: case 0: - if (!set_foreground_window( hwndTop, TRUE )) eat_msg = TRUE; + if (!set_foreground_window( hwndTop, TRUE, FALSE )) eat_msg = TRUE; break; default: WARN( "unknown WM_MOUSEACTIVATE code %d\n", ret ); diff --git a/dlls/win32u/win32u_private.h b/dlls/win32u/win32u_private.h index ee76854c7e4..3b65bb25b53 100644 --- a/dlls/win32u/win32u_private.h +++ b/dlls/win32u/win32u_private.h @@ -95,7 +95,7 @@ extern DWORD get_input_state(void); extern DWORD get_last_input_time(void); extern BOOL get_async_keyboard_state( BYTE state[256] ); extern BOOL set_capture_window( HWND hwnd, UINT gui_flags, HWND *prev_ret ); -extern BOOL set_foreground_window( HWND hwnd, BOOL mouse ); +extern BOOL set_foreground_window( HWND hwnd, BOOL mouse, BOOL force ); extern BOOL set_active_window( HWND hwnd, HWND *prev, BOOL mouse, BOOL focus, DWORD new_active_thread_id ); extern BOOL set_ime_composition_rect( HWND hwnd, RECT rect ); extern void toggle_caret( HWND hwnd ); diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c index 00023632d13..450381c55f3 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -3943,7 +3943,7 @@ BOOL set_window_pos( WINDOWPOS *winpos, int parent_x, int parent_y ) if ((style & (WS_CHILD | WS_POPUP)) == WS_CHILD) send_message( winpos->hwnd, WM_CHILDACTIVATE, 0, 0 ); else if (!(style & WS_MINIMIZE)) - set_foreground_window( winpos->hwnd, FALSE ); + set_foreground_window( winpos->hwnd, FALSE, FALSE ); }
if(!(orig_flags & SWP_DEFERERASE)) @@ -4288,7 +4288,7 @@ static void activate_other_window( HWND hwnd ) TRACE( "win = %p fg = %p\n", hwnd_to, fg ); if (!fg || hwnd == fg) { - if (set_foreground_window( hwnd_to, FALSE )) return; + if (set_foreground_window( hwnd_to, FALSE, FALSE )) return; } if (NtUserSetActiveWindow( hwnd_to )) NtUserSetActiveWindow( 0 ); } @@ -5998,6 +5998,9 @@ ULONG_PTR WINAPI NtUserCallHwnd( HWND hwnd, DWORD code ) activate_other_window( hwnd ); return 0;
+ case NtUserCallHwnd_SetForegroundWindowInternal: + return set_foreground_window( hwnd, FALSE, TRUE ); + case NtUserCallHwnd_GetDpiForWindow: return get_dpi_for_window( hwnd );
diff --git a/dlls/winemac.drv/window.c b/dlls/winemac.drv/window.c index 26ea212043d..acb90420bcd 100644 --- a/dlls/winemac.drv/window.c +++ b/dlls/winemac.drv/window.c @@ -1762,7 +1762,7 @@ void macdrv_window_got_focus(HWND hwnd, const macdrv_event *event) if (can_window_become_foreground(hwnd) && !(style & WS_MINIMIZE)) { TRACE("setting foreground window to %p\n", hwnd); - NtUserSetForegroundWindow(hwnd); + NtUserSetForegroundWindowInternal(hwnd); return; }
@@ -1786,7 +1786,7 @@ void macdrv_window_lost_focus(HWND hwnd, const macdrv_event *event) { send_message(hwnd, WM_CANCELMODE, 0, 0); if (hwnd == NtUserGetForegroundWindow()) - NtUserSetForegroundWindow(NtUserGetDesktopWindow()); + NtUserSetForegroundWindowInternal(NtUserGetDesktopWindow()); } }
@@ -1815,7 +1815,7 @@ void macdrv_app_deactivated(void) if (get_active_window() == NtUserGetForegroundWindow()) { TRACE("setting fg to desktop\n"); - NtUserSetForegroundWindow(NtUserGetDesktopWindow()); + NtUserSetForegroundWindowInternal(NtUserGetDesktopWindow()); } }
@@ -1978,7 +1978,7 @@ void macdrv_window_drag_begin(HWND hwnd, const macdrv_event *event) if (ma != MA_NOACTIVATEANDEAT && ma != MA_NOACTIVATE) { TRACE("setting foreground window to %p\n", hwnd); - NtUserSetForegroundWindow(hwnd); + NtUserSetForegroundWindowInternal(hwnd); } }
diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 939be6e3c08..b838fd84191 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -620,7 +620,7 @@ LRESULT WAYLAND_WindowMessage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) wayland_configure_window(hwnd); return 0; case WM_WAYLAND_SET_FOREGROUND: - NtUserSetForegroundWindow(hwnd); + NtUserSetForegroundWindowInternal(hwnd); return 0; default: FIXME("got window msg %x hwnd %p wp %lx lp %lx\n", msg, hwnd, (long)wp, lp); diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c index f33db32839f..53c617d2d68 100644 --- a/dlls/winex11.drv/event.c +++ b/dlls/winex11.drv/event.c @@ -657,7 +657,7 @@ static void set_focus( Display *display, HWND focus, Time time )
if (!is_net_supported( x11drv_atom(_NET_ACTIVE_WINDOW) )) { - NtUserSetForegroundWindow( focus ); + NtUserSetForegroundWindowInternal( focus );
threadinfo.cbSize = sizeof(threadinfo); NtUserGetGUIThreadInfo( 0, &threadinfo ); @@ -879,7 +879,7 @@ static BOOL X11DRV_FocusIn( HWND hwnd, XEvent *xev ) if (!hwnd) hwnd = x11drv_thread_data()->last_focus; if (hwnd && can_activate_window(hwnd)) set_focus( event->display, hwnd, CurrentTime ); } - else NtUserSetForegroundWindow( hwnd ); + else NtUserSetForegroundWindowInternal( hwnd ); return TRUE; }
@@ -910,7 +910,7 @@ static void focus_out( Display *display , HWND hwnd ) if (hwnd == NtUserGetForegroundWindow()) { TRACE( "lost focus, setting fg to desktop\n" ); - NtUserSetForegroundWindow( NtUserGetDesktopWindow() ); + NtUserSetForegroundWindowInternal( NtUserGetDesktopWindow() ); } } } diff --git a/include/ntuser.h b/include/ntuser.h index 664bcfe459d..438fca0f52d 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -1305,6 +1305,7 @@ enum NtUserCallHwnd_IsWindowEnabled, NtUserCallHwnd_IsWindowUnicode, NtUserCallHwnd_IsWindowVisible, + NtUserCallHwnd_SetForegroundWindowInternal, /* temporary exports */ NtUserGetFullWindowHandle, NtUserIsCurrentProcessWindow, @@ -1316,6 +1317,11 @@ static inline void NtUserActivateOtherWindow( HWND hwnd ) NtUserCallHwnd( hwnd, NtUserCallHwnd_ActivateOtherWindow ); }
+static inline BOOL NtUserSetForegroundWindowInternal( HWND hwnd ) +{ + return NtUserCallHwnd( hwnd, NtUserCallHwnd_SetForegroundWindowInternal ); +} + static inline void *NtUserGetDialogInfo( HWND hwnd ) { return (void *)NtUserCallHwnd( hwnd, NtUserCallHwnd_GetDialogInfo ); diff --git a/server/protocol.def b/server/protocol.def index 1881f0a628a..6712a661817 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -3117,6 +3117,7 @@ enum coords_relative /* Set the system foreground window */ @REQ(set_foreground_window) user_handle_t handle; /* handle to the foreground window */ + int internal; /* foreground change comes from the host */ @REPLY user_handle_t previous; /* handle to the previous foreground window */ int send_msg_old; /* whether we have to send a msg to the old window */
From: Rémi Bernon rbernon@codeweavers.com
When called from the application.
Changing foreground window to the desktop seems like a bad idea, but it also seem to allow later foreground changes back to application windows, as many D3D tests suggest.
Currently we translate these calls into requests to activate the root window, which will usually have no effect, so allowing further changes should be safe.
When the internal flag is set, we actually change the foreground to the desktop window, which should later forbid any further changes made from the application side, as this happens when a non-Wine or foreign window has been focused. --- server/queue.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/server/queue.c b/server/queue.c index 82d5a395957..fb27524fd49 100644 --- a/server/queue.c +++ b/server/queue.c @@ -3810,11 +3810,11 @@ DECL_HANDLER(set_foreground_window) reply->send_msg_old = (reply->previous && desktop->foreground_input != queue->input); reply->send_msg_new = FALSE;
- if (is_valid_foreground_window( req->handle ) && - (thread = get_window_thread( req->handle )) && - thread->queue->input->desktop == desktop) + if (is_valid_foreground_window( req->handle ) && (thread = get_window_thread( req->handle )) && + (input = thread->queue->input) && input->desktop == desktop) { - set_foreground_input( desktop, thread->queue->input ); + int is_desktop = thread->process == get_top_window_owner( desktop ); + set_foreground_input( desktop, req->internal || !is_desktop ? input : NULL ); reply->send_msg_new = (desktop->foreground_input != queue->input); } else set_win32_error( ERROR_INVALID_WINDOW_HANDLE );
From: Rémi Bernon rbernon@codeweavers.com
Pegasus Mail posts a WM_USER + 8991 message to its own window when it loses focus, then calls SetFocus on its window when processing this message. Several other applications have been seen calling SetWindowPos during WM_ACTIVATE message, which might also attempt to reactivate the window.
That processing happens shortly after we have changed the foreground window to the desktop window, when focus is lost, then SetFocus tries to change the foreground window again.
This SetFocus behavior is tested, and should work like this, but would only activate the window if the process is allowed to do so. Windows has various rules around this, and it seems to boil down to something like:
* Allow taking focus if the process never was foreground, ie: when process is starting and gets initial focus on its windows.
* Allow taking focus if the process was foreground but lost it recently because of a window being destroyed.
* Forbid taking focus back if the process had foreground and called SetForegroundWindow explicitly to give it to another process.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58167 --- dlls/user32/tests/win.c | 2 +- server/process.c | 1 + server/process.h | 1 + server/queue.c | 47 +++++++++++++++++++++++++++++++++++++---- server/winstation.c | 1 + 5 files changed, 47 insertions(+), 5 deletions(-)
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 354d6ed5ca3..f2b9888c63a 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -3834,7 +3834,7 @@ static void test_SetFocus(HWND hwnd) flush_events( TRUE ); ok( GetFocus() == 0, "got focus %p\n", GetFocus() ); ok( GetActiveWindow() == 0, "got active %p\n", GetActiveWindow() ); - todo_wine ok( GetForegroundWindow() == 0, "got foreground %p\n", GetForegroundWindow() ); + ok( GetForegroundWindow() == 0, "got foreground %p\n", GetForegroundWindow() );
SetFocus( other ); ok( GetFocus() == other, "got focus %p\n", GetFocus() ); diff --git a/server/process.c b/server/process.c index 52abaa21d75..4a06595c66e 100644 --- a/server/process.c +++ b/server/process.c @@ -682,6 +682,7 @@ struct process *create_process( int fd, struct process *parent, unsigned int fla process->is_system = 0; process->debug_children = 1; process->is_terminating = 0; + process->set_foreground = 0; process->imagelen = 0; process->image = NULL; process->job = NULL; diff --git a/server/process.h b/server/process.h index 5c136fb5103..7cfcc39c258 100644 --- a/server/process.h +++ b/server/process.h @@ -63,6 +63,7 @@ struct process unsigned int is_system:1; /* is it a system process? */ unsigned int debug_children:1;/* also debug all child processes */ unsigned int is_terminating:1;/* is process terminating? */ + unsigned int set_foreground:1;/* has process called set_foreground_window */ data_size_t imagelen; /* length of image path in bytes */ WCHAR *image; /* main exe image full path */ struct job *job; /* job object associated with this process */ diff --git a/server/queue.c b/server/queue.c index fb27524fd49..6ffb0ef81eb 100644 --- a/server/queue.c +++ b/server/queue.c @@ -22,6 +22,7 @@
#include <assert.h> #include <stdarg.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> @@ -112,6 +113,7 @@ struct thread_input int caret_hide; /* caret hide count */ int caret_state; /* caret on/off state */ struct list msg_list; /* list of hardware messages */ + timeout_t user_time; /* time of last user input */ unsigned char desktop_keystate[256]; /* desktop keystate when keystate was synced */ input_shm_t *shared; /* thread input in session shared memory */ }; @@ -253,6 +255,7 @@ static struct thread_input *create_thread_input( struct thread *thread ) if ((input = alloc_object( &thread_input_ops ))) { list_init( &input->msg_list ); + input->user_time = 0; input->shared = NULL;
if (!(input->desktop = get_thread_desktop( thread, 0 /* FIXME: access rights */ ))) @@ -665,6 +668,12 @@ static void set_foreground_input( struct desktop *desktop, struct thread_input * input_shm_t *input_shm, *old_input_shm; shared_object_t dummy_obj = {0};
+ if (input) + { + input->user_time = monotonic_time; + if (debug_level) fprintf( stderr, "%04x: updating input %p user_time %ju\n", current->id, input, (uintmax_t)input->user_time); + } + if (desktop->foreground_input == input) return; input_shm = input ? input->shared : &dummy_obj.shm.input; old_input_shm = desktop->foreground_input ? desktop->foreground_input->shared : &dummy_obj.shm.input; @@ -1927,6 +1936,13 @@ static void queue_hardware_message( struct desktop *desktop, struct message *msg
win = find_hardware_message_window( desktop, input, msg, &msg_code, &thread ); flags = thread ? get_rawinput_device_flags( thread->process, msg ) : 0; + if (thread) input = thread->queue->input; + if (input && (get_hardware_msg_bit( msg->msg ) & (QS_KEY | QS_MOUSEBUTTON))) + { + input->user_time = monotonic_time; + if (debug_level) fprintf( stderr, "%04x: updating input %p user_time %ju\n", current->id, input, (uintmax_t)input->user_time); + } + if (!win || !thread || (flags & RIDEV_NOLEGACY)) { if (input && !(flags & RIDEV_NOLEGACY)) update_thread_input_key_state( input, msg->msg, msg->wparam ); @@ -1935,8 +1951,6 @@ static void queue_hardware_message( struct desktop *desktop, struct message *msg return; }
- input = thread->queue->input; - if (win != msg->win) always_queue = 1; if (!always_queue || merge_message( input, msg )) free_message( msg ); else @@ -1994,12 +2008,24 @@ static struct thread *get_foreground_thread( struct desktop *desktop, user_handl if (desktop->foreground_input) { input_shm_t *input_shm = desktop->foreground_input->shared; - return get_window_thread( input_shm->focus ); + if (!(window = input_shm->focus)) window = input_shm->active; } if (window) return get_window_thread( window ); return NULL; }
+static int is_current_process_foreground( struct desktop *desktop ) +{ + struct thread *thread; + int ret; + + if (!(thread = get_foreground_thread( desktop, 0 ))) return 1; + ret = thread->process == current->process || thread->process->id == current->process->parent_id; + release_object( thread ); + + return ret; +} + /* user32 reserves 1 & 2 for winemouse and winekeyboard, * keep this in sync with user_private.h */ #define WINE_MOUSE_HANDLE 1 @@ -3802,11 +3828,24 @@ DECL_HANDLER(set_foreground_window) struct thread_input *input; struct msg_queue *queue = get_current_queue();
- if (!(desktop = get_thread_desktop( current, 0 ))) return; + if (!queue || !(desktop = get_thread_desktop( current, 0 ))) return;
if (!(input = desktop->foreground_input)) reply->previous = 0; else reply->previous = input->shared->active;
+ if (!req->internal) + { + if (!current->process->set_foreground) current->process->set_foreground = 1; + else if (!is_current_process_foreground( desktop ) && queue->input && input && queue->input->user_time < input->user_time) + { + if (debug_level) fprintf( stderr, "%04x: refusing set_foreground_window user_time %ju < %ju\n", current->id, + (uintmax_t)queue->input->user_time, (uintmax_t)input->user_time ); + set_win32_error( ERROR_ACCESS_DENIED ); + release_object( desktop ); + return; + } + } + reply->send_msg_old = (reply->previous && desktop->foreground_input != queue->input); reply->send_msg_new = FALSE;
diff --git a/server/winstation.c b/server/winstation.c index 52c3f1ce1eb..a36253e20db 100644 --- a/server/winstation.c +++ b/server/winstation.c @@ -864,6 +864,7 @@ DECL_HANDLER(set_thread_desktop) { if (old_desktop) remove_desktop_thread( old_desktop, current ); add_desktop_thread( new_desktop, current ); + current->process->set_foreground = 0; } reply->locator = get_shared_object_locator( new_desktop->shared ); }
v3: Allow any process to steal foreground if there's no current foreground input. Allow children of the foreground process to steal foreground too, some user32:input rawinput tests need it.
Also add some dedicated window, which will get focused when none of the test windows are, to some ddraw and user32 tests calling `SetForegroundWindow(GetDesktopWindow())` and creating / destroying windows while processing X11 events [^1].
Regardless of the heuristics added here, it's not going to be possible to fully reliably tell between focus loss that are requested by a user action, and those which are simply caused by some window being destroyed, minimized or hidden.
We want to respect the user decisions above all, and prevent applications from stealing focus, so the tests need to be massaged to make sure they do not rely on getting foreground magically back after having lost it for good.
[^1]: It's not that much an issue if tests aren't processing events because they end up missing and batching host focus changes at once, and usually allow further SetForegroundWindow calls as the host restores focus to the new window automatically after their creation, but that's not a common use case.
This merge request was approved by Elizabeth Figura.
ddraw changes look fine.