Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58210 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=38975
Supersedes https://gitlab.winehq.org/wine/wine/-/merge_requests/8013
-- v6: server: Update fill window invalidate rect with window resize. server: Pass NULL as child region if region is NULL.
From: Paul Gofman pgofman@codeweavers.com
--- dlls/user32/tests/win.c | 137 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+)
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 166653f0d2c..dc7e7387cdf 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -126,6 +126,17 @@ static void flush_events( BOOL remove_messages ) } }
+static void pump_messages(void) +{ + MSG msg; + + while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } +} + /* check the values returned by the various parent/owner functions on a given window */ static void check_parents( HWND hwnd, HWND ga_parent, HWND gwl_parent, HWND get_parent, HWND gw_owner, HWND ga_root, HWND ga_root_owner ) @@ -10880,6 +10891,8 @@ static void test_update_region(void) const RECT rc = {15, 15, 40, 40}; const POINT wnd_orig = {30, 20}; const POINT child_orig = {10, 5}; + RECT r, expect_rect; + BOOL bret;
parent = CreateWindowExA(0, "MainWindowClass", NULL, WS_VISIBLE | WS_CLIPCHILDREN, @@ -10937,7 +10950,131 @@ static void test_update_region(void)
DeleteObject(rgn1); DeleteObject(rgn2); + + pump_messages(); + /* Test that NULL invalidated region means current full client rect and not the one at the moment of + * invalidation. */ + ValidateRect(parent, NULL); + GetUpdateRect(parent, &r, FALSE); + SetRect(&expect_rect, 0, 0, 0, 0); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + InvalidateRect(parent, NULL, FALSE); + SetRect(&r, 0, 0, 10, 10); + /* Adding a rectangle to NULL one still keeps that as full window. */ + InvalidateRect(parent, &r, FALSE); + GetUpdateRect(parent, &r, FALSE); + GetClientRect(parent, &expect_rect); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + SetWindowPos(parent, NULL, 0, 0, 350, 200, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + GetUpdateRect(parent, &r, FALSE); + GetClientRect(parent, &expect_rect); + todo_wine ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + ValidateRect(parent, NULL); + + SetWindowPos(parent, NULL, 0, 0, 300, 150, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + GetUpdateRect(parent, &r, FALSE); + SetRect(&expect_rect, 0, 0, 0, 0); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + RedrawWindow(parent, NULL, 0, RDW_INVALIDATE | RDW_FRAME); + RedrawWindow(parent, NULL, 0, RDW_INVALIDATE); + GetUpdateRect(parent, &r, FALSE); + GetClientRect(parent, &expect_rect); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + SetWindowPos(parent, NULL, 0, 0, 350, 200, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + GetUpdateRect(parent, &r, FALSE); + GetClientRect(parent, &expect_rect); + todo_wine ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + pump_messages(); + RedrawWindow(parent, NULL, 0, RDW_VALIDATE | RDW_FRAME | RDW_ALLCHILDREN); + + ValidateRect(hwnd, NULL); + SetRect(&expect_rect, 0, 0, 0, 0); + GetUpdateRect(hwnd, &r, FALSE); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + GetClientRect(hwnd, &expect_rect); + RedrawWindow(parent, NULL, 0, RDW_INVALIDATE | RDW_ALLCHILDREN); + GetUpdateRect(hwnd, &r, FALSE); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + SetWindowPos(hwnd, NULL, 0, 0, 205, 105, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + SetWindowPos(hwnd, NULL, 0, 0, 210, 110, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + GetUpdateRect(hwnd, &r, FALSE); + GetClientRect(hwnd, &expect_rect); + todo_wine ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + ValidateRect(hwnd, NULL); + ValidateRect(parent, NULL); + SetWindowPos(hwnd, NULL, 0, 0, 200, 100, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + + SetRect(&expect_rect, 0, 0, 0, 0); + GetUpdateRect(hwnd, &r, FALSE); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + GetClientRect(hwnd, &expect_rect); + InvalidateRect(hwnd, NULL, FALSE); + GetUpdateRect(hwnd, &r, FALSE); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + SetWindowPos(hwnd, NULL, 0, 0, 210, 110, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + GetUpdateRect(hwnd, &r, FALSE); + GetClientRect(hwnd, &expect_rect); + todo_wine ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + ValidateRect(hwnd, NULL); + ValidateRect(parent, NULL); + SetWindowPos(hwnd, NULL, 0, 0, 200, 100, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + + SetRect(&expect_rect, 0, 0, 0, 0); + GetUpdateRect(hwnd, &r, FALSE); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + GetClientRect(hwnd, &expect_rect); + InvalidateRect(hwnd, NULL, FALSE); + GetUpdateRect(hwnd, &r, FALSE); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + /* Child window bottom is outside parent window. Invalidated area is still new child window extents + * coordinates cropped to visible part. */ + SetWindowPos(hwnd, NULL, 0, 150, 210, 100, SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + GetClientRect(hwnd, &expect_rect); + GetClientRect(parent, &r); + expect_rect.bottom = r.bottom - 150; + GetUpdateRect(hwnd, &r, FALSE); + todo_wine ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + ValidateRect(hwnd, NULL); + ValidateRect(parent, NULL); + SetWindowPos(hwnd, NULL, 0, 0, 200, 100, SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + + SetWindowPos(parent, NULL, 0, 0, 300, 150, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + GetUpdateRect(parent, &r, FALSE); + SetRect(&expect_rect, 0, 0, 0, 0); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + + GetClientRect(parent, &r); + InvalidateRect(parent, &r, FALSE); + expect_rect = r; + GetUpdateRect(parent, &r, FALSE); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + SetWindowPos(parent, NULL, 0, 0, 350, 200, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + GetUpdateRect(parent, &r, FALSE); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + GetClientRect(parent, &r); + ok(!EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + + ValidateRect(parent, NULL); + SetWindowPos(parent, NULL, 0, 0, 300, 150, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + GetUpdateRect(parent, &r, FALSE); + SetRect(&expect_rect, 0, 0, 0, 0); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + InvalidateRect(parent, NULL, FALSE); + SetRect(&r, 0, 0, 0, 0); + /* Subtracting empty rectangle from update region turns 'full client rect' into the specific coordinates + * region (unlike adding rectangle). */ + bret = ValidateRect(parent, &r); + ok(bret, "got error %lu.\n", GetLastError()); + GetClientRect(parent, &expect_rect); + GetUpdateRect(parent, &r, FALSE); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + SetWindowPos(parent, NULL, 0, 0, 350, 200, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); + GetUpdateRect(parent, &r, FALSE); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + + ValidateRect(parent, NULL); DestroyWindow(parent); + pump_messages(); }
static void test_window_without_child_style(void)
From: Rémi Bernon rbernon@codeweavers.com
--- server/window.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/window.c b/server/window.c index 914d376a44a..473af1f1ff4 100644 --- a/server/window.c +++ b/server/window.c @@ -1688,7 +1688,7 @@ static void redraw_window( struct window *win, struct region *region, int frame, if (rect_in_region( child_rgn, &child->window_rect )) { offset_region( child_rgn, -child->client_rect.left, -child->client_rect.top ); - redraw_window( child, child_rgn, 1, flags ); + redraw_window( child, region ? child_rgn : NULL, 1, flags ); } } free_region( child_rgn );
From: Paul Gofman pgofman@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58210 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=38975 --- dlls/user32/tests/win.c | 10 +++++----- server/window.c | 9 ++++++++- 2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index dc7e7387cdf..fc1b79d0628 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -10968,7 +10968,7 @@ static void test_update_region(void) SetWindowPos(parent, NULL, 0, 0, 350, 200, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); GetUpdateRect(parent, &r, FALSE); GetClientRect(parent, &expect_rect); - todo_wine ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); ValidateRect(parent, NULL);
SetWindowPos(parent, NULL, 0, 0, 300, 150, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); @@ -10983,7 +10983,7 @@ static void test_update_region(void) SetWindowPos(parent, NULL, 0, 0, 350, 200, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); GetUpdateRect(parent, &r, FALSE); GetClientRect(parent, &expect_rect); - todo_wine ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); pump_messages(); RedrawWindow(parent, NULL, 0, RDW_VALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
@@ -10999,7 +10999,7 @@ static void test_update_region(void) SetWindowPos(hwnd, NULL, 0, 0, 210, 110, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); GetUpdateRect(hwnd, &r, FALSE); GetClientRect(hwnd, &expect_rect); - todo_wine ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); ValidateRect(hwnd, NULL); ValidateRect(parent, NULL); SetWindowPos(hwnd, NULL, 0, 0, 200, 100, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); @@ -11014,7 +11014,7 @@ static void test_update_region(void) SetWindowPos(hwnd, NULL, 0, 0, 210, 110, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); GetUpdateRect(hwnd, &r, FALSE); GetClientRect(hwnd, &expect_rect); - todo_wine ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); ValidateRect(hwnd, NULL); ValidateRect(parent, NULL); SetWindowPos(hwnd, NULL, 0, 0, 200, 100, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); @@ -11033,7 +11033,7 @@ static void test_update_region(void) GetClientRect(parent, &r); expect_rect.bottom = r.bottom - 150; GetUpdateRect(hwnd, &r, FALSE); - todo_wine ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); + ok(EqualRect(&r, &expect_rect), "got %s, expected %s.\n", wine_dbgstr_rect(&r), wine_dbgstr_rect(&expect_rect)); ValidateRect(hwnd, NULL); ValidateRect(parent, NULL); SetWindowPos(hwnd, NULL, 0, 0, 200, 100, SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); diff --git a/server/window.c b/server/window.c index 473af1f1ff4..9c0ace28db8 100644 --- a/server/window.c +++ b/server/window.c @@ -134,6 +134,7 @@ static const struct object_ops window_ops = #define PAINT_NONCLIENT 0x0040 /* needs WM_NCPAINT */ #define PAINT_DELAYED_ERASE 0x0080 /* still needs erase after WM_ERASEBKGND */ #define PAINT_PIXEL_FORMAT_CHILD 0x0100 /* at least one child has a custom pixel format */ +#define PAINT_INVALIDATED 0x0200 /* window has been fully invalidated */
/* growable array of user handles */ struct user_handle_array @@ -1466,7 +1467,7 @@ static void set_update_region( struct window *win, struct region *region ) inc_window_paint_count( win, -1 ); free_region( win->update_region ); } - win->paint_flags &= ~(PAINT_ERASE | PAINT_DELAYED_ERASE | PAINT_NONCLIENT); + win->paint_flags &= ~(PAINT_ERASE | PAINT_DELAYED_ERASE | PAINT_NONCLIENT | PAINT_INVALIDATED); win->update_region = NULL; if (region) free_region( region ); } @@ -1628,6 +1629,7 @@ static void redraw_window( struct window *win, struct region *region, int frame,
if (!add_update_region( win, tmp )) return;
+ if (!region) win->paint_flags |= PAINT_INVALIDATED; if (flags & RDW_FRAME) win->paint_flags |= PAINT_NONCLIENT; if (flags & RDW_ERASE) win->paint_flags |= PAINT_ERASE; } @@ -1651,6 +1653,7 @@ static void redraw_window( struct window *win, struct region *region, int frame, if (flags & RDW_NOFRAME) validate_non_client( win ); if (flags & RDW_NOERASE) win->paint_flags &= ~(PAINT_ERASE | PAINT_DELAYED_ERASE); } + win->paint_flags &= ~PAINT_INVALIDATED; }
if ((flags & RDW_INTERNALPAINT) && !(win->paint_flags & PAINT_INTERNAL)) @@ -1962,6 +1965,10 @@ static void set_window_pos( struct window *win, struct window *previous,
if (win->update_region) { + int frame = win->paint_flags & PAINT_NONCLIENT; + if ((win->paint_flags & PAINT_INVALIDATED) && get_window_visible_rect( win, &rect, frame )) + set_region_rect( win->update_region, &rect ); + if (get_window_visible_rect( win, &rect, 1 )) { struct region *tmp = create_empty_region();
On Tue May 20 15:30:16 2025 +0000, Rémi Bernon wrote:
changed this line in [version 6 of the diff](/wine/wine/-/merge_requests/8095/diffs?diff_id=179171&start_sha=128e3c8f69832d4db892d36e1dec58045c6c1c26#3dde8c1935f0e74922f8b34a2af10931253453d9_1644_1644)
I don't think it's better. I've dropped the change as it seems to be such a concern. I don't think the current code is better but I don't care so much.
having to track down 6 different calls sites to figure under which condition that additional parameter can be set to a 1/0 constant, based on seemingly arbitrary decision as it is hardcoded in most of them
I feel like I might have a better solution: still remove `frame` parameter, but instead add a flag `WINE_RDW_NESTED` to indicate we're (in)validating a child window recursively. Then we can just compute:
- `frame = !!(flags & RDW_FRAME)` in `if (flags & RDW_INVALIDATE)` case, and - `frame = !!(flags & WINE_RDW_NESTED)` in `if (flags & RDW_VALIDATE)` case.
On Tue May 20 15:31:28 2025 +0000, Jinoh Kang wrote:
having to track down 6 different calls sites to figure under which
condition that additional parameter can be set to a 1/0 constant, based on seemingly arbitrary decision as it is hardcoded in most of them I feel like I might have a better solution: still remove `frame` parameter, but instead add a flag `WINE_RDW_NESTED` to indicate we're (in)validating a child window recursively. Then we can just compute:
- `frame = !!(flags & RDW_FRAME)` in `if (flags & RDW_INVALIDATE)` case, and
- `frame = !!(flags & WINE_RDW_NESTED)` in `if (flags & RDW_VALIDATE)` case.
If you don't care much then consider this resolved.
On Tue May 20 15:31:55 2025 +0000, Jinoh Kang wrote:
If you don't care much then consider this resolved.
(For reference, I submitted my alternative approach for this specific aspect as !8097.)
Anything wrong with this?