pragma Singleton import QtQml import Quickshell import Quickshell.Services.Notifications Singleton { id: root readonly property bool active: !lockscreenActive && !displayInhibited property bool lockscreenActive: false property bool displayInhibited: false property alias trackedNotifications: server.trackedNotifications readonly property var groups: { function matchesGroupKey(notif, groupKey) { var matches = true; for (const prop in groupKey.test) { if (notif[prop] !== groupKey.test[prop]) { matches = false; break; } } return matches; } var groups = new Map(); var notifs = new Array(); for (const [ix, notif] of server.trackedNotifications.values.entries()) { var didGroup = false; for (const groupKey of root.groupKeys) { if (!matchesGroupKey(notif, groupKey)) continue; const key = JSON.stringify({ "key": groupKey, "values": Object.assign({}, ...(Array.from(groupKey["group-by"]).map(prop => { var res = {}; res[prop] = notif[prop]; return res; }))) }); if (!groups.has(key)) groups.set(key, new Array()); groups.get(key).push({ "ix": ix, "notif": notif }); didGroup = true; break; } if (!didGroup) notifs.push([{ "ix": ix, "notif": notif }]); } notifs.push(...groups.values()); notifs.sort((as, bs) => Math.min(...(as.map(o => o.ix))) - Math.min(...(bs.map(o => o.ix)))); return notifs.map(ns => ns.map(n => n.notif)); } property var groupKeys: [ { "test": { "appName": "Element" }, "group-by": [ "summary" ] } ]; property var history: [] Component { id: expirationTimer QtObject { id: timer required property QtObject parent required property int expirationTime property list data: [ Timer { running: root.active interval: timer.expirationTime onTriggered: timer.parent.expire() } ] } } Component { id: notificationLock RetainableLock {} } readonly property SystemClock clock: SystemClock { precision: SystemClock.Minutes } function formatTime(time) { const now = root.clock.date; const diff = now - time; const minutes = Math.ceil(diff / 60000); const hours = Math.floor(minutes / 60); if (hours < 1) { if (minutes < 1) return "now"; if (minutes == 1) return "1 minute"; return `${minutes} minutes`; } const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()) const timeDate = new Date(time.getFullYear(), time.getMonth(), time.getDate()) const days = Math.floor((nowDate - timeDate) / (1000 * 86400)) const timeStr = time.toLocaleTimeString(Qt.locale(), "HH:mm"); if (days === 0) return timeStr; if (days === 1) return `yesterday ${timeStr}`; const dateStr = time.toLocaleTimeString(Qt.locale(), "YYYY-MM-DD"); return `${dateStr} ${timeStr}`; } NotificationServer { id: server bodySupported: true actionsSupported: true actionIconsSupported: true imageSupported: true bodyMarkupSupported: true bodyImagesSupported: true onNotification: notification => { var timeout = notification.expireTimeout * 1000; if (notification.appName == "poweralertd") timeout = 2000; if (timeout > 0) { Object.defineProperty(notification, "expirationTimer", { configurable: true, enumerable: true, writable: true }); notification.expirationTimer = expirationTimer.createObject(notification, { parent: notification, expirationTime: timeout }); } Object.defineProperty(notification, "receivedTime", { configurable: true, enumerable: true, writable: true }); notification.receivedTime = root.clock.date; notification.closed.connect((reason) => server.onNotificationClosed(notification, reason)); notification.tracked = true; } function onNotificationClosed(notification, reason) { root.history.push({ lock: notificationLock.createObject(root, { locked: true, object: notification }), notification: notification }); } } }