dots-hyprland/.config/quickshell/services/Notifications.qml
2025-04-21 23:29:31 +02:00

143 lines
4.6 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Notifications
import Qt.labs.platform
Singleton {
id: root
property var filePath: `${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/notifications/notifications.json`
property var list: []
// Quickshell's notification IDs starts at 1 on each run, while saved notifications
// can already contain higher IDs. This is a workaround to avoid id collisions
property int idOffset
signal initDone();
signal notify(notification: var);
signal discard(id: var);
signal discardAll();
signal timeout(id: var);
NotificationServer {
id: notifServer
// actionIconsSupported: true
actionsSupported: true
bodyHyperlinksSupported: true
bodyImagesSupported: true
bodyMarkupSupported: true
bodySupported: true
imageSupported: true
keepOnReload: false
persistenceSupported: true
onNotification: (notification) => {
notification.tracked = true
const newNotifObject = {
"id": notification.id + root.idOffset,
"actions": notification.actions.map((action) => {
return {
"identifier": action.identifier,
"text": action.text,
}
}),
"appIcon": notification.appIcon,
"appName": notification.appName,
"body": notification.body,
"image": notification.image,
"summary": notification.summary,
"time": Date.now(),
"urgency": notification.urgency.toString(),
}
root.list = [...root.list, newNotifObject];
root.notify(newNotifObject);
notifFileView.setText(JSON.stringify(root.list, null, 2))
}
}
function discardNotification(id) {
const index = root.list.findIndex((notif) => notif.id === id);
const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id + root.idOffset === id);
if (index !== -1) {
root.list.splice(index, 1);
notifFileView.setText(JSON.stringify(root.list, null, 2))
triggerListChange()
}
if (notifServerIndex !== -1) {
notifServer.trackedNotifications.values[notifServerIndex].dismiss()
}
root.discard(id);
}
function discardAllNotifications() {
root.list = []
triggerListChange()
notifFileView.setText(JSON.stringify(root.list, null, 2))
notifServer.trackedNotifications.values.forEach((notif) => {
notif.dismiss()
})
root.discardAll();
}
function timeoutNotification(id) {
root.timeout(id);
}
function timeoutAll() {
root.list.forEach((notif) => {
root.timeout(notif.id);
})
}
function attemptInvokeAction(id, notifIdentifier) {
const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id + root.idOffset === id);
if (notifServerIndex !== -1) {
const notifServerNotif = notifServer.trackedNotifications.values[notifServerIndex];
const action = notifServerNotif.actions.find((action) => action.identifier === notifIdentifier);
action.invoke()
}
// else console.log("Notification not found in server: " + id)
root.discard(id);
}
function triggerListChange() {
root.list = root.list.slice(0)
}
function refresh() {
notifFileView.reload()
}
Component.onCompleted: {
refresh()
}
FileView {
id: notifFileView
path: filePath
onLoaded: {
const fileContents = notifFileView.text()
root.list = JSON.parse(fileContents)
// Find largest id
let maxId = 0
root.list.forEach((notif) => {
maxId = Math.max(maxId, notif.id)
})
console.log("[Notifications] File loaded")
root.idOffset = maxId
root.initDone()
}
onLoadFailed: (error) => {
if(error == FileViewError.FileNotFound) {
console.log("[Notifications] File not found, creating new file.")
root.list = []
notifFileView.setText(JSON.stringify(root.list))
} else {
console.log("[Notifications] Error loading file: " + error)
}
}
}
}