From 1728c38fbe7a6e561a1ebe88a1b4d30b01230827 Mon Sep 17 00:00:00 2001 From: lkn Date: Mon, 22 Jun 2026 18:59:08 +0200 Subject: [PATCH] wayland: fix resource leaks causing freeze after extended use Three leaks identified via static analysis: 1. frame_buf_destroy never freed fb->bg_pixmap. Every window with a textured titlebar leaked a full pixman_image_t on close. 2. handle_xwayland_destroy did not clean up frame_bufs (unlike the XDG path). Every XWayland window close leaked 4-5 pixman images (frame + titlebar + resizebar + buttons). 3. wl_deferred_decoration struct leaked if client disconnected before initial_commit fired. Added destroy listener to free it. --- src/backend/wayland/wl_framebuf.c | 2 ++ src/backend/wayland/wl_xdg.c | 17 +++++++++++++++++ src/backend/wayland/wl_xwayland.c | 18 ++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/src/backend/wayland/wl_framebuf.c b/src/backend/wayland/wl_framebuf.c index 010384e..ce323b4 100644 --- a/src/backend/wayland/wl_framebuf.c +++ b/src/backend/wayland/wl_framebuf.c @@ -28,6 +28,8 @@ extern void *W_GetViewBacking(unsigned long id); void frame_buf_destroy(struct wlr_buffer *wlr_buf) { struct wl_frame_buf *fb = wl_container_of(wlr_buf, fb, base); + if (fb->bg_pixmap) + pixman_image_unref(fb->bg_pixmap); if (fb->image) pixman_image_unref(fb->image); wfree(fb); diff --git a/src/backend/wayland/wl_xdg.c b/src/backend/wayland/wl_xdg.c index 3fe8af8..c8afe51 100644 --- a/src/backend/wayland/wl_xdg.c +++ b/src/backend/wayland/wl_xdg.c @@ -484,9 +484,12 @@ handle_toplevel_destroy(struct wl_listener *listener, void *data) */ struct wl_deferred_decoration { struct wl_listener commit_listener; + struct wl_listener destroy_listener; struct wlr_xdg_toplevel_decoration_v1 *deco; }; +static void handle_deferred_decoration_destroy(struct wl_listener *listener, void *data); + static void handle_deferred_decoration_commit(struct wl_listener *listener, void *data) { @@ -499,11 +502,23 @@ handle_deferred_decoration_commit(struct wl_listener *listener, void *data) return; wl_list_remove(&dd->commit_listener.link); + wl_list_remove(&dd->destroy_listener.link); wlr_xdg_toplevel_decoration_v1_set_mode(dd->deco, WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); wfree(dd); } +static void +handle_deferred_decoration_destroy(struct wl_listener *listener, void *data) +{ + struct wl_deferred_decoration *dd = + wl_container_of(listener, dd, destroy_listener); + (void)data; + wl_list_remove(&dd->commit_listener.link); + wl_list_remove(&dd->destroy_listener.link); + wfree(dd); +} + void handle_new_toplevel_decoration(struct wl_listener *listener, void *data) { @@ -519,6 +534,8 @@ handle_new_toplevel_decoration(struct wl_listener *listener, void *data) dd->deco = deco; dd->commit_listener.notify = handle_deferred_decoration_commit; wl_signal_add(&surface->surface->events.commit, &dd->commit_listener); + dd->destroy_listener.notify = handle_deferred_decoration_destroy; + wl_signal_add(&deco->events.destroy, &dd->destroy_listener); } } diff --git a/src/backend/wayland/wl_xwayland.c b/src/backend/wayland/wl_xwayland.c index 9a7a204..f0dfe2a 100644 --- a/src/backend/wayland/wl_xwayland.c +++ b/src/backend/wayland/wl_xwayland.c @@ -28,6 +28,8 @@ void view_attach_scene_destroy_listener(struct wl_toplevel_view *view); void wm_dock_app_add(struct wl_toplevel_view *v); void wm_dock_app_remove(WNativeWindow id); void wl_pointer_warp_to(int x, int y); +extern void W_UnregisterBacking(unsigned long id); +struct wl_frame_buf *frame_buf_find(WNativeWindow id); #define VIEW_FROM_LISTENER(ptr, field) \ ((struct wl_toplevel_view *)((char *)(ptr) - offsetof(struct wl_toplevel_view, field))) @@ -237,7 +239,23 @@ handle_xwayland_destroy(struct wl_listener *listener, void *data) wfree(view->wm_command[ci]); wfree(view->wm_command); } + /* Destroy frame_bufs belonging to this window (mirrors handle_toplevel_destroy) */ + WNativeWindow frame_id = view->frame_id; wfree(view); + if (frame_id) { + struct wl_frame_buf *_fb, *_next; + wl_list_for_each_safe(_fb, _next, &wl_state.frame_list, link) { + if (_fb->id == frame_id || _fb->parent_id == frame_id) { + if (_fb->scene_buf) + wlr_scene_node_destroy(&_fb->scene_buf->node); + if (_fb->tree) + wlr_scene_node_destroy(&_fb->tree->node); + W_UnregisterBacking(_fb->id); + wl_list_remove(&_fb->link); + wlr_buffer_drop(&_fb->base); + } + } + } } static void