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.
This commit is contained in:
Rémi Bernon 2024-10-16 23:19:16 +02:00 committed by Alexandre Julliard
parent 700ee59470
commit 0dc7e40468
Notes: Alexandre Julliard 2024-11-15 22:25:28 +01:00
Approved-by: Alexandre Julliard (@julliard)
Merge-Request: https://gitlab.winehq.org/wine/wine/merge_requests/6822
3 changed files with 52 additions and 12 deletions

View file

@ -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();

View file

@ -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
*/

View file

@ -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 );