[PATCH v2 0/4] MR11543: Fix repaints and focus problems on controls on property manager in SolidWorks (bug #27403)
This patches serie fixes the bug 27403 in SolidWorks. It has been tested with different graphic cards, and with many other applications without showing regressions. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=27403 I tried to separate it in small patches individually tested. This is my first commit, so i will kindly welcome any advices ! -- v2: win32u: Don't disable cache DCs on release after EndPaint. winex11: Suppress spurious foreground changes from rapid back-to-back SetActiveWindow calls. winex11: Don't send WM_CANCELMODE for intra-process focus changes. https://gitlab.winehq.org/wine/wine/-/merge_requests/11543
From: Denis Bonnenfant <denis.bonnenfant@diderot.org> For child windows, SetWindowPos with after=HWND_TOPMOST and flags=0 causes Wine's shared-surface model to trigger a visible-region recalculation that generates a WM_ERASEBKGND chain overwriting sibling window content. On real Windows, the compositor handles z-ordering transparently without triggering parent repaints. SolidWorks uses this pattern to bring PropertyManager controls to the front, which progressively erases section header text (bug 27403). Skip the call for this pattern (child window, after=HWND_TOPMOST, flags=0) and instead invalidate the window directly so it receives WM_PAINT without going through the parent erase path. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=27403 --- dlls/win32u/window.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c index 924990e8d4f..850ed7446d7 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -4161,6 +4161,21 @@ done: BOOL WINAPI NtUserSetWindowPos( HWND hwnd, HWND after, INT x, INT y, INT cx, INT cy, UINT flags ) { WINDOWPOS winpos; + HWND parent; + + /* SolidWorks calls SetWindowPos(child, HWND_TOPMOST, x, y, cx, cy, 0) to bring + * PropertyManager controls to the front. In Wine's shared-surface model any + * repaint chain triggered by SetWindowPos causes parent WM_ERASEBKGND to overwrite + * section header text. On real Windows the compositor handles z-ordering + * transparently without triggering parent repaints. Skip the call entirely to + * prevent the repaint cascade, but invalidate the control so it paints on the + * next message pump without going through the parent erase path. */ + parent = NtUserGetAncestor( hwnd, GA_PARENT ); + if (flags == 0 && after == HWND_TOPMOST && parent && parent != get_desktop_window()) + { + NtUserRedrawWindow( hwnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME ); + return TRUE; + } TRACE( "hwnd %p, after %p, %d,%d (%dx%d), flags %08x\n", hwnd, after, x, y, cx, cy, flags ); if(TRACE_ON(win)) dump_winpos_flags(flags); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11543
From: Denis Bonnenfant <denis.bonnenfant@diderot.org> WM_CANCELMODE should only be sent when the application loses focus to another process, not when focus moves between windows within the same Wine process. Sending it for intra-process focus changes causes applications to cancel ongoing operations (e.g. dismiss dropdowns or numeric input fields) when focus is simply moving to a sibling window. SolidWorks creates a transient suggestion popup and immediately restores focus to the input field; the spurious WM_CANCELMODE caused by the intermediate FocusOut event dismissed the field's popup list. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=27403 --- dlls/winex11.drv/event.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c index 38ad7f89d66..e47d76ce449 100644 --- a/dlls/winex11.drv/event.c +++ b/dlls/winex11.drv/event.c @@ -746,7 +746,9 @@ static void focus_out( Display *display , HWND hwnd ) if (is_virtual_desktop()) return; if (hwnd != NtUserGetForegroundWindow()) return; - if (!(NtUserGetWindowLongW( hwnd, GWL_STYLE ) & WS_MINIMIZE)) + /* Don't send WM_CANCELMODE for intra-process focus changes; only when focus + * leaves the Wine process entirely, matching Windows behavior. */ + if (!is_current_process_focused() && !(NtUserGetWindowLongW( hwnd, GWL_STYLE ) & WS_MINIMIZE)) send_message( hwnd, WM_CANCELMODE, 0, 0 ); /* don't reset the foreground window, if the window which is -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11543
From: Denis Bonnenfant <denis.bonnenfant@diderot.org> When an application calls SetActiveWindow(A) then SetActiveWindow(B) in quick succession, Wine sends two _NET_ACTIVE_WINDOW requests to the X11 window manager. The WM confirms A before our B request arrives; processing the A confirmation as a foreground change sends WM_ACTIVATE (deactivate) to the B window before it has finished activating. Track the last _NET_ACTIVE_WINDOW request sent (process_last_nav_request). In net_active_window_notify, when a confirmed value does not match the last request, treat it as an intermediate state: restore desired/pending to the last request and keep expect_serial non-zero so GetWindowStateUpdates defers the foreground update until the WM confirms the final request. SolidWorks triggers this with its PropertyManager numeric input: it creates a suggestion popup, calls SetActiveWindow(popup) then SetActiveWindow(field), causing a spurious WM_ACTIVATE(deactivate) on the input field that dismisses the field and loses the typed value. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=27403 --- dlls/winex11.drv/window.c | 46 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 297695749a1..e221b67cf31 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -1787,6 +1787,11 @@ static UINT window_update_client_config( struct x11drv_win_data *data ) return flags; } +/* Process-wide last _NET_ACTIVE_WINDOW request target (volatile for cross-thread visibility). + * If current_state.net_active_window != this value, we're seeing an intermediate state + * from rapid back-to-back SetActiveWindow calls and should suppress foreground changes. */ +static volatile Window process_last_nav_request; + /*********************************************************************** * GetWindowStateUpdates (X11DRV.@) */ @@ -1806,6 +1811,15 @@ BOOL X11DRV_GetWindowStateUpdates( HWND hwnd, UINT *state_cmd, UINT *swp_flags, { *foreground = hwnd_from_window( thread_data->display, thread_data->current_state.net_active_window ); if (*foreground == old_foreground) *foreground = 0; + /* Suppress foreground change when current _NET_ACTIVE_WINDOW doesn't match the + * last request we sent — it's an intermediate state from rapid back-to-back + * SetActiveWindow calls (e.g. app does A then B; WM confirms A before B arrives). */ + if (*foreground) + { + Window last_request = process_last_nav_request; + if (last_request && last_request != thread_data->current_state.net_active_window) + *foreground = 0; + } } if ((data = get_win_data( hwnd ))) @@ -1988,9 +2002,31 @@ void net_active_window_notify( unsigned long serial, Window value, Time time ) received = wine_dbg_sprintf( "_NET_ACTIVE_WINDOW %p/%lx serial %lu time %lu", current_hwnd, value, serial, time ); expected = *expect_serial ? wine_dbg_sprintf( ", expected %p/%lx serial %lu", pending_hwnd, *pending, *expect_serial ) : ""; - if (!handle_state_change( serial, expect_serial, sizeof(value), &value, desired, pending, - current, expected, "", received, NULL )) - return; + { + /* Save what we last requested before handle_state_change overwrites it on mismatch. */ + Window prev_pending = *pending; + BOOL had_serial = !!(*expect_serial); + if (!handle_state_change( serial, expect_serial, sizeof(value), &value, desired, pending, + current, expected, "", received, NULL )) + return; + /* When we sent rapid back-to-back _NET_ACTIVE_WINDOW requests (A then B), the WM + * confirms A before our B request arrives. The A confirmation is a "mismatch" against + * our last requested state B. Forwarding that intermediate A state would trigger a + * spurious Win32 foreground change. Instead: restore desired/pending to B and keep + * expect_serial non-zero so X11DRV_GetWindowStateUpdates defers foreground updates + * until the WM confirms B. */ + if (had_serial && value != prev_pending) + { + *desired = prev_pending; + *pending = prev_pending; + *expect_serial = 1; /* non-zero sentinel: blocks foreground update until B is confirmed */ + return; + } + } + + /* Confirmed value matches our last request — clear the global so future + * PropertyNotify events are not incorrectly treated as intermediate states. */ + if (value == process_last_nav_request) process_last_nav_request = 0; NtUserPostMessage( NtUserGetForegroundWindow(), WM_WINE_WINDOW_STATE_CHANGED, 0, 0 ); } @@ -2044,6 +2080,10 @@ void set_net_active_window( HWND hwnd, HWND previous ) if (!is_net_supported( x11drv_atom(_NET_ACTIVE_WINDOW) )) return; if (!(window = X11DRV_get_whole_window( hwnd ))) return; + /* Update the global expected target before the dedup guard so that even when we + * skip sending a new request (pending already matches), other threads processing + * a stale PropertyNotify can still detect and suppress the intermediate state. */ + process_last_nav_request = window; if (data->pending_state.net_active_window == window) return; if (window_set_pending_activate( hwnd, &withdrawn )) return; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11543
From: Denis Bonnenfant <denis.bonnenfant@diderot.org> Cache DCs should remain valid after EndPaint until they are reacquired for a different window, matching Windows behaviour. Disabling them immediately prevents applications that use the HDC after EndPaint from working correctly, e.g. SolidWorks calling DrawThemeTextEx with the BeginPaint HDC after EndPaint, resulting in invisible checkbox labels in the PropertyManager panel. Scope the fix to the EndPaint path only: plain GetDC/ReleaseDC cache DCs are still disabled immediately on release, as tested by dlls/user32/tests/dce.c ("Released cache DCs are 'disabled'"). Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=27403 --- dlls/win32u/dce.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dlls/win32u/dce.c b/dlls/win32u/dce.c index 5e8d82aed34..d1e0eb7e976 100644 --- a/dlls/win32u/dce.c +++ b/dlls/win32u/dce.c @@ -1243,7 +1243,11 @@ static INT release_dc( HWND hwnd, HDC hdc, BOOL end_paint ) { dce->count = 0; set_dc_pixel_format_internal( hdc, 0, &drawables ); - set_dce_flags( dce->hdc, DCHF_DISABLEDC ); + /* Keep the HDC valid after EndPaint so apps that reuse the BeginPaint HDC + * afterwards keep working, matching Windows behaviour (e.g. SolidWorks calling + * DrawThemeTextEx with the BeginPaint HDC after EndPaint). Plain GetDC/ReleaseDC + * cache DCs are disabled immediately, as tested by dlls/user32/tests/dce.c. */ + if (!end_paint) set_dce_flags( dce->hdc, DCHF_DISABLEDC ); } ret = TRUE; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11543
participants (2)
-
Denis Bonnenfant -
denis bonnenfant (@denis.bonnenfant)