wayland: NULL view->scene_node when wlroots auto-destroys it

Crash on closing a toplevel: handle_toplevel_destroy called
wlr_scene_node_destroy on view->scene_node, but the node had
already been destroyed by wlroots a few stack frames earlier when
the WMaker frame's tree was destroyed (frame_destroy → tree
destroy cascades to children).

Fix: register a destroy listener on view->scene_node when it's
created.  When wlroots destroys the node (typically because its
parent tree is destroyed during unmanage), the listener fires and
NULLs view->scene_node, so handle_toplevel_destroy's existing
'if (view->scene_node)' check correctly skips the redestroy.

handle_toplevel_destroy itself removes the listener before its
own wlr_scene_node_destroy call so we don't double-remove the
listener entry from the wlroots signal list.
This commit is contained in:
Window Maker 2026-05-24 09:02:35 +02:00
parent 27fa3f78c6
commit 8cb56fc141
1 changed files with 37 additions and 1 deletions

View File

@ -255,6 +255,12 @@ struct wl_toplevel_view {
struct wl_listener xw_geometry_listener;
/* Phase 18: scene graph tree for this surface (NULL if scene unavailable). */
struct wlr_scene_tree *scene_node;
/* Listener fires when scene_node is destroyed (e.g. when a parent
* tree is destroyed by wl_frame_destroy as part of unmanage).
* Without it, view->scene_node becomes a dangling pointer and the
* later handle_toplevel_destroy crashes calling wlr_scene_node_destroy
* a second time. */
struct wl_listener scene_destroy_listener;
WNativeWindow id; /* synthetic handle, >= 0x10000000 */
WNativeWindow frame_id; /* frame_buf that owns this view (0 = none) */
int frame_rel_x, frame_rel_y; /* offset within frame */
@ -1079,6 +1085,29 @@ handle_toplevel_set_app_id(struct wl_listener *listener, void *data)
/*
* Phase 38: toplevel map — surface is ready to be managed.
*/
static void
handle_toplevel_scene_destroy(struct wl_listener *listener, void *data)
{
(void)data;
struct wl_toplevel_view *view =
VIEW_FROM_LISTENER(listener, scene_destroy_listener);
/* wlroots is destroying view->scene_node (typically because its
* parent tree — the WMaker frame's fb->tree — is being destroyed
* as part of unmanage). NULL the pointer so the later
* handle_toplevel_destroy doesn't try to redestroy it. */
view->scene_node = NULL;
wl_list_remove(&view->scene_destroy_listener.link);
}
static void
view_attach_scene_destroy_listener(struct wl_toplevel_view *view)
{
if (!view || !view->scene_node) return;
view->scene_destroy_listener.notify = handle_toplevel_scene_destroy;
wl_signal_add(&view->scene_node->node.events.destroy,
&view->scene_destroy_listener);
}
static void
handle_toplevel_map(struct wl_listener *listener, void *data)
{
@ -1119,6 +1148,7 @@ handle_toplevel_map(struct wl_listener *listener, void *data)
view->scene_node = wlr_scene_subsurface_tree_create(
_vp, view->xdg_surface->surface);
if (view->scene_node) {
view_attach_scene_destroy_listener(view);
/* Deferred reparent: move client surface under its
* frame grouping tree now that the node exists. */
if (view->frame_id) {
@ -1232,6 +1262,10 @@ handle_toplevel_destroy(struct wl_listener *listener, void *data)
wl_list_remove(&view->link);
/* Phase 18: remove scene node. */
if (view->scene_node) {
/* Remove our destroy listener BEFORE destroying so the listener
* doesn't fire and try to remove itself from a list we're
* already cleaning up. */
wl_list_remove(&view->scene_destroy_listener.link);
wlr_scene_node_destroy(&view->scene_node->node);
view->scene_node = NULL;
}
@ -1405,8 +1439,10 @@ handle_xwayland_map(struct wl_listener *listener, void *data)
struct wlr_scene_tree *_vp = wl_state.level_trees[WL_LAYER_NORMAL]
? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree;
view->scene_node = wlr_scene_subsurface_tree_create(_vp, xw->surface);
if (view->scene_node)
if (view->scene_node) {
view_attach_scene_destroy_listener(view);
wlr_scene_node_set_position(&view->scene_node->node, view->x, view->y);
}
} else if (!xw->surface) {
wwarning("wl_backend: xwayland map id=0x%lx: surface is NULL, window will not render",
view->id);