feat: basic notifications
Also fix pixel issue with notif area background Add more log rules to run script
This commit is contained in:
parent
ed55afbcd8
commit
6b64c8a918
9 changed files with 313 additions and 24 deletions
15
config/NotifsConfig.qml
Normal file
15
config/NotifsConfig.qml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
readonly property int defaultExpireTimeout: 3000
|
||||
readonly property Sizes sizes: Sizes {}
|
||||
|
||||
component Sizes: QtObject {
|
||||
readonly property int width: 400
|
||||
readonly property int image: 50
|
||||
readonly property int badge: 20
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import "root:/widgets"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
|
|
@ -53,18 +54,9 @@ MouseArea {
|
|||
active: BarConfig.tray.recolourIcons
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: MultiEffect {
|
||||
sourceComponent: Colouriser {
|
||||
source: icon
|
||||
colorizationColor: root.colour
|
||||
colorization: 1
|
||||
|
||||
Behavior on colorizationColor {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ Shape {
|
|||
radiusX: root.rounding
|
||||
radiusY: root.rounding
|
||||
}
|
||||
PathLine {
|
||||
relativeX: 1
|
||||
relativeY: 0
|
||||
}
|
||||
|
||||
Behavior on fillColor {
|
||||
ColorAnimation {
|
||||
|
|
|
|||
|
|
@ -7,16 +7,18 @@ import QtQuick
|
|||
Column {
|
||||
id: root
|
||||
|
||||
// padding: Appearance.padding.large
|
||||
padding: Appearance.padding.large
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
StyledRect {
|
||||
width: 300
|
||||
height: 100
|
||||
// color: Qt.rgba(255, 0, 0, 0.4)
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: [...Notifs.list]
|
||||
}
|
||||
|
||||
Notification {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
188
modules/notifications/Notification.qml
Normal file
188
modules/notifications/Notification.qml
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
|
||||
StyledRect {
|
||||
id: root
|
||||
|
||||
required property Notifs.Notif modelData
|
||||
readonly property bool hasImage: modelData.image.length > 0
|
||||
readonly property bool hasAppIcon: modelData.appIcon.length > 0
|
||||
readonly property int imageSize: summary.height + bodyPreview.height
|
||||
property bool expanded
|
||||
|
||||
color: Colours.palette.m3surfaceContainer
|
||||
radius: Appearance.rounding.normal
|
||||
implicitWidth: NotifsConfig.sizes.width
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Appearance.padding.normal
|
||||
|
||||
implicitHeight: summary.height + bodyPreview.height + anchors.margins * 2
|
||||
|
||||
Loader {
|
||||
id: image
|
||||
|
||||
active: root.hasImage
|
||||
asynchronous: true
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: -parent.anchors.margins
|
||||
width: root.hasImage || root.hasAppIcon ? root.imageSize : 0
|
||||
height: root.hasImage || root.hasAppIcon ? root.imageSize : 0
|
||||
visible: root.hasImage || root.hasAppIcon
|
||||
|
||||
sourceComponent: ClippingRectangle {
|
||||
radius: Appearance.rounding.full
|
||||
width: root.imageSize
|
||||
height: root.imageSize
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: Qt.resolvedUrl(root.modelData.image)
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
cache: false
|
||||
asynchronous: true
|
||||
}
|
||||
}
|
||||
|
||||
states: State {
|
||||
name: "expanded"
|
||||
when: root.expanded
|
||||
|
||||
AnchorChanges {
|
||||
anchors.verticalCenter: undefined
|
||||
anchors.top: image.parent.top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: appIcon
|
||||
|
||||
active: root.hasAppIcon
|
||||
asynchronous: true
|
||||
|
||||
anchors.horizontalCenter: root.hasImage ? undefined : image.horizontalCenter
|
||||
anchors.verticalCenter: root.hasImage ? undefined : image.verticalCenter
|
||||
anchors.right: root.hasImage ? image.right : undefined
|
||||
anchors.bottom: root.hasImage ? image.bottom : undefined
|
||||
|
||||
sourceComponent: StyledRect {
|
||||
radius: Appearance.rounding.full
|
||||
color: Colours.palette.m3tertiaryContainer
|
||||
implicitWidth: root.hasImage ? NotifsConfig.sizes.badge : root.imageSize
|
||||
implicitHeight: root.hasImage ? NotifsConfig.sizes.badge : root.imageSize
|
||||
|
||||
IconImage {
|
||||
id: icon
|
||||
|
||||
anchors.centerIn: parent
|
||||
visible: false
|
||||
implicitSize: Math.round(parent.width * 0.6)
|
||||
source: Quickshell.iconPath(root.modelData.appIcon)
|
||||
asynchronous: true
|
||||
}
|
||||
|
||||
Colouriser {
|
||||
anchors.fill: icon
|
||||
source: icon
|
||||
colorizationColor: Colours.palette.m3onTertiaryContainer
|
||||
}
|
||||
}
|
||||
|
||||
states: State {
|
||||
name: "expanded"
|
||||
when: !root.hasImage && root.expanded
|
||||
|
||||
AnchorChanges {
|
||||
anchors.verticalCenter: undefined
|
||||
anchors.top: image.parent.top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: summary
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: image.right
|
||||
anchors.leftMargin: Appearance.spacing.small
|
||||
|
||||
text: root.modelData.summary
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: timeSep
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: summary.right
|
||||
anchors.leftMargin: Appearance.spacing.small
|
||||
|
||||
text: "•"
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: time
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: timeSep.right
|
||||
anchors.leftMargin: Appearance.spacing.small
|
||||
|
||||
animate: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
text: root.modelData.timeStr
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: expandBtn
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
MaterialIcon {
|
||||
animate: true
|
||||
text: root.expanded ? "expand_less" : "expand_more"
|
||||
font.pointSize: Appearance.font.size.smaller
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: bodyPreview
|
||||
|
||||
anchors.left: summary.left
|
||||
anchors.right: expandBtn.left
|
||||
anchors.top: summary.bottom
|
||||
anchors.rightMargin: Appearance.spacing.small
|
||||
|
||||
text: bodyPreviewMetrics.elidedText
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: bodyPreviewMetrics
|
||||
|
||||
text: root.modelData.body
|
||||
font.family: bodyPreview.font.family
|
||||
font.pointSize: bodyPreview.font.pointSize
|
||||
elide: Text.ElideRight
|
||||
elideWidth: bodyPreview.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,14 +12,7 @@ Variants {
|
|||
id: root
|
||||
|
||||
required property ShellScreen modelData
|
||||
property bool osdVisible
|
||||
|
||||
Timer {
|
||||
// running: true
|
||||
repeat: true
|
||||
interval: 2000
|
||||
onTriggered: root.osdVisible = !root.osdVisible
|
||||
}
|
||||
property bool osdVisible: Notifs.list.length > 0
|
||||
|
||||
LazyLoader {
|
||||
loading: true
|
||||
|
|
@ -38,7 +31,9 @@ Variants {
|
|||
|
||||
anchors.top: true
|
||||
anchors.bottom: true
|
||||
anchors.left: true
|
||||
anchors.right: true
|
||||
|
||||
width: wrapper.width
|
||||
height: wrapper.height
|
||||
|
||||
|
|
|
|||
4
run.fish
4
run.fish
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
set -l dbus 'quickshell.dbus.properties.warning = false' # System tray dbus property errors
|
||||
set -l text_input 'qt.qpa.wayland.textinput.warning = false' # Text input focus when open window
|
||||
set -l notifs 'quickshell.service.notifications.warning = false' # Notification server warnings on reload
|
||||
set -l sni 'quickshell.service.sni.host.warning = false' # StatusNotifierItem warnings on reload
|
||||
set -l hypr 'invalid nullptr parameter' # Error that always pops up on Hyprland
|
||||
set -l intercept '^qsintercept:.*(:[0-9]+){2}$' # Empty qsintercept lines
|
||||
set -l loop 'Binding loop detected' # Binding loops
|
||||
set -l process 'QProcess: Destroyed while process' # Long running processes on reload
|
||||
set -l async_loader 'items in the process of being created at engine destruction' # Async loaders on reload
|
||||
|
||||
qs -c caelestia --log-rules "$dbus;$text_input" | grep -vE -e $hypr -e $intercept -e $loop -e $process -e $async_loader
|
||||
qs -c caelestia --log-rules "$dbus;$text_input;$notifs;$sni" | grep -vE -e $hypr -e $intercept -e $loop -e $process -e $async_loader
|
||||
|
|
|
|||
76
services/Notifs.qml
Normal file
76
services/Notifs.qml
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
pragma Singleton
|
||||
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property list<Notif> list: []
|
||||
|
||||
NotificationServer {
|
||||
id: server
|
||||
|
||||
keepOnReload: false
|
||||
actionsSupported: true
|
||||
bodyHyperlinksSupported: true
|
||||
bodyImagesSupported: true
|
||||
bodyMarkupSupported: true
|
||||
imageSupported: true
|
||||
|
||||
onNotification: notif => {
|
||||
notif.tracked = true;
|
||||
|
||||
root.list.push(notifComp.createObject(root, {
|
||||
popup: true,
|
||||
notification: notif
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
component Notif: QtObject {
|
||||
id: notif
|
||||
|
||||
property bool popup
|
||||
readonly property date time: new Date()
|
||||
readonly property string timeStr: {
|
||||
const diff = Time.date.getTime() - time.getTime();
|
||||
const m = Math.floor(diff / 60000);
|
||||
const h = Math.floor(m / 60);
|
||||
|
||||
if (h < 1 && m < 1)
|
||||
return "now";
|
||||
if (h < 1)
|
||||
return `${m}m`;
|
||||
return `${h}h`;
|
||||
}
|
||||
|
||||
required property Notification notification
|
||||
readonly property string summary: notification.summary
|
||||
readonly property string body: notification.body
|
||||
readonly property string appIcon: notification.appIcon
|
||||
readonly property string image: notification.image
|
||||
|
||||
readonly property Timer timer: Timer {
|
||||
running: true
|
||||
interval: notif.notification.expireTimeout > 0 ? notif.notification.expireTimeout : NotifsConfig.defaultExpireTimeout
|
||||
onTriggered: notif.popup = false
|
||||
}
|
||||
|
||||
readonly property Connections conn: Connections {
|
||||
target: notif.notification
|
||||
|
||||
function onClosed(): void {
|
||||
notif.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: notifComp
|
||||
|
||||
Notif {}
|
||||
}
|
||||
}
|
||||
15
widgets/Colouriser.qml
Normal file
15
widgets/Colouriser.qml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import "root:/config"
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
|
||||
MultiEffect {
|
||||
colorization: 1
|
||||
|
||||
Behavior on colorizationColor {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue