diff --git a/quickshell/Services/NotificationService.qml b/quickshell/Services/NotificationService.qml index 0294f3f2..0615b580 100644 --- a/quickshell/Services/NotificationService.qml +++ b/quickshell/Services/NotificationService.qml @@ -19,6 +19,7 @@ Singleton { readonly property string historyFile: Paths.strip(Paths.cache) + "/notification_history.json" readonly property string imageCacheDir: Paths.strip(Paths.cache) + "/notification_images" property bool historyLoaded: false + property int historyEntryCounter: 0 property list notificationQueue: [] property list visibleNotifications: [] @@ -73,6 +74,12 @@ Singleton { onTriggered: root.performSaveHistory() } + function _makeHistoryEntryId(sourceId, timestamp) { + historyEntryCounter += 1; + const safeSource = sourceId && sourceId !== "" ? sourceId : "notification"; + return safeSource + "_" + (timestamp || Date.now()) + "_" + historyEntryCounter; + } + function getImageCachePath(wrapper) { const ts = wrapper.time ? wrapper.time.getTime() : Date.now(); const id = wrapper.notification?.id?.toString() || "0"; @@ -80,12 +87,13 @@ Singleton { } function updateHistoryImage(wrapperId, imagePath) { - const idx = historyList.findIndex(n => n.id === wrapperId); + const idx = historyList.findIndex(n => n.sourceNotificationId === wrapperId || n.id === wrapperId); if (idx < 0) return; const item = historyList[idx]; const updated = { id: item.id, + sourceNotificationId: item.sourceNotificationId || item.id, summary: item.summary, body: item.body, htmlBody: item.htmlBody, @@ -113,8 +121,11 @@ Singleton { } else if (imageUrl && !imageUrl.startsWith("image://qsimage/")) { persistableImage = imageUrl; } + const sourceNotificationId = wrapper.notification?.id?.toString() || ""; + const timestamp = wrapper.time.getTime(); const data = { - id: wrapper.notification?.id?.toString() || Date.now().toString(), + id: _makeHistoryEntryId(sourceNotificationId, timestamp), + sourceNotificationId: sourceNotificationId, summary: wrapper.summary || "", body: wrapper.body || "", htmlBody: wrapper.htmlBody || wrapper.body || "", @@ -122,7 +133,7 @@ Singleton { appIcon: wrapper.appIcon || "", image: persistableImage, urgency: urg, - timestamp: wrapper.time.getTime(), + timestamp: timestamp, desktopEntry: wrapper.desktopEntry || "" }; let newList = [data, ...historyList]; @@ -152,6 +163,8 @@ Singleton { const now = Date.now(); const maxAgeMs = maxAgeDays > 0 ? maxAgeDays * 24 * 60 * 60 * 1000 : 0; const loaded = []; + const seenIds = {}; + let needsRewrite = false; for (const item of historyAdapter.notifications || []) { if (maxAgeMs > 0 && (now - item.timestamp) > maxAgeMs) @@ -162,8 +175,18 @@ Singleton { if (htmlBody) { htmlBody = htmlBody.replace(/]*>/gi, ""); } + const sourceNotificationId = (item.sourceNotificationId || item.id || "").toString(); + let historyId = (item.id || "").toString(); + if (!historyId || seenIds[historyId]) { + historyId = _makeHistoryEntryId(sourceNotificationId, item.timestamp || now); + needsRewrite = true; + } + if (!item.sourceNotificationId) + needsRewrite = true; + seenIds[historyId] = true; loaded.push({ - id: item.id || "", + id: historyId, + sourceNotificationId: sourceNotificationId, summary: item.summary || "", body: body, htmlBody: htmlBody, @@ -177,7 +200,7 @@ Singleton { } historyList = loaded; historyLoaded = true; - if (maxAgeMs > 0 && loaded.length !== (historyAdapter.notifications || []).length) + if ((maxAgeMs > 0 && loaded.length !== (historyAdapter.notifications || []).length) || needsRewrite) saveHistory(); } catch (e) { console.warn("NotificationService: load history failed:", e);