From: Gabriel Ivăncescu gabrielopcode@gmail.com
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 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47945 Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com Signed-off-by: Rémi Bernon rbernon@codeweavers.com ---
I believe this is the correct fix to the failing dinput mouse test.
The test fails because the focus is changed back to the second window after it's been created, when the WM_TAKE_FOCUS event actually arrives: as we do not wait for the X11 focus to be set on CreateWindow, it can create an additional and unexpected focus change later on.
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 dd8837c11da..2649d561a6b 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)) { @@ -806,9 +814,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; }
@@ -1426,6 +1442,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; @@ -1441,6 +1458,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 98cab8947be..e9b827c7e9a 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -333,6 +333,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 */
Commit 71d35d8940118bc6de6522913fb8c473fa5b2c24 broke the way WM_TAKE_FOCUS protocol is implemented: WM_MOUSEACTIVATE now replies MA_NOACTIVATE by default when using HTCAPTION.
We use the WM_MOUSEACTIVATE -although Windows does not- regardless of the way focus is changed to check whether a window wants focus, and Windows sometimes changes focus regardless of the message reply.
Steam and the Wine system tray are affected for instance.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com ---
With the previous patch, it's also possible to revert 71d35d8940118bc6de6522913fb8c473fa5b2c24, but the WM_MOUSEACTIVATE reply it changes is indeed correct.
dlls/winex11.drv/event.c | 2 +- dlls/winex11.drv/window.c | 2 +- dlls/winex11.drv/x11drv.h | 1 + 3 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c index 2649d561a6b..957d7c5efdf 100644 --- a/dlls/winex11.drv/event.c +++ b/dlls/winex11.drv/event.c @@ -714,7 +714,7 @@ static void handle_wm_protocols( HWND hwnd, XClientMessageEvent *event ) * whether the window wants to be activated */ LRESULT ma = SendMessageW( hwnd, WM_MOUSEACTIVATE, (WPARAM)GetAncestor( hwnd, GA_ROOT ), - MAKELONG(HTCAPTION,WM_LBUTTONDOWN) ); + MAKELONG( HTMENU, WM_LBUTTONDOWN ) ); if (ma != MA_NOACTIVATEANDEAT && ma != MA_NOACTIVATE) { set_focus( event->display, hwnd, event_time, event->serial ); diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 99e4094ebd9..89ea42b7567 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -169,7 +169,7 @@ struct has_popup_result BOOL found; };
-static BOOL is_managed( HWND hwnd ) +BOOL is_managed( HWND hwnd ) { struct x11drv_win_data *data = get_win_data( hwnd ); BOOL ret = data && data->managed; diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index e9b827c7e9a..0e55e5115f8 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -607,6 +607,7 @@ extern void change_systray_owner( Display *display, Window systray_window ) DECL extern void update_systray_balloon_position(void) DECLSPEC_HIDDEN; extern HWND create_foreign_window( Display *display, Window window ) DECLSPEC_HIDDEN; extern BOOL update_clipboard( HWND hwnd ) DECLSPEC_HIDDEN; +extern BOOL is_managed( HWND hwnd ) DECLSPEC_HIDDEN;
static inline void mirror_rect( const RECT *window_rect, RECT *rect ) {
On 12/23/19 1:51 PM, Rémi Bernon wrote:
Commit 71d35d8940118bc6de6522913fb8c473fa5b2c24 broke the way WM_TAKE_FOCUS protocol is implemented: WM_MOUSEACTIVATE now replies MA_NOACTIVATE by default when using HTCAPTION.
We use the WM_MOUSEACTIVATE -although Windows does not- regardless of the way focus is changed to check whether a window wants focus, and Windows sometimes changes focus regardless of the message reply.
Steam and the Wine system tray are affected for instance.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com
With the previous patch, it's also possible to revert 71d35d8940118bc6de6522913fb8c473fa5b2c24, but the WM_MOUSEACTIVATE reply it changes is indeed correct.
dlls/winex11.drv/event.c | 2 +- dlls/winex11.drv/window.c | 2 +- dlls/winex11.drv/x11drv.h | 1 + 3 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c index 2649d561a6b..957d7c5efdf 100644 --- a/dlls/winex11.drv/event.c +++ b/dlls/winex11.drv/event.c @@ -714,7 +714,7 @@ static void handle_wm_protocols( HWND hwnd, XClientMessageEvent *event ) * whether the window wants to be activated */ LRESULT ma = SendMessageW( hwnd, WM_MOUSEACTIVATE, (WPARAM)GetAncestor( hwnd, GA_ROOT ),
MAKELONG(HTCAPTION,WM_LBUTTONDOWN) );
MAKELONG( HTMENU, WM_LBUTTONDOWN ) ); if (ma != MA_NOACTIVATEANDEAT && ma != MA_NOACTIVATE) { set_focus( event->display, hwnd, event_time, event->serial );
diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 99e4094ebd9..89ea42b7567 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -169,7 +169,7 @@ struct has_popup_result BOOL found; };
-static BOOL is_managed( HWND hwnd ) +BOOL is_managed( HWND hwnd ) { struct x11drv_win_data *data = get_win_data( hwnd ); BOOL ret = data && data->managed; diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index e9b827c7e9a..0e55e5115f8 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -607,6 +607,7 @@ extern void change_systray_owner( Display *display, Window systray_window ) DECL extern void update_systray_balloon_position(void) DECLSPEC_HIDDEN; extern HWND create_foreign_window( Display *display, Window window ) DECLSPEC_HIDDEN; extern BOOL update_clipboard( HWND hwnd ) DECLSPEC_HIDDEN; +extern BOOL is_managed( HWND hwnd ) DECLSPEC_HIDDEN;
static inline void mirror_rect( const RECT *window_rect, RECT *rect ) {
I forgot to cleanup the rest of the patch, sorry about that.