[Bug 60171] New: A click that falls through a native-surface-less active top-level window incorrectly deactivates it
http://bugs.winehq.org/show_bug.cgi?id=60171 Bug ID: 60171 Summary: A click that falls through a native-surface-less active top-level window incorrectly deactivates it Product: Wine Version: 11.14 Hardware: x86-64 OS: Linux Status: UNCONFIRMED Keywords: patch Severity: normal Priority: P2 Component: win32u Assignee: wine-bugs@list.winehq.org Reporter: devel@velmeden.info Target Milestone: --- Distribution: ArchLinux Created attachment 81813 --> http://bugs.winehq.org/attachment.cgi?id=81813 Patch *I ran into this bug but could not find or reproduce the underlying cause myself, so I investigated it together with an AI assistant. The text and patch below is written by the AI, based on that interactive troubleshooting session.* ## Summary `process_mouse_message()` (`dlls/win32u/message.c`) reactivates whatever top-level window a click's X11 event resolves to, and deactivates the previously-active window as a side effect, whenever the clicked window differs from the currently active one. This is correct when the click is a genuine user action on a different window. It is wrong when the currently active top-level window has no native (X11) surface of its own: any click physically within that window's on-screen bounds can only ever be delivered by the X server to whatever real window happens to be underneath or around it (typically an ancestor), since the active window itself has nothing on screen to receive input. Wine currently can't tell these two cases apart, so it deactivates a window the user visually clicked on, purely because of how the click was routed at the X11 level. ## Why a top-level window can have no native surface Some applications create ordinary (non-`WS_CHILD`) top-level windows whose visible content is presented through some other mechanism than Wine's own X11 "whole window" — for example, a toolkit-hosted child surface embedded inside a native host window, where the toolkit's own window/surface object is what's actually on screen, while the win32-visible `HWND` exists purely for message routing, focus and activation bookkeeping. In such a case `X11DRV_get_whole_window(hwnd)` legitimately returns `0` for that `HWND` even though it is a real, activatable top-level window as far as the win32 window manager is concerned. ## Observed effect I diagnosed this on Wine 11.14 against Loxone Config (an MFC + Qt6 hybrid application), whose MDI document window is exactly such a case: its X11 "whole window" is destroyed early on and never recreated for the rest of the session (confirmed structural, not a transient timing gap — traced via `create_whole_window()`/`destroy_whole_window()` instrumentation across a full session), while Wine's own bookkeeping still tracks it as the active top-level window (`NtUserGetForegroundWindow()`/`info.hwndActive`). Any real mouse click physically over that window's on-screen area resolves, at the raw X11 level, to whichever window is actually behind it (confirmed via instrumented `X11DRV_ButtonPress` logging the raw event's `window`/`subwindow` fields together with each window's actual X11 id — the click's `ButtonPress` event targets the ancestor's real X11 window, never the document window's, because the latter doesn't have one). `process_mouse_message()` then runs its normal `WM_MOUSEACTIVATE`/`set_foreground_window()`/`set_active_window()` cascade for "a click on a different top-level window", which sends `WM_ACTIVATE(WA_INACTIVE)` to the window that was actually still active and that the click was visually intended for. Any interaction state that window's application logic keeps tied to activation (for example, a pending drag-and-drop-style operation initiated by an earlier click) gets torn down as a result, even though the user's click landed squarely inside that window's visible bounds. This does not require the click to race anything — in the traced case, the affected window had already been without a native surface for ~30 seconds before the failing click, so this is a deterministic misclassification, not a timing-sensitive race. ## Steps to reproduce 1. Download Loxone Config: https://updatefiles.loxone.com/LoxConfig/LoxoneConfigSetup_17010727.zip 2. Unzip it and run the installer under Wine: `wine LoxoneConfigSetup.exe` (click through the installer normally). 3. Launch `LoxoneConfig.exe` and open or create any project. 4. Pick any block/object to add, either via the search dialog (press `F5` to open it, type e.g. `RS` to find the "RS Flipflop" block, and pick it from the results) or by dragging one of the toolbar symbols — either way it attaches to the mouse cursor as a drag preview. 5. Move the mouse over the project canvas and click to place it. Expected: the block is placed on the canvas. Actual: the drag preview disappears on mouse-down, but no block is actually added — no visible change, and `Edit` → `Undo` stays greyed out, confirming the placement was never registered. This is 100% reproducible. ## Fix Attached patch adds a `WindowHasNativeSurface(HWND) -> BOOL` user driver entry point (default `TRUE`; implemented for `winex11.drv` via the existing `X11DRV_get_whole_window()` helper, other drivers unaffected). In `process_mouse_message()`, before running the reactivation cascade for a click that resolves to a different top-level window, check whether the window Wine still considers active has no native surface and geometrically contains the click point; if so, treat this as a click-through rather than a genuine activation change, and skip the cascade. The check is narrowly scoped: it does not redirect the click itself, does not touch window-surface creation/lifecycle, and every other click-resolution/activation path (including everything on drivers other than `winex11.drv`, where the new hook always returns `TRUE`) is unaffected. ## Testing - Live-validated against the real application described above: prior to the fix, the described interaction failed 100% of the time; after the fix, the specific click-misrouting-to-an-ancestor mechanism no longer tears down the document window's activation state (confirmed via the same gdb instrumentation, both on a debug and on a release (`-O2`) Wine build). - `dlls/user32/tests/msg.c` and `dlls/user32/tests/input.c` run clean (via an isolated `Xephyr` X server, since the interactive desktop proved too environment-sensitive for a byte-accurate baseline comparison): no activation/`WM_ACTIVATE`/`WM_MOUSEACTIVATE`-related failures attributable to the patch across multiple runs. ## Known limitation of this report I don't yet have a minimal, app-independent C reproduction case for this specific mechanism (a separate, previously-considered mechanism for the same user-facing symptom turned out, on later investigation, to be a distinct and still-open issue in the same application, unrelated to this patch). I'm glad to build one (the core ingredients — a top-level window with no native surface set as active, and a second, real window overlapping its screen area — should be reproducible with plain Win32 API plus one Wine-internal step to force the surface-less state) if that would help review; in the meantime the patch was developed and validated against a live repro that exhibits the exact structural condition described above, with full gdb-level traces at each step (raw X11 event routing, `set_active_window()` call arguments, and whole-window create/destroy lifecycle) establishing the mechanism precisely rather than by inference. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60171 Benjamin (VBen) <devel@velmeden.info> changed: What |Removed |Added ---------------------------------------------------------------------------- Keywords| |testcase --- Comment #1 from Benjamin (VBen) <devel@velmeden.info> --- Proof of concept: attached `surfaceless-activation-repro.c` is a minimal, app-independent Win32 program (no MFC/Qt, no Loxone Config needed) that reproduces this mechanism directly. What it does: - Creates two ordinary top-level windows, `FRAME` (stays on screen) and `DOC` (made the active/foreground window). - Destroys `DOC`'s X11 "whole window" via `SetParent(DOC, HELPER)`, where `HELPER` is a third, unrelated window that's never shown. `dlls/winex11.drv/window.c`'s `X11DRV_SetParent()` tears down the whole window here even though `DOC` keeps its ordinary `WS_OVERLAPPEDWINDOW` style and win32u still treats it as an independently activatable top-level window -- exactly the win32u/X11-surface mismatch this bug hinges on, just triggered directly instead of through an app's own internal window-hosting choices. - Sets a flag simulating an in-progress, activation-sensitive operation (standing in for the real app's pending drag-and-drop placement). - Fires one real `SendInput` click at a screen point inside `DOC`'s bounds. Since `DOC` has no X11 surface there anymore, the click can only physically land on `FRAME`. Results (6 runs each, same tree, toggling the attached patch): - Unpatched (commit `1012f3d`, tagged `wine-11.14`): 6/6 reproduce the bug -- `DOC` receives `WM_ACTIVATE(WA_INACTIVE)`, the simulated pending operation is torn down, and `FRAME` becomes active with `WA_CLICKACTIVE`, the same activation-cause code seen in the real Loxone Config failure. - Patched: 6/6 pass -- `DOC` keeps its activation state and the pending operation survives, even though the click still physically falls through to `FRAME`. Paired logs from one representative run of each attached (`surfaceless-activation-repro-unpatched.log`, `surfaceless-activation-repro-patched.log`). -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60171 --- Comment #2 from Benjamin (VBen) <devel@velmeden.info> --- Created attachment 81832 --> http://bugs.winehq.org/attachment.cgi?id=81832 code to reproduce -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60171 --- Comment #3 from Benjamin (VBen) <devel@velmeden.info> --- Created attachment 81833 --> http://bugs.winehq.org/attachment.cgi?id=81833 reproduction log unpatched -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60171 --- Comment #4 from Benjamin (VBen) <devel@velmeden.info> --- Created attachment 81834 --> http://bugs.winehq.org/attachment.cgi?id=81834 reproduction log patched -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
participants (1)
-
WineHQ Bugzilla