[PATCH v3] winex11.drv: Don't set the foreground for focus events that are older than the last SetFocus sent without an event.

Gabriel Ivăncescu gabrielopcode at gmail.com
Mon Sep 23 07:17:28 CDT 2019


When creating a visible window, the focus and the respective messages are
immediately sent to the created window to match Windows behavior. However,
the X11 server also sends either FocusIn events or the WM sends ClientMessages
with the WM_TAKE_FOCUS atom (by default) if the windows are managed, since
it received focus. If the application didn't have a message loop up to
process these events, they will remain in the queue.

If only one window is created, this is not a problem because the focus set
manually and the focus set when processing the events will be the same and
no message is sent. However, if 2 or more windows are created before the
message loop, when these events are later processed by the app's message
loop, they will change the focus and send the messages again, even though
we've already done so earlier, which confuses some apps at this point. One
example is Heroes of Might and Magic 5.

See comment #11 on https://bugs.winehq.org/show_bug.cgi?id=39742#c11 --
`When "Full screen" active game starts and then shows desktop but it is not
exited and after a few times switching game/desktop it shows game screen
and it is playable`

Related Proton bug: https://github.com/ValveSoftware/Proton/issues/2401

Signed-off-by: Gabriel Ivăncescu <gabrielopcode at gmail.com>
---

v3: Use XNoOp instead of XSync to make it asynchronous.

I'm not entirely sure if this works in 100% of the cases, but it's
asynchronous. It can't make things worse that without the patch though,
because if it ever fails somehow, it will simply act like the patch wasn't
applied in the first place.

 dlls/winex11.drv/event.c  | 48 ++++++++++++++++++++++++++++++++-------
 dlls/winex11.drv/x11drv.h |  2 ++
 2 files changed, 42 insertions(+), 8 deletions(-)

diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c
index f79f40c..67339cb 100644
--- a/dlls/winex11.drv/event.c
+++ b/dlls/winex11.drv/event.c
@@ -594,14 +594,22 @@ static void set_input_focus( struct x11drv_win_data *data )
 /**********************************************************************
  *              set_focus
  */
-static void set_focus( Display *display, HWND hwnd, Time time )
+static void set_focus( Display *display, HWND hwnd, Time time, unsigned long serial )
 {
+    struct x11drv_thread_data *thread_data = x11drv_thread_data();
     HWND focus;
     Window win;
     GUITHREADINFO threadinfo;
 
-    TRACE( "setting foreground window to %p\n", hwnd );
-    SetForegroundWindow( hwnd );
+    if ((long)(thread_data->setfocus_serial - serial) > 0)
+    {
+        TRACE( "ignoring old serial %lu/%lu\n", serial, thread_data->setfocus_serial );
+    }
+    else
+    {
+        TRACE( "setting foreground window to %p\n", hwnd );
+        SetForegroundWindow( hwnd );
+    }
 
     threadinfo.cbSize = sizeof(threadinfo);
     GetGUIThreadInfo(0, &threadinfo);
@@ -709,7 +717,7 @@ static void handle_wm_protocols( HWND hwnd, XClientMessageEvent *event )
                                        MAKELONG(HTCAPTION,WM_LBUTTONDOWN) );
             if (ma != MA_NOACTIVATEANDEAT && ma != MA_NOACTIVATE)
             {
-                set_focus( event->display, hwnd, event_time );
+                set_focus( event->display, hwnd, event_time, event->serial );
                 return;
             }
         }
@@ -718,7 +726,7 @@ static void handle_wm_protocols( HWND hwnd, XClientMessageEvent *event )
             hwnd = GetForegroundWindow();
             if (!hwnd) hwnd = last_focus;
             if (!hwnd) hwnd = GetDesktopWindow();
-            set_focus( event->display, hwnd, event_time );
+            set_focus( event->display, hwnd, event_time, event->serial );
             return;
         }
         /* try to find some other window to give the focus to */
@@ -726,7 +734,7 @@ static void handle_wm_protocols( HWND hwnd, XClientMessageEvent *event )
         if (hwnd) hwnd = GetAncestor( hwnd, GA_ROOT );
         if (!hwnd) hwnd = GetActiveWindow();
         if (!hwnd) hwnd = last_focus;
-        if (hwnd && can_activate_window(hwnd)) set_focus( event->display, hwnd, event_time );
+        if (hwnd && can_activate_window(hwnd)) set_focus( event->display, hwnd, event_time, event->serial );
     }
     else if (protocol == x11drv_atom(_NET_WM_PING))
     {
@@ -805,9 +813,17 @@ static BOOL X11DRV_FocusIn( HWND hwnd, XEvent *xev )
         if (hwnd) hwnd = GetAncestor( hwnd, GA_ROOT );
         if (!hwnd) hwnd = GetActiveWindow();
         if (!hwnd) hwnd = x11drv_thread_data()->last_focus;
-        if (hwnd && can_activate_window(hwnd)) set_focus( event->display, hwnd, CurrentTime );
+        if (hwnd && can_activate_window(hwnd)) set_focus( event->display, hwnd, CurrentTime, event->serial );
+    }
+    else
+    {
+        struct x11drv_thread_data *thread_data = x11drv_thread_data();
+
+        if ((long)(thread_data->setfocus_serial - event->serial) > 0)
+            TRACE("ignoring old serial %lu/%lu\n", event->serial, thread_data->setfocus_serial);
+        else
+            SetForegroundWindow( hwnd );
     }
-    else SetForegroundWindow( hwnd );
     return TRUE;
 }
 
@@ -1424,6 +1440,7 @@ void wait_for_withdrawn_state( HWND hwnd, BOOL set )
  */
 void CDECL X11DRV_SetFocus( HWND hwnd )
 {
+    struct x11drv_thread_data *thread_data = x11drv_thread_data();
     struct x11drv_win_data *data;
 
     HWND parent;
@@ -1439,6 +1456,21 @@ void CDECL X11DRV_SetFocus( HWND hwnd )
     }
     if (!data->managed || data->embedder) set_input_focus( data );
     release_win_data( data );
+
+    hwnd = GetAncestor(hwnd, GA_ROOT);
+    if (hwnd != thread_data->setfocus_hwnd)
+    {
+        thread_data->setfocus_hwnd = hwnd;
+
+        /* If we are not processing an event, store the current serial so that
+           we ignore all older focus-related events. This prevents the focus
+           from being reverted later when the app processes its messages. */
+        if (!thread_data->current_event)
+        {
+            thread_data->setfocus_serial = NextRequest(thread_data->display);
+            XNoOp(thread_data->display);
+        }
+    }
 }
 
 
diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h
index e71156c..481ccdb 100644
--- a/dlls/winex11.drv/x11drv.h
+++ b/dlls/winex11.drv/x11drv.h
@@ -330,6 +330,8 @@ struct x11drv_thread_data
     HWND     last_xic_hwnd;        /* last xic window */
     XFontSet font_set;             /* international text drawing font set */
     Window   selection_wnd;        /* window used for selection interactions */
+    HWND     setfocus_hwnd;        /* top-level window corresponding to the last SetFocus */
+    unsigned long setfocus_serial; /* serial number when last SetFocus without an event happened */
     unsigned long warp_serial;     /* serial number of last pointer warp request */
     Window   clip_window;          /* window used for cursor clipping */
     HWND     clip_hwnd;            /* message window stored in desktop while clipping is active */
-- 
2.21.0




More information about the wine-devel mailing list