[PATCH v2 2/3] winex11.drv: Wait for the WM to release the cursor before handling FocusIn events.

Rémi Bernon rbernon at codeweavers.com
Tue Sep 24 03:52:12 CDT 2019


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 at 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




More information about the wine-devel mailing list