[PATCH 1/2] user32: Silently ignore temporary foreground loss.

Stefan Dösinger stefan at codeweavers.com
Tue Jan 13 08:49:02 CST 2015


The extra GetForegroundWindow checks are needed because check_wnd_state
skips the foreground check if the current foreground window is owned by
a different thread. Some message checks have been deactivated because
they are unreliable on Linux. The disabled checks also fail without the
changed implementation.

Note that I don't have to add a similar line in message.c for wparam !=
NULL because set_active_window will not send WM_ACTIVATEAPP(!= 0)
messages if the active window hasn't been set to 0 in the meantime.

This fixes bugs 37843 and the Ghost Recon Advanced Warfighter 2 part of
bug 37716. Both games have the d3d device and focus window in
foreground, switch to a window created by a different thread (via
ShowWindow and SetWindowPos respectively) and switch back to the focus
window before processing messages. The attached test shows that in this
case Windows does not deliver the WM_ACTIVATEAPP messages.

In case of Black Mirror the temporary focus loss seems genuine. The same
series of operations causes a foreground change on Windows as well. The
change happens before the d3d device is created, and creating the d3d
device restores foreground. However, the past foreground change is
recorded in the event queue and when the application processes events
for the focus window's thread again d3d minimizes the window.

In case of GRAW2 the focus change may be incorrect. It is triggered by a
SetWindowPos call, and on Windows SetWindowPos calls SetActiveWindow
instead of SetForegroundWindow. However, if the currently active window
is NULL, SetActiveWindow apparently calls SetForegroundWindow. Before
performing the SetWindowPos call, GRAW2 calls AttachThreadInput, which
currently (and I think in error) sets the active window to NULL. In
either case the game sets the foreground back to the d3d focus window by
destroying the window that (maybe erroneously) got focus before it
processes events again.
---
 dlls/user32/message.c   |   1 +
 dlls/user32/tests/win.c | 223 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 224 insertions(+)

diff --git a/dlls/user32/message.c b/dlls/user32/message.c
index eac4e4d..dfaaf59 100644
--- a/dlls/user32/message.c
+++ b/dlls/user32/message.c
@@ -1872,6 +1872,7 @@ static LRESULT handle_internal_message( HWND hwnd, UINT msg, WPARAM wparam, LPAR
         return EnableWindow( hwnd, wparam );
     case WM_WINE_SETACTIVEWINDOW:
         if (is_desktop_window( hwnd )) return 0;
+        if (!wparam && GetForegroundWindow() == hwnd) return 0;
         return (LRESULT)SetActiveWindow( (HWND)wparam );
     case WM_WINE_KEYBOARD_LL_HOOK:
     case WM_WINE_MOUSE_LL_HOOK:
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c
index 404017f..759506d 100644
--- a/dlls/user32/tests/win.c
+++ b/dlls/user32/tests/win.c
@@ -68,6 +68,7 @@ static DWORD num_settext_msgs;
 static HWND hwndMessage;
 static HWND hwndMain, hwndMain2;
 static HHOOK hhook;
+static BOOL app_activated, app_deactivated;
 
 static const char* szAWRClass = "Winsize";
 static HMENU hmenu;
@@ -802,6 +803,10 @@ static LRESULT WINAPI main_window_procA(HWND hwnd, UINT msg, WPARAM wparam, LPAR
         case WM_SETTEXT:
             num_settext_msgs++;
             break;
+        case WM_ACTIVATEAPP:
+            if (wparam) app_activated = TRUE;
+            else app_deactivated = TRUE;
+            break;
     }
 
     return DefWindowProcA(hwnd, msg, wparam, lparam);
@@ -7979,6 +7984,223 @@ static void test_smresult(void)
     CloseHandle(data.thread_replied);
 }
 
+#define SET_FOREGROUND_STEAL_1          0x01
+#define SET_FOREGROUND_SET_1            0x02
+#define SET_FOREGROUND_STEAL_2          0x04
+#define SET_FOREGROUND_SET_2            0x08
+#define SET_FOREGROUND_INJECT           0x10
+
+struct set_foreground_thread_params
+{
+    UINT msg_quit, msg_command;
+    HWND window1, window2, thread_window;
+    HANDLE command_executed;
+};
+
+static DWORD WINAPI set_foreground_thread(void *params)
+{
+    struct set_foreground_thread_params *p = params;
+    MSG msg;
+
+    p->thread_window = CreateWindowExA(0, "static", "thread window", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+            0, 0, 10, 10, 0, 0, 0, NULL);
+    SetEvent(p->command_executed);
+
+    while(GetMessageA(&msg, 0, 0, 0))
+    {
+        if (msg.message == p->msg_quit)
+            break;
+
+        if (msg.message == p->msg_command)
+        {
+            if (msg.wParam & SET_FOREGROUND_STEAL_1)
+            {
+                SetForegroundWindow(p->thread_window);
+                check_wnd_state(p->thread_window, p->thread_window, p->thread_window, 0);
+            }
+            if (msg.wParam & SET_FOREGROUND_INJECT)
+            {
+                SendNotifyMessageA(p->window1, WM_ACTIVATEAPP, 0, 0);
+            }
+            if (msg.wParam & SET_FOREGROUND_SET_1)
+            {
+                SetForegroundWindow(p->window1);
+                check_wnd_state(0, p->window1, 0, 0);
+            }
+            if (msg.wParam & SET_FOREGROUND_STEAL_2)
+            {
+                SetForegroundWindow(p->thread_window);
+                check_wnd_state(p->thread_window, p->thread_window, p->thread_window, 0);
+            }
+            if (msg.wParam & SET_FOREGROUND_SET_2)
+            {
+                SetForegroundWindow(p->window2);
+                check_wnd_state(0, p->window2, 0, 0);
+            }
+
+            SetEvent(p->command_executed);
+            continue;
+        }
+
+        TranslateMessage(&msg);
+        DispatchMessageA(&msg);
+    }
+
+    DestroyWindow(p->thread_window);
+    return 0;
+}
+
+static void test_activateapp(HWND window1)
+{
+    HWND window2, test_window;
+    HANDLE thread;
+    struct set_foreground_thread_params thread_params;
+    DWORD tid;
+    MSG msg;
+
+    window2 = CreateWindowExA(0, "static", "window 2", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+            300, 0, 10, 10, 0, 0, 0, NULL);
+    thread_params.msg_quit = WM_USER;
+    thread_params.msg_command = WM_USER + 1;
+    thread_params.window1 = window1;
+    thread_params.window2 = window2;
+    thread_params.command_executed = CreateEventW(NULL, FALSE, FALSE, NULL);
+
+    thread = CreateThread(NULL, 0, set_foreground_thread, &thread_params, 0, &tid);
+    WaitForSingleObject(thread_params.command_executed, INFINITE);
+
+    SetForegroundWindow(window1);
+    check_wnd_state(window1, window1, window1, 0);
+    while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
+
+    /* Steal foreground: WM_ACTIVATEAPP(0) is delivered. */
+    app_activated = app_deactivated = FALSE;
+    PostThreadMessageA(tid, thread_params.msg_command, SET_FOREGROUND_STEAL_1, 0);
+    WaitForSingleObject(thread_params.command_executed, INFINITE);
+    test_window = GetForegroundWindow();
+    ok(test_window == thread_params.thread_window, "Expected foreground window %p, got %p\n",
+            thread_params.thread_window, test_window);
+    /* Active and Focus window are sometimes 0 on KDE. Ignore them.
+     * check_wnd_state(window1, thread_params.thread_window, window1, 0); */
+    ok(!app_activated, "Received WM_ACTIVATEAPP(1), did not expect it.\n");
+    ok(!app_deactivated, "Received WM_ACTIVATEAPP(0), did not expect it.\n");
+    while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
+    check_wnd_state(0, thread_params.thread_window, 0, 0);
+    test_window = GetForegroundWindow();
+    ok(test_window == thread_params.thread_window, "Expected foreground window %p, got %p\n",
+            thread_params.thread_window, test_window);
+    ok(!app_activated, "Received WM_ACTIVATEAPP(1), did not expect it.\n");
+    /* This message is reliable on Windows and inside a virtual desktop.
+     * It is unreliable on KDE (50/50) and never arrives on FVWM.
+     * ok(app_deactivated, "Expected WM_ACTIVATEAPP(0), did not receive it.\n"); */
+
+    /* Set foreground: WM_ACTIVATEAPP (1) is delivered. */
+    app_activated = app_deactivated = FALSE;
+    PostThreadMessageA(tid, thread_params.msg_command, SET_FOREGROUND_SET_1, 0);
+    WaitForSingleObject(thread_params.command_executed, INFINITE);
+    check_wnd_state(0, 0, 0, 0);
+    test_window = GetForegroundWindow();
+    ok(!test_window, "Expected foreground window 0, got %p\n", test_window);
+    ok(!app_activated, "Received WM_ACTIVATEAPP(!= 0), did not expect it.\n");
+    ok(!app_deactivated, "Received WM_ACTIVATEAPP(0), did not expect it.\n");
+    while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
+    check_wnd_state(window1, window1, window1, 0);
+    test_window = GetForegroundWindow();
+    ok(test_window == window1, "Expected foreground window %p, got %p\n",
+            window1, test_window);
+    ok(app_activated, "Expected WM_ACTIVATEAPP(1), did not receive it.\n");
+    ok(!app_deactivated, "Received WM_ACTIVATEAPP(0), did not expect it.\n");
+
+    /* Steal foreground then set it back: No messages are delivered. */
+    app_activated = app_deactivated = FALSE;
+    PostThreadMessageA(tid, thread_params.msg_command, SET_FOREGROUND_STEAL_1 | SET_FOREGROUND_SET_1, 0);
+    WaitForSingleObject(thread_params.command_executed, INFINITE);
+    test_window = GetForegroundWindow();
+    ok(test_window == window1, "Expected foreground window %p, got %p\n",
+            window1, test_window);
+    check_wnd_state(window1, window1, window1, 0);
+    ok(!app_activated, "Received WM_ACTIVATEAPP(1), did not expect it.\n");
+    ok(!app_deactivated, "Received WM_ACTIVATEAPP(0), did not expect it.\n");
+    while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
+    test_window = GetForegroundWindow();
+    ok(test_window == window1, "Expected foreground window %p, got %p\n",
+            window1, test_window);
+    check_wnd_state(window1, window1, window1, 0);
+    ok(!app_activated, "Received WM_ACTIVATEAPP(1), did not expect it.\n");
+    ok(!app_deactivated, "Received WM_ACTIVATEAPP(0), did not expect it.\n");
+
+    /* This is not implemented with a plain WM_ACTIVATEAPP filter. */
+    app_activated = app_deactivated = FALSE;
+    PostThreadMessageA(tid, thread_params.msg_command, SET_FOREGROUND_STEAL_1
+            | SET_FOREGROUND_INJECT | SET_FOREGROUND_SET_1, 0);
+    WaitForSingleObject(thread_params.command_executed, INFINITE);
+    test_window = GetForegroundWindow();
+    ok(test_window == window1, "Expected foreground window %p, got %p\n",
+            window1, test_window);
+    check_wnd_state(window1, window1, window1, 0);
+    ok(!app_activated, "Received WM_ACTIVATEAPP(1), did not expect it.\n");
+    ok(!app_deactivated, "Received WM_ACTIVATEAPP(0), did not expect it.\n");
+    while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
+    test_window = GetForegroundWindow();
+    ok(test_window == window1, "Expected foreground window %p, got %p\n",
+            window1, test_window);
+    check_wnd_state(window1, window1, window1, 0);
+    ok(!app_activated, "Received WM_ACTIVATEAPP(1), did not expect it.\n");
+    ok(app_deactivated, "Expected WM_ACTIVATEAPP(0), did not receive it.\n");
+
+    SetForegroundWindow(thread_params.thread_window);
+
+    /* Set foreground then remove: Both messages are delivered. */
+    app_activated = app_deactivated = FALSE;
+    PostThreadMessageA(tid, thread_params.msg_command, SET_FOREGROUND_SET_1 | SET_FOREGROUND_STEAL_2, 0);
+    WaitForSingleObject(thread_params.command_executed, INFINITE);
+    test_window = GetForegroundWindow();
+    ok(test_window == thread_params.thread_window, "Expected foreground window %p, got %p\n",
+            thread_params.thread_window, test_window);
+    check_wnd_state(0, thread_params.thread_window, 0, 0);
+    ok(!app_activated, "Received WM_ACTIVATEAPP(1), did not expect it.\n");
+    ok(!app_deactivated, "Received WM_ACTIVATEAPP(0), did not expect it.\n");
+    while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
+    test_window = GetForegroundWindow();
+    ok(test_window == thread_params.thread_window, "Expected foreground window %p, got %p\n",
+            thread_params.thread_window, test_window);
+    /* Active and focus are window1 on wine for some reason. GetCapture() returns
+     * 0 though, so we get a test success in todo_wine.
+     * todo_wine check_wnd_state(0, thread_params.thread_window, 0, 0); */
+    ok(app_activated, "Expected WM_ACTIVATEAPP(1), did not receive it.\n");
+    todo_wine ok(app_deactivated, "Expected WM_ACTIVATEAPP(0), did not receive it.\n");
+
+    SetForegroundWindow(window1);
+    test_window = GetForegroundWindow();
+    ok(test_window == window1, "Expected foreground window %p, got %p\n",
+            window1, test_window);
+    check_wnd_state(window1, window1, window1, 0);
+
+    /* Switch to a different window from the same thread? No messages. */
+    app_activated = app_deactivated = FALSE;
+    PostThreadMessageA(tid, thread_params.msg_command, SET_FOREGROUND_STEAL_1 | SET_FOREGROUND_SET_2, 0);
+    WaitForSingleObject(thread_params.command_executed, INFINITE);
+    test_window = GetForegroundWindow();
+    ok(test_window == window1, "Expected foreground window %p, got %p\n",
+            window1, test_window);
+    check_wnd_state(window1, window1, window1, 0);
+    ok(!app_activated, "Received WM_ACTIVATEAPP(1), did not expect it.\n");
+    ok(!app_deactivated, "Received WM_ACTIVATEAPP(0), did not expect it.\n");
+    while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
+    test_window = GetForegroundWindow();
+    ok(test_window == window2, "Expected foreground window %p, got %p\n",
+            window2, test_window);
+    check_wnd_state(window2, window2, window2, 0);
+    ok(!app_activated, "Received WM_ACTIVATEAPP(1), did not expect it.\n");
+    ok(!app_deactivated, "Received WM_ACTIVATEAPP(0), did not expect it.\n");
+
+    PostThreadMessageA(tid, thread_params.msg_quit, 0, 0);
+    WaitForSingleObject(thread, INFINITE);
+
+    CloseHandle(thread_params.command_executed);
+    DestroyWindow(window2);
+}
+
 START_TEST(win)
 {
     char **argv;
@@ -8110,6 +8332,7 @@ START_TEST(win)
     test_update_region();
     test_window_without_child_style();
     test_smresult();
+    test_activateapp(hwndMain);
 
     /* add the tests above this line */
     if (hhook) UnhookWindowsHookEx(hhook);
-- 
2.0.5




More information about the wine-patches mailing list