nosignal-shell/services/Notifs.qml
2026-04-09 21:56:51 +10:00

166 lines
4.2 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Notifications
import Caelestia
import qs.components.misc
import qs.services
import qs.config
import qs.utils
Singleton {
id: root
property list<NotifData> list: []
readonly property list<NotifData> notClosed: list.filter(n => !n.closed)
readonly property list<NotifData> 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 {}
}
}