[PATCH 0/1] MR10133: winex11.drv: Do not set the pending NET_ACTIVE_WINDOW when we're skipping it.
From: Gabriel Ivăncescu <gabrielopcode@gmail.com> When the window is created unmapped (on X11 side), and receives focus which is set correctly on the win32 side, it can receive a NET_ACTIVE_WINDOW before it is mapped. This leads to set_net_active_window being skipped with the WARN, even though we set the pending_state to the window. Later when it is mapped, set_net_active_window will return early because of pending_state's net_active_window being set to the same window, even though we actually skipped it. Fixes a regression starting with 0dc7e4046836595335cd64c92cc132791d78fba4 (although it's not fixed by reverting its logic now). Signed-off-by: Gabriel Ivăncescu <gabrielopcode@gmail.com> --- dlls/winex11.drv/window.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index a9f6dd08276..26cb17a2c77 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -2056,7 +2056,6 @@ void set_net_active_window( HWND hwnd, HWND previous ) xev.xclient.data.l[3] = 0; xev.xclient.data.l[4] = 0; - data->pending_state.net_active_window = window; data->net_active_window_serial = NextRequest( data->display ); if (withdrawn) @@ -2066,6 +2065,7 @@ void set_net_active_window( HWND hwnd, HWND previous ) XNoOp( data->display ); return; } + data->pending_state.net_active_window = window; TRACE( "requesting _NET_ACTIVE_WINDOW %p/%lx serial %lu\n", hwnd, window, data->net_active_window_serial ); XSendEvent( data->display, DefaultRootWindow( data->display ), False, -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10133
Why doesn't the window get activated when being mapped? This should be the case unless we request it not to be, because of SWP_NOACTIVATE, in which case it probably shouldn't anyway? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10133#note_129908
On Wed Feb 18 16:39:51 2026 +0000, Rémi Bernon wrote:
Why doesn't the window get activated when being mapped? This should be the case unless we request it not to be, because of SWP_NOACTIVATE, in which case it probably shouldn't anyway? Activated where? Isn't that what set_net_active_window is supposed to do?
The problem is that the pending state is supposed to represent a pending operation we already did, right? But we never did it, because we skipped it (and just did a XNoOp instead), so exiting early in such case doesn't make sense. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10133#note_129909
On Wed Feb 18 16:39:50 2026 +0000, Gabriel Ivăncescu wrote:
Activated where? Isn't that what set_net_active_window is supposed to do? The problem is that the pending state is supposed to represent a pending operation we already did, right? But we never did it, because we skipped it (and just did a XNoOp instead), so exiting early in such case doesn't make sense. Windows are activated automatically when they are being mapped, if their WM_HINTS input field is 1 or offered activation through WM_TAKE_FOCUS if they indicate support for it (which is essentially the same, only that with WM_TAKE_FOCUS we have an additional option to accept or refuse activation), unless their _NET_WM_USER_TIME property is set to 0.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10133#note_129910
The problem is that the pending state is supposed to represent a pending operation we already did, right? But we never did it, because we skipped it (and just did a XNoOp instead), so exiting early in such case doesn't make sense.
Kind of, yeah, but the same is true for the serial, and the XNoOp is assuming that the window is soon mapped and therefore activated. This was mostly an attempt at fixing some issue with X11 WM getting confused about the request on an unmapped window. I'm not even sure it actually worked, it was for https://bugs.winehq.org/show_bug.cgi?id=58505 but didn't fully fix it, and it's possible I misdiagnosed the problem. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10133#note_129911
On Wed Feb 18 16:46:36 2026 +0000, Rémi Bernon wrote:
The problem is that the pending state is supposed to represent a pending operation we already did, right? But we never did it, because we skipped it (and just did a XNoOp instead), so exiting early in such case doesn't make sense. Kind of, yeah, but the same is true for the serial, and the XNoOp is assuming that the window is soon mapped and therefore activated. This was mostly an attempt at fixing some issue with X11 WM getting confused about the request on an unmapped window. I'm not even sure it actually worked, it was for https://bugs.winehq.org/show_bug.cgi?id=58505 but didn't fully fix it, and it's possible I misdiagnosed the problem. From what I read, windows are *not* activated automatically. They receive events (ClientMessage with WM_TAKE_FOCUS, or _NET_WM_STATE, etc) and that's when they're supposed to let the WM know they want to get focus/be activated or not.
And that's exactly the part that fails, because we see it's "pending" even though it's not and we return early, never letting it know we want to be activated. If we remove the entire skipping logic, then it fails for other reasons so that's not a good idea either. I then get stuff like: warn:event:handle_wm_protocols Ignoring window 0x200a4/7400001 WM_TAKE_FOCUS serial 171, event_time 5459169, foreground 0x200a4 during WM_STATE change But the point is that the pending_state should be set when we actually have a pending operation on it, not a no-op. I don't understand what sense does it make to have the pending state set to something that we haven't actually requested (and thus isn't pending)??? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10133#note_129965
On Thu Feb 19 15:36:44 2026 +0000, Gabriel Ivăncescu wrote:
From what I read, windows are *not* activated automatically. They receive events (ClientMessage with WM_TAKE_FOCUS, or _NET_WM_STATE, etc) and that's when they're supposed to let the WM know they want to get focus/be activated or not. And that's exactly the part that fails, because we see it's "pending" even though it's not and we return early, never letting it know we want to be activated. If we remove the entire skipping logic, then it fails for other reasons so that's not a good idea either. I then get stuff like: warn:event:handle_wm_protocols Ignoring window 0x200a4/7400001 WM_TAKE_FOCUS serial 171, event_time 5459169, foreground 0x200a4 during WM_STATE change But the point is that the pending_state should be set when we actually have a pending operation on it, not a no-op. I don't understand what sense does it make to have the pending state set to something that we haven't actually requested (and thus isn't pending)??? I forgot to mention it happens with just spawning winecfg, simplest testcase possible. It doesn't receive focus. According to ICCCM, the WM does not unilaterally activate a client window on map without the client's participation when WM_TAKE_FOCUS or similar semantics apply.
And _NET_ACTIVE_WINDOW (which is what is needed) is not assumed explicitly and has to be sent manually, but that never happens since we return early. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10133#note_129966
On Thu Feb 19 15:42:17 2026 +0000, Gabriel Ivăncescu wrote:
I forgot to mention it happens with just spawning winecfg, simplest testcase possible. It doesn't receive focus. According to ICCCM, the WM does not unilaterally activate a client window on map without the client's participation when WM_TAKE_FOCUS or similar semantics apply. And _NET_ACTIVE_WINDOW (which is what is needed) is not assumed explicitly and has to be sent manually, but that never happens since we return early. `WM_TAKE_FOCUS` reply is delayed when there's a `WM_STATE` change pending. We expect to receive the `WM_STATE` PropertyNotify before we can decide whether to accept or not focus, because it's otherwise possible to have multiple `WM_STATE` changes pending in sequence (either for one window that gets mapped/minimized/mapped in short sequence, or because another window is being mapped at the same time), and we need that sequence to complete before we decide which one wins the focus race or we may trigger more race conditions.
This is not delayed wrt. the _NET_ACTIVE_WINDOW requests, so changing the pending_state.active_window or its serial isn't going to make any difference there. The issue might be that the WM sends `WM_TAKE_FOCUS` first, before actually changing the `WM_STATE` property, and doesn't send more `WM_TAKE_FOCUS` requests after that? Note that `handle_wm_state_notify` also tries to explicitly activate the window again for cases like this, so it's unclear why this doesn't work for you. Fwiw window activation works fine with every WM I have tried, Mutter, Kwin, Openbox, Fvwm.
But the point is that the pending_state should be set when we actually have a pending operation on it, not a no-op. I don't understand what sense does it make to have the pending state set to something that we haven't actually requested (and thus isn't pending)???
Sure, it's a bit weird, but that is meant to work with the GetWindowStateUpdates side or things, to delay changes that would make Win32 active window match the X11 one, if we have just requested explicit window activation, or if we're about to map a window. In the latter case ActivateWindow is called before the WindowPosChanged call that will trigger the mapping of the window, this is a bit unfortunate and could perhaps be changed to make it less awkward but it's a bit delicate to get it right. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10133#note_129967
On Thu Feb 19 16:05:32 2026 +0000, Rémi Bernon wrote:
`WM_TAKE_FOCUS` reply is delayed when there's a `WM_STATE` change pending. We expect to receive the `WM_STATE` PropertyNotify before we can decide whether to accept or not focus, because it's otherwise possible to have multiple `WM_STATE` changes pending in sequence (either for one window that gets mapped/minimized/mapped in short sequence, or because another window is being mapped at the same time), and we need that sequence to complete before we decide which one wins the focus race or we may trigger more race conditions. This is not delayed wrt. the _NET_ACTIVE_WINDOW requests, so changing the pending_state.active_window or its serial isn't going to make any difference there. The issue might be that the WM sends `WM_TAKE_FOCUS` first, before actually changing the `WM_STATE` property, and doesn't send more `WM_TAKE_FOCUS` requests after that? Note that `handle_wm_state_notify` also tries to explicitly activate the window again for cases like this, so it's unclear why this doesn't work for you. Fwiw window activation works fine with every WM I have tried, Mutter, Kwin, Openbox, Fvwm.
But the point is that the pending_state should be set when we actually have a pending operation on it, not a no-op. I don't understand what sense does it make to have the pending state set to something that we haven't actually requested (and thus isn't pending)??? Sure, it's a bit weird, but that is meant to work with the GetWindowStateUpdates side or things, to delay changes that would make Win32 active window match the X11 one, if we have just requested explicit window activation, or if we're about to map a window. In the latter case ActivateWindow is called before the WindowPosChanged call that will trigger the mapping of the window, this is a bit unfortunate and could perhaps be changed to make it less awkward but it's a bit delicate to get it right. Yes, `handle_wm_state_notify` is exactly what is calling `set_net_active_window` here, with activate set to TRUE / 1. And it doesn't work because of the reason I explained: it returns early since it sees the pending state net_active_window match the one we "skipped" because it was unmapped. I mean this line:
if (data->pending_state.net_active_window == window) return; how can it possibly work then? I've tested this on compiz where it has this problem. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10133#note_129974
On Thu Feb 19 17:06:11 2026 +0000, Gabriel Ivăncescu wrote:
Yes, `handle_wm_state_notify` is exactly what is calling `set_net_active_window` here, with activate set to TRUE / 1. And it doesn't work because of the reason I explained: it returns early since it sees the pending state net_active_window match the one we "skipped" because it was unmapped. I mean this line: if (data->pending_state.net_active_window == window) return; how can it possibly work then? I've tested this on compiz where it has this problem. I see. Well it seems that other WMs only offer activation *after* sending the WM_STATE update, which kind of makes sense IMO: no good reason for clients that aren't aware of a window being mapped to expect it to be activated either.
I'm not completely sure how to fix it, it's a very brittle assembly and changes are often causing more issues than they fix. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10133#note_129978
On Thu Feb 19 17:13:56 2026 +0000, Rémi Bernon wrote:
I see. Well it seems that other WMs only offer activation *after* sending the WM_STATE update, which kind of makes sense IMO: no good reason for clients that aren't aware of a window being mapped to expect it to be activated either. I'm not completely sure how to fix it, it's a very brittle assembly and changes are often causing more issues than they fix. What's exactly wrong with this patch's fix though? If we skip the activation (which is fine), shouldn't we later activate it if needed?
I just fail to see a justification for the pending_state's net_active_window to be set to a window which we did not explicitly sent _NET_ACTIVE_WINDOW to. Can you give an example sequence where the current logic is good and this patch fails? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10133#note_129979
This merge request was approved by Rémi Bernon. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10133
participants (3)
-
Gabriel Ivăncescu -
Gabriel Ivăncescu (@insn) -
Rémi Bernon (@rbernon)