pragma Singleton pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Services.Notifications import qs.Widgets import qs.Services Singleton { id: notificationRoot readonly property var notificationServer: notificationServer readonly property var trackedNotifications: notificationServer.trackedNotifications readonly property var notificationsNumber: notificationServer.trackedNotifications.values.length property bool notificationsMuted: HyprlandService.hasFullscreen || HyprlandService.isScreencasting property ListModel globalList: ListModel {} NotificationServer { id: notificationServer imageSupported: true } Connections { target: notificationServer function onNotification(notif) { if(notif.transient) return; if (notif.body === "MediaOngoingActivity") return; notif.tracked = true; notificationRoot.addNotification(globalList, notif); if (notif.lastGeneration) return; // Use the refactored helper notificationRoot.addNotification(notificationList, notif); } } ListModel { id: notificationList } /** * @param {ListModel} model * @param {var} targetNotif */ function removeNotification(model, targetNotif) { if (!model || typeof model.remove !== "function") { console.warn("removeNotification(): invalid model"); return; } for (let i = 0; i < model.count; i++) { if (model.get(i).notif === targetNotif) { model.remove(i); break; } } } /** * @param {ListModel} model * @param {var} notif */ function addNotification(model, notif) { if (!model || typeof model.append !== "function") { console.warn("addNotification(): invalid model"); return; } // Avoid duplicates for (let i = 0; i < model.count; i++) { if (model.get(i).notif === notif) return; } model.append({ notif }); notif.closed.connect(function (reason) { removeNotification(model, notif); }); } // function notificationDismiss(notif) { // removeNotification(notificationList, notif); // removeNotification(globalList, notif); // notif.dismiss(); // } function notificationDismiss(notif) { removeNotification(notificationList, notif); removeNotification(globalList, notif); if (notif && typeof notif.dismiss === "function") notif.dismiss(); // dismiss first } LazyLoader { id: popupLoader active: notificationList.count && !notificationRoot.notificationsMuted component: PanelWindow { id: notificationWindow screen: { Quickshell.screens.filter(screen => screen.name == HyprlandService.focusedMon.name)[0]; } anchors.top: true margins.top: screen.height / 100 exclusiveZone: 0 implicitWidth: 400 implicitHeight: screen.height color: "transparent" mask: Region { item: listView } ListView { id: listView model: notificationList implicitWidth: parent.width implicitHeight: contentHeight orientation: ListView.Vertical clip: true spacing: 5 interactive: false delegate: NotificationWrapper { id: notifWrapper required property var modelData notification: modelData implicitWidth: listView.width startTimer: NotificationUrgency.toString(notification?.urgency) === "Critical"? false: true timerDuration: 5000 onDismissed: { if (notification && typeof notification.dismiss === "function") notificationRoot.notificationDismiss(notification); } onTimedout: { notificationRoot.removeNotification(notificationList, notification) } } Component.onCompleted: positionViewAtEnd() } } } }