From 6157880bc724edc802bacd994f5bafa9d02375bd Mon Sep 17 00:00:00 2001 From: zhaoyingzhen Date: Fri, 24 Apr 2026 11:43:20 +0800 Subject: [PATCH] fix: prevent crash when closing wayland popup surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Problem: dde-tray-loader crashes with SEGFAULT in QWaylandWindow::decoration() during popup closing. - Reason: Reentrancy in QWaylandShmBackingStore::beginPaint(). It dispatches Wayland events synchronously, which can trigger plugin_popup_close() in the middle of a paint operation. Synchronously closing the window destroys the platform handle, causing beginPaint to crash when it resumes. - Solution 1: Use QMetaObject::invokeMethod with Qt::QueuedConnection in plugin_popup_close() to defer window destruction until the current paint operation completes. - Solution 2: Implement a global NullHandleGuard event filter to intercept and drop any UpdateRequest events directed at widgets or windows whose Wayland handles have been invalidated. - 问题: 弹窗关闭时 dde-tray-loader 崩溃,堆栈指向 QWaylandWindow::decoration()。 - 原因: QWaylandShmBackingStore::beginPaint() 在绘制过程中同步分发 Wayland 事件,导致 plugin_popup_close() 被重入调用。同步关闭窗口会销毁底层 handle,导致 beginPaint 恢复执行时访问空指针崩溃。 - 解决 1: 在 plugin_popup_close() 中使用 QMetaObject::invokeMethod 配合队列连接,将窗口销毁操作推迟到当前绘制周期结束之后。 - 解决 2: 实现全局事件过滤器 NullHandleGuard,拦截并丢弃所有指向已销毁 Wayland 表面或无效窗口的 UpdateRequest 请求,防止僵尸绘制事件触发崩溃。 Log: prevent crash when closing wayland popup surfaces Pms: BUG-358679 Change-Id: I2610d04675ee628080eab2c52171f39d9d752c91 --- .../pluginsurface.cpp | 86 ++++++++++++++++++- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/src/tray-wayland-integration/pluginsurface.cpp b/src/tray-wayland-integration/pluginsurface.cpp index 170b41f3d..f436dc9da 100644 --- a/src/tray-wayland-integration/pluginsurface.cpp +++ b/src/tray-wayland-integration/pluginsurface.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -9,9 +9,59 @@ #include "qwayland-plugin-manager-v1.h" #include +#include +#include +#include #include namespace Plugin { + +class NullHandleGuard : public QObject { +public: + static void install() { + static bool installed = false; + if (!installed && qApp) { + qApp->installEventFilter(new NullHandleGuard(qApp)); + installed = true; + } + } + + NullHandleGuard(QObject *parent = nullptr) : QObject(parent) {} + +protected: + bool eventFilter(QObject *watched, QEvent *event) override { + if (event->type() == QEvent::UpdateRequest) { + QWindow *win = nullptr; + QWidget *w = qobject_cast(watched); + QWindow *wnd = qobject_cast(watched); + + if (w) { + win = w->windowHandle(); + if (!win && w->window()) { + win = w->window()->windowHandle(); + } + } else if (wnd) { + win = wnd; + } + + // Drop UpdateRequest to prevent QWaylandShmBackingStore from crashing + // in beginPaint/decoration() when trying to paint a destroyed window. + if (win) { + // Case 1: The QWindow exists, but its underlying Wayland surface is destroyed. + if (!win->handle()) { + qDebug() << "NullHandleGuard Dropped UpdateRequest for" << watched << "(null Wayland handle)"; + return true; + } + } else if (w) { + // Case 2: The QWidget has lost its QWindow entirely. + // A widget without a QWindow cannot be painted to the screen. + qDebug() << "NullHandleGuard Dropped UpdateRequest for" << watched << "(missing QWindow)"; + return true; + } + } + return QObject::eventFilter(watched, event); + } +}; PluginSurface::PluginSurface(PluginManagerIntegration *manager, QtWaylandClient::QWaylandWindow *window) : QtWaylandClient::QWaylandShellSurface(window) , QtWayland::plugin() @@ -132,8 +182,38 @@ PluginPopupSurface::~PluginPopupSurface() void PluginPopupSurface::plugin_popup_close() { - // it would be delete this object directly. - m_window->close(); + // Install the global safeguard just in case + NullHandleGuard::install(); + + // Use QPointer to ensure m_window is still valid when the queued lambda executes + QPointer safeWindow(m_window); + + // DEFER the destruction! + // Why: QWaylandShmBackingStore::beginPaint() can spin the Wayland event loop + // (e.g., waiting for buffers) which synchronously dispatches this Wayland event. + // If we destroy m_window here, beginPaint() resumes with a dangling pointer + // and crashes immediately. Deferring to the next event loop iteration ensures + // that the current paint frame completes before we destroy the Wayland surface. + QMetaObject::invokeMethod(qApp, [safeWindow]() { + if (!safeWindow) { + return; + } + + QWidget *popupWidget = nullptr; + for (QWidget *w : QApplication::topLevelWidgets()) { + if (w && w->windowHandle() == safeWindow.data()) { + popupWidget = w; + break; + } + } + + if (popupWidget) { + popupWidget->hide(); + } + + // Safely close the QWindow. This destroys the Wayland surface. + safeWindow->close(); + }, Qt::QueuedConnection); } void PluginPopupSurface::plugin_popup_geometry(int32_t x, int32_t y, int32_t width, int32_t height)