From: Namo Nath <nn.git@tuta.io> Win32 overlay applications often execute rapid SW_HIDE/SW_SHOW toggles, minimize off-screen, and initialize without an explicit GW_OWNER. Under Wayland, these actions sever zxdg_foreign parent bonds and permanently suspend render loops. This isolates Wayland protocol integrity from Win32 state fluctuations: - Role Integrity: Gate role clearance for overlays to prevent the compositor from severing parent bonds during visibility toggles. - Absolute Teardown: Move zxdg_exported/imported handle destruction to final surface teardown, allowing protocol bonds to survive SW_HIDE. - Render Loop Preservation: Force 1x1 geometry at the origin when imported overlays attempt off-screen (-32000) minimization. This prevents Wayland from withholding frame callbacks, which would otherwise freeze the client. - Latched Dynamic Parenting: Gated behind explicit DWM requests, dynamically infer and latch the parent window via Z-order sniffing (GW_HWNDNEXT) to safely attach orphaned overlays without asynchronous race conditions. - Subsurface Stacking Guard: Bypass local wl_subsurface stacking logic for zxdg_imported surfaces to prevent null-object protocol violations. --- dlls/winewayland.drv/wayland_surface.c | 98 ++++++++++++++++++++----- dlls/winewayland.drv/waylanddrv.h | 1 + dlls/winewayland.drv/window.c | 99 ++++++++++++++++++++++---- 3 files changed, 169 insertions(+), 29 deletions(-) diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 10c1606d8f4..4e01066956b 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -407,6 +407,8 @@ void wayland_surface_make_toplevel(struct wayland_surface *surface) WCHAR text[1024]; HWND owner_hwnd; WCHAR prop_name[] = {'W','a','y','l','a','n','d','H','a','n','d','l','e',0}; + DWORD ex_style = NtUserGetWindowLongW(surface->hwnd, GWL_EXSTYLE); + BOOL layered_transparent = (ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT); TRACE("surface=%p\n", surface); @@ -428,6 +430,16 @@ void wayland_surface_make_toplevel(struct wayland_surface *surface) /* CROSS-PROCESS HIERARCHY: xdg_foreign Import / Export Routing */ owner_hwnd = NtUserGetWindowRelative(surface->hwnd, GW_OWNER); + if (layered_transparent && !owner_hwnd) + { + /* Fallback heuristic for unowned overlays */ + owner_hwnd = NtUserGetForegroundWindow(); + if (owner_hwnd == surface->hwnd || !owner_hwnd) + { + owner_hwnd = NtUserGetWindowRelative(surface->hwnd, GW_HWNDNEXT); + } + } + /* Restore global import/export for all standard owned windows and overlays */ if (owner_hwnd && process_wayland.zxdg_importer_v2) { @@ -526,6 +538,17 @@ err: */ void wayland_surface_clear_role(struct wayland_surface *surface) { + DWORD ex_style = NtUserGetWindowLongW(surface->hwnd, GWL_EXSTYLE); + BOOL layered_transparent = (ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT); + + /* Prevent role destruction for overlays during Win32 HIDE/SHOW toggles. + * This keeps the zxdg_foreign parent relationship and wl_surface alive. */ + if (layered_transparent && surface->role != WAYLAND_SURFACE_ROLE_NONE) + { + TRACE("Gating role clearance for overlay surface %p to preserve parent bond\n", surface); + return; + } + TRACE("surface=%p\n", surface); /* some objects are shared between several roles */ @@ -542,17 +565,6 @@ void wayland_surface_clear_role(struct wayland_surface *surface) break; case WAYLAND_SURFACE_ROLE_TOPLEVEL: - if (surface->zxdg_exported_v2) - { - zxdg_exported_v2_destroy(surface->zxdg_exported_v2); - surface->zxdg_exported_v2 = NULL; - } - if (surface->zxdg_imported_v2) - { - zxdg_imported_v2_destroy(surface->zxdg_imported_v2); - surface->zxdg_imported_v2 = NULL; - } - if (surface->xdg_toplevel_icon) { xdg_toplevel_icon_manager_v1_set_icon( @@ -728,6 +740,12 @@ static void wayland_surface_get_rect_in_monitor(struct wayland_surface *surface, */ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface, RECT rect) { + int width = rect.right - rect.left; + int height = rect.bottom - rect.top; + + /* Guard against invalid geometry dimensions causing xdg protocol violations */ + if (width <= 0 || height <= 0) return; + /* If the window size is bigger than the current state accepts, use the * largest visible (from Windows' perspective) subregion of the window. */ if ((surface->current.state & (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED | @@ -881,6 +899,15 @@ static void wayland_surface_reconfigure_subsurface(struct wayland_surface *surfa struct wayland_win_data *owner_data; struct wayland_surface *owner_surface; + /* Guard: If this surface is associated with an imported handle + * it is strictly a Toplevel client. Any attempt to use subsurface + * stacking protocols is a fatal violation. */ + if (surface->zxdg_imported_v2 || surface->zxdg_exported_v2) + { + memset(&surface->processing, 0, sizeof(surface->processing)); + return; + } + if (!surface->processing.serial || !surface->processing.processed) return; if (!(owner_data = wayland_win_data_get(surface->owner_hwnd))) return; @@ -888,18 +915,31 @@ static void wayland_surface_reconfigure_subsurface(struct wayland_surface *surfa { RECT rect = surface->window.rect; + /* Final Protocol Check: Ensure valid local target */ + if (!owner_surface->wl_surface) + { + memset(&surface->processing, 0, sizeof(surface->processing)); + wayland_win_data_release(owner_data); + return; + } + OffsetRect(&rect, -owner_surface->window.rect.left, -owner_surface->window.rect.top); rect = map_rect_to_surface(surface, rect); TRACE("hwnd=%p rect=%s\n", surface->hwnd, wine_dbgstr_rect(&rect)); - wl_subsurface_set_position(surface->wl_subsurface, rect.left, rect.top); - if (owner_data->client_surface && owner_data->client_surface->wl_subsurface) - wl_subsurface_place_above(surface->wl_subsurface, owner_data->client_surface->wl_surface); - else - wl_subsurface_place_above(surface->wl_subsurface, owner_surface->wl_surface); - wl_surface_commit(owner_surface->wl_surface); + if (surface->wl_subsurface) + { + wl_subsurface_set_position(surface->wl_subsurface, rect.left, rect.top); + + /* Guard: Place local subsurfaces relative only to local parents */ + if (owner_data->client_surface && owner_data->client_surface->wl_surface) + wl_subsurface_place_above(surface->wl_subsurface, owner_data->client_surface->wl_surface); + else + wl_subsurface_place_above(surface->wl_subsurface, owner_surface->wl_surface); + } + wl_surface_commit(owner_surface->wl_surface); memset(&surface->processing, 0, sizeof(surface->processing)); } @@ -916,6 +956,30 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) { struct wayland_window_config *window = &surface->window; RECT rect = map_rect_to_surface(surface, surface->window.rect); + BOOL zxdg_imported = (surface->zxdg_imported_v2 != NULL); + + /* PRESERVE RENDER LOOP: Do not attach a NULL buffer for off-screen overlays. + * Wayland compositors suspend frame callbacks for unmapped surfaces, which + * permanently freezes the overlay's render loop. Force 1x1 geometry instead. */ + if (zxdg_imported && (window->rect.left <= -32000 || window->rect.top <= -32000)) + { + RECT forced_rect = { 0, 0, 1, 1 }; + TRACE("Trapped overlay %p off-screen; forcing 1x1 geometry to sustain frame callbacks\n", surface->hwnd); + + wayland_surface_reconfigure_xdg(surface, forced_rect); + wayland_surface_reconfigure_size(surface, 1, 1); + return TRUE; + } + + /* Standard unmap behavior for normal applications */ + if (window->rect.left <= -32000 || window->rect.top <= -32000) + { + TRACE("Trapped off-screen position %d,%d for %p; unmapping Wayland surface\n", + window->rect.left, window->rect.top, surface->hwnd); + wl_surface_attach(surface->wl_surface, NULL, 0, 0); + wl_surface_commit(surface->wl_surface); + return TRUE; + } TRACE("hwnd=%p window=%s,%#x processing=%s,%#x current=%s,%#x\n", surface->hwnd, wine_dbgstr_rect(&rect), window->state, diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 22b283bd6fe..0b3a74937e5 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -291,6 +291,7 @@ struct wayland_shm_buffer struct wayland_surface { HWND hwnd; + HWND dynamic_owner; /* Inferred parent for overlays lacking GW_OWNER */ struct wl_surface *wl_surface; struct wp_viewport *wp_viewport; diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index d7294c323e1..62400244ca2 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -242,26 +242,42 @@ static BOOL wayland_win_data_create_wayland_surface(struct wayland_win_data *dat struct wayland_surface *surface; enum wayland_surface_role role; BOOL visible; - DWORD exstyle = NtUserGetWindowLongW(data->hwnd, GWL_EXSTYLE); + DWORD style = NtUserGetWindowLongW(data->hwnd, GWL_STYLE); + DWORD ex_style = NtUserGetWindowLongW(data->hwnd, GWL_EXSTYLE); + BOOL layered_transparent = (ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT); TRACE("hwnd=%p\n", data->hwnd); - visible = ((NtUserGetWindowLongW(data->hwnd, GWL_STYLE) & WS_VISIBLE) == WS_VISIBLE) && - (!(exstyle & WS_EX_LAYERED) || data->layered_attribs_set); + visible = ((style & WS_VISIBLE) == WS_VISIBLE) || layered_transparent; + + if (!layered_transparent) + visible = visible && (!(ex_style & WS_EX_LAYERED) || data->layered_attribs_set); if (!visible) role = WAYLAND_SURFACE_ROLE_NONE; - else if (owner_surface) role = WAYLAND_SURFACE_ROLE_SUBSURFACE; + /* GATE: Overlays MUST remain TOPLEVEL to sustain the zxdg_foreign handle. + * Never allow a downgrade to SUBSURFACE even if Win32 parenting suggests it. */ + else if (owner_surface && !layered_transparent) role = WAYLAND_SURFACE_ROLE_SUBSURFACE; else role = WAYLAND_SURFACE_ROLE_TOPLEVEL; /* we can temporarily clear the role of a surface but cannot assign a different one after it's set */ if ((surface = data->wayland_surface) && role && surface->role && surface->role != role) { - /* Make sure any attached client surface is detached before we destroy the surface. - * They will be reattached when win32u updates them again after WindowPosChanged. - */ - data->wayland_surface = NULL; - update_client_surfaces(data->hwnd); - wayland_surface_destroy(surface); + /* If an overlay already has a Toplevel role, refuse to clear it. + * This preserves the active parent bond during style/visibility toggles. */ + if (layered_transparent && surface->role == WAYLAND_SURFACE_ROLE_TOPLEVEL) + { + TRACE("Preserving established Toplevel role for overlay %p\n", data->hwnd); + role = WAYLAND_SURFACE_ROLE_TOPLEVEL; + } + else + { + /* Make sure any attached client surface is detached before we destroy the surface. + * They will be reattached when win32u updates them again after WindowPosChanged. + */ + data->wayland_surface = NULL; + update_client_surfaces(data->hwnd); + wayland_surface_destroy(surface); + } } if (!(surface = data->wayland_surface) && !(surface = wayland_surface_create(data->hwnd))) return FALSE; @@ -350,8 +366,12 @@ static void wayland_win_data_update_wayland_state(struct wayland_win_data *data) { struct wayland_surface *surface = data->wayland_surface; struct wayland_client_surface *client = data->client_surface; - DWORD ex_style = NtUserGetWindowLongW(data->hwnd, GWL_EXSTYLE); struct wl_region *opaque_region = NULL; + + DWORD ex_style = NtUserGetWindowLongW(data->hwnd, GWL_EXSTYLE); + BOOL layered = (ex_style & WS_EX_LAYERED) != 0; + BOOL layered_transparent = (ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT); + RECT rect; int center_w, center_h; @@ -361,6 +381,61 @@ static void wayland_win_data_update_wayland_state(struct wayland_win_data *data) break; case WAYLAND_SURFACE_ROLE_TOPLEVEL: if (!surface->xdg_surface) break; /* surface role has been cleared */ + + /* Dynamic Late-ARGB / Overlay Transition Handling + * Only execute for explicit DWM Glass requests */ + if (data->dwm_mode == WAYLAND_DWM_EXTEND_GLASS && !surface->zxdg_imported_v2 && process_wayland.zxdg_importer_v2) + { + HWND owner_hwnd = NtUserGetWindowRelative(surface->hwnd, GW_OWNER); + static int latch_retries = 0; + + /* Latch the target parent window */ + if (!owner_hwnd && latch_retries < 60) + { + if (!surface->dynamic_owner) + { + HWND next_hwnd = NtUserGetWindowRelative(surface->hwnd, GW_HWNDNEXT); + + /* Strict Guard: Must be a valid window, not ourselves, and not the desktop root */ + if (next_hwnd && next_hwnd != surface->hwnd && next_hwnd != NtUserGetDesktopWindow()) + { + surface->dynamic_owner = next_hwnd; + latch_retries = 0; + } + } + owner_hwnd = surface->dynamic_owner; + latch_retries++; + } + + if (owner_hwnd) + { + WCHAR prop_name[] = {'W','a','y','l','a','n','d','H','a','n','d','l','e',0}; + HANDLE prop = NtUserGetProp(owner_hwnd, prop_name); + if (prop) + { + RTL_ATOM atom = (RTL_ATOM)(ULONG_PTR)prop; + char *handle_str = get_global_atom_name(atom); + if (handle_str) + { + TRACE("LATCHED: Dynamically importing surface %p to parent %p (retry %d)\n", surface, owner_hwnd, latch_retries); + surface->zxdg_imported_v2 = zxdg_importer_v2_import_toplevel( + process_wayland.zxdg_importer_v2, handle_str); + zxdg_imported_v2_set_parent_of(surface->zxdg_imported_v2, surface->wl_surface); + free(handle_str); + + /* Force protocol state commit to map the overlay without requiring a geometry resize */ + wl_surface_commit(surface->wl_surface); + } + } + else if (latch_retries < 60) + { + /* ASYNC RACE FIX: The target exists, but hasn't exported its handle yet. + * Clear the latch so we try again on the next frame flush. */ + surface->dynamic_owner = NULL; + } + } + } + wayland_surface_update_state_toplevel(surface); break; case WAYLAND_SURFACE_ROLE_SUBSURFACE: @@ -375,7 +450,7 @@ static void wayland_win_data_update_wayland_state(struct wayland_win_data *data) /* GLOBAL SCISSOR: Opaque Region Calculation */ /* Full Surface Alpha: DWM Glass or explicit click-through overlays */ - if (data->dwm_mode == WAYLAND_DWM_EXTEND_GLASS || ((ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT))) + if (data->dwm_mode == WAYLAND_DWM_EXTEND_GLASS || layered || layered_transparent ) { opaque_region = NULL; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10180