Files
DeltaruneQuickshell/Shell/Notifications/NotificationModel.qml
2026-03-02 19:28:15 +02:00

212 lines
6.9 KiB
QML

import QtQuick
import Quickshell.Services.Notifications
QtObject {
id: root
property ListModel notifications: ListModel {}
property var groupedNotifications: []
property var groupExpansion: ({})
property int versionCounter: 0
function nextVersion() {
versionCounter += 1;
return versionCounter;
}
function indexOfId(notificationId) {
for (let i = 0; i < notifications.count; i++) {
const row = notifications.get(i);
if (row.rowNotificationId === notificationId)
return i;
}
return -1;
}
function normalizedString(value) {
if (value === undefined || value === null)
return "";
return String(value);
}
function hintString(notificationObject, name) {
if (!notificationObject || !notificationObject.hints)
return "";
const value = notificationObject.hints[name];
return value === undefined || value === null ? "" : String(value);
}
function groupKeyFor(notificationObject) {
const desktopEntry = normalizedString(hintString(notificationObject, "desktop-entry")).trim().toLowerCase();
if (desktopEntry.length > 0)
return "desktop:" + desktopEntry;
const appName = normalizedString(notificationObject ? notificationObject.appName : "").trim().toLowerCase();
if (appName.length > 0)
return "app:" + appName;
const appIcon = normalizedString(notificationObject ? notificationObject.appIcon : "").trim().toLowerCase();
if (appIcon.length > 0)
return "icon:" + appIcon;
const image = normalizedString(notificationObject ? notificationObject.image : "").trim().toLowerCase();
if (image.length > 0)
return "image:" + image;
return "id:" + String(notificationObject ? notificationObject.id : Date.now());
}
function sortRowsNewestFirst(left, right) {
return (right.createdAt || 0) - (left.createdAt || 0);
}
function groupSortNewestFirst(left, right) {
return (right.newestAt || 0) - (left.newestAt || 0);
}
function rebuildGroups() {
const grouped = ({});
for (let i = 0; i < notifications.count; i++) {
const row = notifications.get(i);
const key = row.rowGroupKey;
if (!grouped[key]) {
grouped[key] = {
groupKey: key,
appName: row.rowAppName,
appIcon: row.rowAppIcon,
desktopEntry: row.rowDesktopEntry,
newestAt: row.rowCreatedAt,
notifications: []
};
}
grouped[key].notifications.push({
notificationId: row.rowNotificationId,
notifObject: row.rowObject,
notifVersion: row.rowVersion,
createdAt: row.rowCreatedAt,
appName: row.rowAppName,
appIcon: row.rowAppIcon,
image: row.rowImage
});
if (row.rowCreatedAt > grouped[key].newestAt)
grouped[key].newestAt = row.rowCreatedAt;
}
const groups = [];
for (const groupKey in grouped) {
if (!grouped.hasOwnProperty(groupKey))
continue;
const group = grouped[groupKey];
group.notifications.sort(sortRowsNewestFirst);
groups.push(group);
}
groups.sort(groupSortNewestFirst);
groupedNotifications = groups;
}
function groupIsExpanded(groupKey) {
return Boolean(groupExpansion[groupKey]);
}
function setGroupExpanded(groupKey, expanded) {
const next = Object.assign({}, groupExpansion);
next[groupKey] = Boolean(expanded);
groupExpansion = next;
}
function toggleGroupExpanded(groupKey) {
setGroupExpanded(groupKey, !groupIsExpanded(groupKey));
}
function addNotification(notificationObject) {
if (notificationObject.lastGeneration) {
notificationObject.dismiss();
return;
}
notificationObject.tracked = true;
const idCopy = notificationObject.id;
const objectCopy = notificationObject;
notificationObject.closed.connect(function () {
const rowIndex = indexOfId(idCopy);
if (rowIndex >= 0 && notifications.get(rowIndex).rowObject === objectCopy) {
notifications.remove(rowIndex);
rebuildGroups();
}
});
const nowMs = Date.now();
const groupKey = groupKeyFor(notificationObject);
const existingIndex = indexOfId(notificationObject.id);
if (existingIndex >= 0) {
notifications.set(existingIndex, {
rowNotificationId: notificationObject.id,
rowObject: notificationObject,
rowVersion: nextVersion(),
rowCreatedAt: nowMs,
rowGroupKey: groupKey,
rowAppName: normalizedString(notificationObject.appName),
rowAppIcon: normalizedString(notificationObject.appIcon),
rowImage: normalizedString(notificationObject.image),
rowDesktopEntry: hintString(notificationObject, "desktop-entry")
});
rebuildGroups();
return;
}
notifications.append({
rowNotificationId: notificationObject.id,
rowObject: notificationObject,
rowVersion: nextVersion(),
rowCreatedAt: nowMs,
rowGroupKey: groupKey,
rowAppName: normalizedString(notificationObject.appName),
rowAppIcon: normalizedString(notificationObject.appIcon),
rowImage: normalizedString(notificationObject.image),
rowDesktopEntry: hintString(notificationObject, "desktop-entry")
});
rebuildGroups();
}
function closeById(notificationId, reason) {
const rowIndex = indexOfId(notificationId);
if (rowIndex < 0)
return;
const row = notifications.get(rowIndex);
const notifObject = row.rowObject;
if (notifObject) {
if (reason === "timeout") {
notifObject.expire();
} else if (reason === "click" || reason === "dismiss" || reason === "drag") {
notifObject.dismiss();
}
}
notifications.remove(rowIndex);
rebuildGroups();
}
property NotificationServer server: NotificationServer {
keepOnReload: false
bodySupported: true
bodyMarkupSupported: false
bodyHyperlinksSupported: false
actionsSupported: true
// NotificationServer is Quickshell's DBus implementation for
// org.freedesktop.Notifications, so this is the notification source.
onNotification: function (notificationObject) {
root.addNotification(notificationObject);
}
}
}