diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 67210b0..7781095 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -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);