[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