nova-shell/modules/NotifService.qml

152 lines
4.3 KiB
QML

pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Notifications
import "." as M
QtObject {
id: root
property var list: []
property bool dnd: false
readonly property var active: list.filter(n => !n.closed)
readonly property var popups: list.filter(n => n.popup && !n.closed)
readonly property int count: active.length
function dismiss(notifId) {
const n = list.find(n => n.id === notifId);
if (n) {
n.popup = false;
n.closed = true;
n.notification?.dismiss();
_changed();
}
}
function dismissAll() {
for (const n of list.slice()) {
n.popup = false;
n.closed = true;
n.notification?.dismiss();
}
_changed();
}
function dismissPopup(notifId) {
const n = list.find(n => n.id === notifId);
if (n) {
n.popup = false;
_changed();
}
}
function toggleDnd() {
dnd = !dnd;
}
function _changed() {
list = list.slice();
_saveTimer.restart();
}
property NotificationServer _server: NotificationServer {
actionsSupported: true
bodyMarkupSupported: true
imageSupported: true
persistenceSupported: true
keepOnReload: false
onNotification: notif => {
notif.tracked = true;
const data = {
id: notif.id,
summary: notif.summary,
body: notif.body,
appName: notif.appName,
appIcon: notif.appIcon,
image: notif.image,
urgency: notif.urgency,
actions: notif.actions ? notif.actions.map(a => ({
identifier: a.identifier,
text: a.text,
invoke: () => a.invoke()
})) : [],
time: Date.now(),
popup: !root.dnd,
closed: false,
notification: notif
};
root.list = [data, ...root.list];
// Dismiss excess popups (keep in history, just hide popup)
const max = M.Modules.notifications.maxPopups || 4;
const currentPopups = root.list.filter(n => n.popup && !n.closed);
if (currentPopups.length > max) {
for (let i = max; i < currentPopups.length; i++)
currentPopups[i].popup = false;
root._changed();
}
// Auto-expire popup
if (data.popup) {
const timeout = notif.expireTimeout > 0 ? notif.expireTimeout : (M.Modules.notifications.timeout || 3000);
Qt.callLater(() => {
_expireTimer.createObject(root, {
_notifId: data.id,
interval: timeout
});
});
}
}
}
property Component _expireTimer: Component {
Timer {
property string _notifId
running: true
onTriggered: {
root.dismissPopup(_notifId);
destroy();
}
}
}
// Persistence
property Timer _saveTimer: Timer {
interval: 1000
onTriggered: {
const data = root.active.map(n => ({
id: n.id,
summary: n.summary,
body: n.body,
appName: n.appName,
appIcon: n.appIcon,
image: n.image,
urgency: n.urgency,
time: n.time
}));
_storage.setText(JSON.stringify(data));
}
}
property FileView _storage: FileView {
path: (Quickshell.env("XDG_STATE_HOME") || (Quickshell.env("HOME") + "/.local/state")) + "/nova-shell/notifs.json"
onLoaded: {
try {
const data = JSON.parse(text());
for (const n of data) {
n.popup = false;
n.closed = false;
n.notification = null;
n.actions = [];
}
root.list = data.concat(root.list);
} catch (e) {}
}
}
}