From 490ecb34ff74e80562db35584b25acca7a732dd1 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Sat, 28 Mar 2026 06:34:15 +0100 Subject: [PATCH] =?UTF-8?q?wayland:=20Phase=2040=20=E2=80=94=20wire=20topl?= =?UTF-8?q?evel=20unmap=20to=20wUnmanageWindow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guard all dpy==NULL / frame==NULL crash sites in the unmanage path: - wUnmanageWindow (window.c): frame ptr set to NULL-safe expression; window_unmap, raise_delay timer, and owner border change all guarded on frame != NULL - wSelectWindow (actions.c): early return if !wwin->frame - wNETCleanupFrameExtents (wmspec.c): early return if !dpy - wClientRestore (client.c): early return if !dpy Wire handle_toplevel_unmap → context_find → wUnmanageWindow(destroyed=True) so XDG toplevels are removed from scr->focused_window on client disconnect. Smoke test: add Phase 40 check — grep "toplevel unmapped". Co-Authored-By: Claude Sonnet 4.6 --- src/actions.c | 3 +++ src/backend/wayland/wl_backend.c | 13 +++++++++++-- src/client.c | 2 ++ src/window.c | 10 ++++++---- src/wmspec.c | 2 ++ tests/wl-smoke-test.sh | 7 +++++++ 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/actions.c b/src/actions.c index 7300050..fe6b821 100644 --- a/src/actions.c +++ b/src/actions.c @@ -2159,6 +2159,9 @@ void wSelectWindow(WWindow *wwin, Bool flag) { WScreen *scr = wwin->screen_ptr; + if (!wwin->frame) + return; /* Wayland: no frame window to set border on */ + if (flag) { wwin->flags.selected = 1; if (wwin->frame->selected_border_pixel) diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 2799901..ab4171d 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -157,7 +157,7 @@ handle_toplevel_map(struct wl_listener *listener, void *data) } /* - * Phase 38: toplevel unmap — surface hidden (minimised or gone). + * Phase 40: toplevel unmap — surface hidden or destroyed; unmanage the window. */ static void handle_toplevel_unmap(struct wl_listener *listener, void *data) @@ -166,7 +166,16 @@ handle_toplevel_unmap(struct wl_listener *listener, void *data) (void)data; wwarning("wl_backend: toplevel unmapped id=0x%lx", view->id); - /* TODO Phase 39: call wUnmanageWindow */ + + /* Phase 40: remove the WWindow from the WM's managed list. */ + WScreen *scr = wScreenWithNumber(0); + if (scr) { + WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); + if (desc && desc->parent_type == WCLASS_WINDOW) { + WWindow *wwin = (WWindow *)desc->parent; + wUnmanageWindow(wwin, False, True); + } + } } /* diff --git a/src/client.c b/src/client.c index 34f48d9..958e025 100644 --- a/src/client.c +++ b/src/client.c @@ -49,6 +49,8 @@ */ void wClientRestore(WWindow * wwin) { + if (!dpy) + return; /* Wayland: no X11 reparent/border-width */ #if 0 int gx, gy; diff --git a/src/window.c b/src/window.c index 4262f75..b9dd221 100644 --- a/src/window.c +++ b/src/window.c @@ -1472,7 +1472,8 @@ WWindow *wManageInternalWindow(WScreen *scr, WNativeWindow window, WNativeWindow */ void wUnmanageWindow(WWindow *wwin, Bool restore, Bool destroyed) { - WCoreWindow *frame = wwin->frame->core; + /* Wayland: frame may be NULL when managing frameless surfaces */ + WCoreWindow *frame = wwin->frame ? wwin->frame->core : NULL; WWindow *owner = NULL; WWindow *newFocusedWindow = NULL; int wasFocused; @@ -1507,7 +1508,8 @@ void wUnmanageWindow(WWindow *wwin, Bool restore, Bool destroyed) wm_backend->key_ungrab_all(wwin->client_win); } - wm_backend->window_unmap(frame->window); + if (frame) + wm_backend->window_unmap(frame->window); wm_backend->window_unmap(wwin->client_win); @@ -1516,7 +1518,7 @@ void wUnmanageWindow(WWindow *wwin, Bool restore, Bool destroyed) /* remove all pending events on window */ /* I think this only matters for autoraise */ - if (wPreferences.raise_delay) + if (wPreferences.raise_delay && wwin->frame) WMDeleteTimerWithClientData(wwin->frame->core); wm_backend->event_flush(); @@ -1613,7 +1615,7 @@ void wUnmanageWindow(WWindow *wwin, Bool restore, Bool destroyed) WMPostNotificationName(WMNUnmanaged, wwin, NULL); if (wasFocused) { if (newFocusedWindow != owner && owner) { - if (wwin->flags.is_gnustep == 0) + if (wwin->flags.is_gnustep == 0 && owner->frame) wFrameWindowChangeState(owner->frame, WS_UNFOCUSED); } wSetFocusTo(scr, newFocusedWindow); diff --git a/src/wmspec.c b/src/wmspec.c index faa0d77..2cb5e38 100644 --- a/src/wmspec.c +++ b/src/wmspec.c @@ -2021,5 +2021,7 @@ void wNETFrameExtents(WWindow *wwin) void wNETCleanupFrameExtents(WWindow *wwin) { + if (!dpy) + return; /* Wayland: no X11 _NET_FRAME_EXTENTS property */ XDeleteProperty(dpy, wwin->client_win, net_frame_extents); } diff --git a/tests/wl-smoke-test.sh b/tests/wl-smoke-test.sh index 7005652..0d1fae2 100755 --- a/tests/wl-smoke-test.sh +++ b/tests/wl-smoke-test.sh @@ -20,6 +20,8 @@ # mapped" when weston-simple-shm creates its XDG toplevel surface # 9. (Phase 39) wManageWindow called for mapped XDG toplevel: compositor # logs "wManageWindow succeeded" for the synthetic surface ID +# 10. (Phase 40) wUnmanageWindow called when client disconnects: compositor +# logs "toplevel unmapped" and exits cleanly after cleanup # # This test grows as wl_backend vtable slots are implemented. # @@ -155,6 +157,11 @@ if command -v weston-simple-shm >/dev/null 2>&1; then grep -q 'wManageWindow succeeded' /tmp/wl-smoke-stderr \ || die "compositor did not log 'wManageWindow succeeded' (wManageWindow call failed)" echo " manage check: wManageWindow succeeded for weston-simple-shm toplevel" + + # -- 10. Phase 40: wUnmanageWindow fired when client disconnects ------ + grep -q 'toplevel unmapped' /tmp/wl-smoke-stderr \ + || die "compositor did not log 'toplevel unmapped' (wUnmanageWindow not triggered)" + echo " unmanage check: toplevel unmapped and wUnmanageWindow called" else echo " client check: skipped (weston-simple-shm not installed)" fi