Test
This commit is contained in:
+22
@@ -105,6 +105,28 @@ download \
|
||||
"$SCRIPT_DIR/modules/jl/assets/correct.svg" \
|
||||
"correct.svg"
|
||||
|
||||
# ----------------------------
|
||||
# Notification sounds
|
||||
# ----------------------------
|
||||
echo
|
||||
info "Preparing notification sounds..."
|
||||
mkdir -p "$SCRIPT_DIR/assets/sounds"
|
||||
|
||||
download \
|
||||
"https://raw.githubusercontent.com/deepin-community/sound-theme-freedesktop/master/stereo/dialog-information.oga" \
|
||||
"$SCRIPT_DIR/assets/sounds/notification-low.oga" \
|
||||
"notification-low.oga"
|
||||
|
||||
download \
|
||||
"https://raw.githubusercontent.com/deepin-community/sound-theme-freedesktop/master/stereo/dialog-warning.oga" \
|
||||
"$SCRIPT_DIR/assets/sounds/notification-normal.oga" \
|
||||
"notification-normal.oga"
|
||||
|
||||
download \
|
||||
"https://raw.githubusercontent.com/deepin-community/sound-theme-freedesktop/master/stereo/bell.oga" \
|
||||
"$SCRIPT_DIR/assets/sounds/notification-critical.oga" \
|
||||
"notification-critical.oga"
|
||||
|
||||
# ----------------------------
|
||||
# Nerd Font
|
||||
# ----------------------------
|
||||
|
||||
@@ -3,10 +3,13 @@ import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import "./widgets"
|
||||
import "../../config"
|
||||
import "../notifications"
|
||||
|
||||
PanelWindow {
|
||||
id: panel
|
||||
|
||||
required property var manager
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors.top: true
|
||||
@@ -47,6 +50,8 @@ PanelWindow {
|
||||
|
||||
WindowInfo {}
|
||||
|
||||
NotificationIndicator {manager: panel.manager}
|
||||
|
||||
}
|
||||
|
||||
Row { // bar widgets center
|
||||
|
||||
@@ -4,11 +4,14 @@ import Quickshell
|
||||
|
||||
Item {
|
||||
id: bar_root
|
||||
|
||||
required property var manager
|
||||
|
||||
Repeater { // Bars
|
||||
model: Quickshell.screens
|
||||
delegate: Loader {
|
||||
active: true // loads object
|
||||
sourceComponent: Bar {screen: modelData} // creates instance of bar component
|
||||
sourceComponent: Bar {screen: modelData; manager: root.manager} // creates instance of bar component
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Widgets
|
||||
import "../../config"
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
|
||||
required property var notification
|
||||
required property var manager
|
||||
|
||||
width: parent.width
|
||||
height: Math.max(64, cardContent.height + 24)
|
||||
radius: Config.radius
|
||||
|
||||
property var urgency: notification.urgency
|
||||
property bool isCritical: urgency === NotificationUrgency.Critical
|
||||
|
||||
color: {
|
||||
if (isCritical) return Qt.alpha(Colors.errorContainer, 0.95)
|
||||
if (urgency === NotificationUrgency.Low) return Qt.alpha(Colors.surfaceContainerHigh, 0.95)
|
||||
return Qt.alpha(Colors.primaryContainer, 0.95)
|
||||
}
|
||||
|
||||
border {
|
||||
width: Config.border_width
|
||||
color: {
|
||||
if (isCritical) return Colors.error
|
||||
if (urgency === NotificationUrgency.Low) return Colors.outline
|
||||
return Colors.primaryContainer_fg
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation on opacity {
|
||||
id: fadeInAnim
|
||||
from: 0
|
||||
to: 1
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
NumberAnimation on x {
|
||||
id: slideInAnim
|
||||
from: parent ? parent.width : 100
|
||||
to: 0
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
fadeInAnim.start()
|
||||
slideInAnim.start()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: {
|
||||
manager.dismissNotification(notification)
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: cardContent
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: 12
|
||||
}
|
||||
spacing: 10
|
||||
|
||||
Item {
|
||||
id: iconContainer
|
||||
width: 28
|
||||
height: 28
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
IconImage {
|
||||
id: notifIcon
|
||||
anchors.fill: parent
|
||||
icon: notification.appIcon
|
||||
sourceSize.width: 28
|
||||
sourceSize.height: 28
|
||||
visible: notification.appIcon !== ""
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "\uF0F3"
|
||||
font.pixelSize: 16
|
||||
font.family: Config.font
|
||||
visible: !notifIcon.visible
|
||||
color: Colors.primary_fg
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: textColumn
|
||||
width: parent.width - iconContainer.width - parent.spacing - 20
|
||||
spacing: 3
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
id: summaryText
|
||||
width: parent.width
|
||||
text: notification.summary || notification.appName || "Notification"
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
font.family: Config.font
|
||||
color: {
|
||||
if (isCritical) return Colors.error
|
||||
if (urgency === NotificationUrgency.Low) return Colors.secondaryContainer_fg
|
||||
return Colors.primary
|
||||
}
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
Text {
|
||||
id: bodyText
|
||||
width: parent.width
|
||||
text: notification.body || ""
|
||||
visible: text !== ""
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: 11
|
||||
font.family: Config.font
|
||||
color: isCritical ? Colors.errorContainer_fg : Colors.surface_fg
|
||||
maximumLineCount: 2
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "\u00D7"
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
color: isCritical ? Colors.error : Colors.secondary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
opacity: 0.6
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
manager.dismissNotification(notification)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Widgets
|
||||
import "../../config"
|
||||
|
||||
MouseArea {
|
||||
id: indicator
|
||||
|
||||
required property var manager
|
||||
|
||||
height: bar.height
|
||||
width: indicatorRow.width + Config.spacing_fun(height) * 2
|
||||
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
property bool popupOpen: false
|
||||
|
||||
onClicked: {
|
||||
popupOpen = !popupOpen
|
||||
if (popupOpen) {
|
||||
notifPopup.visible = true
|
||||
} else {
|
||||
notifPopup.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: indicator.containsMouse || popupOpen ? Colors.tertiaryContainer : "transparent"
|
||||
radius: Config.radius_fun(indicator.height)
|
||||
border.width: (indicator.containsMouse || popupOpen) ? Config.border_width : 0
|
||||
border.color: (indicator.containsMouse || popupOpen) ? Colors.tertiaryContainer_fg : "transparent"
|
||||
|
||||
Row {
|
||||
id: indicatorRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Config.spacing_fun(indicator.height) / 2
|
||||
|
||||
Item {
|
||||
id: iconWrap
|
||||
width: Config.iconSize(indicator.height)
|
||||
height: Config.iconSize(indicator.height)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
IconImage {
|
||||
id: notifIcon
|
||||
anchors.fill: parent
|
||||
icon: manager.lastNotification ? manager.lastNotification.appIcon : ""
|
||||
sourceSize.width: Config.iconSize(indicator.height)
|
||||
sourceSize.height: Config.iconSize(indicator.height)
|
||||
visible: manager.lastNotification && manager.lastNotification.appIcon !== ""
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "\uF0F3"
|
||||
font.pixelSize: Config.iconSize(indicator.height)
|
||||
font.family: Config.font
|
||||
visible: !notifIcon.visible
|
||||
color: indicator.containsMouse ? Colors.tertiary : Colors.secondary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: countBadge
|
||||
width: Math.max(badgeText.width + 6, 16)
|
||||
height: 16
|
||||
radius: 8
|
||||
visible: manager.notificationCount > 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: manager.hasCritical ? Colors.error : Colors.primary
|
||||
|
||||
Text {
|
||||
id: badgeText
|
||||
anchors.centerIn: parent
|
||||
text: manager.notificationCount > 99 ? "99+" : manager.notificationCount.toString()
|
||||
font.pixelSize: Config.font_size_tiny(indicator.height)
|
||||
font.family: Config.font
|
||||
font.bold: true
|
||||
color: manager.hasCritical ? Colors.error_fg : Colors.primary_fg
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: criticalDot
|
||||
width: 8
|
||||
height: 8
|
||||
radius: 4
|
||||
visible: manager.hasCritical
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Colors.error
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
loops: Animation.Infinite
|
||||
PropertyAnimation { from: 1.0; to: 0.4; duration: 800 }
|
||||
PropertyAnimation { from: 0.4; to: 1.0; duration: 800 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PopupWindow {
|
||||
id: notifPopup
|
||||
visible: indicator.popupOpen
|
||||
color: "transparent"
|
||||
|
||||
anchor {
|
||||
item: indicator
|
||||
edges: Edges.Top | Edges.Left
|
||||
gravity: Edges.Bottom | Edges.Right
|
||||
adjustment: PopupAdjustment.FlipY | PopupAdjustment.SlideX
|
||||
onAnchoring: {
|
||||
anchor.rect.x = (indicator.width - notifPopup.width) / 2
|
||||
anchor.rect.y = indicator.height + 4
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: 280
|
||||
implicitHeight: Math.max(60, popupContent.height + 24)
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.alpha(Colors.primaryContainer, 0.95)
|
||||
radius: Config.radius
|
||||
border {
|
||||
width: Config.border_width
|
||||
color: Colors.primaryContainer_fg
|
||||
}
|
||||
|
||||
Column {
|
||||
id: popupContent
|
||||
width: parent.width
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: 12
|
||||
}
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "Notifications"
|
||||
font.pixelSize: Config.font_size_small(indicator.height)
|
||||
font.family: Config.font
|
||||
font.bold: true
|
||||
color: Colors.primary
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: manager.notificationCount > 0
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: manager.notifications
|
||||
|
||||
delegate: Rectangle {
|
||||
width: parent.width
|
||||
height: delegateText.height + 10
|
||||
radius: Config.small_radius
|
||||
color: Qt.alpha(Colors.surfaceContainerHighest, 0.5)
|
||||
|
||||
required property var model
|
||||
|
||||
property var notif: model.object
|
||||
|
||||
Row {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: 6
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
spacing: 6
|
||||
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 24
|
||||
radius: 3
|
||||
color: {
|
||||
if (notif.urgency === NotificationUrgency.Critical) return Colors.error
|
||||
if (notif.urgency === NotificationUrgency.Low) return Colors.outline
|
||||
return Colors.primary
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - 24
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
id: delegateText
|
||||
width: parent.width
|
||||
text: notif.summary || notif.appName || "Notification"
|
||||
font.pixelSize: Config.labelSize(indicator.height)
|
||||
font.family: Config.font
|
||||
font.bold: true
|
||||
color: Colors.surface_fg
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: notif.body || ""
|
||||
visible: text !== ""
|
||||
font.pixelSize: Config.font_size_tiny(indicator.height)
|
||||
font.family: Config.font
|
||||
color: Colors.surfaceVariant_fg
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "No notifications"
|
||||
font.pixelSize: Config.font_size_small(indicator.height)
|
||||
font.family: Config.font
|
||||
color: Colors.secondary
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: manager.notificationCount === 0
|
||||
height: 30
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible) indicator.popupOpen = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import QtQuick
|
||||
import QtMultimedia
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import "../../config"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
NotificationServer {
|
||||
id: notificationServer
|
||||
imageSupported: true
|
||||
actionsSupported: true
|
||||
bodyMarkupSupported: false
|
||||
|
||||
onNotification: (notification) => {
|
||||
notification.tracked = true
|
||||
root.onNotificationReceived(notification)
|
||||
}
|
||||
}
|
||||
|
||||
property ObjectModel notifications: ObjectModel {}
|
||||
property int notificationCount: 0
|
||||
property var lastNotification: null
|
||||
property bool hasCritical: false
|
||||
|
||||
readonly property string soundDir: Config.configDir + "/assets/sounds"
|
||||
|
||||
property var _dismissTimes: ({})
|
||||
|
||||
function onNotificationReceived(notification) {
|
||||
notifications.append(notification)
|
||||
notificationCount = notifications.count
|
||||
lastNotification = notification
|
||||
if (notification.urgency === NotificationUrgency.Critical)
|
||||
hasCritical = true
|
||||
|
||||
playSound(notification.urgency)
|
||||
scheduleDismiss(notification)
|
||||
}
|
||||
|
||||
function playSound(urgency) {
|
||||
var path
|
||||
var vol
|
||||
switch (urgency) {
|
||||
case NotificationUrgency.Low:
|
||||
path = soundDir + "/notification-low.oga"
|
||||
vol = 0.35
|
||||
break
|
||||
case NotificationUrgency.Critical:
|
||||
path = soundDir + "/notification-critical.oga"
|
||||
vol = 1.0
|
||||
break
|
||||
default:
|
||||
path = soundDir + "/notification-normal.oga"
|
||||
vol = 0.7
|
||||
break
|
||||
}
|
||||
|
||||
if (player.hasAudio) player.stop()
|
||||
player.source = "file://" + path
|
||||
audioOutput.volume = vol
|
||||
player.play()
|
||||
}
|
||||
|
||||
function scheduleDismiss(notification) {
|
||||
var timeout = notification.expireTimeout
|
||||
if (timeout <= 0) {
|
||||
if (notification.urgency === NotificationUrgency.Critical)
|
||||
timeout = 60
|
||||
else
|
||||
timeout = 5
|
||||
}
|
||||
_dismissTimes[notification.id] = Date.now() / 1000 + timeout
|
||||
if (!refreshTimer.running)
|
||||
refreshTimer.running = true
|
||||
}
|
||||
|
||||
function dismissNotification(notification) {
|
||||
for (var i = 0; i < notifications.count; i++) {
|
||||
if (notifications.get(i).id === notification.id) {
|
||||
notifications.remove(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
notificationCount = notifications.count
|
||||
delete _dismissTimes[notification.id]
|
||||
if (notificationCount === 0) {
|
||||
hasCritical = false
|
||||
lastNotification = null
|
||||
} else {
|
||||
lastNotification = notifications.get(0)
|
||||
hasCritical = false
|
||||
for (var j = 0; j < notifications.count; j++) {
|
||||
if (notifications.get(j).urgency === NotificationUrgency.Critical) {
|
||||
hasCritical = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
notification.tracked = false
|
||||
}
|
||||
|
||||
function dismissById(nid) {
|
||||
for (var i = 0; i < notifications.count; i++) {
|
||||
var n = notifications.get(i)
|
||||
if (n.id === nid) {
|
||||
dismissNotification(n)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dismissAll() {
|
||||
while (notifications.count > 0) {
|
||||
var n = notifications.get(0)
|
||||
dismissNotification(n)
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: refreshTimer
|
||||
interval: 1000
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
var now = Date.now() / 1000
|
||||
var toDismiss = []
|
||||
for (var i = 0; i < notifications.count; i++) {
|
||||
var n = notifications.get(i)
|
||||
var t = _dismissTimes[n.id]
|
||||
if (t !== undefined && now >= t)
|
||||
toDismiss.push(n)
|
||||
}
|
||||
for (var j = 0; j < toDismiss.length; j++)
|
||||
dismissNotification(toDismiss[j])
|
||||
if (notifications.count === 0)
|
||||
running = false
|
||||
}
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
id: player
|
||||
audioOutput: AudioOutput {
|
||||
id: audioOutput
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.Notifications
|
||||
import "../../config"
|
||||
|
||||
Variants {
|
||||
id: root
|
||||
|
||||
required property var manager
|
||||
|
||||
model: Quickshell.screens
|
||||
|
||||
delegate: PanelWindow {
|
||||
id: popupWindow
|
||||
screen: modelData
|
||||
color: "transparent"
|
||||
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
|
||||
width: 380
|
||||
height: Math.min(implicitHeight, screen.height * 0.6)
|
||||
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.namespace: "notification-popup"
|
||||
|
||||
margins {
|
||||
top: 8
|
||||
right: 8
|
||||
}
|
||||
|
||||
readonly property var hyprMonitor: Hyprland.monitorFor(screen)
|
||||
visible: hyprMonitor === Hyprland.focusedMonitor
|
||||
|
||||
implicitHeight: contentColumn.height
|
||||
|
||||
Connections {
|
||||
target: Hyprland
|
||||
function onFocusedMonitorChanged() {
|
||||
popupWindow.visible = popupWindow.hyprMonitor === Hyprland.focusedMonitor
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.manager.notifications
|
||||
function onCountChanged() {
|
||||
popupWindow.visible = popupWindow.hyprMonitor === Hyprland.focusedMonitor
|
||||
&& root.manager.notifications.count > 0
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
topPadding: 4
|
||||
|
||||
Repeater {
|
||||
model: root.manager.notifications
|
||||
|
||||
delegate: NotificationCard {
|
||||
width: contentColumn.width
|
||||
notification: model.object
|
||||
manager: root.manager
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
NotificationManager 1.0 NotificationManager.qml
|
||||
NotificationPopup 1.0 NotificationPopup.qml
|
||||
NotificationIndicator 1.0 NotificationIndicator.qml
|
||||
NotificationCard 1.0 NotificationCard.qml
|
||||
@@ -3,17 +3,24 @@
|
||||
import Quickshell //for PanelWindow
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick //for Text
|
||||
import Quickshell.Services.Notifications
|
||||
import "./modules/bar"
|
||||
import "./modules/wp"
|
||||
import "./modules/wlogout"
|
||||
import "./modules/jl"
|
||||
import "./modules/background"
|
||||
import "./modules/notifications"
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
|
||||
// Notifications
|
||||
NotificationManager { id: notificationManager }
|
||||
|
||||
NotificationPopup { manager: notificationManager }
|
||||
|
||||
// Bar
|
||||
BarLoader {}
|
||||
BarLoader { manager: notificationManager }
|
||||
|
||||
//Wallpaper picker
|
||||
WallpaperPickerLoader {}
|
||||
|
||||
Reference in New Issue
Block a user