From 0dc7e4046836595335cd64c92cc132791d78fba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Bernon?= Date: Wed, 16 Oct 2024 23:19:16 +0200 Subject: [PATCH] winex11: Ignore focus changes during WM_STATE transitions. When WM_STATE is being quickly updated, and depending on the WM we might receive transient focus changes, which will disrupt the Win32 state and make us randomly lose focus. Ignore them instead, when a window is being shown, and wait for WM_STATE to be updated and stable. We will eventually receive a WM_TAKE_FOCUS / FocusIn event *after* a window has been shown. When a window is hidden or minimized, we will receive the FocusOut event during the WM_STATE transition, and can safely handle it in this case, as we should have done all the Win32 side effects and have changed the foreground window already. When there's no window state change pending, the focus change event is unexpected, coming from the user or WM, and we handle it normally. --- dlls/winex11.drv/event.c | 46 ++++++++++++++++++++++++++++++--------- dlls/winex11.drv/window.c | 17 +++++++++++++-- dlls/winex11.drv/x11drv.h | 1 + 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c index 5300982d6b2..7c3b2d5ade3 100644 --- a/dlls/winex11.drv/event.c +++ b/dlls/winex11.drv/event.c @@ -761,12 +761,20 @@ static void handle_wm_protocols( HWND hwnd, XClientMessageEvent *event ) } else if (protocol == x11drv_atom(WM_TAKE_FOCUS)) { - HWND last_focus = x11drv_thread_data()->last_focus; + HWND last_focus = x11drv_thread_data()->last_focus, foreground = NtUserGetForegroundWindow(); - TRACE( "got take focus msg for %p, enabled=%d, visible=%d (style %08x), focus=%p, active=%p, fg=%p, last=%p\n", - hwnd, NtUserIsWindowEnabled(hwnd), NtUserIsWindowVisible(hwnd), - (int)NtUserGetWindowLongW(hwnd, GWL_STYLE), - get_focus(), get_active_window(), NtUserGetForegroundWindow(), last_focus ); + if (window_has_pending_wm_state( hwnd, -1 )) + { + WARN( "Ignoring window %p/%lx WM_TAKE_FOCUS serial %lu, event_time %ld, foreground %p during WM_STATE change\n", + hwnd, event->window, event->serial, event_time, foreground ); + return; + } + + TRACE( "window %p/%lx WM_TAKE_FOCUS serial %lu, event_time %ld, foreground %p\n", hwnd, event->window, + event->serial, event_time, foreground ); + TRACE( " enabled %u, visible %u, style %#x, focus %p, active %p, last %p\n", + NtUserIsWindowEnabled( hwnd ), NtUserIsWindowVisible( hwnd ), (int)NtUserGetWindowLongW( hwnd, GWL_STYLE ), + get_focus(), get_active_window(), last_focus ); if (can_activate_window(hwnd)) { @@ -783,7 +791,7 @@ static void handle_wm_protocols( HWND hwnd, XClientMessageEvent *event ) } else if (hwnd == NtUserGetDesktopWindow()) { - hwnd = NtUserGetForegroundWindow(); + hwnd = foreground; if (!hwnd) hwnd = last_focus; if (!hwnd) hwnd = NtUserGetDesktopWindow(); set_focus( event->display, hwnd, event_time ); @@ -845,14 +853,23 @@ BOOL is_current_process_focused(void) */ static BOOL X11DRV_FocusIn( HWND hwnd, XEvent *xev ) { + HWND foreground = NtUserGetForegroundWindow(); XFocusChangeEvent *event = &xev->xfocus; BOOL was_grabbed; + if (event->detail == NotifyPointer) return FALSE; if (!hwnd) return FALSE; - TRACE( "win %p xwin %lx detail=%s mode=%s\n", hwnd, event->window, focus_details[event->detail], focus_modes[event->mode] ); + if (window_has_pending_wm_state( hwnd, -1 )) + { + WARN( "Ignoring window %p/%lx FocusIn serial %lu, detail %s, mode %s, foreground %p during WM_STATE change\n", + hwnd, event->window, event->serial, focus_details[event->detail], focus_modes[event->mode], foreground ); + return FALSE; + } + + TRACE( "window %p/%lx FocusIn serial %lu, detail %s, mode %s, foreground %p\n", hwnd, event->window, + event->serial, focus_details[event->detail], focus_modes[event->mode], foreground ); - if (event->detail == NotifyPointer) return FALSE; /* when focusing in the virtual desktop window, re-apply the cursor clipping rect */ if (is_virtual_desktop() && hwnd == NtUserGetDesktopWindow()) reapply_cursor_clipping(); if (hwnd == NtUserGetDesktopWindow()) return FALSE; @@ -921,10 +938,9 @@ static void focus_out( Display *display , HWND hwnd ) */ static BOOL X11DRV_FocusOut( HWND hwnd, XEvent *xev ) { + HWND foreground = NtUserGetForegroundWindow(); XFocusChangeEvent *event = &xev->xfocus; - TRACE( "win %p xwin %lx detail=%s mode=%s\n", hwnd, event->window, focus_details[event->detail], focus_modes[event->mode] ); - if (event->detail == NotifyPointer) { if (!hwnd && event->window == x11drv_thread_data()->clip_window) @@ -938,6 +954,16 @@ static BOOL X11DRV_FocusOut( HWND hwnd, XEvent *xev ) } if (!hwnd) return FALSE; + if (window_has_pending_wm_state( hwnd, NormalState )) /* ignore FocusOut only if the window is being shown */ + { + WARN( "Ignoring window %p/%lx FocusOut serial %lu, detail %s, mode %s, foreground %p during WM_STATE change\n", + hwnd, event->window, event->serial, focus_details[event->detail], focus_modes[event->mode], foreground ); + return FALSE; + } + + TRACE( "window %p/%lx FocusOut serial %lu, detail %s, mode %s, foreground %p\n", hwnd, event->window, + event->serial, focus_details[event->detail], focus_modes[event->mode], foreground ); + /* in virtual desktop mode or when keyboard is grabbed, release any cursor grab but keep the clipping rect */ keyboard_grabbed = event->mode == NotifyGrab || event->mode == NotifyWhileGrabbed; if (is_virtual_desktop() || keyboard_grabbed) ungrab_clipping_window(); diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 02a1eb13c48..aa1eee354ac 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -1418,8 +1418,8 @@ static void window_set_wm_state( struct x11drv_win_data *data, UINT new_state ) data->pending_state.wm_state = new_state; data->wm_state_serial = NextRequest( data->display ); - TRACE( "window %p/%lx, requesting WM_STATE %#x -> %#x serial %lu\n", data->hwnd, data->whole_window, - old_state, new_state, data->wm_state_serial ); + TRACE( "window %p/%lx, requesting WM_STATE %#x -> %#x serial %lu, foreground %p\n", data->hwnd, data->whole_window, + old_state, new_state, data->wm_state_serial, NtUserGetForegroundWindow() ); switch (MAKELONG(old_state, new_state)) { @@ -1593,6 +1593,19 @@ void window_configure_notify( struct x11drv_win_data *data, unsigned long serial *expect_serial = 0; } +BOOL window_has_pending_wm_state( HWND hwnd, UINT state ) +{ + struct x11drv_win_data *data; + BOOL pending; + + if (!(data = get_win_data( hwnd ))) return FALSE; + if (state != -1 && data->pending_state.wm_state != state) pending = FALSE; + else pending = !!data->wm_state_serial; + release_win_data( data ); + + return pending; +} + /*********************************************************************** * make_window_embedded */ diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index f1f0f97486b..aecd49ac042 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -659,6 +659,7 @@ extern void set_gl_drawable_parent( HWND hwnd, HWND parent ); extern void destroy_gl_drawable( HWND hwnd ); extern void destroy_vk_surface( HWND hwnd ); +extern BOOL window_has_pending_wm_state( HWND hwnd, UINT state ); extern void window_wm_state_notify( struct x11drv_win_data *data, unsigned long serial, UINT value ); extern void window_net_wm_state_notify( struct x11drv_win_data *data, unsigned long serial, UINT value ); extern void window_configure_notify( struct x11drv_win_data *data, unsigned long serial, const RECT *rect );