From bb1b7a72f259aae25e831b33cff63c93076a9126 Mon Sep 17 00:00:00 2001 From: LFRon Date: Sun, 12 Apr 2026 20:12:55 +0800 Subject: [PATCH 1/8] fix: Copy the list to guard against re-entrant modification if a send triggers client destruction (which removes entries from the vector). Which add null check to avoiding crashes --- .../personalization/impl/appearance_impl.cpp | 3 ++ .../personalizationmanager.cpp | 32 ++++++++++++------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/modules/personalization/impl/appearance_impl.cpp b/src/modules/personalization/impl/appearance_impl.cpp index bca451ba0..f63747282 100644 --- a/src/modules/personalization/impl/appearance_impl.cpp +++ b/src/modules/personalization/impl/appearance_impl.cpp @@ -98,7 +98,10 @@ personalization_appearance_context_v1::personalization_appearance_context_v1( this, [](struct wl_resource *resource) { auto *p = personalization_appearance_context_v1::fromResource(resource); + if (!p) + return; Q_EMIT p->beforeDestroy(); + wl_resource_set_user_data(resource, nullptr); delete p; }); diff --git a/src/modules/personalization/personalizationmanager.cpp b/src/modules/personalization/personalizationmanager.cpp index c91d00be8..1306da85d 100644 --- a/src/modules/personalization/personalizationmanager.cpp +++ b/src/modules/personalization/personalizationmanager.cpp @@ -196,26 +196,32 @@ void PersonalizationV1::onAppearanceContextCreated(personalization_appearance_co connect(context, &Appearance::roundCornerRadiusChanged, this, [this](int32_t radius) { Helper::instance()->config()->setWindowRadius(radius); - for (auto *context : m_appearanceContexts) { - context->sendRoundCornerRadius(radius); + // Copy the list to guard against re-entrant modification if a send + // triggers client destruction (which removes entries from the vector). + const auto contexts = m_appearanceContexts; + for (auto *ctx : contexts) { + ctx->sendRoundCornerRadius(radius); } }); connect(context, &Appearance::iconThemeChanged, this, [this](const QString &theme) { Helper::instance()->config()->setIconThemeName(theme); - for (auto *context : m_appearanceContexts) { - context->sendIconTheme(theme.toUtf8()); + const auto contexts = m_appearanceContexts; + for (auto *ctx : contexts) { + ctx->sendIconTheme(theme.toUtf8()); } }); connect(context, &Appearance::activeColorChanged, this, [this](const QString &color) { Helper::instance()->config()->setActiveColor(color); - for (auto *context : m_appearanceContexts) { - context->sendActiveColor(color.toUtf8()); + const auto contexts = m_appearanceContexts; + for (auto *ctx : contexts) { + ctx->sendActiveColor(color.toUtf8()); } }); connect(context, &Appearance::windowOpacityChanged, this, [this](uint32_t opacity) { Helper::instance()->config()->setWindowOpacity(opacity); - for (auto *context : m_appearanceContexts) { - context->sendWindowOpacity(opacity); + const auto contexts = m_appearanceContexts; + for (auto *ctx : contexts) { + ctx->sendWindowOpacity(opacity); } }); connect(context, &Appearance::windowThemeTypeChanged, this, [this](int32_t type) { @@ -223,14 +229,16 @@ void PersonalizationV1::onAppearanceContextCreated(personalization_appearance_co if (dconfigType.has_value()) { Helper::instance()->config()->setWindowThemeType(*dconfigType); } - for (auto *context : m_appearanceContexts) { - context->sendWindowThemeType(type); + const auto contexts = m_appearanceContexts; + for (auto *ctx : contexts) { + ctx->sendWindowThemeType(type); } }); connect(context, &Appearance::titlebarHeightChanged, this, [this](uint32_t height) { Helper::instance()->config()->setWindowTitlebarHeight(height); - for (auto *context : m_appearanceContexts) { - context->sendWindowTitlebarHeight(height); + const auto contexts = m_appearanceContexts; + for (auto *ctx : contexts) { + ctx->sendWindowTitlebarHeight(height); } }); From 881c01a9866ce3a436d6e3bba2c6a09fef2edb1d Mon Sep 17 00:00:00 2001 From: LFRon Date: Sun, 12 Apr 2026 20:13:50 +0800 Subject: [PATCH 2/8] fix: Guard against dangling pointers: no matter how the wrapper is destroyed (timeout, close request, match, workspace cleanup, parent destruction, etc.), remove it from the prelaunch list. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `m_prelaunchWrappers` 列表存储原始 `SurfaceWrapper*` 指针。当 wrapper 通过超时、workspace 清理、QML 父对象销毁等路径被 destroy 后,其指针仍残留在列表中。后续 `handlePrelaunchSplashRequested` 中 `std::any_of` 遍历时访问已释放内存(对应崩溃日志 5 中 `w->appId()` 处的 `QAtomicOps::ref` 崩溃)。 __修复:__ 创建 prelaunch wrapper 后,连接 `QObject::destroyed` 信号自动从 `m_prelaunchWrappers` 移除,确保无论通过何种路径销毁都不会留下悬空指针。 --- src/core/shellhandler.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/core/shellhandler.cpp b/src/core/shellhandler.cpp index 459cfba62..a2cdb5179 100644 --- a/src/core/shellhandler.cpp +++ b/src/core/shellhandler.cpp @@ -186,6 +186,12 @@ void ShellHandler::createPrelaunchSplash(const QString &appId, iconBuffer->unlock(); } m_prelaunchWrappers.append(wrapper); + // Guard against dangling pointers: no matter how the wrapper is destroyed + // (timeout, close request, match, workspace cleanup, parent destruction, etc.), + // remove it from the prelaunch list. + connect(wrapper, &QObject::destroyed, this, [this, wrapper]() { + m_prelaunchWrappers.removeAll(wrapper); + }); m_workspace->addSurface(wrapper); setupSurfaceActiveWatcher(wrapper); registerSurfaceToForeignToplevel(wrapper); From d54959ee58f73085207e9efee9dd61a888040609 Mon Sep 17 00:00:00 2001 From: LFRon Date: Sun, 12 Apr 2026 23:27:07 +0800 Subject: [PATCH 3/8] fix: Only remove the listener from the signal and clear the surface reference. Do NOT call wl_resource_destroy() here: if the relative_surface belongs to the same wl_client as the dock preview context, we would modify the client's resource map during wl_map_for_each iteration inside wl_client_destroy, corrupting the map and causing a crash (null pointer in wl_signal_emit). Even for cross-client cases, calling wl_resource_destroy from within a wl_signal_emit on the surface's destroy signal is unsafe because the subsequent treeland_dock_preview_context_resource_destroy would wl_list_remove the listener that is currently being dispatched.The context resource will be cleaned up when the dock client disconnects or explicitly calls the destroy request. --- .../impl/foreign_toplevel_manager_impl.cpp | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/modules/foreign-toplevel/impl/foreign_toplevel_manager_impl.cpp b/src/modules/foreign-toplevel/impl/foreign_toplevel_manager_impl.cpp index 495fd4cae..975063b5a 100644 --- a/src/modules/foreign-toplevel/impl/foreign_toplevel_manager_impl.cpp +++ b/src/modules/foreign-toplevel/impl/foreign_toplevel_manager_impl.cpp @@ -817,10 +817,22 @@ static void treeland_foreign_toplevel_manager_handle_get_dock_preview_context( context->destroy_listener_wrapper.context = context; context->destroy_listener_wrapper.wrapped_listener.notify = [](struct wl_listener *listener, [[maybe_unused]] void *data) { treeland_dock_preview_context_v1::TDPCPODWrapper *wrapper = wl_container_of(listener, wrapper, wrapped_listener); - wl_resource_destroy(wrapper->context->resource); - - // wl_list_remove(&context->destroy_listener.link); - // context->relative_surface = nullptr; + // Only remove the listener from the signal and clear the surface reference. + // Do NOT call wl_resource_destroy() here: if the relative_surface belongs + // to the same wl_client as the dock preview context, we would modify the + // client's resource map during wl_map_for_each iteration inside + // wl_client_destroy, corrupting the map and causing a crash (null pointer + // in wl_signal_emit). Even for cross-client cases, calling + // wl_resource_destroy from within a wl_signal_emit on the surface's + // destroy signal is unsafe because the subsequent + // treeland_dock_preview_context_resource_destroy would wl_list_remove the + // listener that is currently being dispatched. + // + // The context resource will be cleaned up when the dock client disconnects + // or explicitly calls the destroy request. + wl_list_remove(&wrapper->wrapped_listener.link); + wl_list_init(&wrapper->wrapped_listener.link); + wrapper->context->relative_surface = nullptr; }; wl_signal_add(&context->relative_surface->events.destroy, &context->destroy_listener_wrapper.wrapped_listener); From f923c1cfbf6b51f33b9ac994aae72bf4aa825cbc Mon Sep 17 00:00:00 2001 From: LFRon Date: Sun, 12 Apr 2026 23:28:51 +0800 Subject: [PATCH 4/8] fix: Heap corruption during rendering ("corrupted size vs. prev_size") MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In `markWrapperToRemoved()`, `m_shellSurface` was set to null and signals were disconnected, but `m_surfaceItem` was NOT hidden and the wrapper itself remained visible in the QML scene graph. When the next render frame fires (via `wlr_output_send_frame` → QSG batch renderer), the scene graph traverses the still-visible `WSurfaceItem` which internally accesses freed `wlr_surface`/`wlr_buffer` GPU memory — causing heap corruption. __Fix__: Added `m_surfaceItem->setVisible(false)` and `QQuickItem::setVisible(false)` in `markWrapperToRemoved()` to immediately hide the surface item and wrapper from the scene graph, preventing the renderer from accessing freed wlroots resources before `deleteLater()` runs. --- src/surface/surfacewrapper.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/surface/surfacewrapper.cpp b/src/surface/surfacewrapper.cpp index 35340c933..46fbcc920 100644 --- a/src/surface/surfacewrapper.cpp +++ b/src/surface/surfacewrapper.cpp @@ -996,8 +996,21 @@ void SurfaceWrapper::markWrapperToRemoved() } m_subSurfaces.clear(); m_shellSurface = nullptr; - if (m_surfaceItem) + if (m_surfaceItem) { m_surfaceItem->disconnect(this); + // Hide the surfaceItem immediately to prevent the scene graph renderer + // from accessing stale wlr_surface/wlr_buffer data during the next + // render frame. The underlying wlroots resources are already freed at + // this point (the wayland client disconnected or the surface was + // destroyed), but deleteLater() won't run until the next event-loop + // iteration. Without hiding, the QSG renderer will traverse the still- + // visible WSurfaceItem and read freed GPU buffer memory, causing heap + // corruption ("corrupted size vs. prev_size"). + m_surfaceItem->setVisible(false); + } + + // Also hide the wrapper itself so the scene graph skips the entire subtree. + QQuickItem::setVisible(false); if (!isWindowAnimationRunning()) { deleteLater(); From c65ba56f1527a66ccbe6dab8dd90d2bd5b97c5a7 Mon Sep 17 00:00:00 2001 From: LFRon Date: Mon, 13 Apr 2026 10:58:32 +0800 Subject: [PATCH 5/8] fix: Do NOT call wl_resource_destroy from WClient::destroyed. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 崩溃发生在 `wl_client_destroy` → `wl_connection_destroy` 过程中,报 "double free or corruption (out)"。这是典型的堆损坏,发生在 client 资源销毁迭代过程中。 __Bug 位置:__ `src/modules/capture/impl/capturev1impl.cpp` capture 模块中有 4 处在 `WClient::destroyed` 信号处理器中调用 `wl_resource_destroy()`: 1. `treeland_capture_manager_v1::addClientResource` → `destroyClientResource` 调用 `wl_resource_destroy` 2. `treeland_capture_context_v1::setResource` → `wl_resource_destroy(this->resource)` 3. `treeland_capture_session_v1::setResource` → `wl_resource_destroy(this->resource)` 4. `treeland_capture_frame_v1::setResource` → `wl_resource_destroy(this->resource)` 当 `wl_client_destroy` 运行时,libwayland 会遍历 client 的 resource map 并逐个销毁资源。在遍历过程中 `WClient::destroyed` 信号触发,上述处理器又去调用 `wl_resource_destroy()`,导致在迭代过程中修改 resource map → 堆损坏 → crash。 __修复:__ - Manager: `WClient::destroyed` 只清理内部跟踪列表,不调用 `wl_resource_destroy` - Context/Session/Frame: `WClient::destroyed` 只将 `this->resource` 置为 `nullptr`,不调用 `wl_resource_destroy` - libwayland 会自动在 `wl_client_destroy` 中销毁所有 client 资源,无需手动干预 这与之前修复的 dock_preview_context 问题(commit d54959e)属于同一类 bug 模式。 --- src/modules/capture/impl/capturev1impl.cpp | 35 ++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/modules/capture/impl/capturev1impl.cpp b/src/modules/capture/impl/capturev1impl.cpp index dcc63f8d5..5dadf36c0 100644 --- a/src/modules/capture/impl/capturev1impl.cpp +++ b/src/modules/capture/impl/capturev1impl.cpp @@ -138,19 +138,26 @@ treeland_capture_manager_v1::treeland_capture_manager_v1(wl_display *display, QO void treeland_capture_manager_v1::addClientResource(wl_client *client, wl_resource *resource) { WClient *wClient = WClient::get(client); + // Do NOT connect WClient::destroyed to wl_resource_destroy here. + // When wl_client_destroy runs, it iterates the client's resource map + // and destroys each resource. Calling wl_resource_destroy from within + // a WClient::destroyed handler (which fires during that iteration) + // corrupts the resource map and causes "double free or corruption". + // The resource will be automatically cleaned up by libwayland. + // We only need to track the entry for explicit destroy requests and + // remove stale entries when the client goes away. connect(wClient, &WClient::destroyed, this, [this, wClient, resource]() { - destroyClientResource(wClient, resource); + clientResources.removeOne({ wClient, resource }); }); clientResources.push_back({ wClient, resource }); } void treeland_capture_manager_v1::destroyClientResource(WClient *client, wl_resource *resource) { - for (const auto &pair : clientResources) { - if (pair.first == client && pair.second == resource) { - wl_resource_destroy(pair.second); - clientResources.removeOne(pair); - } + // Called from the explicit protocol destroy request only (not during client teardown). + QPair entry{ client, resource }; + if (clientResources.removeOne(entry)) { + wl_resource_destroy(resource); } } @@ -292,8 +299,12 @@ void treeland_capture_context_v1::sendSourceReady(QRect region, uint32_t source_ void treeland_capture_context_v1::setResource(wl_client *client, wl_resource *resource) { WClient *wClient = WClient::get(client); + // Do NOT call wl_resource_destroy from WClient::destroyed. + // libwayland already destroys all client resources during wl_client_destroy; + // doing so again from this handler corrupts the resource map ("double free"). + // Just null out the pointer so we don't send on a stale resource. connect(wClient, &WClient::destroyed, this, [this] { - wl_resource_destroy(this->resource); + this->resource = nullptr; }); this->resource = resource; } @@ -321,8 +332,11 @@ void handle_treeland_capture_frame_v1_copy(wl_client *client, void treeland_capture_session_v1::setResource(wl_client *client, wl_resource *resource) { WClient *wClient = WClient::get(client); + // Do NOT call wl_resource_destroy from WClient::destroyed. + // libwayland already destroys all client resources during wl_client_destroy; + // doing so again from this handler corrupts the resource map ("double free"). connect(wClient, &WClient::destroyed, this, [this] { - wl_resource_destroy(this->resource); + this->resource = nullptr; }); this->resource = resource; } @@ -348,8 +362,11 @@ void treeland_capture_session_v1::sendSourceResizeCancel() void treeland_capture_frame_v1::setResource(wl_client *client, wl_resource *resource) { WClient *wClient = WClient::get(client); + // Do NOT call wl_resource_destroy from WClient::destroyed. + // libwayland already destroys all client resources during wl_client_destroy; + // doing so again from this handler corrupts the resource map ("double free"). connect(wClient, &WClient::destroyed, this, [this] { - wl_resource_destroy(this->resource); + this->resource = nullptr; }); this->resource = resource; } From 80aeea3ff0b5b26b3d3bcb846acbf03a5aee3fff Mon Sep 17 00:00:00 2001 From: LFRon Date: Mon, 13 Apr 2026 11:16:29 +0800 Subject: [PATCH 6/8] fix: Gradia-like capture causes treeland crashed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using Gradia (or any screenshot tool via xdg-desktop-portal) to capture, there is no mask surface. `m_canvas` is only set in `componentComplete()` when `captureManager()->maskShellSurface()` and `captureManager()->maskSurfaceWrapper()` both exist. Without a mask, `m_canvas` remains `nullptr`. When the capture `finishSelect` signal fires, `doneSelection()` unconditionally dereferences `m_canvas->surfaceItem()`, causing a null pointer crash (`this=0x0`). __Fix__: Added a null guard before accessing `m_canvas->surfaceItem()`: ```cpp if (m_canvas && m_canvas->surfaceItem()) m_canvas->surfaceItem()->setSubsurfacesVisible(false); ``` Summary of all fixes in this branch: 1. `foreign_toplevel_manager_impl.cpp` — dock_preview_context surface destroy listener no longer calls `wl_resource_destroy` (crash #1) 2. `surfacewrapper.cpp` — hide surfaceItem immediately in `markWrapperToRemoved` to prevent renderer UAF (crash #2) 3. `capturev1impl.cpp` — removed 4 instances of `wl_resource_destroy` from `WClient::destroyed` handlers (crash #3) 4. `capture.cpp` — null guard for `m_canvas` in `doneSelection()` (crash #4, this fix) --- src/modules/capture/capture.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/capture/capture.cpp b/src/modules/capture/capture.cpp index d9dd6bf6a..0e2bba17c 100644 --- a/src/modules/capture/capture.cpp +++ b/src/modules/capture/capture.cpp @@ -580,7 +580,10 @@ void CaptureSourceSelector::doneSelection() this, &CaptureSourceSelector::createImage); m_internalContentItem->setVisible(false); - m_canvas->surfaceItem()->setSubsurfacesVisible(false); + // m_canvas may be null when no mask surface exists (e.g. capture via + // xdg-desktop-portal without a mask). Guard against null dereference. + if (m_canvas && m_canvas->surfaceItem()) + m_canvas->surfaceItem()->setSubsurfacesVisible(false); } void CaptureSourceSelector::cancelSelection() From fc23a95e9e0eddac300e706c19b74f4ea572610f Mon Sep 17 00:00:00 2001 From: LFRon Date: Mon, 13 Apr 2026 13:52:21 +0800 Subject: [PATCH 7/8] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E4=BA=86=20`src/modu?= =?UTF-8?q?les/personalization/impl/personalization=5Fmanager=5Fimpl.cpp`?= =?UTF-8?q?=20=E4=B8=AD=204=20=E4=B8=AA=E8=B5=84=E6=BA=90=E9=94=80?= =?UTF-8?q?=E6=AF=81=E5=9B=9E=E8=B0=83=E5=87=BD=E6=95=B0=E4=B8=8E=E5=90=91?= =?UTF-8?q?=E7=A9=BA=E6=8C=87=E9=92=88=E6=A3=80=E9=94=99=E8=AF=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 所有四个 `_resource_destroy` 回调函数都写成了 `if (resource) return;`(资源非空就直接返回),导致清理代码(`delete` 对象 + `wl_list_remove`)__永远不会执行__。这意味着: 1. `personalization_window_context_v1` / `wallpaper_context` / `cursor_context` 对象永远不会被释放 → 内存泄漏 2. `wl_list` 链接节点不会从管理器的资源列表中移除 → 当 libwayland 释放资源内存后,链表中残留悬空指针 3. 当管理器后续遍历 `resources` 列表(比如广播状态变更)→ 访问已释放内存 → __堆损坏__("free(): invalid pointer") --- .../impl/personalization_manager_impl.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/modules/personalization/impl/personalization_manager_impl.cpp b/src/modules/personalization/impl/personalization_manager_impl.cpp index b11670e34..ddabbecef 100644 --- a/src/modules/personalization/impl/personalization_manager_impl.cpp +++ b/src/modules/personalization/impl/personalization_manager_impl.cpp @@ -90,7 +90,7 @@ personalization_window_context_v1::~personalization_window_context_v1() static void personalization_window_context_resource_destroy(struct wl_resource *resource) { - if (resource) + if (!resource) return; auto window_context = personalization_window_context_v1::from_resource(resource); @@ -222,7 +222,7 @@ personalization_wallpaper_context_v1::~personalization_wallpaper_context_v1() static void personalization_wallpaper_context_resource_destroy(struct wl_resource *resource) { - if (resource) + if (!resource) return; auto wallpaper_context = personalization_wallpaper_context_v1::from_resource(resource); @@ -312,7 +312,7 @@ personalization_cursor_context_v1::~personalization_cursor_context_v1() static void personalization_cursor_context_resource_destroy(struct wl_resource *resource) { - if (resource) + if (!resource) return; auto cursor_context = personalization_cursor_context_v1::from_resource(resource); @@ -360,15 +360,11 @@ treeland_personalization_manager_v1 *treeland_personalization_manager_v1::from_r static void treeland_personalization_manager_resource_destroy(struct wl_resource *resource) { - if (resource) + if (!resource) return; - auto manager = treeland_personalization_manager_v1::from_resource(resource); - if (!manager) { - return; - } - - delete manager; + // Do not delete the manager here — the manager is a singleton owned by + // the display. Only remove the per-client resource from the list. wl_list_remove(wl_resource_get_link(resource)); } From 3852e83d37291f5d9438783cbecd2c55ed41d15b Mon Sep 17 00:00:00 2001 From: LFRon Date: Mon, 13 Apr 2026 13:57:35 +0800 Subject: [PATCH 8/8] fix: `wl_list_remove` double-remove crash in `src/modules/personalization/impl/personalization_manager_impl.cpp`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix corrected inverted null checks (`if (resource)` → `if (!resource)`) in 4 resource destroy callbacks, which enabled the cleanup code to run. However, the cleanup code called `wl_list_remove(wl_resource_get_link(resource))` — and libwayland's `remove_and_destroy_resource` __also__ calls `wl_list_remove` on the same link after the callback returns. This double-remove corrupts the linked list, causing crashes in `wl_list_remove` during `wl_client_destroy`. __Fix__: Removed all `wl_list_remove` calls from the 4 resource destroy callbacks: - `personalization_window_context_resource_destroy` — delete context object only, no `wl_list_remove` - `personalization_wallpaper_context_resource_destroy` — delete context object only, no `wl_list_remove` - `personalization_cursor_context_resource_destroy` — delete context object only, no `wl_list_remove` - `treeland_personalization_manager_resource_destroy` — no-op (manager is singleton, libwayland handles list removal) libwayland handles `wl_list_remove(&resource->link)` automatically in `remove_and_destroy_resource` after calling the destroy callback. --- .../impl/personalization_manager_impl.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/modules/personalization/impl/personalization_manager_impl.cpp b/src/modules/personalization/impl/personalization_manager_impl.cpp index ddabbecef..8f15d3074 100644 --- a/src/modules/personalization/impl/personalization_manager_impl.cpp +++ b/src/modules/personalization/impl/personalization_manager_impl.cpp @@ -99,7 +99,9 @@ static void personalization_window_context_resource_destroy(struct wl_resource * } delete window_context; - wl_list_remove(wl_resource_get_link(resource)); + // Do NOT call wl_list_remove here — libwayland's remove_and_destroy_resource + // already removes the resource link from the list after calling this callback. + // Calling it here would double-remove and corrupt the list. } namespace Personalization { @@ -231,7 +233,7 @@ static void personalization_wallpaper_context_resource_destroy(struct wl_resourc } delete wallpaper_context; - wl_list_remove(wl_resource_get_link(resource)); + // Do NOT call wl_list_remove here — libwayland handles it. } void set_cursor_theme([[maybe_unused]] struct wl_client *client, struct wl_resource *resource, const char *name) @@ -321,7 +323,7 @@ static void personalization_cursor_context_resource_destroy(struct wl_resource * } delete cursor_context; - wl_list_remove(wl_resource_get_link(resource)); + // Do NOT call wl_list_remove here — libwayland handles it. } void create_personalization_window_context_listener(struct wl_client *client, @@ -360,12 +362,11 @@ treeland_personalization_manager_v1 *treeland_personalization_manager_v1::from_r static void treeland_personalization_manager_resource_destroy(struct wl_resource *resource) { - if (!resource) - return; - - // Do not delete the manager here — the manager is a singleton owned by - // the display. Only remove the per-client resource from the list. - wl_list_remove(wl_resource_get_link(resource)); + // Do not delete the manager — it is a singleton owned by the display. + // Do not call wl_list_remove here — libwayland's remove_and_destroy_resource + // already calls wl_list_remove(&resource->link) after this callback returns. + // Doing it here would double-remove and corrupt the linked list. + Q_UNUSED(resource); } void create_personalization_window_context_listener(struct wl_client *client,