Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d20fdf8fa0 | ||
|
|
a17a365b81 |
11 changed files with 107 additions and 34 deletions
|
|
@ -108,6 +108,7 @@ programs.nova-shell.modules = {
|
|||
power.enable = false; # if you enjoy living dangerously without a logout button
|
||||
lock.enable = false; # if you prefer your session unlocked and your secrets free
|
||||
lock.screenshot = false; # disable blurred desktop screenshot background
|
||||
lock.notifications = false; # hide notification icons on the lock screen
|
||||
lock.mpris = false; # hide media controls on the lock screen
|
||||
lock.volume = false; # hide volume slider on the lock screen
|
||||
|
||||
|
|
|
|||
|
|
@ -106,6 +106,11 @@ in
|
|||
default = true;
|
||||
description = "Show blurred desktop screenshot as lock screen background.";
|
||||
};
|
||||
notifications = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Show notification app icons on the lock screen.";
|
||||
};
|
||||
mpris = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
|
|
|
|||
|
|
@ -141,6 +141,74 @@ WlSessionLockSurface {
|
|||
}
|
||||
}
|
||||
|
||||
// Notification pills
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 6
|
||||
visible: (S.Modules.lock.notifications ?? true) && _notifGroups.length > 0
|
||||
|
||||
readonly property var _notifGroups: {
|
||||
const notifs = S.NotifService.list.filter(n => n.state !== "dismissed");
|
||||
const groups = {};
|
||||
for (const n of notifs) {
|
||||
const key = n.appIcon || n.appName || "unknown";
|
||||
if (!groups[key])
|
||||
groups[key] = {
|
||||
icon: n.appIcon,
|
||||
name: n.appName,
|
||||
count: 0
|
||||
};
|
||||
groups[key].count++;
|
||||
}
|
||||
return Object.values(groups);
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: parent._notifGroups
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
width: _pillRow.implicitWidth + 12
|
||||
height: 24
|
||||
radius: 12
|
||||
color: Qt.rgba(S.Theme.base01.r, S.Theme.base01.g, S.Theme.base01.b, 0.7)
|
||||
border.color: Qt.rgba(S.Theme.base03.r, S.Theme.base03.g, S.Theme.base03.b, 0.3)
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
id: _pillRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
|
||||
Image {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 14
|
||||
height: 14
|
||||
source: {
|
||||
const icon = modelData.icon;
|
||||
if (!icon)
|
||||
return "";
|
||||
if (icon.startsWith("/"))
|
||||
return icon;
|
||||
return Quickshell.iconPath(icon) ?? "";
|
||||
}
|
||||
sourceSize: Qt.size(14, 14)
|
||||
visible: source !== ""
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: modelData.count > 1 ? modelData.count.toString() : ""
|
||||
color: S.Theme.base04
|
||||
font.pixelSize: S.Theme.fontSize - 2
|
||||
font.family: S.Theme.fontFamily
|
||||
visible: modelData.count > 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: 1
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ M.HoverPanel {
|
|||
|
||||
// DND toggle
|
||||
Text {
|
||||
text: M.NotifService.dnd ? "\uDB82\uDE93" : "\uDB80\uDC9C"
|
||||
color: M.NotifService.dnd ? S.Theme.base09 : S.Theme.base04
|
||||
text: S.NotifService.dnd ? "\uDB82\uDE93" : "\uDB80\uDC9C"
|
||||
color: S.NotifService.dnd ? S.Theme.base09 : S.Theme.base04
|
||||
font.pixelSize: S.Theme.fontSize
|
||||
font.family: S.Theme.iconFontFamily
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
|
@ -42,7 +42,7 @@ M.HoverPanel {
|
|||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: M.NotifService.toggleDnd()
|
||||
onClicked: S.NotifService.toggleDnd()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ M.HoverPanel {
|
|||
font.pixelSize: S.Theme.fontSize
|
||||
font.family: S.Theme.iconFontFamily
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: M.NotifService.count > 0
|
||||
visible: S.NotifService.count > 0
|
||||
|
||||
MouseArea {
|
||||
id: clearArea
|
||||
|
|
@ -88,7 +88,7 @@ M.HoverPanel {
|
|||
// Group notifications by appName, sorted by max urgency desc then most recent time desc
|
||||
readonly property var _groups: {
|
||||
const map = {};
|
||||
for (const n of M.NotifService.list) {
|
||||
for (const n of S.NotifService.list) {
|
||||
const key = n.appName || "";
|
||||
if (!map[key])
|
||||
map[key] = {
|
||||
|
|
@ -167,14 +167,14 @@ M.HoverPanel {
|
|||
}
|
||||
|
||||
function _cascadeDismiss() {
|
||||
if (M.NotifService.list.length === 0)
|
||||
if (S.NotifService.list.length === 0)
|
||||
return;
|
||||
const ids = M.NotifService.list.map(n => n.id);
|
||||
const ids = S.NotifService.list.map(n => n.id);
|
||||
_startCascade(_getVisibleNotifDelegates(), ids);
|
||||
}
|
||||
|
||||
function _cascadeGroupDismiss(appName) {
|
||||
const ids = M.NotifService.list.filter(n => n.appName === appName).map(n => n.id);
|
||||
const ids = S.NotifService.list.filter(n => n.appName === appName).map(n => n.id);
|
||||
if (ids.length === 0)
|
||||
return;
|
||||
_startCascade(_getVisibleNotifDelegates(appName), ids);
|
||||
|
|
@ -184,7 +184,7 @@ M.HoverPanel {
|
|||
const ids = _pendingDismissIds;
|
||||
_pendingDismissIds = [];
|
||||
for (const id of ids)
|
||||
M.NotifService.dismiss(id);
|
||||
S.NotifService.dismiss(id);
|
||||
}
|
||||
|
||||
property Component _cascadeTimer: Component {
|
||||
|
|
@ -467,7 +467,7 @@ M.HoverPanel {
|
|||
ScriptAction {
|
||||
script: {
|
||||
if (notifDelegate._notif && !notifDelegate._skipDismiss)
|
||||
M.NotifService.dismiss(notifDelegate._notif.id);
|
||||
S.NotifService.dismiss(notifDelegate._notif.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -476,7 +476,7 @@ M.HoverPanel {
|
|||
|
||||
// Empty state
|
||||
Text {
|
||||
visible: M.NotifService.count === 0
|
||||
visible: S.NotifService.count === 0
|
||||
width: menuWindow.contentWidth
|
||||
height: 48
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ PanelWindow {
|
|||
|
||||
required property var screen
|
||||
|
||||
visible: M.NotifService.popups.length > 0 && !S.NiriIpc.overviewOpen
|
||||
visible: S.NotifService.popups.length > 0 && !S.NiriIpc.overviewOpen
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
|
|
@ -34,7 +34,7 @@ PanelWindow {
|
|||
property var _knownIds: ({})
|
||||
|
||||
Repeater {
|
||||
model: M.NotifService.popups.slice(0, S.Modules.notifications.maxPopups || 4)
|
||||
model: S.NotifService.popups.slice(0, S.Modules.notifications.maxPopups || 4)
|
||||
|
||||
delegate: Item {
|
||||
id: popupItem
|
||||
|
|
@ -74,7 +74,7 @@ PanelWindow {
|
|||
}
|
||||
|
||||
Connections {
|
||||
target: M.NotifService
|
||||
target: S.NotifService
|
||||
function onPopupExpiring(notifId) {
|
||||
if (notifId === popupItem.modelData.id)
|
||||
popupItem.animateDismiss(false);
|
||||
|
|
@ -152,7 +152,7 @@ PanelWindow {
|
|||
easing.type: Easing.OutCubic
|
||||
}
|
||||
ScriptAction {
|
||||
script: popupItem._fullDismiss ? M.NotifService.dismiss(popupItem.modelData.id) : M.NotifService.dismissPopup(popupItem.modelData.id)
|
||||
script: popupItem._fullDismiss ? S.NotifService.dismiss(popupItem.modelData.id) : S.NotifService.dismissPopup(popupItem.modelData.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,28 +8,28 @@ M.BarSection {
|
|||
id: root
|
||||
spacing: S.Theme.moduleSpacing
|
||||
tooltip: {
|
||||
const parts = [M.NotifService.count + " notification" + (M.NotifService.count !== 1 ? "s" : "")];
|
||||
if (M.NotifService.dnd)
|
||||
const parts = [S.NotifService.count + " notification" + (S.NotifService.count !== 1 ? "s" : "")];
|
||||
if (S.NotifService.dnd)
|
||||
parts.push("Do not disturb");
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
required property var bar
|
||||
|
||||
readonly property bool hasUrgent: M.NotifService.list.some(n => n.urgency === NotificationUrgency.Critical && n.state !== "dismissed")
|
||||
readonly property bool hasUrgent: S.NotifService.list.some(n => n.urgency === NotificationUrgency.Critical && n.state !== "dismissed")
|
||||
|
||||
M.BarIcon {
|
||||
icon: {
|
||||
if (M.NotifService.dnd)
|
||||
return M.NotifService.count > 0 ? "\uDB80\uDCA0" : "\uDB82\uDE93";
|
||||
return M.NotifService.count > 0 ? "\uDB84\uDD6B" : "\uDB80\uDC9C";
|
||||
if (S.NotifService.dnd)
|
||||
return S.NotifService.count > 0 ? "\uDB80\uDCA0" : "\uDB82\uDE93";
|
||||
return S.NotifService.count > 0 ? "\uDB84\uDD6B" : "\uDB80\uDC9C";
|
||||
}
|
||||
color: M.NotifService.dnd ? S.Theme.base04 : root.accentColor
|
||||
color: S.NotifService.dnd ? S.Theme.base04 : root.accentColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
M.BarLabel {
|
||||
id: countLabel
|
||||
label: M.NotifService.count > 0 ? String(M.NotifService.count) + (root.hasUrgent ? "!" : "") : ""
|
||||
label: S.NotifService.count > 0 ? String(S.NotifService.count) + (root.hasUrgent ? "!" : "") : ""
|
||||
color: root.hasUrgent ? S.Theme.base08 : root.accentColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
|
|
@ -61,9 +61,9 @@ M.BarSection {
|
|||
}
|
||||
|
||||
Connections {
|
||||
target: M.NotifService
|
||||
target: S.NotifService
|
||||
function onCountChanged() {
|
||||
if (M.NotifService.count > 0)
|
||||
if (S.NotifService.count > 0)
|
||||
popAnim.start();
|
||||
}
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ M.BarSection {
|
|||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: M.NotifService.toggleDnd()
|
||||
onTapped: S.NotifService.toggleDnd()
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
|
|
|
|||
|
|
@ -31,8 +31,6 @@ PowerProfileModule 1.0 PowerProfileModule.qml
|
|||
IdleInhibitorModule 1.0 IdleInhibitorModule.qml
|
||||
NotificationsModule 1.0 NotificationsModule.qml
|
||||
ProcessList 1.0 ProcessList.qml
|
||||
singleton NotifService 1.0 NotifService.qml
|
||||
NotifItem 1.0 NotifItem.qml
|
||||
NotifPopup 1.0 NotifPopup.qml
|
||||
NotifCenter 1.0 NotifCenter.qml
|
||||
NotifCard 1.0 NotifCard.qml
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ QtObject {
|
|||
property var lock: ({
|
||||
enable: true,
|
||||
screenshot: true,
|
||||
notifications: true,
|
||||
mpris: true,
|
||||
volume: true
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import QtQuick
|
||||
import Quickshell.Services.Notifications
|
||||
import "." as M
|
||||
import "../services" as S
|
||||
import "." as S
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
|
@ -32,7 +31,7 @@ QtObject {
|
|||
|
||||
// Relative time string — recomputed whenever NotifService._now ticks (single global 5s timer)
|
||||
readonly property string timeStr: {
|
||||
const diff = M.NotifService._now - time;
|
||||
const diff = S.NotifService._now - time;
|
||||
const m = Math.floor(diff / 60000);
|
||||
if (m < 1)
|
||||
return "now";
|
||||
|
|
@ -48,7 +47,7 @@ QtObject {
|
|||
target: root.notification
|
||||
function onClosed() {
|
||||
if (root.state !== "dismissed")
|
||||
M.NotifService.dismiss(root.id);
|
||||
S.NotifService.dismiss(root.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3,8 +3,7 @@ pragma Singleton
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import "." as M
|
||||
import "../services" as S
|
||||
import "." as S
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
|
@ -4,3 +4,5 @@ singleton SystemStats 1.0 SystemStats.qml
|
|||
singleton Modules 1.0 Modules.qml
|
||||
singleton NiriIpc 1.0 NiriIpc.qml
|
||||
singleton PowerProfileService 1.0 PowerProfileService.qml
|
||||
singleton NotifService 1.0 NotifService.qml
|
||||
NotifItem 1.0 NotifItem.qml
|
||||
|
|
|
|||
Reference in a new issue