feat: add notification sound and notification window enhancements. Ready for alpha.NotificationBody needs work

This commit is contained in:
Hannes
2026-08-26 00:38:20 +02:00
parent 0b20e8eb85
commit 3cd6807681
7 changed files with 193 additions and 88 deletions
+2 -1
View File
@@ -2,4 +2,5 @@
Colors.qml Colors.qml
modules/wlogout/scripts/helper.sh modules/wlogout/scripts/helper.sh
config/path/ConfigPath.qml config/path/ConfigPath.qml
config/path/WallpaperPath.qml config/path/WallpaperPath.qml
modules/notification/assets/notification.ogg
+9
View File
@@ -105,6 +105,15 @@ download \
"$SCRIPT_DIR/modules/jl/assets/correct.svg" \ "$SCRIPT_DIR/modules/jl/assets/correct.svg" \
"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 # Nerd Font
# ---------------------------- # ----------------------------
@@ -7,20 +7,20 @@ import Quickshell.Services.Pipewire
Item { Item {
id: root id: root
PwObjectTracker {
objects: [ Pipewire.defaultAudioSource ]
}
property var audioSource: 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 bool mute: audioSource?.audio?.muted ?? false
property real volume: Math.round((audioSource?.audio?.volume ?? 0) * 1000) / 10 property real volume: Math.round((audioSource?.audio?.volume ?? 0) * 1000) / 10
property bool volumeChanged: false property bool volumeChanged: false
PwObjectTracker {
objects: [ root.audioSource ]
}
Connections { Connections {
target: audioSource?.audio target: root.audioSource?.audio
function onVolumeChanged() { function onVolumeChanged() {
root.volumeChanged = true; root.volumeChanged = true;
+46 -15
View File
@@ -5,18 +5,24 @@ import "../../config"
import "./notificationServer" import "./notificationServer"
PanelWindow { PanelWindow {
mask: Region {} // mask: Region {}
id: notificationWindow id: notificationWindow
anchors.top: true anchors.top: true
anchors.left: true anchors.left: true
implicitHeight: screen.height/6 // implicitHeight: screen.height/6
implicitWidth: screen.width/6 implicitHeight: contentHeight+(content.spacing*(notificationListModel.count-1))
implicitWidth: contentWidth
color: "transparent" color: "transparent"
ListModel {
id: notificationListModel
}
Connections { Connections {
target: NotificationS target: NotificationS
@@ -25,17 +31,11 @@ PanelWindow {
return return
if (NotificationS.correctMonitor(screen)) { if (NotificationS.correctMonitor(screen)) {
console.log("[Notification]: This notification belongs to me!", screen.x, screen.y) console.log("[Notification]: This notification belongs to me!", screen.x, screen.y)
// currentNotifications.push(notification) notificationListModel.append({notificationData: notification})
// currentNotifications = currentNotifications
currentNotification = notification
currentNotifications = currentNotifications.concat([notification])
} }
} }
} }
property var currentNotification: null
property var currentNotifications: []
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -44,22 +44,53 @@ PanelWindow {
color: "transparent" color: "transparent"
Column { Column {
id: content
anchors.fill: parent anchors.fill: parent
anchors.margins: 10 spacing: -2
spacing: 5
Repeater { Repeater {
model: currentNotifications id: notificationRepeater
model: notificationListModel
delegate: NotificationBody { delegate: NotificationBody {
required property int index required property int index
required property var notificationData
notification: currentNotifications[index] notification: notificationData
width: notificationWindow.width
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: { Component.onCompleted: {
console.log( console.log(
"[Notification]:", "[Notification]:",
+77 -50
View File
@@ -6,7 +6,84 @@ Rectangle {
id: root id: root
required property var notification 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: { Component.onCompleted: {
console.log( console.log(
@@ -16,54 +93,4 @@ Rectangle {
notification.appName 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
}
}
} }
@@ -2,15 +2,16 @@ pragma Singleton
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io
import Quickshell.Hyprland import Quickshell.Hyprland
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
import "../../../config"
Item { Item {
id: notificationServerState id: notificationServerState
property var focusedMonitor: null property var focusedMonitor: null
property var newestNotification: null property var newestNotification: null
property var trackedNotifications: [] // TODO
signal notificationReceived(var notification) signal notificationReceived(var notification)
@@ -18,33 +19,69 @@ Item {
return (Hyprland.monitorFor(screen) === focusedMonitor) 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 { NotificationServer {
id: notificationServer id: notificationServer
onNotification: function(notification) { onNotification: function(notification) {
if(notification.lastGeneration){ // if(notification.lastGeneration){
console.log( // console.log(
"[NotificationServer] Closing Notification:", // "[NotificationServer] Closing Notification:",
notification.appName, notification.id, notification.summary, notification.body // notification.appName, notification.id, notification.summary, notification.body
) // )
notification.dismiss() // notification.dismiss()
return // return
} // }
notification.tracked = true notification.tracked = true
console.log( console.log(
"[NotificationServer] Notification received:", "[NotificationServer] Notification received:",
notification.appName, notification.id, notification.summary, notification.body notification.appName, notification.id, notification.summary, notification.body
) )
notificationReceived(notification)
let timer = notificationTimerComponent.createObject(
notificationServerState,
{ notification: notification }
)
Hyprland.refreshMonitors() Hyprland.refreshMonitors()
waitTimer.start() timer.start()
} }
} }
Timer { Component {
id: waitTimer id: notificationTimerComponent
interval: 2000
repeat: false Timer {
onTriggered: focusedMonitor = Hyprland.focusedMonitor 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")
}
} }