summaryrefslogtreecommitdiffstats
path: root/home-configuration/config/quickshell/modules/notifications
diff options
context:
space:
mode:
Diffstat (limited to 'home-configuration/config/quickshell/modules/notifications')
-rw-r--r--home-configuration/config/quickshell/modules/notifications/NotificationCard.qml257
-rw-r--r--home-configuration/config/quickshell/modules/notifications/NotificationFocus.qml123
-rw-r--r--home-configuration/config/quickshell/modules/notifications/Notifications.qml125
3 files changed, 505 insertions, 0 deletions
diff --git a/home-configuration/config/quickshell/modules/notifications/NotificationCard.qml b/home-configuration/config/quickshell/modules/notifications/NotificationCard.qml
new file mode 100644
index 0000000..983cbd7
--- /dev/null
+++ b/home-configuration/config/quickshell/modules/notifications/NotificationCard.qml
@@ -0,0 +1,257 @@
+import QtQuick
+import QtQuick.Layouts
+import Quickshell.Services.Notifications
+import "../../config.js" as Config
+
+Rectangle {
+ id: root
+
+ required property var notification
+
+ signal activated()
+
+ property bool dismissing: false
+
+ Layout.fillWidth: true
+
+ implicitHeight: body.implicitHeight + 20
+
+ radius: Config.blockRadius
+
+ color: Config.barBackground
+
+ border.width: 1
+ border.color: Config.notificationBorder
+
+ x: dismissing ? Config.notificationSlideDistance : 0
+ opacity: dismissing ? 0 : 1
+
+ Component.onCompleted: {
+ x = Config.notificationSlideDistance
+ opacity = 0
+
+ enterAnimation.start()
+
+ if (notification.urgency !== NotificationUrgency.Critical)
+ dismissTimer.start()
+ }
+
+ Timer {
+ id: dismissTimer
+
+ interval: Config.notificationTimeout
+ repeat: false
+
+ onTriggered: {
+ dismiss()
+ }
+ }
+
+ function dismiss() {
+ if (dismissing)
+ return
+
+ dismissTimer.stop()
+ dismissing = true
+ exitAnimation.start()
+ }
+
+ SequentialAnimation {
+ id: enterAnimation
+
+ ParallelAnimation {
+ NumberAnimation {
+ target: root
+ property: "x"
+ from: Config.notificationSlideDistance
+ to: 0
+ duration: Config.notificationAnimationDuration
+ easing.type: Easing.OutCubic
+ }
+
+ NumberAnimation {
+ target: root
+ property: "opacity"
+ from: 0
+ to: 1
+ duration: Config.notificationAnimationDuration
+ easing.type: Easing.OutCubic
+ }
+ }
+ }
+
+ SequentialAnimation {
+ id: exitAnimation
+
+ ParallelAnimation {
+ NumberAnimation {
+ target: root
+ property: "x"
+ to: Config.notificationSlideDistance
+ duration: Config.notificationAnimationDuration
+ easing.type: Easing.InCubic
+ }
+
+ NumberAnimation {
+ target: root
+ property: "opacity"
+ to: 0
+ duration: Config.notificationAnimationDuration
+ easing.type: Easing.InCubic
+ }
+ }
+
+ ScriptAction {
+ script: root.notification.expire()
+ }
+ }
+
+ ColumnLayout {
+ id: body
+
+ anchors.fill: parent
+ anchors.margins: 10
+
+ spacing: 6
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Image {
+ visible: root.notification.appIcon !== ""
+
+ source: root.notification.appIcon
+
+ sourceSize.width: 24
+ sourceSize.height: 24
+
+ Layout.preferredWidth: 24
+ Layout.preferredHeight: 24
+
+ fillMode: Image.PreserveAspectFit
+ asynchronous: true
+ }
+
+ Text {
+ Layout.fillWidth: true
+
+ text: root.notification.appName || "Notification"
+
+ color: Config.textPrimary
+
+ font.family: Config.fontFamily
+ font.pixelSize: 12
+ font.bold: true
+
+ elide: Text.ElideRight
+ }
+
+ Text {
+ text: "×"
+
+ color: "#6c7086"
+
+ font.family: Config.fontFamily
+ font.pixelSize: 18
+ font.bold: true
+
+ MouseArea {
+ anchors.fill: parent
+
+ cursorShape: Qt.PointingHandCursor
+
+ onClicked: {
+ root.dismiss()
+ }
+ }
+ }
+ }
+
+ Text {
+ Layout.fillWidth: true
+
+ text: root.notification.summary
+
+ color: Config.textPrimary
+
+ font.family: Config.fontFamily
+ font.pixelSize: Config.fontSize
+ font.bold: true
+
+ wrapMode: Text.Wrap
+ }
+
+ Text {
+ visible: root.notification.body !== ""
+
+ Layout.fillWidth: true
+
+ text: root.notification.body
+
+ color: Config.textSecondary
+
+ font.family: Config.fontFamily
+ font.pixelSize: 12
+
+ wrapMode: Text.Wrap
+
+ maximumLineCount: 4
+ elide: Text.ElideRight
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 3
+
+ radius: 2
+
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0
+ color: Config.accentGreen
+ }
+
+ GradientStop {
+ position: 0.33
+ color: Config.accentYellow
+ }
+
+ GradientStop {
+ position: 0.66
+ color: Config.accentOrange
+ }
+
+ GradientStop {
+ position: 1.0
+ color: Config.accentRed
+ }
+ }
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+
+ cursorShape: Qt.PointingHandCursor
+
+ onClicked: {
+ root.activated()
+ }
+ }
+
+ MouseArea {
+ anchors.right: parent.right
+ anchors.top: parent.top
+
+ width: 36
+ height: 36
+
+ z: 10
+
+ cursorShape: Qt.PointingHandCursor
+
+ onClicked: {
+ root.dismiss()
+ }
+ }
+}
diff --git a/home-configuration/config/quickshell/modules/notifications/NotificationFocus.qml b/home-configuration/config/quickshell/modules/notifications/NotificationFocus.qml
new file mode 100644
index 0000000..af52eaa
--- /dev/null
+++ b/home-configuration/config/quickshell/modules/notifications/NotificationFocus.qml
@@ -0,0 +1,123 @@
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import "../../config.js" as Config
+
+Item {
+ id: root
+
+ property string pendingApp: ""
+
+ Process {
+ id: windowsProcess
+
+ command: ["niri", "msg", "-j", "windows"]
+
+ stdout: StdioCollector {
+ id: windowsOutput
+
+ onStreamFinished: {
+ var windows
+
+ try {
+ windows = JSON.parse(this.text)
+ } catch (error) {
+ console.error(
+ "Failed to parse niri windows:",
+ error
+ )
+
+ root.pendingApp = ""
+ return
+ }
+
+ var app = root.pendingApp.toLowerCase()
+ root.pendingApp = ""
+
+ if (Config.notificationVesktopNames.indexOf(app) !== -1) {
+ for (var i = 0; i < windows.length; i++) {
+ var niriWindow = windows[i]
+ var appId = (niriWindow.app_id || "").toLowerCase()
+
+ if (appId === Config.notificationVesktopAppId) {
+ console.log(
+ "FOCUSING VESKTOP WINDOW:",
+ niriWindow.id,
+ niriWindow.title
+ )
+
+ focusProcess.exec([
+ "niri",
+ "msg",
+ "action",
+ "focus-window",
+ "--id",
+ String(niriWindow.id)
+ ])
+
+ return
+ }
+ }
+
+ console.log(
+ "No matching Niri window found for:",
+ app
+ )
+
+ return
+ }
+
+ for (var j = 0; j < windows.length; j++) {
+ var window = windows[j]
+ var windowAppId = (window.app_id || "").toLowerCase()
+
+ if (
+ windowAppId === app ||
+ windowAppId === app + ".desktop"
+ ) {
+ console.log(
+ "FOCUSING WINDOW:",
+ window.id,
+ window.app_id,
+ window.title
+ )
+
+ focusProcess.exec([
+ "niri",
+ "msg",
+ "action",
+ "focus-window",
+ "--id",
+ String(window.id)
+ ])
+
+ return
+ }
+ }
+
+ console.log(
+ "No matching Niri window found for:",
+ app
+ )
+ }
+ }
+ }
+
+ Process {
+ id: focusProcess
+ }
+
+ function focusSource(notification) {
+ if (!notification)
+ return
+
+ root.pendingApp = notification.appName
+
+ windowsProcess.exec([
+ "niri",
+ "msg",
+ "-j",
+ "windows"
+ ])
+ }
+}
diff --git a/home-configuration/config/quickshell/modules/notifications/Notifications.qml b/home-configuration/config/quickshell/modules/notifications/Notifications.qml
new file mode 100644
index 0000000..2965b90
--- /dev/null
+++ b/home-configuration/config/quickshell/modules/notifications/Notifications.qml
@@ -0,0 +1,125 @@
+import QtQuick
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Wayland
+import Quickshell.Services.Notifications
+import "../../config.js" as Config
+import "./"
+
+Item {
+ id: root
+
+ NotificationServer {
+ id: server
+
+ bodySupported: true
+ imageSupported: true
+ actionsSupported: true
+ actionIconsSupported: true
+ bodyMarkupSupported: true
+
+ onNotification: function(notification) {
+ console.log("NOTIFICATION")
+ console.log(" app:", notification.appName)
+ console.log(" summary:", notification.summary)
+ console.log(" body:", notification.body)
+ console.log(" actions:", notification.actions.length)
+
+ for (var i = 0; i < notification.actions.length; i++) {
+ console.log(
+ " action:",
+ notification.actions[i].identifier,
+ notification.actions[i].text
+ )
+ }
+
+ notification.tracked = true
+ }
+ }
+
+ NotificationFocus {
+ id: focus
+ }
+
+ function activateNotification(notification) {
+ if (!notification)
+ return
+
+ console.log(
+ "ACTIVATE:",
+ notification.appName,
+ notification.summary
+ )
+
+ focus.focusSource(notification)
+
+ for (var i = 0; i < notification.actions.length; i++) {
+ var action = notification.actions[i]
+
+ if (action.identifier === "default") {
+ console.log(
+ "Invoking default action:",
+ action.text
+ )
+
+ action.invoke()
+ return
+ }
+ }
+
+ console.log("No default action available")
+ notification.dismiss()
+ }
+
+ PanelWindow {
+ id: window
+
+ screen: Quickshell.screens.find(function(screen) {
+ return screen.name === Config.barOutput
+ })
+
+ anchors {
+ top: true
+ right: true
+ }
+
+ margins {
+ top: Config.notificationMarginTop
+ right: Config.notificationMarginRight
+ }
+
+ exclusionMode: ExclusionMode.Ignore
+
+ WlrLayershell.layer: WlrLayer.Overlay
+ WlrLayershell.namespace: "notifications"
+
+ color: "transparent"
+
+ implicitWidth: Config.notificationWidth
+ implicitHeight: list.implicitHeight
+
+ ColumnLayout {
+ id: list
+
+ anchors.top: parent.top
+ anchors.right: parent.right
+
+ width: Config.notificationWidth
+ spacing: Config.notificationSpacing
+
+ Repeater {
+ model: server.trackedNotifications
+
+ delegate: NotificationCard {
+ required property var modelData
+
+ notification: modelData
+
+ onActivated: {
+ root.activateNotification(modelData)
+ }
+ }
+ }
+ }
+ }
+}