These patches fix painting in an application that draws outside of a CS_PARENTDC child client area in its WM_PAINT handler.
Comments and suggestions are welcome.
-- v2: win32u: Use parent rectangle for visible region calculations of a CS_PARENTDC child. win32u: Don't clip update region to the window client rectangle. win32u: GetUpdateRgn() should clip update region to the window client area. win32u: GetUpdateRect() should clip update rectangle to the window client area. win32u: Clip PAINTSTRUCT.rcPaint to the window client area. server: For a CS_PARENTDC child use parent for visible region calculations. server: If the being validated region covers whole window then validate everything.
From: Dmitry Timoshkov dmitry@baikal.ru
Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- server/window.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/server/window.c b/server/window.c index 242e93f303a..db459d979fc 100644 --- a/server/window.c +++ b/server/window.c @@ -1530,7 +1530,24 @@ static void redraw_window( struct window *win, struct region *region, int frame, { if ((tmp = crop_region_to_win_rect( win, region, frame ))) { - if (!subtract_region( tmp, win->update_region, tmp )) + if ((child_rgn = create_empty_region())) + { + rectangle_t rect = win->window_rect; + + offset_rect( &rect, -rect.left, -rect.top ); + set_region_rect( child_rgn, &rect ); + + if (subtract_region( child_rgn, child_rgn, tmp ) && is_region_empty( child_rgn ) ) + { + /* region covers whole window: validate everything */ + free_region( tmp ); + tmp = NULL; + } + + free_region( child_rgn ); + } + + if (tmp && !subtract_region( tmp, win->update_region, tmp )) { free_region( tmp ); return;
From: Dmitry Timoshkov dmitry@baikal.ru
Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- server/window.c | 12 ++++++++++++ 1 file changed, 12 insertions(+)
diff --git a/server/window.c b/server/window.c index db459d979fc..39de94103a4 100644 --- a/server/window.c +++ b/server/window.c @@ -1314,6 +1314,18 @@ static struct region *crop_region_to_win_rect( struct window *win, struct region struct region *tmp;
if (!get_window_visible_rect( win, &rect, frame )) return NULL; + + if (win->parent && is_window_using_parent_dc( win )) + { + int offset_x, offset_y; + + if (!get_window_visible_rect( win->parent, &rect, 0 )) return NULL; + + offset_x = rect.left + (frame ? win->window_rect.left : win->client_rect.left); + offset_y = rect.top + (frame ? win->window_rect.top : win->client_rect.top); + offset_rect( &rect, -offset_x, -offset_y ); + } + if (!(tmp = create_empty_region())) return NULL; set_region_rect( tmp, &rect );
From: Dmitry Timoshkov dmitry@baikal.ru
Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- dlls/win32u/dce.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/dlls/win32u/dce.c b/dlls/win32u/dce.c index 5c4f691b460..6ff79c7b13a 100644 --- a/dlls/win32u/dce.c +++ b/dlls/win32u/dce.c @@ -1109,6 +1109,8 @@ static BOOL send_erase( HWND hwnd, UINT flags, HRGN client_rgn, HDC hdc = 0; RECT dummy;
+ TRACE( "hwnd %p, flags %08x, client_rgn %p\n", hwnd, flags, client_rgn ); + if (!clip_rect) clip_rect = &dummy; if (hdc_ret || (flags & UPDATE_ERASE)) { @@ -1119,6 +1121,13 @@ static BOOL send_erase( HWND hwnd, UINT flags, HRGN client_rgn, { INT type = NtGdiGetAppClipBox( hdc, clip_rect );
+ if (get_class_long( hwnd, GCL_STYLE, FALSE ) & CS_PARENTDC) + { + RECT client_rect; + get_client_rect( hwnd, &client_rect ); + intersect_rect( clip_rect, clip_rect, &client_rect ); + } + if (flags & UPDATE_ERASE) { /* don't erase if the clip box is empty */
From: Dmitry Timoshkov dmitry@baikal.ru
Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- dlls/win32u/dce.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/dlls/win32u/dce.c b/dlls/win32u/dce.c index 6ff79c7b13a..9572053aa55 100644 --- a/dlls/win32u/dce.c +++ b/dlls/win32u/dce.c @@ -1446,14 +1446,21 @@ BOOL WINAPI NtUserGetUpdateRect( HWND hwnd, RECT *rect, BOOL erase )
if (rect && NtGdiGetRgnBox( update_rgn, rect ) != NULLREGION) { - HDC hdc = NtUserGetDCEx( hwnd, 0, DCX_USESTYLE ); - DWORD layout = NtGdiSetLayout( hdc, -1, 0 ); /* map_window_points mirrors already */ UINT win_dpi = get_dpi_for_window( hwnd ); - map_window_points( 0, hwnd, (POINT *)rect, 2, win_dpi ); - *rect = map_dpi_rect( *rect, win_dpi, get_thread_dpi() ); - NtGdiTransformPoints( hdc, (POINT *)rect, (POINT *)rect, 2, NtGdiDPtoLP ); - NtGdiSetLayout( hdc, -1, layout ); - NtUserReleaseDC( hwnd, hdc ); + RECT client_rect; + + get_window_rects( hwnd, COORDS_SCREEN, NULL, &client_rect, win_dpi ); + if (intersect_rect( rect, rect, &client_rect)) + { + HDC hdc = NtUserGetDCEx( hwnd, 0, DCX_USESTYLE ); + DWORD layout = NtGdiSetLayout( hdc, -1, 0 ); /* map_window_points mirrors already */ + + map_window_points( 0, hwnd, (POINT *)rect, 2, win_dpi ); + *rect = map_dpi_rect( *rect, win_dpi, get_thread_dpi() ); + NtGdiTransformPoints( hdc, (POINT *)rect, (POINT *)rect, 2, NtGdiDPtoLP ); + NtGdiSetLayout( hdc, -1, layout ); + NtUserReleaseDC( hwnd, hdc ); + } } need_erase = send_erase( hwnd, flags, update_rgn, NULL, NULL );
From: Dmitry Timoshkov dmitry@baikal.ru
Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- dlls/win32u/dce.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/dlls/win32u/dce.c b/dlls/win32u/dce.c index 9572053aa55..12aed66e8d8 100644 --- a/dlls/win32u/dce.c +++ b/dlls/win32u/dce.c @@ -1418,7 +1418,15 @@ INT WINAPI NtUserGetUpdateRgn( HWND hwnd, HRGN hrgn, BOOL erase )
if ((update_rgn = send_ncpaint( hwnd, NULL, &flags ))) { - retval = NtGdiCombineRgn( hrgn, update_rgn, 0, RGN_COPY ); + RECT client_rect; + HRGN client_rgn; + + get_window_rects( hwnd, COORDS_SCREEN, NULL, &client_rect, get_thread_dpi() ); + + client_rgn = NtGdiCreateRectRgn( client_rect.left, client_rect.top, client_rect.right, client_rect.bottom ); + retval = NtGdiCombineRgn( hrgn, update_rgn, client_rgn, RGN_AND ); + NtGdiDeleteObjectApp( client_rgn ); + if (send_erase( hwnd, flags, update_rgn, NULL, NULL )) { flags = UPDATE_DELAYED_ERASE;
From: Dmitry Timoshkov dmitry@baikal.ru
Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- dlls/user32/tests/dce.c | 8 ++++---- dlls/user32/tests/win.c | 24 ++++++++++++------------ dlls/win32u/dce.c | 35 ++++++++++++++++++----------------- 3 files changed, 34 insertions(+), 33 deletions(-)
diff --git a/dlls/user32/tests/dce.c b/dlls/user32/tests/dce.c index 18f31e3708c..64becb6084d 100644 --- a/dlls/user32/tests/dce.c +++ b/dlls/user32/tests/dce.c @@ -515,10 +515,10 @@ static void test_begin_paint(void) EndPaint( hwnd_parentdc, &ps ); GetClientRect( hwnd_parent, &parent_rect );
- todo_wine ok( rect.left == parent_rect.left, "rect.left = %ld, expected %ld\n", rect.left, parent_rect.left ); - todo_wine ok( rect.top == parent_rect.top, "rect.top = %ld, expected %ld\n", rect.top, parent_rect.top ); - todo_wine ok( rect.right == parent_rect.right, "rect.right = %ld, expected %ld\n", rect.right, parent_rect.right ); - todo_wine ok( rect.bottom == parent_rect.bottom, "rect.bottom = %ld, expected %ld\n", rect.bottom, parent_rect.bottom ); + ok( rect.left == parent_rect.left, "rect.left = %ld, expected %ld\n", rect.left, parent_rect.left ); + ok( rect.top == parent_rect.top, "rect.top = %ld, expected %ld\n", rect.top, parent_rect.top ); + ok( rect.right == parent_rect.right, "rect.right = %ld, expected %ld\n", rect.right, parent_rect.right ); + ok( rect.bottom == parent_rect.bottom, "rect.bottom = %ld, expected %ld\n", rect.bottom, parent_rect.bottom );
hdc = GetDC( hwnd_parent ); todo_wine ok( GetPixel( hdc, 60, 60 ) == cr, "error drawing outside of window client area\n" ); diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 775164e3e9f..b92c386251b 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -6274,49 +6274,49 @@ static void test_csparentdc(void) struct parentdc_test test_answer;
#define nothing_todo {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}} - const struct parentdc_test test1 = + const struct parentdc_test test1 = { {{0, 0, 150, 150}, {0, 0, 150, 150}, {0, 0, 150, 150}}, nothing_todo, - {{0, 0, 40, 40}, {-20, -20, 130, 130}, {0, 0, 40, 40}}, {{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}}, - {{0, 0, 40, 40}, {-40, -40, 110, 110}, {0, 0, 40, 40}}, {{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}}, + {{0, 0, 40, 40}, {-20, -20, 130, 130}, {0, 0, 40, 40}}, nothing_todo, + {{0, 0, 40, 40}, {-40, -40, 110, 110}, {0, 0, 40, 40}}, nothing_todo, };
- const struct parentdc_test test2 = + const struct parentdc_test test2 = { {{0, 0, 150, 150}, {0, 0, 50, 50}, {0, 0, 50, 50}}, nothing_todo, - {{0, 0, 40, 40}, {-20, -20, 30, 30}, {0, 0, 30, 30}}, {{0, 0, 0, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}}, - {{0, 0, 40, 40}, {-40, -40, 10, 10}, {0, 0, 10, 10}}, {{0, 0, 0, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}}, + {{0, 0, 40, 40}, {-20, -20, 30, 30}, {0, 0, 30, 30}}, nothing_todo, + {{0, 0, 40, 40}, {-40, -40, 10, 10}, {0, 0, 10, 10}}, nothing_todo, };
- const struct parentdc_test test3 = + const struct parentdc_test test3 = { {{0, 0, 150, 150}, {0, 0, 10, 10}, {0, 0, 10, 10}}, nothing_todo, {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, nothing_todo, {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, nothing_todo, };
- const struct parentdc_test test4 = + const struct parentdc_test test4 = { {{0, 0, 150, 150}, {40, 40, 50, 50}, {40, 40, 50, 50}}, nothing_todo, {{0, 0, 40, 40}, {20, 20, 30, 30}, {20, 20, 30, 30}}, nothing_todo, {{0, 0, 40, 40}, {0, 0, 10, 10}, {0, 0, 10, 10}}, nothing_todo, };
- const struct parentdc_test test5 = + const struct parentdc_test test5 = { {{0, 0, 150, 150}, {20, 20, 60, 60}, {20, 20, 60, 60}}, nothing_todo, {{0, 0, 40, 40}, {-20, -20, 130, 130}, {0, 0, 40, 40}}, {{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}}, - {{0, 0, 40, 40}, {-20, -20, 20, 20}, {0, 0, 20, 20}}, {{0, 0, 0, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}}, + {{0, 0, 40, 40}, {-20, -20, 20, 20}, {0, 0, 20, 20}}, nothing_todo, };
- const struct parentdc_test test6 = + const struct parentdc_test test6 = { {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, nothing_todo, {{0, 0, 40, 40}, {0, 0, 10, 10}, {0, 0, 10, 10}}, nothing_todo, {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, nothing_todo, };
- const struct parentdc_test test7 = + const struct parentdc_test test7 = { {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, nothing_todo, {{0, 0, 40, 40}, {-20, -20, 130, 130}, {0, 0, 40, 40}}, {{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}}, diff --git a/dlls/win32u/dce.c b/dlls/win32u/dce.c index 12aed66e8d8..e530ec83ee2 100644 --- a/dlls/win32u/dce.c +++ b/dlls/win32u/dce.c @@ -1035,10 +1035,13 @@ static BOOL get_update_flags( HWND hwnd, HWND *child, UINT *flags ) */ static HRGN send_ncpaint( HWND hwnd, HWND *child, UINT *flags ) { - HRGN whole_rgn = get_update_region( hwnd, flags, child ); - HRGN client_rgn = 0; + HRGN whole_rgn; DWORD style;
+ TRACE( "hwnd %p, flags %08x\n", hwnd, *flags ); + + whole_rgn = get_update_region( hwnd, flags, child ); + if (child) hwnd = *child;
if (hwnd == get_desktop_window()) return whole_rgn; @@ -1048,6 +1051,7 @@ static HRGN send_ncpaint( HWND hwnd, HWND *child, UINT *flags ) UINT context; RECT client, window, update; INT type; + HRGN nc_rgn = 0;
context = set_thread_dpi_awareness_context( get_window_dpi_awareness_context( hwnd ));
@@ -1059,23 +1063,17 @@ static HRGN send_ncpaint( HWND hwnd, HWND *child, UINT *flags ) update.left < client.left || update.top < client.top || update.right > client.right || update.bottom > client.bottom) { - client_rgn = NtGdiCreateRectRgn( client.left, client.top, client.right, client.bottom ); - NtGdiCombineRgn( client_rgn, client_rgn, whole_rgn, RGN_AND ); - /* check if update rgn contains complete nonclient area */ - if (type == SIMPLEREGION && EqualRect( &window, &update )) + if (type == SIMPLEREGION && update.left <= window.left && update.top <= window.top && + update.right >= window.right && update.bottom >= window.bottom) { - NtGdiDeleteObjectApp( whole_rgn ); - whole_rgn = (HRGN)1; + nc_rgn = (HRGN)1; } - } - else - { - client_rgn = whole_rgn; - whole_rgn = 0; + else + nc_rgn = whole_rgn; }
- if (whole_rgn) /* NOTE: WM_NCPAINT allows wParam to be 1 */ + if (nc_rgn) /* NOTE: WM_NCPAINT allows wParam to be 1 */ { if (*flags & UPDATE_NONCLIENT) { @@ -1086,13 +1084,12 @@ static HRGN send_ncpaint( HWND hwnd, HWND *child, UINT *flags ) if (style & WS_VSCROLL) set_standard_scroll_painted( hwnd, SB_VERT, FALSE );
- send_message( hwnd, WM_NCPAINT, (WPARAM)whole_rgn, 0 ); + send_message( hwnd, WM_NCPAINT, (WPARAM)nc_rgn, 0 ); } - if (whole_rgn > (HRGN)1) NtGdiDeleteObjectApp( whole_rgn ); } set_thread_dpi_awareness_context( context ); } - return client_rgn; + return whole_rgn; }
/*********************************************************************** @@ -1238,6 +1235,8 @@ HDC WINAPI NtUserBeginPaint( HWND hwnd, PAINTSTRUCT *ps ) RECT rect; UINT flags = UPDATE_NONCLIENT | UPDATE_ERASE | UPDATE_PAINT | UPDATE_INTERNALPAINT | UPDATE_NOCHILDREN;
+ TRACE( "hwnd %p, %p\n", hwnd, ps ); + NtUserHideCaret( hwnd );
if (!(hrgn = send_ncpaint( hwnd, NULL, &flags ))) return 0; @@ -1262,6 +1261,8 @@ HDC WINAPI NtUserBeginPaint( HWND hwnd, PAINTSTRUCT *ps ) */ BOOL WINAPI NtUserEndPaint( HWND hwnd, const PAINTSTRUCT *ps ) { + TRACE( "hwnd %p, %p\n", hwnd, ps ); + NtUserShowCaret( hwnd ); flush_window_surfaces( FALSE ); if (!ps) return FALSE;
From: Dmitry Timoshkov dmitry@baikal.ru
Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- dlls/user32/tests/dce.c | 2 +- dlls/win32u/dce.c | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/dlls/user32/tests/dce.c b/dlls/user32/tests/dce.c index 64becb6084d..c7e498c60d0 100644 --- a/dlls/user32/tests/dce.c +++ b/dlls/user32/tests/dce.c @@ -521,7 +521,7 @@ static void test_begin_paint(void) ok( rect.bottom == parent_rect.bottom, "rect.bottom = %ld, expected %ld\n", rect.bottom, parent_rect.bottom );
hdc = GetDC( hwnd_parent ); - todo_wine ok( GetPixel( hdc, 60, 60 ) == cr, "error drawing outside of window client area\n" ); + ok( GetPixel( hdc, 60, 60 ) == cr, "error drawing outside of window client area\n" ); ReleaseDC( hwnd_parent, hdc ); }
diff --git a/dlls/win32u/dce.c b/dlls/win32u/dce.c index e530ec83ee2..7969171c92b 100644 --- a/dlls/win32u/dce.c +++ b/dlls/win32u/dce.c @@ -366,8 +366,16 @@ static void update_visible_region( struct dce *dce ) top_win = wine_server_ptr_handle( reply->top_win ); win_rect.left = reply->win_rect.left; win_rect.top = reply->win_rect.top; - win_rect.right = reply->win_rect.right; - win_rect.bottom = reply->win_rect.bottom; + if (flags & DCX_PARENTCLIP) + { + win_rect.right = reply->top_rect.right; + win_rect.bottom = reply->top_rect.bottom; + } + else + { + win_rect.right = reply->win_rect.right; + win_rect.bottom = reply->win_rect.bottom; + } top_rect.left = reply->top_rect.left; top_rect.top = reply->top_rect.top; top_rect.right = reply->top_rect.right;
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=145870
Your paranoid android.
=== w1064v1507 (32 bit report) ===
user32: win.c:13062: Test failed: window is minimized.
=== w10pro64 (64 bit report) ===
user32: win.c:3818: Test failed: GetForegroundWindow returned 00000000000202C0 win.c:3749: Test failed: SetForegroundWindow failed, error 0 win.c:3752: Test failed: GetForegroundWindow returned 00000000000202C0 win.c:3789: Test failed: GetForegroundWindow returned 00000000000202C0 win.c:3877: Test failed: GetActiveWindow() = 00000000000201A8 win.c:3881: Test failed: GetFocus() = 0000000000000000 win.c:3884: Test failed: GetFocus() = 0000000000000000
=== debian11b (64 bit WoW report) ===
user32: listbox.c:1958: Test failed: got non-empty rect