From d25ea58116d2ec00746493ba737339932b502680 Mon Sep 17 00:00:00 2001 From: Ivy233 Date: Tue, 27 Oct 2026 16:57:43 +0800 Subject: [PATCH] fix(notification): restore synchronous action dispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The expire rework changed NotifyServerApplet::actionInvoked() from Qt::DirectConnection to Qt::QueuedConnection. NotifyModel::invokeAction() still removes the notification right after dispatching the action, so the queued action could reach the manager after the removal and find no entity, making action buttons in the notification center a no-op. Restore the synchronous dispatch so the action is handled before remove() deletes the entity. The notification center keeps a single delete entry: remove() handles user operations (close, dismiss and action invocation) and deletes both the view row and the database entity. Notifications that move into history after a bubble disappears still arrive through NotificationStateChanged(Processed) and are not deleted by this path. Since actionInvoked() now runs on the main thread while the manager lives on the worker thread, XdgActivation must not be parented to the manager. Doing so triggers "Cannot create children for a parent that is in a different thread". Create it without a parent instead; the tokenReady handler deletes it when the token arrives. The manager is long-lived, so no separate manager-destroyed cleanup is kept for this short-lived object. The synchronous blocking is the same as 2.0.56 and is small and intentional. Log: fix action buttons in the notification center Influence: 1. Click action buttons on notifications in the notification center and verify the actions run 2. Verify normal, overlap, and expanded group notifications are removed from the center after the action executes 3. Verify manually deleting notifications still works 4. Verify action buttons in bubbles and the staging area still work 5. Verify a notification enters the history area after its bubble expires 6. Trigger both x-deepin-action and non-extended actions and verify the dde-shell log has no "Cannot create children for a parent that is in a different thread" warning fix(notification): 恢复动作派发的同步调用 过期逻辑重构把 NotifyServerApplet::actionInvoked() 从 Qt::DirectConnection 改成了 Qt::QueuedConnection,而 NotifyModel::invokeAction() 仍在派发动作后立即删除通知。排队后的动作 可能晚于删除到达 manager,此时实体已不存在,导致通知中心的动作按钮无效。 恢复同步派发,确保动作在 remove() 删除实体前已由 manager 处理。通知中心 只保留一个删除入口:remove() 负责用户操作(关闭、忽略和点击动作)对视图 与数据库的删除。气泡消失后进入历史通知仍通过 NotificationStateChanged(Processed) 处理,不经过该删除入口。 由于 actionInvoked() 现在在主线程执行,而 manager 位于工作线程,不能再把 manager 作为 XdgActivation 的 parent,否则会触发 "Cannot create children for a parent that is in a different thread" 警告。改为不设置 parent,由 tokenReady 回调在 token 到达后 deleteLater() 释放。manager 为长生命周期 对象,因此不再为这个短生命周期对象额外保留 manager 销毁时的清理连接。 该同步阻塞与 2.0.56 一致,影响较小且是有意保留的。 Log: 修复通知中心动作按钮点击无效的问题 Influence: 1. 在通知中心点击通知动作按钮,确认动作正常执行 2. 确认普通、重叠及展开的分组通知在动作执行后从通知中心移除 3. 确认手动删除通知仍然正常 4. 确认气泡和通知中心 staging 区域的动作按钮仍然正常 5. 确认气泡过期后通知进入历史通知区域 6. 分别触发 x-deepin-action 和普通 action,确认 dde-shell 日志中不再出现 "Cannot create children for a parent that is in a different thread" 警告 PMS: BUG-380339 --- panels/notification/server/notificationmanager.cpp | 12 ++++++++---- panels/notification/server/notifyserverapplet.cpp | 9 +++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/panels/notification/server/notificationmanager.cpp b/panels/notification/server/notificationmanager.cpp index ce01d302c..02820b638 100644 --- a/panels/notification/server/notificationmanager.cpp +++ b/panels/notification/server/notificationmanager.cpp @@ -144,8 +144,10 @@ void NotificationManager::actionInvoked(qint64 id, uint bubbleId, const QString Q_EMIT ActionInvoked(bubbleId, actionKey); Q_EMIT NotificationClosed(bubbleId, NotifyEntity::Closed); } else { - // For non-extended actions, emit ActivationToken first if available - auto *activation = new DS_NAMESPACE::XdgActivation(this); + // For non-extended actions, emit ActivationToken first if available. + // The activation must not be parented to the worker-thread manager; + // the tokenReady handler below deletes it when the token arrives. + auto *activation = new DS_NAMESPACE::XdgActivation(); connect( activation, &DS_NAMESPACE::XdgActivation::tokenReady, @@ -561,8 +563,10 @@ void NotificationManager::doActionInvoked(const NotifyEntity &entity, const QStr amArgs << "--" << args; } - // Get activation token, then start process - auto *activation = new DS_NAMESPACE::XdgActivation(this); + // Get activation token, then start process. It must not be + // parented to the worker-thread manager; the tokenReady + // handler below deletes it when the token arrives. + auto *activation = new DS_NAMESPACE::XdgActivation(); auto amArgsCopy = amArgs; connect(activation, &DS_NAMESPACE::XdgActivation::tokenReady, this, [amArgsCopy, activation](const QString &token) { diff --git a/panels/notification/server/notifyserverapplet.cpp b/panels/notification/server/notifyserverapplet.cpp index 1fd903568..c0dc588af 100644 --- a/panels/notification/server/notifyserverapplet.cpp +++ b/panels/notification/server/notifyserverapplet.cpp @@ -83,13 +83,18 @@ bool NotifyServerApplet::init() void NotifyServerApplet::actionInvoked(qint64 id, uint bubbleId, const QString &actionKey) { - QMetaObject::invokeMethod(m_manager, "actionInvoked", Qt::QueuedConnection, + // Keep the action synchronous with the notification center's remove() + // call. The center invokes the action and then deletes the notification, + // so the action has to reach the manager before that deletion. This was + // the behavior before the expire rework switched these calls to queued + // connections; the synchronous blocking is small and intentional. + QMetaObject::invokeMethod(m_manager, "actionInvoked", Qt::DirectConnection, Q_ARG(qint64, id), Q_ARG(uint, bubbleId), Q_ARG(QString, actionKey)); } void NotifyServerApplet::actionInvoked(qint64 id, const QString &actionKey) { - QMetaObject::invokeMethod(m_manager, "actionInvoked", Qt::QueuedConnection, + QMetaObject::invokeMethod(m_manager, "actionInvoked", Qt::DirectConnection, Q_ARG(qint64, id), Q_ARG(QString, actionKey)); }