Skip to content

Commit ac598d8

Browse files
committed
fix: prevent crash when closing wayland popup surfaces
- 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
1 parent bdcbe70 commit ac598d8

1 file changed

Lines changed: 81 additions & 3 deletions

File tree

src/tray-wayland-integration/pluginsurface.cpp

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
1+
// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd.
22
//
33
// SPDX-License-Identifier: GPL-3.0-or-later
44

@@ -9,9 +9,57 @@
99
#include "qwayland-plugin-manager-v1.h"
1010

1111
#include <QTimer>
12+
#include <QApplication>
13+
#include <QWidget>
14+
#include <QEvent>
1215
#include <QtWaylandClient/private/qwaylandwindow_p.h>
1316

1417
namespace Plugin {
18+
19+
class NullHandleGuard : public QObject {
20+
public:
21+
static void install() {
22+
static bool installed = false;
23+
if (!installed && qApp) {
24+
qApp->installEventFilter(new NullHandleGuard(qApp));
25+
installed = true;
26+
}
27+
}
28+
29+
NullHandleGuard(QObject *parent = nullptr) : QObject(parent) {}
30+
31+
protected:
32+
bool eventFilter(QObject *watched, QEvent *event) override {
33+
if (event->type() == QEvent::UpdateRequest) {
34+
QWindow *win = nullptr;
35+
QWidget *w = qobject_cast<QWidget *>(watched);
36+
QWindow *wnd = qobject_cast<QWindow *>(watched);
37+
38+
if (w) {
39+
win = w->windowHandle();
40+
if (!win && w->window()) {
41+
win = w->window()->windowHandle();
42+
}
43+
} else if (wnd) {
44+
win = wnd;
45+
}
46+
47+
// Drop UpdateRequest to prevent QWaylandShmBackingStore from crashing
48+
// in beginPaint/decoration() when trying to paint a destroyed window.
49+
if (win) {
50+
// Case 1: The QWindow exists, but its underlying Wayland surface is destroyed.
51+
if (!win->handle()) {
52+
return true;
53+
}
54+
} else if (w) {
55+
// Case 2: The QWidget has lost its QWindow entirely.
56+
// A widget without a QWindow cannot be painted to the screen.
57+
return true;
58+
}
59+
}
60+
return QObject::eventFilter(watched, event);
61+
}
62+
};
1563
PluginSurface::PluginSurface(PluginManagerIntegration *manager, QtWaylandClient::QWaylandWindow *window)
1664
: QtWaylandClient::QWaylandShellSurface(window)
1765
, QtWayland::plugin()
@@ -132,8 +180,38 @@ PluginPopupSurface::~PluginPopupSurface()
132180

133181
void PluginPopupSurface::plugin_popup_close()
134182
{
135-
// it would be delete this object directly.
136-
m_window->close();
183+
// Install the global safeguard just in case
184+
NullHandleGuard::install();
185+
186+
// Use QPointer to ensure m_window is still valid when the queued lambda executes
187+
QPointer<QWindow> safeWindow(m_window);
188+
189+
// DEFER the destruction!
190+
// Why: QWaylandShmBackingStore::beginPaint() can spin the Wayland event loop
191+
// (e.g., waiting for buffers) which synchronously dispatches this Wayland event.
192+
// If we destroy m_window here, beginPaint() resumes with a dangling pointer
193+
// and crashes immediately. Deferring to the next event loop iteration ensures
194+
// that the current paint frame completes before we destroy the Wayland surface.
195+
QMetaObject::invokeMethod(qApp, [safeWindow]() {
196+
if (!safeWindow) {
197+
return;
198+
}
199+
200+
QWidget *popupWidget = nullptr;
201+
for (QWidget *w : QApplication::topLevelWidgets()) {
202+
if (w && w->windowHandle() == safeWindow.data()) {
203+
popupWidget = w;
204+
break;
205+
}
206+
}
207+
208+
if (popupWidget) {
209+
popupWidget->hide();
210+
}
211+
212+
// Safely close the QWindow. This destroys the Wayland surface.
213+
safeWindow->close();
214+
}, Qt::QueuedConnection);
137215
}
138216

139217
void PluginPopupSurface::plugin_popup_geometry(int32_t x, int32_t y, int32_t width, int32_t height)

0 commit comments

Comments
 (0)