From 3cd68076818d8568324dc681c20c849a6e0f3937 Mon Sep 17 00:00:00 2001 From: Hannes Date: Wed, 26 Aug 2026 00:38:20 +0200 Subject: [PATCH] feat: add notification sound and notification window enhancements. Ready for alpha.NotificationBody needs work --- .gitignore | 3 +- install.sh | 9 ++ .../bar/states/Audio/DefaultSourceState.qml | 12 +- modules/notification/Notification.qml | 61 ++++++--- modules/notification/NotificationBody.qml | 127 +++++++++++------- modules/notification/assets/.gitignore | 0 .../notificationServer/NotificationServer.qml | 69 +++++++--- 7 files changed, 193 insertions(+), 88 deletions(-) create mode 100644 modules/notification/assets/.gitignore diff --git a/.gitignore b/.gitignore index 3c93bdc..b8675e3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ Colors.qml modules/wlogout/scripts/helper.sh config/path/ConfigPath.qml -config/path/WallpaperPath.qml \ No newline at end of file +config/path/WallpaperPath.qml +modules/notification/assets/notification.ogg \ No newline at end of file diff --git a/install.sh b/install.sh index 2237d30..c794a4b 100755 --- a/install.sh +++ b/install.sh @@ -105,6 +105,15 @@ download \ "$SCRIPT_DIR/modules/jl/assets/correct.svg" \ "correct.svg" +# ---------------------------- +# Sounds +# ---------------------------- + +download \ + "https://raw.githubusercontent.com/akx/Notifications/master/OGG/Chord.ogg" \ + "$SCRIPT_DIR/modules/notification/assets/notification.ogg" \ + "notification.ogg" + # ---------------------------- # Nerd Font # ---------------------------- diff --git a/modules/bar/states/Audio/DefaultSourceState.qml b/modules/bar/states/Audio/DefaultSourceState.qml index 8b46d87..a7b8485 100644 --- a/modules/bar/states/Audio/DefaultSourceState.qml +++ b/modules/bar/states/Audio/DefaultSourceState.qml @@ -7,20 +7,20 @@ import Quickshell.Services.Pipewire Item { id: root - PwObjectTracker { - objects: [ Pipewire.defaultAudioSource ] - } - property var audioSource: Pipewire.defaultAudioSource - property bool exists: audioSource != null + property bool exists: audioSource != null && audioSource.ready property bool mute: audioSource?.audio?.muted ?? false property real volume: Math.round((audioSource?.audio?.volume ?? 0) * 1000) / 10 property bool volumeChanged: false + PwObjectTracker { + objects: [ root.audioSource ] + } + Connections { - target: audioSource?.audio + target: root.audioSource?.audio function onVolumeChanged() { root.volumeChanged = true; diff --git a/modules/notification/Notification.qml b/modules/notification/Notification.qml index b018305..b444549 100644 --- a/modules/notification/Notification.qml +++ b/modules/notification/Notification.qml @@ -5,18 +5,24 @@ import "../../config" import "./notificationServer" PanelWindow { - mask: Region {} + // mask: Region {} id: notificationWindow anchors.top: true anchors.left: true - implicitHeight: screen.height/6 - implicitWidth: screen.width/6 + // implicitHeight: screen.height/6 + implicitHeight: contentHeight+(content.spacing*(notificationListModel.count-1)) + implicitWidth: contentWidth + color: "transparent" + ListModel { + id: notificationListModel + } + Connections { target: NotificationS @@ -25,17 +31,11 @@ PanelWindow { return if (NotificationS.correctMonitor(screen)) { console.log("[Notification]: This notification belongs to me!", screen.x, screen.y) - // currentNotifications.push(notification) - // currentNotifications = currentNotifications - currentNotification = notification - currentNotifications = currentNotifications.concat([notification]) + notificationListModel.append({notificationData: notification}) } } } - property var currentNotification: null - property var currentNotifications: [] - Rectangle { anchors.fill: parent @@ -44,22 +44,53 @@ PanelWindow { color: "transparent" Column { + id: content anchors.fill: parent - anchors.margins: 10 - spacing: 5 + spacing: -2 Repeater { - model: currentNotifications + id: notificationRepeater + model: notificationListModel delegate: NotificationBody { required property int index + required property var notificationData - notification: currentNotifications[index] - width: notificationWindow.width + notification: notificationData + + onDismissed: function(notification) { + notificationListModel.remove(index) + notification.dismiss() + console.log("[Notification]: Dismissed Notification:", notification.appName, notification.id, notification.summary, notification.body) + } } + onItemAdded: updateContentSize() + onItemRemoved: updateContentSize() } } } + + property real contentWidth: 0 + property real contentHeight: 0 + + function updateContentSize() { + let width = 0 + let height = 0 + + for (let i = 0; i < notificationRepeater.count; ++i) { + let item = notificationRepeater.itemAt(i) + + if (item) { + width = Math.max(width, item.width) + height += item.height + } + } + + contentWidth = width + contentHeight = height + console.log("[updateContentSize] contentWidth:", contentWidth, "contentHeight:", contentHeight) + } + Component.onCompleted: { console.log( "[Notification]:", diff --git a/modules/notification/NotificationBody.qml b/modules/notification/NotificationBody.qml index 00f9d5a..9be6227 100644 --- a/modules/notification/NotificationBody.qml +++ b/modules/notification/NotificationBody.qml @@ -6,7 +6,84 @@ Rectangle { id: root required property var notification + property real duration: 5000 // Expected in milliseconds (e.g., 5000 for 5s) + signal dismissed(var notification) + + implicitWidth: content.implicitWidth + 2*18 + implicitHeight: content.implicitHeight + 18 + progressBar.height + + color: Qt.alpha(Colors.primaryContainer, 0.75) + border.width: Config.border_width + border.color: Colors.primary + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + + onClicked: { + dismissed(root.notification) + } + + Column { + id: content + anchors.fill: parent + anchors.margins: 10 + spacing: 5 + + Text { + id: appNameText + text: root.notification?.id + ": " + (root.notification?.appName ?? "") + font.family: Config.font + font.pixelSize: 18 + color: Colors.primary + elide: Text.ElideRight + } + + Text { + id: summaryText + text: root.notification?.summary ?? "" + font.family: Config.font + font.pixelSize: 22 + color: Colors.primary + wrapMode: Text.Wrap + } + + Text { + id: bodyText + text: root.notification?.body ?? "" + font.family: Config.font + font.pixelSize: 16 + color: Colors.primary + wrapMode: Text.Wrap + } + } + } + + // Timeout progress bar + Rectangle { + id: progressBar + anchors.bottom: parent.bottom + anchors.left: parent.left + height: 10 + color: Colors.primary + + property real progress: 1.0 + width: root.width * progress + + NumberAnimation on progress { + id: anim + running: root.duration > 0 + from: 1.0 + to: 0.0 + // If your duration property comes in seconds (e.g., 5), change to: (root.duration * 1000) + duration: root.duration + paused: mouseArea.containsMouse + + onFinished: root.dismissed(root.notification) + } + } Component.onCompleted: { console.log( @@ -16,54 +93,4 @@ Rectangle { notification.appName ) } - - implicitWidth: parent ? parent.width : 300 - implicitHeight: content.implicitHeight + 20 - - color: "transparent" - - border.width: Config.border_width - border.color: Colors.primary - - Column { - id: content - - anchors.fill: parent - anchors.margins: 10 - - spacing: 5 - - Text { - width: parent.width - - text: root.notification?.appName ?? "" - font.family: Config.font - font.pixelSize: 18 - color: Colors.primary - - elide: Text.ElideRight - } - - Text { - width: parent.width - - text: root.notification?.summary ?? "" - font.family: Config.font - font.pixelSize: 22 - color: Colors.primary - - wrapMode: Text.Wrap - } - - Text { - width: parent.width - - text: root.notification?.body ?? "" - font.family: Config.font - font.pixelSize: 16 - color: Colors.primary - - wrapMode: Text.Wrap - } - } } \ No newline at end of file diff --git a/modules/notification/assets/.gitignore b/modules/notification/assets/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/modules/notification/notificationServer/NotificationServer.qml b/modules/notification/notificationServer/NotificationServer.qml index 2e678ba..ee3100a 100644 --- a/modules/notification/notificationServer/NotificationServer.qml +++ b/modules/notification/notificationServer/NotificationServer.qml @@ -2,15 +2,16 @@ pragma Singleton import QtQuick import Quickshell +import Quickshell.Io import Quickshell.Hyprland import Quickshell.Services.Notifications +import "../../../config" Item { id: notificationServerState property var focusedMonitor: null property var newestNotification: null - property var trackedNotifications: [] // TODO signal notificationReceived(var notification) @@ -18,33 +19,69 @@ Item { return (Hyprland.monitorFor(screen) === focusedMonitor) } + Process { + id: notificationSound + command: ["pw-play", "--volume", "0.25", Config.configDir + "/modules/notification/assets/notification.ogg"] + function play() { + if (running) + return + console.log( + "[NotificationSound] Playing:", Config.configDir + "/modules/notification/assets/notification.ogg" + ) + notificationSound.startDetached() + } + } + NotificationServer { id: notificationServer onNotification: function(notification) { - if(notification.lastGeneration){ - console.log( - "[NotificationServer] Closing Notification:", - notification.appName, notification.id, notification.summary, notification.body - ) - notification.dismiss() - return - } + // if(notification.lastGeneration){ + // console.log( + // "[NotificationServer] Closing Notification:", + // notification.appName, notification.id, notification.summary, notification.body + // ) + // notification.dismiss() + // return + // } notification.tracked = true console.log( "[NotificationServer] Notification received:", notification.appName, notification.id, notification.summary, notification.body ) - notificationReceived(notification) + + let timer = notificationTimerComponent.createObject( + notificationServerState, + { notification: notification } + ) + Hyprland.refreshMonitors() - waitTimer.start() + timer.start() } } - Timer { - id: waitTimer - interval: 2000 - repeat: false - onTriggered: focusedMonitor = Hyprland.focusedMonitor + Component { + id: notificationTimerComponent + + Timer { + required property var notification + + interval: 2000 + repeat: false + + onTriggered: { + focusedMonitor = Hyprland.focusedMonitor + notificationReceived(notification) + if(!notification.lastGeneration){ + notificationSound.play() + } + + // destroy() + } + } } + Component.onCompleted: { + focusedMonitor = Hyprland.focusedMonitor + console.log("[NotificationServer]: Component completed") + } } \ No newline at end of file