From 18f400d9c4514b0332706f88908f3315363075ee Mon Sep 17 00:00:00 2001 From: zhangkun2 Date: Mon, 14 Sep 2026 12:50:39 +0800 Subject: [PATCH] fix: notification bubble timeout should start when bubble is shown, not when enqueued MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When notifications are sent rapidly (e.g. 0.2s interval), the 5-second timeout was calculated from the enqueue time rather than when the bubble actually appears on screen. Due to the 300ms throttle queue in BubbleModel, bubbles could still be pending when their timeout expired, causing the server close signal to be lost (removeById only checks shown bubbles, not the pending queue). This left bubbles permanently stuck on screen and broke the notification center's temporary display area. Fix by deferring pushPendingEntity until the bubble is actually shown: - Store expireTimeout in m_pendingExpireTimeouts at Notify() time - Add onBubbleShowed() to start the timeout when bubble appears - Emit bubbleShown signal from BubbleModel on insert/replace - Forward through BubblePanel → NotifyServerApplet → NotificationManager Closes: BUG-372279 --- panels/notification/bubble/bubblemodel.cpp | 4 ++++ panels/notification/bubble/bubblemodel.h | 3 +++ panels/notification/bubble/bubblepanel.cpp | 7 ++++++ panels/notification/bubble/bubblepanel.h | 1 + .../server/notificationmanager.cpp | 22 ++++++++++++++++++- .../notification/server/notificationmanager.h | 2 ++ .../server/notifyserverapplet.cpp | 5 +++++ .../notification/server/notifyserverapplet.h | 1 + 8 files changed, 44 insertions(+), 1 deletion(-) diff --git a/panels/notification/bubble/bubblemodel.cpp b/panels/notification/bubble/bubblemodel.cpp index c9bbee6fb..0582c94a5 100644 --- a/panels/notification/bubble/bubblemodel.cpp +++ b/panels/notification/bubble/bubblemodel.cpp @@ -82,6 +82,8 @@ void BubbleModel::insertBubble(BubbleItem *bubble) beginInsertRows(QModelIndex(), 0, 0); m_bubbles.prepend(bubble); endInsertRows(); + + Q_EMIT bubbleShown(bubble->id()); } bool BubbleModel::isReplaceBubble(const BubbleItem *bubble) const @@ -98,6 +100,8 @@ BubbleItem *BubbleModel::replaceBubble(BubbleItem *bubble) m_bubbles.replace(replaceIndex, bubble); Q_EMIT dataChanged(index(replaceIndex), index(replaceIndex)); + Q_EMIT bubbleShown(bubble->id()); + return oldBubble; } diff --git a/panels/notification/bubble/bubblemodel.h b/panels/notification/bubble/bubblemodel.h index b9b8f6203..93bcb818a 100644 --- a/panels/notification/bubble/bubblemodel.h +++ b/panels/notification/bubble/bubblemodel.h @@ -38,6 +38,9 @@ class BubbleModel : public QAbstractListModel explicit BubbleModel(QObject *parent = nullptr); ~BubbleModel() override; +Q_SIGNALS: + void bubbleShown(qint64 id); + public: void push(BubbleItem *bubble); diff --git a/panels/notification/bubble/bubblepanel.cpp b/panels/notification/bubble/bubblepanel.cpp index 469fe67f7..ba51aa7eb 100644 --- a/panels/notification/bubble/bubblepanel.cpp +++ b/panels/notification/bubble/bubblepanel.cpp @@ -54,6 +54,8 @@ bool BubblePanel::init() connect(m_bubbles, &BubbleModel::rowsInserted, this, &BubblePanel::onBubbleCountChanged); connect(m_bubbles, &BubbleModel::rowsRemoved, this, &BubblePanel::onBubbleCountChanged); + connect(m_bubbles, &BubbleModel::bubbleShown, this, &BubblePanel::onBubbleShown); + return true; } @@ -168,6 +170,11 @@ void BubblePanel::onBubbleClosed(qint64 id, uint bubbleId, uint reason) Q_ARG(qint64, id), Q_ARG(uint, bubbleId), Q_ARG(uint, reason)); } +void BubblePanel::onBubbleShown(qint64 id) +{ + QMetaObject::invokeMethod(m_notificationServer, "onBubbleShowed", Qt::DirectConnection, Q_ARG(qint64, id)); +} + void BubblePanel::setVisible(const bool visible) { if (visible == m_visible) diff --git a/panels/notification/bubble/bubblepanel.h b/panels/notification/bubble/bubblepanel.h index c6770220f..0afa6be98 100644 --- a/panels/notification/bubble/bubblepanel.h +++ b/panels/notification/bubble/bubblepanel.h @@ -51,6 +51,7 @@ private Q_SLOTS: void addBubble(qint64 id); void closeBubble(qint64 id); void onBubbleCountChanged(); + void onBubbleShown(qint64 id); private: void onBubbleExpired(BubbleItem *); diff --git a/panels/notification/server/notificationmanager.cpp b/panels/notification/server/notificationmanager.cpp index 74c564197..80b3d85eb 100644 --- a/panels/notification/server/notificationmanager.cpp +++ b/panels/notification/server/notificationmanager.cpp @@ -310,7 +310,7 @@ uint NotificationManager::Notify(const QString &appName, uint replacesId, const } // 0: never expire. -1: DefaultTimeOutMSecs if (expireTimeout != 0 && !critical) { - pushPendingEntity(entity, expireTimeout); + m_pendingExpireTimeouts.insert(entity.id(), expireTimeout); } } @@ -513,6 +513,25 @@ void NotificationManager::pushPendingEntity(const NotifyEntity &entity, int expi } } +void NotificationManager::onBubbleShowed(qint64 id) +{ + auto it = m_pendingExpireTimeouts.find(id); + if (it == m_pendingExpireTimeouts.end()) { + return; + } + + auto entity = m_persistence->fetchEntity(id); + if (!entity.isValid()) { + qWarning(notifyLog) << "onBubbleShowed: invalid entity for id" << id; + m_pendingExpireTimeouts.erase(it); + return; + } + + int expireTimeout = it.value(); + m_pendingExpireTimeouts.erase(it); + pushPendingEntity(entity, expireTimeout); +} + void NotificationManager::updateEntityProcessed(qint64 id, uint reason) { auto entity = m_persistence->fetchEntity(id); @@ -768,6 +787,7 @@ void NotificationManager::removePendingEntity(const NotifyEntity &entity) } ++iter; } + m_pendingExpireTimeouts.remove(entity.id()); } void NotificationManager::onScreenLockedChanged(bool screenLocked) diff --git a/panels/notification/server/notificationmanager.h b/panels/notification/server/notificationmanager.h index f2756669d..cb87a2956 100644 --- a/panels/notification/server/notificationmanager.h +++ b/panels/notification/server/notificationmanager.h @@ -30,6 +30,7 @@ class NotificationManager : public QObject, public QDBusContext Q_INVOKABLE void actionInvoked(qint64 id, uint bubbleId, const QString &actionKey); Q_INVOKABLE void notificationClosed(qint64 id, uint bubbleId, uint reason); Q_INVOKABLE void notificationReplaced(qint64 id); + Q_INVOKABLE void onBubbleShowed(qint64 id); void removeNotification(qint64 id); void removeNotifications(const QString &appName); @@ -99,6 +100,7 @@ private slots: QTimer *m_pendingTimeout = nullptr; qint64 m_lastTimeoutPoint = std::numeric_limits::max(); QMultiHash m_pendingTimeoutEntities; + QHash m_pendingExpireTimeouts; QStringList m_systemApps; QMap m_appNamesMap; int m_cleanupDays = 7; diff --git a/panels/notification/server/notifyserverapplet.cpp b/panels/notification/server/notifyserverapplet.cpp index b4de43dfc..77a318cef 100644 --- a/panels/notification/server/notifyserverapplet.cpp +++ b/panels/notification/server/notifyserverapplet.cpp @@ -109,6 +109,11 @@ void NotifyServerApplet::setBlockClosedId(qint64 id) m_manager->setBlockClosedId(id); } +void NotifyServerApplet::onBubbleShowed(qint64 id) +{ + QMetaObject::invokeMethod(m_manager, "onBubbleShowed", Qt::DirectConnection, Q_ARG(qint64, id)); +} + D_APPLET_CLASS(NotifyServerApplet) } diff --git a/panels/notification/server/notifyserverapplet.h b/panels/notification/server/notifyserverapplet.h index 20975e91d..f57ef3a22 100644 --- a/panels/notification/server/notifyserverapplet.h +++ b/panels/notification/server/notifyserverapplet.h @@ -32,6 +32,7 @@ public Q_SLOTS: void removeNotifications(); void removeExpiredNotifications(); void setBlockClosedId(qint64 id); + void onBubbleShowed(qint64 id); private: NotificationManager *m_manager = nullptr;