[Bug 60187] New: Ownerless layered popup windows are mapped override-redirect, so the window manager keeps them above every other window and on every virtual desktop
http://bugs.winehq.org/show_bug.cgi?id=60187 Bug ID: 60187 Summary: Ownerless layered popup windows are mapped override-redirect, so the window manager keeps them above every other window and on every virtual desktop Product: Wine Version: 11.15 Hardware: x86-64 OS: Linux Status: UNCONFIRMED Severity: normal Priority: P2 Component: winex11.drv Assignee: wine-bugs@list.winehq.org Reporter: Wagner.a18@yahoo.com Target Milestone: --- Distribution: --- `is_window_managed()` in `dlls/winex11.drv/window.c` classifies an ownerless `WS_POPUP` window carrying `WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE` as unmanaged, so winex11.drv maps it override-redirect. Override-redirect is the right choice for the transient UI it was designed for (menus, tooltips), but applications also use exactly these flags for long-lived windows — most notably custom frame decorations. Codejock Xtreme Toolkit Pro draws the drop shadow of its main frame into such a window (caption `XTPFrameShadow`), and it stays mapped for as long as the main frame is in its restored state. Because the window is override-redirect, the window manager cannot place it in the normal stacking order and cannot assign it a virtual desktop. The result is a window that: - floats above every other window on the screen, including windows of other applications, regardless of which window has focus - appears on every virtual desktop, while the main frame it belongs to stays on the one it was opened on On Windows the same window is an ordinary top-level window that participates normally in the Z order and disappears with the rest of the application. ## Steps to reproduce 1. Build the attached reproducer: `winegcc -mwindows -o repro repro.c -luser32 -lgdi32` 2. Run it: `wine repro.exe.so` 3. Give another application focus so that it overlaps the reproducer's main window 4. Switch to another virtual desktop ## Current behaviour The semi-transparent grey `ReproOverlay` rectangle stays on top of the other application, and it remains visible after switching virtual desktops. Walking the X window tree shows the overlay is override-redirect while the main window is not: 0x3c00001 592x366 +204+230 VIEWABLE ovr=0 repro main window 0x3c00004 620x420 +190+190 VIEWABLE ovr=1 ReproOverlay ## Expected behaviour The overlay should be stacked with its application and confined to the virtual desktop that application is on, i.e. it should be a managed window. With the patch below applied, the same reproducer maps the overlay as a managed window: 0x3a00001 592x366 +204+230 VIEWABLE ovr=0 repro main window 0x3a00004 620x420 +190+190 VIEWABLE ovr=0 ReproOverlay and the window manager now tracks it like any other window: ReproOverlay WM_STATE : Normal _NET_WM_DESKTOP : 0 _NET_WM_STATE : DEMANDS_ATTENTION, SKIP_TASKBAR, SKIP_PAGER, _KDE_NET_WM_STATE_SKIP_SWITCHER Note `_NET_WM_DESKTOP`, which an override-redirect window cannot carry at all - that is what confines the overlay to one virtual desktop. The skip states come from the existing `WS_EX_NOACTIVATE` handling, so the window does not show up in the taskbar or the window switcher. One observation: KWin also sets `_NET_WM_STATE_DEMANDS_ATTENTION` on the overlay, presumably because it is mapped without ever taking focus. With the three skip states set there is no UI element left for it to affect, so this appears harmless, but it is a behaviour difference worth mentioning. ## Analysis The window falls through every branch of `is_window_managed()`: - it is `WS_POPUP`, so the `WS_CHILD` early-out does not apply - it is shown with `SWP_NOACTIVATE` and is not the active window - it has neither `WS_CAPTION` nor `WS_THICKFRAME` - it is `WS_POPUP` but has no `WS_SYSMENU` and is not fullscreen - it has no `WS_EX_APPWINDOW` - it owns no popups so the function returns FALSE and the window is created with `attr.override_redirect = TRUE`. Observed flags of the real-world case (Codejock `XTPFrameShadow`, read with `GetWindowLongW` from a separate process attached to the same prefix): STYLE 0x94000000 : WS_POPUP WS_VISIBLE WS_CLIPSIBLINGS EXSTYLE 0x08080080 : WS_EX_TOOLWINDOW WS_EX_LAYERED WS_EX_NOACTIVATE owner : NULL ## Proposed patch Treat ownerless layered popups as managed unless they are click-through. `WS_EX_TRANSPARENT` is what separates this case from short-lived drag images, which do need to stay unmanaged. ```diff --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -461,6 +461,16 @@ static BOOL is_window_managed( HWND hwnd, UINT swp_flags, BOOL fullscreen ) /* application windows are managed */ ex_style = NtUserGetWindowLongW( hwnd, GWL_EXSTYLE ); if (ex_style & WS_EX_APPWINDOW) return TRUE; + /* Ownerless layered popups that aren't click-through belong to applications + * drawing their own frame decorations - Codejock's XTPFrameShadow puts the + * drop shadow of its main frame into one. They live as long as the frame + * does, so leaving them unmanaged makes the window manager stack them in + * its override-redirect layer, above every other window and on every + * virtual desktop. Click-through ones (WS_EX_TRANSPARENT) are short-lived + * drag images and have to stay unmanaged. */ + if ((style & WS_POPUP) && !NtUserGetWindowRelative( hwnd, GW_OWNER ) && + (ex_style & (WS_EX_LAYERED | WS_EX_TRANSPARENT)) == WS_EX_LAYERED) + return TRUE; /* windows that own popups are managed */ if (has_owned_popups( hwnd )) return TRUE; /* default: not managed */ ``` No decoration or taskbar regression is expected for such windows: `get_mwm_decorations_for_style()` already returns 0 for both `WS_EX_TOOLWINDOW` and `WS_EX_LAYERED`, and `WS_EX_NOACTIVATE` already causes `SKIP_TASKBAR | SKIP_PAGER | SKIP_SWITCHER` to be set. The heuristic is open to discussion — the point of the report is the mapping decision, not this particular discriminator. ## Environment - Wine 11.15 - KDE Plasma / KWin on Wayland, application running through XWayland - Only verified against KWin. The mapping decision itself is WM-independent, but the precise stacking and virtual-desktop consequences are KWin's behaviour for override-redirect windows. ## Reproducer ```c /* winegcc -mwindows -o repro repro.c -luser32 -lgdi32 */ #include <windows.h> static LRESULT CALLBACK proc( HWND h, UINT m, WPARAM w, LPARAM l ) { if (m == WM_DESTROY) { PostQuitMessage( 0 ); return 0; } if (m == WM_PAINT) { PAINTSTRUCT ps; HDC dc = BeginPaint( h, &ps ); FillRect( dc, &ps.rcPaint, (HBRUSH)GetStockObject( GRAY_BRUSH ) ); EndPaint( h, &ps ); return 0; } return DefWindowProcA( h, m, w, l ); } int WINAPI WinMain( HINSTANCE inst, HINSTANCE prev, char *cmd, int show ) { WNDCLASSA c = { 0 }; HWND main_wnd, overlay; MSG msg; c.lpfnWndProc = proc; c.hInstance = inst; c.hCursor = LoadCursorA( NULL, (LPCSTR)IDC_ARROW ); c.lpszClassName = "ReproMain"; RegisterClassA( &c ); c.lpszClassName = "ReproOverlay"; RegisterClassA( &c ); main_wnd = CreateWindowExA( 0, "ReproMain", "repro main window", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 200, 200, 600, 400, NULL, NULL, inst, NULL ); /* Same flags Codejock's XTPFrameShadow uses for the frame drop shadow. * No owner, and deliberately no WS_EX_TRANSPARENT. */ overlay = CreateWindowExA( WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, "ReproOverlay", "ReproOverlay", WS_POPUP, 190, 190, 620, 420, NULL, NULL, inst, NULL ); SetLayeredWindowAttributes( overlay, 0, 128, LWA_ALPHA ); SetWindowPos( overlay, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW ); while (GetMessageA( &msg, NULL, 0, 0 )) { TranslateMessage( &msg ); DispatchMessageA( &msg ); } return 0; } ``` -- 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