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.
-- v5: win32u: Don't clip update region to the window client rectangle. win32u: GetUpdateRect() should clip update rectangle to the window client area. win32u: GetUpdateRgn() should clip update region to the window client area. win32u: Clip PAINTSTRUCT.rcPaint to the window client area. server: Don't generate WM_PAINT messages for minimized windows. 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 f7f9d5e517f..d36d54e3c9c 100644 --- a/server/window.c +++ b/server/window.c @@ -1641,7 +1641,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())) + { + struct rectangle 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 d36d54e3c9c..b42f4de1049 100644 --- a/server/window.c +++ b/server/window.c @@ -1425,6 +1425,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 = win->parent->client_rect.left - win->parent->window_rect.left + win->window_rect.left; + offset_y = win->parent->client_rect.top - win->parent->window_rect.top + win->window_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 --- server/window.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/server/window.c b/server/window.c index b42f4de1049..04793bf035e 100644 --- a/server/window.c +++ b/server/window.c @@ -1236,6 +1236,7 @@ static struct region *get_visible_region( struct window *win, unsigned int flags /* first check if all ancestors are visible */
if (!is_visible( win )) return region; /* empty region */ + if (win->style & WS_MINIMIZE) return region;
if (is_desktop_window( win )) { @@ -1401,6 +1402,7 @@ static int get_window_visible_rect( struct window *win, struct rectangle *rect, *rect = frame ? win->window_rect : win->client_rect;
if (!(win->style & WS_VISIBLE)) return 0; + if (win->style & WS_MINIMIZE) return 0; if (is_desktop_window( win )) return 1;
while (!is_desktop_window( win->parent ))
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 64788990707..6be1a489c38 100644 --- a/dlls/win32u/dce.c +++ b/dlls/win32u/dce.c @@ -1594,6 +1594,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)) { @@ -1604,6 +1606,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, get_thread_dpi() ); + 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 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/dlls/win32u/dce.c b/dlls/win32u/dce.c index 523df91733e..66112b3b23a 100644 --- a/dlls/win32u/dce.c +++ b/dlls/win32u/dce.c @@ -1920,13 +1920,20 @@ INT WINAPI NtUserGetUpdateRgn( HWND hwnd, HRGN hrgn, BOOL erase ) BOOL WINAPI NtUserGetUpdateRect( HWND hwnd, RECT *rect, BOOL erase ) { UINT flags = UPDATE_NOCHILDREN; - HRGN update_rgn; + HRGN update_rgn, client_rgn; + struct window_rects rects; BOOL need_erase;
if (erase) flags |= UPDATE_NONCLIENT | UPDATE_ERASE;
if (!(update_rgn = send_ncpaint( hwnd, NULL, &flags ))) return FALSE;
+ get_window_rects( hwnd, COORDS_SCREEN, &rects, get_thread_dpi() ); + + client_rgn = NtGdiCreateRectRgn( rects.client.left, rects.client.top, rects.client.right, rects.client.bottom ); + NtGdiCombineRgn( update_rgn, update_rgn, client_rgn, RGN_AND ); + NtGdiDeleteObjectApp( client_rgn ); + if (rect && NtGdiGetRgnBox( update_rgn, rect ) != NULLREGION) { HDC hdc = NtUserGetDCEx( hwnd, 0, DCX_USESTYLE );
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 6be1a489c38..523df91733e 100644 --- a/dlls/win32u/dce.c +++ b/dlls/win32u/dce.c @@ -1893,7 +1893,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 ); + struct window_rects rects; + HRGN client_rgn; + + get_window_rects( hwnd, COORDS_SCREEN, &rects, get_thread_dpi() ); + + client_rgn = NtGdiCreateRectRgn( rects.client.left, rects.client.top, rects.client.right, rects.client.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 | 9 ++++----- dlls/user32/tests/win.c | 24 ++++++++++++------------ dlls/win32u/dce.c | 33 ++++++++++++++++++--------------- 3 files changed, 34 insertions(+), 32 deletions(-)
diff --git a/dlls/user32/tests/dce.c b/dlls/user32/tests/dce.c index 09c6dd0da54..e9cc9817432 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" ); @@ -561,7 +561,6 @@ static void test_cropped_parentdc_paint_clipbox(void) IntersectRect( &expect_rect, &toplevel_rect, &parent_rect );
MapWindowPoints( hwnd_child, hwnd_container, (POINT *)&rect, 2 ); - todo_wine ok( EqualRect( &rect, &expect_rect ), "rect = %s, expected %s\n", wine_dbgstr_rect( &rect ), wine_dbgstr_rect( &expect_rect ) );
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 166653f0d2c..a576f2fea29 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -6340,49 +6340,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 66112b3b23a..a1fb5a8d009 100644 --- a/dlls/win32u/dce.c +++ b/dlls/win32u/dce.c @@ -1523,6 +1523,8 @@ static HRGN send_ncpaint( HWND hwnd, HWND *child, UINT *flags ) HRGN client_rgn = 0; DWORD style;
+ TRACE( "hwnd %p, flags %08x\n", hwnd, *flags ); + if (child) hwnd = *child;
if (hwnd == get_desktop_window()) return whole_rgn; @@ -1544,23 +1546,20 @@ static HRGN send_ncpaint( HWND hwnd, HWND *child, UINT *flags ) update.left < rects.client.left || update.top < rects.client.top || update.right > rects.client.right || update.bottom > rects.client.bottom) { - client_rgn = NtGdiCreateRectRgn( rects.client.left, rects.client.top, rects.client.right, rects.client.bottom ); - NtGdiCombineRgn( client_rgn, client_rgn, whole_rgn, RGN_AND ); - /* check if update rgn contains complete nonclient area */ - if (type == SIMPLEREGION && EqualRect( &rects.window, &update )) + if (type == SIMPLEREGION && update.left <= rects.window.left && update.top <= rects.window.top && + update.right >= rects.window.right && update.bottom >= rects.window.bottom) { - NtGdiDeleteObjectApp( whole_rgn ); - whole_rgn = (HRGN)1; + client_rgn = (HRGN)1; + } + else + { + client_rgn = NtGdiCreateRectRgn( rects.client.left, rects.client.top, rects.client.right, rects.client.bottom ); + NtGdiCombineRgn( client_rgn, client_rgn, whole_rgn, RGN_AND ); } - } - else - { - client_rgn = whole_rgn; - whole_rgn = 0; }
- if (whole_rgn) /* NOTE: WM_NCPAINT allows wParam to be 1 */ + if (client_rgn) /* NOTE: WM_NCPAINT allows wParam to be 1 */ { if (*flags & UPDATE_NONCLIENT) { @@ -1571,13 +1570,13 @@ 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)client_rgn, 0 ); } - if (whole_rgn > (HRGN)1) NtGdiDeleteObjectApp( whole_rgn ); + if (client_rgn > (HRGN)1) NtGdiDeleteObjectApp( client_rgn ); } set_thread_dpi_awareness_context( context ); } - return client_rgn; + return whole_rgn; }
/*********************************************************************** @@ -1699,6 +1698,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\n", hwnd ); + NtUserHideCaret( hwnd );
if (!(hrgn = send_ncpaint( hwnd, NULL, &flags ))) return 0; @@ -1723,6 +1724,8 @@ HDC WINAPI NtUserBeginPaint( HWND hwnd, PAINTSTRUCT *ps ) */ BOOL WINAPI NtUserEndPaint( HWND hwnd, const PAINTSTRUCT *ps ) { + TRACE( "hwnd %p\n", hwnd ); + NtUserShowCaret( hwnd ); flush_window_surfaces( FALSE ); if (!ps) return FALSE;
On Wed May 7 15:07:26 2025 +0000, Dmitry Timoshkov wrote:
changed this line in [version 5 of the diff](/wine/wine/-/merge_requests/5665/diffs?diff_id=176158&start_sha=627f3582fb01b97de145e69a1496941972792c6a#515f291235626778b5253c80373554036e2a43f0_427_847)
I've removed the patch from this MR to make review easier. [@rbernon](https://gitlab.winehq.org/rbernon), could you please have another look at it? Thanks!
Is there anything that could done here?