aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/notifications/NotificationsPage.qml
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-19 09:20:57 +0200
committerDanilo M. <danix@danix.xyz>2026-10-19 09:20:57 +0200
commitf6d518ba241f4d410f1ee6efd210a94a6e6585ae (patch)
tree9a09372f9259adfef01a21f149845d938b7aa6de /desktop/modules/notifications/NotificationsPage.qml
parenteb7629c5804b75d99be717a3f401f0c15adb9029 (diff)
downloadquickshell-f6d518ba241f4d410f1ee6efd210a94a6e6585ae.tar.gz
quickshell-f6d518ba241f4d410f1ee6efd210a94a6e6585ae.zip
desktop: notifications settings page
Edits notifyd's config.json as a draft behind the SaveBar and runs notifyctl reload after the write; a stopped daemon is reported as saved but not applied, since it reads the file at its next start. Muting offers the apps already seen in the queue and history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/modules/notifications/NotificationsPage.qml')
-rw-r--r--desktop/modules/notifications/NotificationsPage.qml176
1 files changed, 176 insertions, 0 deletions
diff --git a/desktop/modules/notifications/NotificationsPage.qml b/desktop/modules/notifications/NotificationsPage.qml
new file mode 100644
index 0000000..b85a6d7
--- /dev/null
+++ b/desktop/modules/notifications/NotificationsPage.qml
@@ -0,0 +1,176 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+import Quickshell
+import QtQuick
+import "../.."
+import "../appearance"
+import "../qtmaildir"
+
+// The rows of the notifications settings page. Everything edits mod.draft.
+Column {
+ id: pg
+ // Not "nm": a property named like the injecting id binds to itself.
+ required property QtObject mod
+ readonly property var d: mod.draft
+ spacing: 8
+
+ function setTimeout(key, v) {
+ pg.mod.edit({ timeouts: Object.assign({}, pg.d.timeouts, { [key]: v }) });
+ }
+
+ Heading { text: "Timeouts" }
+ Text {
+ width: pg.width; wrapMode: Text.Wrap
+ text: "Seconds a balloon stays when the app leaves it to the daemon. An app's own timeout still wins."
+ color: Theme.overlay
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
+ }
+ Repeater {
+ model: [["low", "Low"], ["normal", "Normal"], ["critical", "Critical"]]
+ Row {
+ required property var modelData
+ spacing: 10
+ Text {
+ width: 90; anchors.verticalCenter: parent.verticalCenter
+ text: modelData[1]; color: Theme.text
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ }
+ Field {
+ anchors.verticalCenter: parent.verticalCenter
+ visible: pg.d.timeouts[modelData[0]] !== 0
+ value: String(pg.d.timeouts[modelData[0]])
+ placeholder: "seconds"
+ onEdited: { const n = parseInt(value, 10); if (n > 0 && n <= 86400) pg.setTimeout(modelData[0], n); }
+ }
+ Toggle {
+ anchors.verticalCenter: parent.verticalCenter
+ label: "never"
+ checked: pg.d.timeouts[modelData[0]] === 0
+ // Toggle flips itself on click, which drops the binding; put
+ // it back so Discard shows the draft again.
+ onToggled: {
+ const on = checked;
+ checked = Qt.binding(() => pg.d.timeouts[modelData[0]] === 0);
+ pg.setTimeout(modelData[0], on ? 0 : 10);
+ }
+ }
+ }
+ }
+
+ Heading { text: "Limits" }
+ Repeater {
+ model: [["liveCap", "On screen"], ["historyCap", "History"]]
+ Row {
+ required property var modelData
+ spacing: 10
+ Text {
+ width: 90; anchors.verticalCenter: parent.verticalCenter
+ text: modelData[1]; color: Theme.text
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ }
+ Field {
+ anchors.verticalCenter: parent.verticalCenter
+ value: String(pg.d[modelData[0]])
+ border.color: pg.d[modelData[0]] >= 1 && pg.d[modelData[0]] <= 100 ? Qt.alpha(Theme.text, 0.12) : Theme.red
+ onEdited: pg.mod.edit({ [modelData[0]]: parseInt(value, 10) || 0 })
+ }
+ }
+ }
+
+ Heading { text: "Placement" }
+ Flow {
+ width: pg.width; spacing: 6
+ Repeater {
+ model: Quickshell.screens.map(s => s.name)
+ Button {
+ required property string modelData
+ text: modelData
+ border.color: pg.d.balloons.screen === modelData ? Theme.accent : Qt.alpha(Theme.text, 0.2)
+ onClicked: pg.mod.edit({ balloons: Object.assign({}, pg.d.balloons, { screen: modelData }) })
+ }
+ }
+ }
+ // The four corners as a miniature screen: click the one balloons go to.
+ Grid {
+ columns: 2; spacing: 4
+ Repeater {
+ model: ["top-left", "top-right", "bottom-left", "bottom-right"]
+ Rectangle {
+ required property string modelData
+ width: 60; height: 34; radius: 5
+ color: pg.d.balloons.corner === modelData ? Qt.alpha(Theme.accent, 0.6) : Qt.alpha(Theme.surface, 0.6)
+ border.width: 1; border.color: Qt.alpha(Theme.text, 0.15)
+ MouseArea {
+ anchors.fill: parent; cursorShape: Qt.PointingHandCursor
+ onClicked: pg.mod.edit({ balloons: Object.assign({}, pg.d.balloons, { corner: modelData }) })
+ }
+ }
+ }
+ }
+
+ Heading { text: "Suppression" }
+ Toggle {
+ label: "Critical notifications still pop up under Do Not Disturb"
+ checked: pg.d.dndPassCritical
+ onToggled: {
+ const on = checked;
+ checked = Qt.binding(() => pg.d.dndPassCritical);
+ pg.mod.edit({ dndPassCritical: on });
+ }
+ }
+ Text {
+ width: pg.width; wrapMode: Text.Wrap
+ text: "Muted apps get no balloon. They still appear in the drawer and in history."
+ color: Theme.overlay
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
+ }
+ Flow {
+ width: pg.width; spacing: 6
+ Repeater {
+ model: pg.d.muted
+ Button {
+ required property string modelData
+ text: modelData; icon: ""
+ onClicked: pg.mod.edit({ muted: pg.d.muted.filter(a => a !== modelData) })
+ }
+ }
+ }
+ // Apps seen in the queue and history, not yet muted, one click to add.
+ Flow {
+ width: pg.width; spacing: 6
+ Repeater {
+ model: [...new Set(Notify.queue.concat(Notify.history).map(n => n.app))]
+ .filter(a => a && pg.d.muted.indexOf(a) < 0).sort()
+ Button {
+ required property string modelData
+ text: modelData; icon: ""
+ onClicked: pg.mod.edit({ muted: pg.d.muted.concat([modelData]) })
+ }
+ }
+ }
+ Field {
+ width: 200
+ placeholder: "mute another app by name"
+ onEdited: {
+ const a = value.trim();
+ if (a !== "" && pg.d.muted.indexOf(a) < 0) pg.mod.edit({ muted: pg.d.muted.concat([a]) });
+ value = "";
+ }
+ }
+
+ Text {
+ width: pg.width; wrapMode: Text.Wrap
+ visible: pg.mod.notice !== ""
+ text: pg.mod.notice
+ color: pg.mod.notice === "saved" ? Theme.green : Theme.yellow
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ }
+}