The FocusIn/WM_TAKE_FOCUS events are sent as soon as a window or its frame is clicked, but sometimes the WM is still controlling the window position. Waiting for the cursor grab to be released before sending WM_ACTIVATE message helps with this situation.
When using WM_TAKE_FOCUS we are not going to receive FocusIn - or the eventual NotifyGrab/NotifyUngrab focus events until we set the input focus for ourselves, so the merging is simpler but we may miss the keyboard ungrab notifications.
When not, we pass through any NotifyGrab/NotifyUngrab focus events unless we are delaying a FocusIn event. In this case, we have to merge these notifications to reproduce the keyboard grab state as we cannot process them out of order.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com ---
This is roughly the same as the previous version [1], although instead of having a dedicated loop waiting for the cursor to be released, this delays the FocusIn event handling until XGrabPointer succeeds, while processing the other events normally.
It retries to grab the cursor once for every process_events call, even if no event has been received. In this case we could maybe retry less often, or there could also be a small delay before trying to grab the cursor, which would also be nicer w.r.t. grab transitions between the WM and an eventual desktop shell, as described in [2].
Also, as discussed in the other thread [1], for environments where XGrabPointer would never return successfully - if that exists - this has the same behavior as the previous implementation and the focus event would be delayed forever. In this case, we could make this delay depend on the GrabPointer driver option - or add a dedicated one - and/or check during driver initialization whether it is possible to grab the pointer at least once.
[1] https://www.winehq.org/pipermail/wine-devel/2019-September/150953.html [2] https://www.winehq.org/pipermail/wine-devel/2019-September/150956.html
dlls/winex11.drv/event.c | 94 +++++++++++++++++++++++++++++++++++++++- dlls/winex11.drv/mouse.c | 4 +- 2 files changed, 95 insertions(+), 3 deletions(-)
diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c index 32fdf23bf35..79369854720 100644 --- a/dlls/winex11.drv/event.c +++ b/dlls/winex11.drv/event.c @@ -314,15 +314,101 @@ static enum event_merge_action merge_raw_motion_events( XIRawEvent *prev, XIRawE } #endif
+static int try_grab_pointer( Display *display ) +{ + if (clipping_cursor) + return 1; + + if (XGrabPointer( display, root_window, False, 0, GrabModeAsync, GrabModeAsync, + None, None, CurrentTime ) != GrabSuccess) + return 0; + + XUngrabPointer( display, CurrentTime ); + return 1; +} + /*********************************************************************** * merge_events * * Try to merge 2 consecutive events. */ -static enum event_merge_action merge_events( XEvent *prev, XEvent *next ) +static enum event_merge_action merge_events( Display *display, XEvent *prev, XEvent *next ) { switch (prev->type) { + case ClientMessage: + if (!use_take_focus) + break; + if (root_window != DefaultRootWindow( display )) + break; + if (prev->xclient.message_type != x11drv_atom(WM_PROTOCOLS) || (Atom)prev->xclient.data.l[0] != x11drv_atom(WM_TAKE_FOCUS)) + break; + + switch (next->type) + { + case ClientMessage: + if (next->xclient.message_type == x11drv_atom(WM_PROTOCOLS) && (Atom)next->xclient.data.l[0] == x11drv_atom(WM_TAKE_FOCUS)) + { + TRACE( "Discarding old WM_TAKE_FOCUS message for window %lx\n", prev->xany.window ); + return MERGE_DISCARD; + } + } + + if (try_grab_pointer( display )) + break; + + TRACE( "Unable to grab pointer yet, delaying WM_TAKE_FOCUS event\n" ); + return MERGE_KEEP; + + case FocusIn: + if (use_take_focus) + break; + if (root_window != DefaultRootWindow( display )) + break; + if (prev->xfocus.detail == NotifyPointer || prev->xfocus.detail == NotifyPointerRoot) + break; + if (prev->xfocus.mode == NotifyGrab || prev->xfocus.mode == NotifyUngrab) + break; + + switch (next->type) + { + case FocusIn: + case FocusOut: + if (next->xfocus.detail == NotifyPointer || next->xfocus.detail == NotifyPointerRoot) + return MERGE_KEEP; + if (prev->xany.window != next->xany.window) + break; + + /* merge grab notifications with the delayed focus event */ + if (next->xfocus.mode == NotifyGrab) + { + prev->xfocus.mode = NotifyWhileGrabbed; + return MERGE_IGNORE; + } + else if (next->xfocus.mode == NotifyUngrab) + { + prev->xfocus.mode = NotifyNormal; + return MERGE_IGNORE; + } + + if (next->type == FocusOut) + { + prev->type = 0; /* FocusIn/FocusOut sequence, discard prev as well */ + TRACE( "Discarding FocusIn/FocusOut sequence for window %lx\n", prev->xany.window ); + return MERGE_IGNORE; + } + else + { + TRACE( "Discarding old FocusIn event for window %lx\n", prev->xany.window ); + return MERGE_DISCARD; + } + } + + if (try_grab_pointer( display )) + break; + + TRACE( "Unable to grab pointer yet, delaying FocusIn event\n" ); + return MERGE_KEEP; case ConfigureNotify: switch (next->type) { @@ -409,7 +495,7 @@ static inline BOOL merge_and_handle_events( Display *display, XEvent *prev, XEve enum event_merge_action action = MERGE_DISCARD; BOOL queued = FALSE;
- if (prev->type) action = merge_events( prev, next ); + if (prev->type) action = merge_events( display, prev, next ); switch( action ) { case MERGE_HANDLE: /* handle prev, keep new */ @@ -721,6 +807,10 @@ static void handle_wm_protocols( HWND hwnd, XClientMessageEvent *event ) hwnd, IsWindowEnabled(hwnd), IsWindowVisible(hwnd), GetWindowLongW(hwnd, GWL_STYLE), GetFocus(), GetActiveWindow(), GetForegroundWindow(), last_focus );
+ /* as we delayed the WM_TAKE_FOCUS event, we missed the grab notifications, + * but as we could grab the cursor we assume the keyboard is ungrabbed as well */ + keyboard_grabbed = FALSE; + if (can_activate_window(hwnd)) { /* simulate a mouse click on the caption to find out diff --git a/dlls/winex11.drv/mouse.c b/dlls/winex11.drv/mouse.c index 8d1dc5e35d7..a12998a40ff 100644 --- a/dlls/winex11.drv/mouse.c +++ b/dlls/winex11.drv/mouse.c @@ -395,7 +395,9 @@ static BOOL grab_clipping_window( const RECT *clip ) GetModuleHandleW(0), NULL ))) return TRUE;
- if (keyboard_grabbed) + if (keyboard_grabbed || + XGrabPointer( data->display, root_window, False, 0, + GrabModeAsync, GrabModeAsync, None, None, CurrentTime ) != GrabSuccess) { WARN( "refusing to clip to %s\n", wine_dbgstr_rect(clip) ); last_clip_refused = TRUE; -- 2.23.0