import QtQuick import Quickshell import Quickshell.Io import Quickshell.Wayland import qs.Commons import qs.Ui import "HistoryModel.js" as HistoryModel Item { id: root property var shell: null property var manifest: null property bool opened: false readonly property string pluginId: (manifest && manifest.id) || "blob.notification-center" readonly property string historyDir: Quickshell.env("HOME") + "/.local/state/blob/notifications/history" property var entries: [] property bool doNotDisturb: false readonly property bool empty: root.entries.length === 0 function open(payloadJson) { root.opened = true root.refresh() } function close() { root.opened = false } function dismiss() { root.opened = false if (root.shell && typeof root.shell.hide === "function") root.shell.hide(root.pluginId) } function refresh() { if (!historyProbe.running) historyProbe.running = true if (!dndProbe.running) dndProbe.running = true } function run(command) { Quickshell.execDetached(["bash", "-c", command]) } function clearAll() { root.run("blob-shell notifications clearHistory") root.entries = [] } function toggleSilence() { root.run("blob-shell notifications toggleDnd") refreshTimer.restart() } function dismissEntry(index) { var entry = root.entries[index] if (!entry) return root.run("blob-notify-dismiss " + entry.id) var next = [] for (var i = 0; i < root.entries.length; i++) if (i !== index) next.push(root.entries[i]) root.entries = next } Process { id: historyProbe command: ["bash", "-c", "cat " + root.historyDir + "/*.json 2>/dev/null || true"] stdout: StdioCollector { onStreamFinished: root.entries = HistoryModel.parseHistory(text) } } Process { id: dndProbe command: ["bash", "-c", "blob-shell notifications dndState 2>/dev/null"] stdout: StdioCollector { onStreamFinished: root.doNotDisturb = String(text).trim() === "on" } } Timer { id: refreshTimer interval: 250 onTriggered: root.refresh() } Timer { interval: 5000 running: root.opened repeat: true onTriggered: root.refresh() } PanelWindow { visible: root.opened anchors { top: true; bottom: true; left: true; right: true } color: "transparent" WlrLayershell.namespace: "blob-notification-center" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.None exclusionMode: ExclusionMode.Ignore MouseArea { anchors.fill: parent onClicked: root.dismiss() } Card { anchors.top: parent.top anchors.right: parent.right anchors.topMargin: Style.spaceReal(10) anchors.rightMargin: Style.spaceReal(10) implicitWidth: Style.spaceReal(440) implicitHeight: Style.spaceReal(420) fillColor: Util.alpha(Color.background, Style.panelFillAlpha) spacing: Style.spaceReal(10) Item { width: parent.width implicitHeight: titleText.implicitHeight Text { id: titleText anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter text: "Notifications" textFormat: Text.PlainText color: Color.foreground font.family: Style.font.family font.pixelSize: Style.font.body font.bold: true } Row { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter spacing: Style.spaceReal(4) CardIconButton { icon: root.doNotDisturb ? "" : "" active: root.doNotDisturb onActivated: root.toggleSilence() } CardIconButton { icon: "" onActivated: root.clearAll() } } } ListView { width: parent.width height: Style.spaceReal(340) clip: true spacing: Style.spaceReal(8) visible: !root.empty model: root.entries delegate: NotificationItem { required property var modelData required property int index width: ListView.view.width app: modelData.app summary: modelData.summary body: modelData.body onDismissRequested: root.dismissEntry(index) } } Column { width: parent.width visible: root.empty spacing: Style.spaceReal(8) Text { anchors.horizontalCenter: parent.horizontalCenter text: "" textFormat: Text.PlainText color: Util.alpha(Color.blue, 0.6) font.family: Style.font.family font.pixelSize: Style.font.heading } Text { anchors.horizontalCenter: parent.horizontalCenter text: "No notifications" textFormat: Text.PlainText color: Util.alpha(Color.foreground, 0.6) font.family: Style.font.family font.pixelSize: Style.font.bodySmall } } } } }