[PATCH v6] user32: Send a WM_ACTIVATE message after restoring a minimized top level window.

Zhiyi Zhang zzhang at codeweavers.com
Wed Feb 10 00:33:27 CST 2021


The WmShowRestoreMinimizedOverlappedSeq message sequence in tests
clearly show that there is a WM_ACTIVATE message at the end of
ShowWindow() calls after restoring a minimized window, and it's
not from SetFocus().

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47507
Signed-off-by: Zhiyi Zhang <zzhang at codeweavers.com>
---
v6: Use GetAncestor(hwnd, GA_ROOT) == hwnd to check if a window is a top level window because
    there might be top level windows with WS_CHILD set.

 dlls/user32/tests/msg.c | 247 +++++++++++++++++++++++++++++++++++++++-
 dlls/user32/winpos.c    |   8 +-
 2 files changed, 253 insertions(+), 2 deletions(-)

diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c
index 2ce2b3c6e54..11fb764e6cd 100644
--- a/dlls/user32/tests/msg.c
+++ b/dlls/user32/tests/msg.c
@@ -883,6 +883,145 @@ static const struct message WmShowVisMaxPopupSeq[] = {
     { EVENT_OBJECT_LOCATIONCHANGE, winevent_hook|wparam|lparam, 0, 0 },
     { 0 }
 };
+/* ShowWindow(hwnd, SW_RESTORE) to a minimized window */
+static const struct message WmShowRestoreMinimizedOverlappedSeq[] =
+{
+    { HCBT_MINMAX, hook },
+    { WM_QUERYOPEN, sent },
+    { WM_GETTEXT, sent|optional },
+    { WM_WINDOWPOSCHANGING, sent|wparam, SWP_FRAMECHANGED|SWP_NOCOPYBITS|SWP_STATECHANGED },
+    { WM_GETMINMAXINFO, sent|defwinproc },
+    { WM_NCCALCSIZE, sent },
+    { HCBT_ACTIVATE, hook },
+    { WM_WINDOWPOSCHANGING, sent|wparam|optional, SWP_NOSIZE|SWP_NOMOVE },
+    { WM_NCACTIVATE, sent },
+    { WM_GETTEXT, sent|defwinproc|optional },
+    { WM_ACTIVATE, sent|wparam, WA_ACTIVE },
+    { HCBT_SETFOCUS, hook },
+    { WM_SETFOCUS, sent|defwinproc },
+    { WM_NCPAINT, sent },
+    { WM_GETTEXT, sent|defwinproc|optional },
+    { WM_GETTEXT, sent|defwinproc|optional },
+    { WM_ERASEBKGND, sent },
+    { WM_WINDOWPOSCHANGED, sent|wparam, SWP_FRAMECHANGED|SWP_NOCOPYBITS|SWP_STATECHANGED },
+    { WM_MOVE, sent|defwinproc },
+    { WM_SIZE, sent|defwinproc },
+    { WM_NCCALCSIZE, sent|optional },
+    { WM_NCPAINT, sent|optional },
+    { WM_ERASEBKGND, sent|optional },
+    /* Note this WM_ACTIVATE message even if the window is already active and focused */
+    { WM_ACTIVATE, sent|wparam|lparam, WA_ACTIVE, 0 },
+    { WM_SYNCPAINT, sent|optional },
+    { WM_PAINT, sent },
+    { WM_GETMINMAXINFO, sent|optional },
+    { 0 }
+};
+/* ShowWindow(hwnd, SW_SHOWNOACTIVATE) to a minimized window */
+static const struct message WmShowNoActivateMinimizedOverlappedSeq[] =
+{
+    { HCBT_MINMAX, hook },
+    { WM_QUERYOPEN, sent },
+    { WM_GETTEXT, sent|optional },
+    { WM_WINDOWPOSCHANGING, sent|wparam, SWP_NOACTIVATE|SWP_FRAMECHANGED|SWP_NOCOPYBITS|SWP_STATECHANGED },
+    { WM_GETMINMAXINFO, sent|defwinproc },
+    { WM_NCCALCSIZE, sent },
+    { WM_NCPAINT, sent },
+    { WM_GETTEXT, sent|defwinproc|optional },
+    { WM_ERASEBKGND, sent },
+    { WM_WINDOWPOSCHANGED, sent|wparam, SWP_NOACTIVATE|SWP_FRAMECHANGED|SWP_NOCOPYBITS|SWP_STATECHANGED },
+    { WM_MOVE, sent|defwinproc },
+    { WM_SIZE, sent|defwinproc },
+    /* Following optional messages are on XP/2003 */
+    { WM_NCCALCSIZE, sent|optional },
+    { WM_NCPAINT, sent|optional },
+    { WM_ERASEBKGND, sent|optional },
+    { HCBT_SETFOCUS, hook|optional },
+    { WM_SETFOCUS, sent|optional },
+    { HCBT_ACTIVATE, hook|optional },
+    { WM_WINDOWPOSCHANGING, sent|wparam|optional, SWP_NOSIZE|SWP_NOMOVE },
+    { WM_NCACTIVATE, sent|optional },
+    { WM_GETTEXT, sent|defwinproc|optional },
+    { WM_ACTIVATE, sent|wparam|optional, WA_ACTIVE },
+    { HCBT_SETFOCUS, hook|optional },
+    { WM_SETFOCUS, sent|defwinproc|optional },
+    { WM_KILLFOCUS, sent|optional },
+    { WM_SETFOCUS, sent|optional },
+    /* Note this WM_ACTIVATE message on XP even if the window is already active and focused */
+    { WM_ACTIVATE, sent|wparam|lparam|optional, WA_ACTIVE, 0 },
+    { WM_SYNCPAINT, sent|optional },
+    { WM_PAINT, sent },
+    { WM_GETMINMAXINFO, sent|optional },
+    { 0 }
+};
+/* ShowWindow(hwnd, SW_RESTORE) to an active minimized window */
+static const struct message WmShowRestoreActiveMinimizedOverlappedSeq[] =
+{
+    { HCBT_MINMAX, hook },
+    { WM_QUERYOPEN, sent },
+    { WM_GETTEXT, sent|optional },
+    { WM_NCACTIVATE, sent },
+    { WM_WINDOWPOSCHANGING, sent|wparam, SWP_NOSIZE|SWP_NOMOVE },
+    { WM_WINDOWPOSCHANGED, sent|wparam|optional, SWP_NOSIZE|SWP_NOMOVE|SWP_NOCLIENTSIZE|SWP_NOCLIENTMOVE },
+    { WM_NCCALCSIZE, sent|optional },
+    { WM_MOVE, sent|optional },
+    { WM_SIZE, sent|optional },
+    { WM_GETTEXT, sent|optional },
+    { WM_WINDOWPOSCHANGING, sent|wparam, SWP_FRAMECHANGED|SWP_NOCOPYBITS|SWP_STATECHANGED },
+    { WM_GETMINMAXINFO, sent|defwinproc },
+    { WM_NCCALCSIZE, sent },
+    { WM_NCPAINT, sent },
+    { WM_GETTEXT, sent|defwinproc|optional },
+    { WM_ERASEBKGND, sent },
+    { WM_WINDOWPOSCHANGED, sent|wparam, SWP_FRAMECHANGED|SWP_NOCOPYBITS|SWP_STATECHANGED },
+    { WM_MOVE, sent|defwinproc },
+    { WM_SIZE, sent|defwinproc },
+    { WM_NCCALCSIZE, sent|optional },
+    { WM_NCPAINT, sent|optional },
+    { WM_ERASEBKGND, sent|optional },
+    { HCBT_SETFOCUS, hook },
+    { WM_SETFOCUS, sent },
+    /* Note this WM_ACTIVATE message even if the window is already active */
+    { WM_ACTIVATE, sent|wparam|lparam, WA_ACTIVE, 0 },
+    { WM_SYNCPAINT, sent|optional },
+    { WM_PAINT, sent },
+    { WM_GETMINMAXINFO, sent|optional },
+    { 0 }
+};
+/* ShowWindow(hwnd, SW_SHOWNOACTIVATE) to an active minimized window */
+static const struct message WmShowNoActivateActiveMinimizedOverlappedSeq[] =
+{
+    { HCBT_MINMAX, hook },
+    { WM_QUERYOPEN, sent },
+    { WM_GETTEXT, sent|optional },
+    { WM_NCACTIVATE, sent },
+    { WM_WINDOWPOSCHANGING, sent|wparam, SWP_NOSIZE|SWP_NOMOVE },
+    { WM_WINDOWPOSCHANGED, sent|wparam|optional, SWP_NOSIZE|SWP_NOMOVE|SWP_NOCLIENTSIZE|SWP_NOCLIENTMOVE },
+    { WM_NCCALCSIZE, sent|optional },
+    { WM_MOVE, sent|optional },
+    { WM_SIZE, sent|optional },
+    { WM_GETTEXT, sent|optional },
+    { WM_WINDOWPOSCHANGING, sent|wparam, SWP_NOACTIVATE|SWP_FRAMECHANGED|SWP_NOCOPYBITS|SWP_STATECHANGED },
+    { WM_GETMINMAXINFO, sent|defwinproc },
+    { WM_NCCALCSIZE, sent },
+    { WM_NCPAINT, sent },
+    { WM_GETTEXT, sent|defwinproc|optional },
+    { WM_ERASEBKGND, sent },
+    { WM_WINDOWPOSCHANGED, sent|wparam, SWP_NOACTIVATE|SWP_FRAMECHANGED|SWP_NOCOPYBITS|SWP_STATECHANGED },
+    { WM_MOVE, sent|defwinproc },
+    { WM_SIZE, sent|defwinproc },
+    { WM_NCCALCSIZE, sent|optional },
+    { WM_NCPAINT, sent|optional },
+    { WM_ERASEBKGND, sent|optional },
+    /* Following optional messages are present on XP */
+    { HCBT_SETFOCUS, hook|optional },
+    { WM_SETFOCUS, sent|optional },
+    /* Note this WM_ACTIVATE message even if the window is already active and with flag SW_SHOWNOACTIVATE */
+    { WM_ACTIVATE, sent|wparam|lparam|optional, WA_ACTIVE, 0 },
+    { WM_SYNCPAINT, sent|optional },
+    { WM_PAINT, sent },
+    { WM_GETMINMAXINFO, sent|optional },
+    { 0 }
+};
 /* CreateWindow (for a child popup window, not initially visible) */
 static const struct message WmCreateChildPopupSeq[] = {
     { HCBT_CREATEWND, hook },
@@ -4677,7 +4816,7 @@ static void test_scroll_messages(HWND hwnd)
 
 static void test_showwindow(void)
 {
-    HWND hwnd, hchild;
+    HWND hwnd, hwnd2, hchild;
     RECT rc;
 
     hwnd = CreateWindowExA(0, "TestWindowClass", "Test overlapped", WS_OVERLAPPEDWINDOW,
@@ -4814,6 +4953,68 @@ static void test_showwindow(void)
     ok_sequence(WmShowVisMaxPopupSeq, "ShowWindow(SW_SHOWMAXIMIZED):popup", FALSE);
     DestroyWindow(hwnd);
     flush_sequence();
+
+    /* Test 5:
+     * 1. Restoring a minimized window.
+     */
+    hwnd = CreateWindowA("TestWindowClass", "window1", WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, 0, 100, 100, 0, 0, 0, 0);
+    ok(hwnd != NULL, "Failed to create window\n");
+
+    hwnd2 = CreateWindowA("static", "window2", WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, 0, 100, 100, 0, 0, 0, 0);
+    ok(hwnd2 != NULL, "Failed to create window\n");
+
+    ShowWindow(hwnd, SW_MINIMIZE);
+    SetActiveWindow(hwnd2);
+    ok(GetActiveWindow() == hwnd2, "Unexpected active window\n");
+    flush_events();
+    flush_sequence();
+    ShowWindow(hwnd, SW_RESTORE);
+    flush_events();
+    ok_sequence(WmShowRestoreMinimizedOverlappedSeq,
+                "ShowWindow(hwnd, SW_RESTORE): minimized overlapped", TRUE);
+
+    ShowWindow(hwnd, SW_MINIMIZE);
+    SetActiveWindow(hwnd2);
+    ok(GetActiveWindow() == hwnd2, "Unexpected active window\n");
+    flush_events();
+    flush_sequence();
+    ShowWindow(hwnd, SW_SHOWNOACTIVATE);
+    flush_events();
+    ok_sequence(WmShowNoActivateMinimizedOverlappedSeq,
+                "ShowWindow(hwnd, SW_SHOWNOACTIVATE): minimized overlapped", TRUE);
+
+    DestroyWindow(hwnd2);
+    DestroyWindow(hwnd);
+    flush_sequence();
+
+    /* Test 6:
+     * 1. Restoring a minimized but active window.
+     */
+    hwnd = CreateWindowA("TestWindowClass", "parent", WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, 0, 100, 100, 0, 0, 0, 0);
+    ok(hwnd != NULL, "Failed to create window\n");
+
+    ShowWindow(hwnd, SW_MINIMIZE);
+    SetActiveWindow(hwnd);
+    ok(GetActiveWindow() == hwnd, "Unexpected active window\n");
+    flush_events();
+    flush_sequence();
+    ShowWindow(hwnd, SW_RESTORE);
+    flush_events();
+    ok_sequence(WmShowRestoreActiveMinimizedOverlappedSeq,
+                "ShowWindow(hwnd, SW_RESTORE): active minimized overlapped", TRUE);
+
+    ShowWindow(hwnd, SW_MINIMIZE);
+    SetActiveWindow(hwnd);
+    ok(GetActiveWindow() == hwnd, "Unexpected active window\n");
+    flush_events();
+    flush_sequence();
+    ShowWindow(hwnd, SW_SHOWNOACTIVATE);
+    flush_events();
+    ok_sequence(WmShowNoActivateActiveMinimizedOverlappedSeq,
+                "ShowWindow(hwnd, SW_SHOWNOACTIVATE): active minimized overlapped", TRUE);
+
+    DestroyWindow(hwnd);
+    flush_sequence();
 }
 
 static void test_sys_menu(void)
@@ -15527,6 +15728,42 @@ static const struct message WmRestoreMinimizedOverlappedSeq[] =
     { 0 }
 };
 
+/* DefWindowProcA(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0) to an active minimized window */
+static const struct message WmRestoreActiveMinimizedOverlappedSeq[] =
+{
+    { HCBT_SYSCOMMAND, hook|wparam|lparam, SC_RESTORE, 0 },
+    { HCBT_MINMAX, hook },
+    { WM_QUERYOPEN, sent },
+    { WM_GETTEXT, sent|optional },
+    { WM_NCACTIVATE, sent },
+    { WM_WINDOWPOSCHANGING, sent|wparam, SWP_NOSIZE|SWP_NOMOVE },
+    { WM_WINDOWPOSCHANGED, sent|wparam|optional, SWP_NOSIZE|SWP_NOMOVE|SWP_NOCLIENTSIZE|SWP_NOCLIENTMOVE },
+    { WM_NCCALCSIZE, sent|optional },
+    { WM_MOVE, sent|optional },
+    { WM_SIZE, sent|optional },
+    { WM_GETTEXT, sent|optional },
+    { WM_WINDOWPOSCHANGING, sent|wparam, SWP_FRAMECHANGED|SWP_NOCOPYBITS|SWP_STATECHANGED },
+    { WM_GETMINMAXINFO, sent|defwinproc },
+    { WM_NCCALCSIZE, sent },
+    { WM_NCPAINT, sent },
+    { WM_GETTEXT, sent|defwinproc|optional },
+    { WM_ERASEBKGND, sent },
+    { WM_WINDOWPOSCHANGED, sent|wparam, SWP_FRAMECHANGED|SWP_NOCOPYBITS|SWP_STATECHANGED },
+    { WM_MOVE, sent|defwinproc },
+    { WM_SIZE, sent|defwinproc },
+    { WM_NCCALCSIZE, sent|optional },
+    { WM_NCPAINT, sent|optional },
+    { WM_ERASEBKGND, sent|optional },
+    { HCBT_SETFOCUS, hook },
+    { WM_SETFOCUS, sent },
+    /* Note this WM_ACTIVATE messages even if the window is already active */
+    { WM_ACTIVATE, sent|wparam|lparam, WA_ACTIVE, 0 },
+    { WM_SYNCPAINT, sent|optional },
+    { WM_PAINT, sent },
+    { WM_GETMINMAXINFO, sent|optional },
+    { 0 }
+};
+
 struct rbuttonup_thread_data
 {
     HWND hwnd;
@@ -15588,7 +15825,15 @@ static void test_defwinproc(void)
     DefWindowProcA(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
     flush_events();
     ok_sequence(WmRestoreMinimizedOverlappedSeq, "DefWindowProcA(SC_RESTORE):overlapped", TRUE);
+
+    ShowWindow(hwnd, SW_MINIMIZE);
+    SetActiveWindow(hwnd);
+    ok(GetActiveWindow() == hwnd, "Unexpected active window\n");
+    flush_events();
     flush_sequence();
+    DefWindowProcA(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
+    flush_events();
+    ok_sequence(WmRestoreActiveMinimizedOverlappedSeq, "DefWindowProcA(SC_RESTORE):active minimized overlapped", TRUE);
 
     GetCursorPos(&pos);
     GetWindowRect(hwnd, &rect);
diff --git a/dlls/user32/winpos.c b/dlls/user32/winpos.c
index 072b574f3aa..c6f806f7beb 100644
--- a/dlls/user32/winpos.c
+++ b/dlls/user32/winpos.c
@@ -1179,7 +1179,13 @@ static BOOL show_window( HWND hwnd, INT cmd )
     else WIN_ReleasePtr( wndPtr );
 
     /* if previous state was minimized Windows sets focus to the window */
-    if (style & WS_MINIMIZE) SetFocus( hwnd );
+    if (style & WS_MINIMIZE)
+    {
+        SetFocus( hwnd );
+        /* Send a WM_ACTIVATE message for a top level window, even if the window is already active */
+        if (GetAncestor( hwnd, GA_ROOT ) == hwnd && !(swp & SWP_NOACTIVATE))
+            SendMessageW( hwnd, WM_ACTIVATE, WA_ACTIVE, 0 );
+    }
 
 done:
     SetThreadDpiAwarenessContext( context );
-- 
2.27.0



More information about the wine-devel mailing list