pragma Singleton pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Io import Quickshell.Services.Notifications import Caelestia import Caelestia.Config import qs.components.misc import qs.services import qs.utils Singleton { id: root property list list: [] readonly property list notClosed: list.filter(n => !n.closed) readonly property list popups: list.filter(n => n.popup) property alias dnd: props.dnd property bool loaded function hasFullscreen(): bool { for (const monitor of Hypr.monitors.values) { if (monitor?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1)) return true; } return false; } function shouldShowPopup(): bool { if (props.dnd || [...Visibilities.screens.values()].some(v => v.sidebar)) return false; if (Config.notifs.fullscreen === "off" && hasFullscreen()) return false; return true; } onDndChanged: { if (!Config.utilities.toasts.dndChanged) return; if (dnd) Toaster.toast(qsTr("Do not disturb enabled"), qsTr("Popup notifications are now disabled"), "do_not_disturb_on"); else Toaster.toast(qsTr("Do not disturb disabled"), qsTr("Popup notifications are now enabled"), "do_not_disturb_off"); } onListChanged: { if (loaded) saveTimer.restart(); } Timer { id: saveTimer interval: 1000 onTriggered: storage.setText(JSON.stringify(root.notClosed.map(n => ({ time: n.time, id: n.id, summary: n.summary, body: n.body, appIcon: n.appIcon, appName: n.appName, image: n.image, expireTimeout: n.expireTimeout, urgency: n.urgency, resident: n.resident, hasActionIcons: n.hasActionIcons, actions: n.actions })))) } PersistentProperties { id: props property bool dnd reloadableId: "notifs" } NotificationServer { id: server keepOnReload: false actionsSupported: true bodyHyperlinksSupported: true bodyImagesSupported: true bodyMarkupSupported: true imageSupported: true persistenceSupported: true onNotification: notif => { notif.tracked = true; const comp = notifComp.createObject(root, { popup: root.shouldShowPopup(), notification: notif }); root.list = [comp, ...root.list]; } } FileView { id: storage printErrors: false path: `${Paths.state}/notifs.json` onLoaded: { const data = JSON.parse(text()); for (const notif of data) root.list.push(notifComp.createObject(root, notif)); root.list.sort((a, b) => b.time - a.time); root.loaded = true; } onLoadFailed: err => { if (err === FileViewError.FileNotFound) { root.loaded = true; Qt.callLater(() => setText("[]")); } } } // qmllint disable unresolved-type CustomShortcut { // qmllint enable unresolved-type name: "clearNotifs" description: "Clear all notifications" onPressed: { for (const notif of root.list.slice()) notif.close(); } } IpcHandler { function clear(): void { for (const notif of root.list.slice()) notif.close(); } function isDndEnabled(): bool { return props.dnd; } function toggleDnd(): void { props.dnd = !props.dnd; } function enableDnd(): void { props.dnd = true; } function disableDnd(): void { props.dnd = false; } target: "notifs" } Component { id: notifComp NotifData {} } }