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 | 106 ++++++++++++++++++++----- dlls/winewayland.drv/waylanddrv.h | 1 + dlls/winewayland.drv/window.c | 93 +++++++++++++++++++--- 3 files changed, 169 insertions(+), 31 deletions(-) diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 3ba630904cf..b9d8ad21b3e 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -326,6 +326,10 @@ static const struct zxdg_exported_v2_listener zxdg_exported_v2_listener = { 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); @@ -347,6 +351,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) { @@ -441,6 +455,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); switch (surface->role) @@ -449,17 +474,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( @@ -636,6 +650,9 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface { RECT rect; + /* 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 | @@ -792,24 +809,45 @@ static void wayland_surface_reconfigure_subsurface(struct wayland_surface *surfa struct wayland_surface *toplevel_surface; int local_x, local_y, x, y; + /* Gaurd: 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 && (toplevel_data = wayland_win_data_get_nolock(surface->toplevel_hwnd)) && (toplevel_surface = toplevel_data->wayland_surface)) { + /* Final Protocol Check: Ensure valid local target */ + if (!toplevel_surface->wl_surface) + { + memset(&surface->processing, 0, sizeof(surface->processing)); + return; + } + local_x = surface->window.rect.left - toplevel_surface->window.rect.left; local_y = surface->window.rect.top - toplevel_surface->window.rect.top; wayland_surface_coords_from_window(surface, local_x, local_y, &x, &y); - TRACE("hwnd=%p pos=%d,%d\n", surface->hwnd, x, y); + TRACE("hwnd=%p pos=%d,%d subsurface stacking guard active\n", surface->hwnd, x, y); - wl_subsurface_set_position(surface->wl_subsurface, x, y); - if (toplevel_data->client_surface) - wl_subsurface_place_above(surface->wl_subsurface, toplevel_data->client_surface->wl_surface); - else - wl_subsurface_place_above(surface->wl_subsurface, toplevel_surface->wl_surface); + if (surface->wl_subsurface) + { + wl_subsurface_set_position(surface->wl_subsurface, x, y); + + /* Guard: Place local subsurfaces relative only to local parents */ + if (toplevel_data->client_surface && toplevel_data->client_surface->wl_surface) + wl_subsurface_place_above(surface->wl_subsurface, toplevel_data->client_surface->wl_surface); + else + wl_subsurface_place_above(surface->wl_subsurface, toplevel_surface->wl_surface); + } + wl_surface_commit(toplevel_surface->wl_surface); - memset(&surface->processing, 0, sizeof(surface->processing)); } } @@ -824,12 +862,36 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) { struct wayland_window_config *window = &surface->window; int win_width, win_height, width, height; + BOOL zxdg_imported = (surface->zxdg_imported_v2 != NULL); - win_width = surface->window.rect.right - surface->window.rect.left; - win_height = surface->window.rect.bottom - surface->window.rect.top; + /* 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)) + { + TRACE("Trapped overlay %p off-screen; forcing 1x1 geometry to sustain frame callbacks\n", surface->hwnd); + width = 1; + height = 1; - wayland_surface_coords_from_window(surface, win_width, win_height, - &width, &height); + wayland_surface_reconfigure_xdg(surface, width, height); + wayland_surface_reconfigure_size(surface, width, height); + 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; + } + + win_width = window->rect.right - window->rect.left; + win_height = window->rect.bottom - window->rect.top; + + wayland_surface_coords_from_window(surface, win_width, win_height, &width, &height); TRACE("hwnd=%p window=%dx%d,%#x processing=%dx%d,%#x current=%dx%d,%#x\n", surface->hwnd, win_width, win_height, window->state, diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index a64a52274bd..853ff4b599d 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -280,6 +280,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 f7b10eef58e..55bae9e4a56 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -258,23 +258,39 @@ 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 (toplevel_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 (toplevel_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) { - if (client) wayland_client_surface_attach(client, NULL); - wayland_surface_destroy(data->wayland_surface); - data->wayland_surface = NULL; + /* 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 + { + if (client) wayland_client_surface_attach(client, NULL); + wayland_surface_destroy(data->wayland_surface); + data->wayland_surface = NULL; + } } if (!(surface = data->wayland_surface) && !(surface = wayland_surface_create(data->hwnd))) return FALSE; @@ -358,8 +374,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; @@ -369,6 +389,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: @@ -383,7 +458,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