From 8d9d7ff0ef41d7e8c2e36683b1001c72eb9d17b7 Mon Sep 17 00:00:00 2001 From: David Mireles Date: Sun, 5 Jul 2026 20:01:31 -0600 Subject: [PATCH] fix(notifications): dismiss popup reliably on user-initiated close paths (#2761) The notification popup's close button (X), action button clicks, and the cardClick body's else branch all set notificationData.popup = false directly. This relies on wrapperConn.onPopupChanged firing startExit(), which can be interrupted by four races: 1. enterDelay (160 ms Timer) starts notificationData.timer after the user clicks, on an already-orphan wrapper. 2. The dismiss Timer keeps running post-click and flips popup again. 3. wrapperConn.target is set imperatively in onNotificationDataChanged (line 293) and may be stale after NotificationPopupManager._sync() reorders wrappers. 4. exiting or _isDestroying stuck true gates wrapperConn.enabled. Introduce a single helper dismissPopupReliably() that stops the timer, sets popup = false, and kicks off startExit() via Qt.callLater as a belt-and-suspenders fallback. Apply to the three vulnerable handlers. This matches the existing upstream pattern used in the hover, contextMenu, and Component.onDestruction paths (which all stop the timer before mutating popup state). Closes #2760 --- .../Notifications/Popup/NotificationPopup.qml | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml index 142ea34b1..501a5844f 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml @@ -168,6 +168,19 @@ PanelWindow { popupContextMenuLoader.active = false; } + function dismissPopupReliably() { + if (!notificationData || win.exiting || win._isDestroying) + return; + if (notificationData.timer) + notificationData.timer.stop(); + notificationData.popup = false; + // Fallback if wrapperConn.onPopupChanged doesn't reach startExit. + Qt.callLater(() => { + if (!win.exiting && !win._isDestroying) + startExit(); + }); + } + visible: !_finalized WlrLayershell.layer: { const shouldUseOverlay = notificationData && (SettingsData.notificationOverlayEnabled || notificationData.urgency === NotificationUrgency.Critical); @@ -1007,8 +1020,7 @@ PanelWindow { z: 15 onClicked: { - if (notificationData && !win.exiting) - notificationData.popup = false; + dismissPopupReliably(); } } @@ -1080,8 +1092,7 @@ PanelWindow { onClicked: { if (modelData && modelData.invoke) modelData.invoke(); - if (notificationData && !win.exiting) - notificationData.popup = false; + dismissPopupReliably(); } } } @@ -1163,7 +1174,7 @@ PanelWindow { notificationData.actions[0].invoke(); NotificationService.dismissNotification(notificationData); } else { - notificationData.popup = false; + dismissPopupReliably(); } } }