From 7081a48426302f50d478a5f7c67d4263d8f80503 Mon Sep 17 00:00:00 2001 From: Eriq Taing Date: Sat, 13 Sep 2025 23:51:14 -0400 Subject: [PATCH] Introduced Notification server, and added background thorugh quickshell --- GlobalStates.qml | 1 + background/Background.qml | 36 ++++++ background/Wallpaper.qml | 19 +++ bar/Bar.qml | 2 +- bar/Media.qml | 2 +- bar/NotificationIcon.qml | 17 ++- common/Config.qml | 4 +- common/MprisController.qml | 6 +- ...tionServer.qml => NotificationService.qml} | 1 + common/widgets/RippleButton.qml | 2 + osd/NotificationItem.qml | 111 ++++++++++++++++++ osd/NotificationPanel.qml | 69 +++++++++++ shell.qml | 3 + 13 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 background/Background.qml create mode 100644 background/Wallpaper.qml rename common/{NotificationServer.qml => NotificationService.qml} (92%) create mode 100644 osd/NotificationItem.qml create mode 100644 osd/NotificationPanel.qml diff --git a/GlobalStates.qml b/GlobalStates.qml index 29fd0c5..3a17426 100644 --- a/GlobalStates.qml +++ b/GlobalStates.qml @@ -7,4 +7,5 @@ Singleton { id: root property bool mediaControlsOpen: false property bool osdVolumeOpen: false + property bool notificationPanelOpen: false } \ No newline at end of file diff --git a/background/Background.qml b/background/Background.qml new file mode 100644 index 0000000..e895496 --- /dev/null +++ b/background/Background.qml @@ -0,0 +1,36 @@ +pragma ComponentBehavior: Bound + +import qs.common +import Quickshell +import Quickshell.Wayland +import QtQuick + +Loader { + asynchronous: true + active: Config.background.enabled + + sourceComponent: Variants { + model: Quickshell.screens + + PanelWindow { + id: background + + required property ShellScreen modelData + + screen: modelData + WlrLayershell.namespace: "hydro-os-background" + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.layer: WlrLayer.Background + color: "black" + + anchors { + top: true + bottom: true + left: true + right: true + } + + Wallpaper {} + } + } +} \ No newline at end of file diff --git a/background/Wallpaper.qml b/background/Wallpaper.qml new file mode 100644 index 0000000..80ec5b1 --- /dev/null +++ b/background/Wallpaper.qml @@ -0,0 +1,19 @@ +pragma ComponentBehavior: Bound + +import qs.common +import QtQuick + +Item { + id: root + + property string source: Config.options.background.wallpaperPath + property Image current: one + + anchors.fill: parent + + Image { + id: one + source: root.source + anchors.fill: parent + } +} \ No newline at end of file diff --git a/bar/Bar.qml b/bar/Bar.qml index 72ae17d..b15f8a0 100644 --- a/bar/Bar.qml +++ b/bar/Bar.qml @@ -29,7 +29,7 @@ Scope { right: true } - implicitHeight: 45 + implicitHeight: Config.options.bar.height RowLayout { // Left Section id: leftSection diff --git a/bar/Media.qml b/bar/Media.qml index c48e90b..be32dd5 100644 --- a/bar/Media.qml +++ b/bar/Media.qml @@ -33,7 +33,7 @@ Item { } else if (event.button === Qt.ForwardButton) { MprisController.shiftPlayer(1); } else if (event.button === Qt.LeftButton) { - GlobalStates.mediaControlsOpen = !GlobalStates.mediaControlsOpen + GlobalStates.mediaControlsOpen = !GlobalStates.mediaControlsOpen; } } } diff --git a/bar/NotificationIcon.qml b/bar/NotificationIcon.qml index 86bfba3..d2753f7 100644 --- a/bar/NotificationIcon.qml +++ b/bar/NotificationIcon.qml @@ -1,3 +1,4 @@ +import qs import qs.common import qs.common.widgets import QtQuick @@ -9,6 +10,14 @@ Item { height: parent.height implicitWidth: rowLayout.implicitWidth + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onPressed: (event) => { + GlobalStates.notificationPanelOpen = !GlobalStates.notificationPanelOpen; + } + } + RowLayout { id: rowLayout @@ -19,9 +28,9 @@ Item { Layout.alignment: Qt.AlignVCenter font.pixelSize: Appearance.font.pixelSize.larger color: Appearance.m3colors.m3error - text: "" + NotificationServer.amountNotifications + text: "" + NotificationService.amountNotifications visible: { - NotificationServer.amountNotifications > 0 + NotificationService.amountNotifications > 0 } } @@ -29,9 +38,9 @@ Item { Layout.alignment: Qt.AlignVCenter visible: true fill: 1 - text: NotificationServer.amountNotifications > 0 ? "notifications_unread" : "notifications" + text: NotificationService.amountNotifications > 0 ? "notifications_unread" : "notifications" iconSize: Appearance.font.pixelSize.larger - color: NotificationServer.amountNotifications > 0 ? Appearance.m3colors.m3error : Appearance.m3colors.m3onSecondaryContainer + color: NotificationService.amountNotifications > 0 ? Appearance.m3colors.m3error : Appearance.m3colors.m3onSecondaryContainer } } } \ No newline at end of file diff --git a/common/Config.qml b/common/Config.qml index b39a1d3..6c3b286 100644 --- a/common/Config.qml +++ b/common/Config.qml @@ -40,11 +40,13 @@ Singleton { } property JsonObject background: JsonObject { - property string wallpaperPath: "" + property bool enabled: true + property string wallpaperPath: "/usr/share/hydro-os/DefaultBackground.png" property string thumbnailPath: "" } property JsonObject bar: JsonObject { + property int height: 45 property bool bottom: false // Instead of top property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle property bool borderless: false // true for no grouping of items diff --git a/common/MprisController.qml b/common/MprisController.qml index 83465df..d38410f 100644 --- a/common/MprisController.qml +++ b/common/MprisController.qml @@ -7,7 +7,7 @@ import Quickshell.Services.Mpris Singleton { readonly property list activePlayers: Mpris.players.values readonly property var meaningfulPlayers: filterDuplicatePlayers(activePlayers) - readonly property bool hasPlayers: meaningfulPlayers.length > 0 + readonly property bool hasPlayers: activePlayers.length > 0 property int playerIndex: 0 function activePlayer() { @@ -15,7 +15,7 @@ Singleton { return null; } assertIndex(); - return meaningfulPlayers[playerIndex]; + return activePlayers[playerIndex]; } function shiftPlayer(shift) { @@ -23,7 +23,7 @@ Singleton { } function assertIndex() { - if (playerIndex < 0 || playerIndex >= meaningfulPlayers.length) { + if (playerIndex < 0 || playerIndex >= activePlayers.length) { playerIndex = (playerIndex + activePlayers.length) % activePlayers.length } } diff --git a/common/NotificationServer.qml b/common/NotificationService.qml similarity index 92% rename from common/NotificationServer.qml rename to common/NotificationService.qml index d718381..03c6fd5 100644 --- a/common/NotificationServer.qml +++ b/common/NotificationService.qml @@ -9,6 +9,7 @@ Singleton { NotificationServer { id: server + actionsSupported: true onNotification: notif => { notif.tracked = true; diff --git a/common/widgets/RippleButton.qml b/common/widgets/RippleButton.qml index 2f1017d..97a543f 100644 --- a/common/widgets/RippleButton.qml +++ b/common/widgets/RippleButton.qml @@ -22,6 +22,7 @@ Button { property var releaseAction // When left clicking (release) property var altAction // When right clicking property var middleClickAction // When middle clicking + property color buttonTextColor: Appearance?.m3colors.m3onBackground ?? "black" property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent" property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED" @@ -197,5 +198,6 @@ Button { contentItem: StyledText { text: root.buttonText + color: root.buttonTextColor } } \ No newline at end of file diff --git a/osd/NotificationItem.qml b/osd/NotificationItem.qml new file mode 100644 index 0000000..d961574 --- /dev/null +++ b/osd/NotificationItem.qml @@ -0,0 +1,111 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Widgets +import Quickshell.Services.Notifications +import qs.common +import qs.common.widgets + +Item { + id: root + required property Notification notif + required property real textWidth + required property real notificationRounding + property real spacing: 8 + + Layout.fillWidth: true + + implicitHeight: content.implicitHeight + + + ColumnLayout { + id: content + Layout.fillWidth: true + spacing: 0 + Rectangle { + implicitHeight: appTitle.implicitHeight + implicitWidth: root.width + anchors.margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 + color: Appearance.m3colors.m3primaryContainer + topLeftRadius: root.notificationRounding + topRightRadius: root.notificationRounding + RowLayout { + spacing: 0 // For some reason, adding a spacing of just the value will not work properly + IconImage { + visible: root.notif.appIcon.length > 0 + id: appIcon + source: Quickshell.iconPath(root.notif.appIcon) + width: appTitle.height - root.spacing + height: appTitle.height - root.spacing + Layout.leftMargin: root.spacing / 2 + } + Text { + // app title + id: appTitle + text: root.notif.appName + color: Appearance.m3colors.m3onPrimaryContainer + Layout.leftMargin: root.spacing / 2 + } + } + } + Rectangle { + visible: notifSummary.visible || notifBody.visible + implicitHeight: body.implicitHeight + implicitWidth: root.width + anchors.margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 + color: Appearance.colors.colLayer2 + bottomLeftRadius: root.notificationRounding + bottomRightRadius: root.notificationRounding + ColumnLayout { + id: body + spacing: root.spacing + Text { + visible: root.notif.summary.length > 0 + Layout.leftMargin: root.spacing + Layout.rightMargin: root.spacing + id: notifSummary + text: root.notif.summary + color: Appearance.colors.colOnLayer2 + wrapMode: Text.WordWrap + } + Text { + visible: root.notif.body.length > 0 + Layout.leftMargin: root.spacing + Layout.rightMargin: root.spacing + Layout.preferredWidth: textWidth + id: notifBody + text: root.notif.body + color: Appearance.colors.colOnLayer2 + wrapMode: Text.WordWrap + maximumLineCount: 4 + } + RowLayout { + id: actionLayer + Layout.fillWidth: true + Layout.leftMargin: root.spacing + Layout.rightMargin: root.spacing + Layout.bottomMargin: root.spacing + spacing: root.spacing + RippleButton { + buttonText: "Dismiss" + colBackground: Appearance.m3colors.m3primaryContainer + releaseAction: root.notif.dismiss + buttonRadius: root.notificationRounding + } + + Repeater { + model: root.notif.actions + RippleButton { + buttonText: modelData.text + buttonTextColor: Appearance.m3colors.m3onTertiaryContainer + colBackground: Appearance.m3colors.m3tertiaryContainer + releaseAction: modelData.invoke + buttonRadius: root.notificationRounding + } + } + } + } + } + } +} \ No newline at end of file diff --git a/osd/NotificationPanel.qml b/osd/NotificationPanel.qml new file mode 100644 index 0000000..0fee388 --- /dev/null +++ b/osd/NotificationPanel.qml @@ -0,0 +1,69 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Services.Notifications +import qs +import qs.common + +Scope { + id: root + property bool visible: false + property int panelWidth: 350 + + Loader { + id: notificationPanelLoader + active: GlobalStates.notificationPanelOpen + /** + onActiveChanged: { + if (!notificationPanelLoader.active & NotificationServer.amountNotifications == 0) { + GlobalStates.notificationPanelOpen = false; + } + } + //*/ + + sourceComponent: PanelWindow { + id: notificationPanelRoot + visible: true + + exclusionMode: ExclusionMode.Ignore + exclusiveZone: 0 + + implicitWidth: root.panelWidth + + + anchors { + top: true + right: true + bottom: true + } + margins { + top: Config.options.bar.height + } + + Rectangle { + anchors { + fill: parent + margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 + } + color: Config.options.bar.showBackground ? Appearance.colors.colLayer1 : "transparent" + } + + ColumnLayout { + id: notifs + anchors.margins: 4 + anchors.left: parent.left + anchors.right: parent.right + Repeater { + model: NotificationService.notifications + NotificationItem { + required property Notification modelData + notif: modelData + textWidth: root.panelWidth - 14 + notificationRounding: Appearance.rounding.unsharpenmore + } + } + } + } + } +} \ No newline at end of file diff --git a/shell.qml b/shell.qml index 471cf0c..100c6d8 100644 --- a/shell.qml +++ b/shell.qml @@ -2,9 +2,12 @@ import Quickshell import qs.bar import qs.osd +import qs.background Scope { Bar {} VolumeDisplay {} //MediaControls {} + NotificationPanel {} + Background {} } \ No newline at end of file