[PATCH v4 1/4] user32/tests: Test for window exposure behaviours.

Jinoh Kang wine at gitlab.winehq.org
Thu Jun 16 20:42:07 CDT 2022


From: Jinoh Kang <jinoh.kang.kr at gmail.com>

Signed-off-by: Jinoh Kang <jinoh.kang.kr at gmail.com>
---
 dlls/user32/tests/msg.c | 378 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 378 insertions(+)

diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c
index 2705914d5e5..c62b25446a2 100644
--- a/dlls/user32/tests/msg.c
+++ b/dlls/user32/tests/msg.c
@@ -9035,6 +9035,378 @@ static void test_paint_messages(void)
     DeleteObject( hrgn2 );
 }
 
+static void visualize_region_differences( HWND hwnd, HWND hother, HRGN hrgn_expect, HRGN hrgn_actual )
+{
+    HBRUSH b_expectonly, b_actualonly, b_intersect;
+    HRGN hrgn_intersect;
+    HWND hstatic, hshow, hhide;
+    HDC hdc, hdctmp;
+    HBITMAP hbitmap;
+    MSG msg;
+    RECT rect;
+    DWORD start_time, elapsed, timeout = 60 * 1000;
+    BOOL toggle = TRUE, stop = FALSE;
+
+    start_time = GetTickCount();
+
+    b_expectonly = CreateSolidBrush( RGB( 64, 64, 255 ));
+    b_actualonly = CreateSolidBrush( RGB( 255, 64, 64 ));
+    b_intersect = CreateSolidBrush( RGB( 159, 64, 159 ));
+
+    hrgn_intersect = CreateRectRgn( 0, 0, 0, 0 );
+    CombineRgn( hrgn_intersect, hrgn_expect, hrgn_actual, RGN_AND );
+
+    GetClientRect( hwnd, &rect );
+    hdc = GetDC( hwnd );
+    hbitmap = CreateCompatibleBitmap( hdc, rect.right, rect.bottom );
+    hdctmp = CreateCompatibleDC( hdc );
+    ReleaseDC( hwnd, hdc );
+
+    SelectObject( hdctmp, hbitmap );
+    FillRgn( hdctmp, hrgn_expect, b_expectonly );
+    FillRgn( hdctmp, hrgn_actual, b_actualonly );
+    FillRgn( hdctmp, hrgn_intersect, b_intersect );
+
+    DeleteObject( hdctmp );
+    DeleteObject( hrgn_intersect );
+    DeleteObject( b_intersect );
+    DeleteObject( b_actualonly );
+    DeleteObject( b_expectonly );
+
+    hstatic = CreateWindowExA( 0, WC_STATICA, "", WS_CHILD | SS_BITMAP,
+                              0, 0, rect.right, rect.bottom, hwnd, 0, 0, NULL );
+    SendMessageA( hstatic, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbitmap );
+
+    hshow = hstatic;
+    hhide = hother;
+
+    for (;;)
+    {
+        if (stop) toggle = hshow == hother;
+        if (toggle)
+        {
+            HWND htmp;
+            HDWP hdwp;
+
+            hdwp = BeginDeferWindowPos( !!hhide + !!hshow );
+            if (hhide)
+            {
+                DeferWindowPos( hdwp, hhide, NULL, 0, 0, 0, 0,
+                                SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER );
+            }
+            if (hshow)
+            {
+                DeferWindowPos( hdwp, hshow, HWND_TOP, 0, 0, 0, 0,
+                                SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE );
+            }
+            EndDeferWindowPos( hdwp );
+
+            htmp = hshow;
+            hshow = hhide;
+            hhide = htmp;
+            toggle = FALSE;
+        }
+        if (stop) break;
+        if ((elapsed = GetTickCount() - start_time) >= timeout)
+        {
+            stop = TRUE;
+            continue;
+        }
+        MsgWaitForMultipleObjects( 0, NULL, FALSE, timeout - elapsed, QS_ALLINPUT );
+        while (PeekMessageA( &msg, 0, 0, 0, PM_REMOVE ))
+        {
+            TranslateMessage( &msg );
+            DispatchMessageA( &msg );
+            if (msg.message == WM_MOUSEMOVE)
+            {
+                start_time = GetTickCount();
+            }
+            else if (msg.message == WM_LBUTTONUP || (msg.message == WM_CHAR && msg.wParam == VK_SPACE))
+            {
+                toggle = !toggle;
+            }
+            else if (msg.message == WM_RBUTTONUP || (msg.message == WM_CHAR && msg.wParam == VK_RETURN))
+            {
+                stop = TRUE;
+            }
+        }
+    }
+
+    DestroyWindow( hstatic );
+    DeleteObject( hbitmap );
+}
+
+#define subtest_swp_paint_regions(w,p,c) subtest_swp_paint_regions_(__LINE__,w,p,c)
+
+static void subtest_swp_paint_regions_( int line, int wrap_toplevel, LPCSTR parent_class, LPCSTR child_class )
+{
+    static const struct exposure_test {
+        int ex_style, style;
+        BOOL shuffle_zorder;
+        HWND insert_after;
+    } exposure_tests[] = {
+        {                0, WS_CLIPCHILDREN, FALSE, NULL },
+        {                0,               0, FALSE, NULL },
+        { WS_EX_COMPOSITED, WS_CLIPCHILDREN, FALSE, NULL },
+        { WS_EX_COMPOSITED, WS_CLIPCHILDREN, TRUE , HWND_TOP },
+        { WS_EX_COMPOSITED, WS_CLIPCHILDREN, TRUE , HWND_BOTTOM },
+        { WS_EX_COMPOSITED,               0, FALSE, NULL },
+        { WS_EX_COMPOSITED,               0, TRUE , HWND_TOP },
+        { WS_EX_COMPOSITED,               0, TRUE , HWND_BOTTOM },
+    };
+    size_t i;
+    HWND htoplevel = NULL, hparent, hchild, hauxchild;
+    const RECT rect_old = { 10, 10, 100, 100 };
+    HRGN hrgn_old_vis = CreateRectRgn( 0, 0, 0, 0 );
+    HRGN hrgn_new_vis = CreateRectRgn( 0, 0, 0, 0 );
+    HRGN hrgn_expect = CreateRectRgn( 0, 0, 0, 0 );
+    HRGN hrgn_actual = CreateRectRgn( 0, 0, 0, 0 );
+    HRGN hrgn_old_vis_child = CreateRectRgn( 0, 0, 0, 0 );
+    HRGN hrgn_new_vis_child = CreateRectRgn( 0, 0, 0, 0 );
+    HRGN hrgn_expect_child = CreateRectRgn( 0, 0, 0, 0 );
+    HRGN hrgn_actual_child = CreateRectRgn( 0, 0, 0, 0 );
+    int base_style;
+    BOOL is_composition_possible, has_parentdc_anomaly;
+    WNDCLASSA parent_wc;
+
+    if (wrap_toplevel)
+    {
+        htoplevel = CreateWindowExA( 0, "SimpleWindowClass", "Test toplevel", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+                                     100, 100, 400, 400, 0, 0, 0, NULL );
+        ok( htoplevel != 0, "Failed to create top-level window: %lu\n", GetLastError() );
+        base_style = WS_CHILD | WS_VISIBLE;
+    }
+    else
+    {
+        base_style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
+    }
+
+    ok( GetClassInfoA( GetModuleHandleA( NULL ), parent_class, &parent_wc ),
+        "GetClassInfoA failed\n" );
+
+    is_composition_possible = (base_style & (WS_POPUP|WS_CHILD)) != WS_CHILD ||
+                              (parent_wc.style & CS_PARENTDC) == 0;
+
+    has_parentdc_anomaly = (base_style & (WS_POPUP|WS_CHILD)) != WS_CHILD &&
+                           (parent_wc.style & CS_PARENTDC) != 0;
+
+    hparent = CreateWindowExA( 0, parent_class, "Test parent", base_style,
+                               80, 80, 200, 200, htoplevel, 0, 0, NULL );
+    ok( hparent != 0, "Creating parent window (%s) returned error %lu\n",
+        debugstr_a( parent_class ), GetLastError() );
+
+    hchild = CreateWindowExA( 0, child_class, "Test child", WS_CHILD | WS_VISIBLE | WS_BORDER,
+                              rect_old.left, rect_old.top,
+                              rect_old.right - rect_old.left, rect_old.bottom - rect_old.top,
+                              hparent, 0, 0, NULL );
+    ok( hchild != 0, "Creating child window (%s) returned error %lu\n",
+        debugstr_a( child_class ), GetLastError() );
+
+    hauxchild = CreateWindowExA( 0, child_class, "Auxiliary child for z order test", WS_CHILD | WS_VISIBLE,
+                                 110, 0, 0, 0, hparent, 0, 0, NULL );
+    ok( hauxchild != 0, "Creating child window (%s) returned error %lu\n",
+        debugstr_a( child_class ), GetLastError() );
+
+    for (i = 0; i < ARRAY_SIZE(exposure_tests); i++)
+    {
+        const struct exposure_test *extest = &exposure_tests[i];
+        BOOL has_ws_ex_composited = (extest->ex_style & WS_EX_COMPOSITED) != 0;
+        BOOL is_composited = is_composition_possible && has_ws_ex_composited;
+        BOOL is_zorder_redraw = is_composited && extest->shuffle_zorder;
+        int delta;
+
+        winetest_push_context( "%d: SetWindowPos redraw #%Id (ex_style = %#x, style = %#x, shuffle_zorder = %d, insert_after = %Id)",
+                               line, i, extest->ex_style, extest->style, extest->shuffle_zorder, (LONG_PTR)extest->insert_after );
+
+        SetWindowLongA( hparent, GWL_EXSTYLE, extest->ex_style );
+        SetWindowLongA( hparent, GWL_STYLE, base_style | extest->style );
+        RedrawWindow( hparent, NULL, NULL, RDW_INVALIDATE|RDW_ERASE|RDW_FRAME );
+
+        for (delta = -20; delta <= 20; delta += 20)
+        {
+            RECT rect_old_vis, rect_new, rect_new_vis;
+            RECT rect_parent_clip, rect_child_clip;
+            RECT rect_old_vis_child, rect_new_vis_child;
+            BOOL rgn_equal;
+
+            winetest_push_context( "delta = %+d", delta );
+
+            SetWindowPos( hchild, HWND_TOP,
+                          rect_old.left,
+                          rect_old.top,
+                          rect_old.right - rect_old.left,
+                          rect_old.bottom - rect_old.top,
+                          SWP_NOACTIVATE );
+
+            rect_new = rect_old;
+            OffsetRect( &rect_new, delta, delta );
+
+            rect_old_vis_child = rect_old;
+            MapWindowPoints( hparent, hchild, (POINT *)&rect_old_vis_child, 2 );
+
+            SetRectRgn( hrgn_actual, 0, 0, 0, 0 );
+            SetRectRgn( hrgn_actual_child, 0, 0, 0, 0 );
+
+            UpdateWindow( hparent );
+            flush_events();
+
+            if (extest->shuffle_zorder)
+            {
+                /* bring sibling to top/bottom first so we can trigger z-order change */
+                SetWindowPos( hauxchild, extest->insert_after, 0, 0, 0, 0,
+                              SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE );
+            }
+
+            SetWindowPos( hchild, extest->insert_after,
+                          rect_new.left,
+                          rect_new.top,
+                          rect_new.right - rect_new.left,
+                          rect_new.bottom - rect_new.top,
+                          SWP_NOACTIVATE |
+                          (extest->shuffle_zorder || extest->insert_after ? 0 : SWP_NOZORDER) );
+
+            ok( GetUpdateRgn( hparent, hrgn_actual, FALSE ) != ERROR,
+                "GetUpdateRgn on parentshall succeed\n" );
+            ok( GetUpdateRgn( hchild, hrgn_actual_child, FALSE ) != ERROR,
+                "GetUpdateRgn on child shall succeed\n" );
+
+            /* Compute parent window expose region */
+            GetClientRect( hparent, &rect_parent_clip );
+            IntersectRect( &rect_old_vis, &rect_old, &rect_parent_clip );
+            SetRectRgn( hrgn_old_vis, rect_old_vis.left, rect_old_vis.top, rect_old_vis.right, rect_old_vis.bottom );
+            IntersectRect( &rect_new_vis, &rect_new, &rect_parent_clip );
+            SetRectRgn( hrgn_new_vis, rect_new_vis.left, rect_new_vis.top, rect_new_vis.right, rect_new_vis.bottom );
+
+            if (!EqualRect( &rect_old, &rect_new ) || is_zorder_redraw)
+            {
+                CombineRgn( hrgn_expect, hrgn_old_vis, hrgn_new_vis, is_composited ? RGN_OR : RGN_DIFF );
+            }
+            else
+            {
+                SetRectRgn( hrgn_expect, 0, 0, 0, 0 );
+            }
+
+            rgn_equal = EqualRgn( hrgn_expect, hrgn_actual );
+            if (!rgn_equal && broken( has_parentdc_anomaly && is_composited &&
+                                      LOBYTE(LOWORD(GetVersion())) < 8 ) /* Win7 */)
+            {
+                trace( "Forcing non-composited update region (broken)\n" );
+                CombineRgn( hrgn_expect, hrgn_old_vis, hrgn_new_vis, RGN_DIFF );
+                rgn_equal = EqualRgn( hrgn_expect, hrgn_actual );
+            }
+            todo_wine_if( EqualRect( &rect_old, &rect_new ) ? is_zorder_redraw :
+                          ((extest->style & WS_CLIPCHILDREN) == 0 || is_composited) )
+            ok( !!rgn_equal, "Parent update region shall match expected region\n" );
+
+            if (!rgn_equal && winetest_debug > 0)
+            {
+                printf( "Expected parent update region: " );
+                dump_region( hrgn_expect );
+                printf( "Actual parent update region: " );
+                dump_region( hrgn_actual );
+                printf( "Old child window visible area: %s\n", wine_dbgstr_rect( &rect_old_vis ) );
+                printf( "New child window visible area: %s\n", wine_dbgstr_rect( &rect_new_vis ) );
+            }
+
+            if (winetest_interactive)
+            {
+                if (!rgn_equal)
+                {
+                    visualize_region_differences( hparent, hchild, hrgn_expect, hrgn_actual );
+                }
+
+                /* Let the position change be visible to the user */
+                flush_events();
+            }
+
+            rect_new_vis_child = rect_new;
+            MapWindowPoints( hparent, hchild, (POINT *)&rect_new_vis_child, 2 );
+
+            /* Compute child window expose region */
+            GetClientRect( hchild, &rect_child_clip );
+            if (is_composited)
+            {
+                RECT rect_outer_clip;
+                GetClientRect( hparent, &rect_outer_clip );
+                MapWindowPoints( hparent, hchild, (POINT *)&rect_outer_clip, 2 );
+                IntersectRect( &rect_child_clip, &rect_child_clip, &rect_outer_clip );
+            }
+            IntersectRect( &rect_old_vis_child, &rect_old_vis_child, &rect_child_clip );
+            SetRectRgn( hrgn_old_vis_child, rect_old_vis_child.left, rect_old_vis_child.top, rect_old_vis_child.right, rect_old_vis_child.bottom );
+            IntersectRect( &rect_new_vis_child, &rect_new_vis_child, &rect_child_clip );
+            SetRectRgn( hrgn_new_vis_child, rect_new_vis_child.left, rect_new_vis_child.top, rect_new_vis_child.right, rect_new_vis_child.bottom );
+
+            if (!EqualRect( &rect_old, &rect_new ) || is_zorder_redraw)
+            {
+                CombineRgn( hrgn_expect_child, hrgn_new_vis_child, hrgn_old_vis_child, is_composited ? RGN_OR : RGN_DIFF );
+            }
+            else
+            {
+                SetRectRgn( hrgn_expect_child, 0, 0, 0, 0 );
+            }
+
+            rgn_equal = EqualRgn( hrgn_expect_child, hrgn_actual_child );
+            if (!rgn_equal && broken( has_parentdc_anomaly && is_composited &&
+                                      LOBYTE(LOWORD(GetVersion())) < 8 ) /* Win7 */)
+            {
+                trace( "Forcing non-composited update region (broken)\n" );
+                CombineRgn( hrgn_expect_child, hrgn_new_vis_child, hrgn_old_vis_child, RGN_DIFF );
+                rgn_equal = EqualRgn( hrgn_expect, hrgn_actual );
+            }
+            todo_wine_if( EqualRect( &rect_old, &rect_new ) ? is_zorder_redraw :
+                          ((extest->style & WS_CLIPCHILDREN) == 0 || is_composited) )
+            ok( !!rgn_equal, "Child update region shall match expected region\n" );
+
+            if (!rgn_equal && winetest_debug > 0)
+            {
+                printf( "Expected child update region: " );
+                dump_region( hrgn_expect_child );
+                printf( "Actual child update region: " );
+                dump_region( hrgn_actual_child );
+                printf( "Old child window client visible area: %s\n", wine_dbgstr_rect( &rect_old_vis_child ) );
+                printf( "New child window client visible area: %s\n", wine_dbgstr_rect( &rect_new_vis_child ) );
+            }
+
+            if (winetest_interactive)
+            {
+                if (!rgn_equal)
+                {
+                    visualize_region_differences( hchild, NULL, hrgn_expect_child, hrgn_actual_child );
+                }
+
+                /* Let the position change be visible to the user */
+                flush_events();
+            }
+
+            winetest_pop_context();
+        }
+
+        winetest_pop_context();
+    }
+
+    DestroyWindow( hauxchild );
+    DestroyWindow( hchild );
+    DestroyWindow( hparent );
+    if (htoplevel) DestroyWindow( htoplevel );
+
+    DeleteObject( hrgn_actual );
+    DeleteObject( hrgn_expect );
+    DeleteObject( hrgn_new_vis );
+    DeleteObject( hrgn_old_vis );
+}
+
+static void test_swp_paint_regions(void)
+{
+    subtest_swp_paint_regions( 1, "SimpleWindowClass", "SimpleWindowClass" );
+    subtest_swp_paint_regions( 1, "SimpleWindowClass", "SimpleWindowClassWithParentDC" );
+    subtest_swp_paint_regions( 1, "SimpleWindowClassWithParentDC", "SimpleWindowClass" );
+    subtest_swp_paint_regions( 1, "SimpleWindowClassWithParentDC", "SimpleWindowClassWithParentDC" );
+    subtest_swp_paint_regions( 0, "SimpleWindowClass", "SimpleWindowClass" );
+    subtest_swp_paint_regions( 0, "SimpleWindowClass", "SimpleWindowClassWithParentDC" );
+    subtest_swp_paint_regions( 0, "SimpleWindowClassWithParentDC", "SimpleWindowClass" );
+    subtest_swp_paint_regions( 0, "SimpleWindowClassWithParentDC", "SimpleWindowClassWithParentDC" );
+}
+
 struct wnd_event
 {
     HWND hwnd;
@@ -10387,6 +10759,11 @@ static BOOL RegisterWindowClasses(void)
     cls.lpszClassName = "TestDialogClass";
     if(!RegisterClassA(&cls)) return FALSE;
 
+    cls.lpfnWndProc = DefWindowProcA;
+    cls.style = CS_PARENTDC;
+    cls.lpszClassName = "SimpleWindowClassWithParentDC";
+    if(!RegisterClassA(&cls)) return FALSE;
+
     clsW.style = 0;
     clsW.lpfnWndProc = MsgCheckProcW;
     clsW.cbClsExtra = 0;
@@ -18870,6 +19247,7 @@ START_TEST(msg)
     test_combobox_messages();
     test_wmime_keydown_message();
     test_paint_messages();
+    test_swp_paint_regions();
     test_interthread_messages();
     test_message_conversion();
     test_accelerators();
-- 
GitLab


https://gitlab.winehq.org/wine/wine/-/merge_requests/231



More information about the wine-devel mailing list