mirror of
https://github.com/tuxdotrs/tpanel.git
synced 2026-09-19 18:19:02 +05:30
feat(notifications): implement notification service
This commit is contained in:
11
assets/icons/notification.svg
Normal file
11
assets/icons/notification.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="style=bulk">
|
||||
<g id="notification-box-line">
|
||||
<path id="Subtract" fill-rule="evenodd" clip-rule="evenodd" d="M8.75 1.99994C5.02208 1.99994 2 5.02201 2 8.74994L2 15.2577C2 18.9857 5.02208 22.0077 8.75 22.0077L15.2578 22.0077C18.9857 22.0077 22.0078 18.9857 22.0078 15.2577L22.0078 8.74994C22.0078 8.09759 21.9153 7.46685 21.7426 6.87014C21.2141 8.15089 19.9531 9.05228 18.4816 9.05228C16.5342 9.05228 14.9554 7.47356 14.9554 5.52611C14.9554 4.05459 15.8568 2.79359 17.1376 2.26514C16.5409 2.09248 15.9101 1.99994 15.2578 1.99994L8.75 1.99994Z" fill="#BFBFBF"/>
|
||||
<path id="vector (Stroke)" fill-rule="evenodd" clip-rule="evenodd" d="M8.75 15.5001C8.75 15.0858 9.08579 14.7501 9.5 14.7501H14.5C14.9142 14.7501 15.25 15.0858 15.25 15.5001C15.25 15.9143 14.9142 16.2501 14.5 16.2501H9.5C9.08579 16.2501 8.75 15.9143 8.75 15.5001Z" fill="#000000"/>
|
||||
<circle id="vector" cx="18.4816" cy="5.52623" r="2.77617" fill="#000000"/>
|
||||
</g>
|
||||
</g>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
117
modules/osd/NotificationCard.qml
Normal file
117
modules/osd/NotificationCard.qml
Normal file
@@ -0,0 +1,117 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import qs.config
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property Notification notif
|
||||
readonly property bool critical: notif?.urgency === NotificationUrgency.Critical
|
||||
readonly property int timeoutDuration: {
|
||||
const seconds = Math.round((notif?.expireTimeout ?? 0) / 1000);
|
||||
return seconds > 0 ? seconds : 5;
|
||||
}
|
||||
readonly property string iconName: {
|
||||
if (!notif)
|
||||
return "";
|
||||
|
||||
const hints = notif.hints ?? {};
|
||||
const hinted = String(hints["image-path"] ?? hints["image_path"] ?? hints["icon_path"] ?? "");
|
||||
return notif.appIcon || notif.desktopEntry || hinted;
|
||||
}
|
||||
readonly property url themedIconUrl: iconName !== "" ? Quickshell.iconPath(iconName, false) : ""
|
||||
readonly property url iconUrl: `${Quickshell.shellPath("assets")}/icons/notification.svg`
|
||||
|
||||
implicitWidth: 360
|
||||
implicitHeight: content.implicitHeight + Appearance.padding * 2
|
||||
radius: Appearance.radius
|
||||
color: Appearance.colors.background
|
||||
border.width: critical ? 1 : 0
|
||||
border.color: Appearance.colors.accent
|
||||
|
||||
Timer {
|
||||
id: countdown
|
||||
|
||||
interval: root.timeoutDuration * 1000
|
||||
repeat: false
|
||||
running: !root.critical && !hovered.hovered && visible
|
||||
onTriggered: root.notif.expire()
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hovered
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: content
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Appearance.padding
|
||||
spacing: Appearance.spacing
|
||||
|
||||
Item {
|
||||
width: 40
|
||||
height: 40
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: root.iconUrl
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: root.themedIconUrl
|
||||
visible: root.themedIconUrl !== "" && status === Image.Ready
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Text {
|
||||
text: root.notif?.appName ?? ""
|
||||
color: Appearance.colors.accent
|
||||
font.family: Appearance.font.family
|
||||
font.pointSize: Appearance.font.pointSize - 2
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Text {
|
||||
text: root.notif?.summary ?? ""
|
||||
color: Appearance.colors.foreground
|
||||
font.family: Appearance.font.family
|
||||
font.pointSize: Appearance.font.pointSize
|
||||
font.weight: Font.DemiBold
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Text {
|
||||
text: root.notif?.body ?? ""
|
||||
visible: text !== ""
|
||||
color: Appearance.colors.foreground
|
||||
opacity: 0.8
|
||||
font.family: Appearance.font.family
|
||||
font.pointSize: Appearance.font.pointSize - 1
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 3
|
||||
elide: Text.ElideRight
|
||||
textFormat: Text.PlainText
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
74
services/Notifications.qml
Normal file
74
services/Notifications.qml
Normal file
@@ -0,0 +1,74 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Notifications currently shown in the OSD, tracked by the server.
|
||||
readonly property alias popups: server.trackedNotifications
|
||||
readonly property int popupCount: popups.values.length
|
||||
|
||||
// Do-not-disturb: notifications are recorded but not displayed.
|
||||
property bool dnd: false
|
||||
|
||||
// Maximum amount of simultaneously displayed popups.
|
||||
property int maxPopups: 5
|
||||
|
||||
// History of received notifications (newest first), for future dashboards.
|
||||
property var history: []
|
||||
property int historyLimit: 100
|
||||
|
||||
NotificationServer {
|
||||
id: server
|
||||
|
||||
keepOnReload: false
|
||||
|
||||
bodySupported: true
|
||||
bodyMarkupSupported: true
|
||||
actionsSupported: true
|
||||
|
||||
onNotification: notification => {
|
||||
root.addHistory(notification);
|
||||
|
||||
if (root.dnd || notification.transient)
|
||||
return;
|
||||
|
||||
notification.tracked = true;
|
||||
|
||||
const tracked = server.trackedNotifications.values;
|
||||
if (tracked.length > root.maxPopups)
|
||||
tracked[0].expire();
|
||||
}
|
||||
}
|
||||
|
||||
function clearHistory(): void {
|
||||
root.history = [];
|
||||
}
|
||||
|
||||
function addHistory(notification: Notification): void {
|
||||
const entry = {
|
||||
id: notification.id,
|
||||
appName: notification.appName,
|
||||
appIcon: notification.appIcon,
|
||||
desktopEntry: notification.desktopEntry,
|
||||
summary: notification.summary,
|
||||
body: notification.body,
|
||||
image: notification.image,
|
||||
urgency: notification.urgency,
|
||||
transient: notification.transient,
|
||||
time: Date.now(),
|
||||
actions: notification.actions.map(action => ({
|
||||
identifier: action.identifier,
|
||||
text: action.text
|
||||
}))
|
||||
};
|
||||
|
||||
let next = [entry, ...root.history];
|
||||
if (next.length > root.historyLimit)
|
||||
next = next.slice(0, root.historyLimit);
|
||||
root.history = next;
|
||||
}
|
||||
}
|
||||
55
windows/Notification.qml
Normal file
55
windows/Notification.qml
Normal file
@@ -0,0 +1,55 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.config
|
||||
import qs.services
|
||||
import "../modules/osd"
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
readonly property int cardWidth: 420
|
||||
|
||||
color: "transparent"
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
visible: Notifications.popupCount > 0
|
||||
implicitWidth: root.cardWidth
|
||||
implicitHeight: stack.implicitHeight
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
right: true
|
||||
}
|
||||
margins {
|
||||
top: 50 + Appearance.margin
|
||||
right: 10 + Appearance.margin
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: stack
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: stack
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
spacing: Appearance.spacing
|
||||
|
||||
Repeater {
|
||||
model: [...Notifications.popups.values].reverse()
|
||||
|
||||
NotificationCard {
|
||||
id: card
|
||||
|
||||
required property var modelData
|
||||
|
||||
notif: modelData
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user