diff --git a/shell/applets/MprisApplet.qml b/shell/applets/MprisApplet.qml index c4c6ea9..9cc349c 100644 --- a/shell/applets/MprisApplet.qml +++ b/shell/applets/MprisApplet.qml @@ -23,7 +23,6 @@ Column { id: _artImgPrev anchors.fill: parent fillMode: Image.PreserveAspectCrop - sourceSize: Qt.size(width, height) asynchronous: true opacity: 0 @@ -42,7 +41,6 @@ Column { id: _artImg anchors.fill: parent fillMode: Image.PreserveAspectCrop - sourceSize: Qt.size(width, height) asynchronous: true opacity: 0 diff --git a/shell/applets/NotifApplet.qml b/shell/applets/NotifApplet.qml index 5280810..0864da5 100644 --- a/shell/applets/NotifApplet.qml +++ b/shell/applets/NotifApplet.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell import "../services" as S import "../modules" as M @@ -75,14 +76,12 @@ Column { if (!map[key]) map[key] = { appName: key, - resolvedIcon: "", + appIcon: n.appIcon, notifs: [], maxUrgency: 0, maxTime: 0 }; map[key].notifs.push(n); - if (!map[key].resolvedIcon && n.resolvedIcon) - map[key].resolvedIcon = n.resolvedIcon; if (n.urgency > map[key].maxUrgency) map[key].maxUrgency = n.urgency; if (n.time > map[key].maxTime) @@ -102,7 +101,7 @@ Column { arr.push({ type: "header", appName: g.appName, - resolvedIcon: g.resolvedIcon, + appIcon: g.appIcon, count: g.notifs.length, collapsed: collapsed, summaries: g.notifs.map(n => n.summary || "") @@ -300,7 +299,14 @@ Column { anchors.topMargin: (28 - height) / 2 width: S.Theme.fontSize + 2 height: S.Theme.fontSize + 2 - source: notifDelegate._type === "header" ? (notifDelegate.modelData.resolvedIcon || "") : "" + source: { + if (notifDelegate._type !== "header") + return ""; + const ic = notifDelegate.modelData.appIcon; + if (!ic) + return ""; + return (ic.startsWith("/") || ic.startsWith("file://")) ? ic : Quickshell.iconPath(ic, "dialog-information"); + } visible: status === Image.Ready fillMode: Image.PreserveAspectFit sourceSize: Qt.size(S.Theme.fontSize + 2, S.Theme.fontSize + 2) diff --git a/shell/dock/AppletDock.qml b/shell/dock/AppletDock.qml index 6febda3..c245147 100644 --- a/shell/dock/AppletDock.qml +++ b/shell/dock/AppletDock.qml @@ -16,7 +16,7 @@ PanelWindow { property bool _winVisible: false - WlrLayershell.layer: S.DockState.mode === "pinned" ? WlrLayer.Bottom : WlrLayer.Overlay + WlrLayershell.layer: S.DockState.mode === "pinned" ? WlrLayer.Top : WlrLayer.Overlay WlrLayershell.exclusiveZone: S.DockState.mode === "pinned" ? _dockWidth : 0 WlrLayershell.namespace: "nova-dock" @@ -136,55 +136,10 @@ PanelWindow { } } - // Dock bar - mirrors main bar style, hosts dock toggle when pinned - Item { - id: _dockBar - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - height: S.Theme.barHeight - visible: S.DockState.mode === "pinned" - - transform: Translate { - x: root._slideX - } - - Rectangle { - anchors.fill: parent - color: S.Theme.base00 - } - - // Bottom border - gradient matching bar style (base0C to base09, but just the right end) - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - height: 1 - color: S.Theme.base09 - } - - // Dock toggle - M.BarGroup { - anchors.centerIn: parent - M.BarModule { - tooltip: "Unpin dock" - onTapped: S.DockState.toggle() - - M.BarIcon { - icon: "\uDB80\uDD8B" - anchors.verticalCenter: parent.verticalCenter - } - } - } - } - // Content - inset past the edge line + gap Flickable { id: _flickable - anchors.top: _dockBar.visible ? _dockBar.bottom : parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom + anchors.fill: parent anchors.leftMargin: S.Theme.groupSpacing + 1 contentHeight: _column.height clip: true diff --git a/shell/lock/LockNotifPills.qml b/shell/lock/LockNotifPills.qml index 82025bd..35cf4ad 100644 --- a/shell/lock/LockNotifPills.qml +++ b/shell/lock/LockNotifPills.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell import "../services" as S Row { @@ -11,15 +12,13 @@ Row { const notifs = S.NotifService.list.filter(n => n.state !== "dismissed"); const groups = {}; for (const n of notifs) { - const key = n.appName || "unknown"; + const key = n.appIcon || n.appName || "unknown"; if (!groups[key]) groups[key] = { - resolvedIcon: "", + icon: n.appIcon, name: n.appName, count: 0 }; - if (!groups[key].resolvedIcon && n.resolvedIcon) - groups[key].resolvedIcon = n.resolvedIcon; groups[key].count++; } return Object.values(groups); @@ -63,7 +62,14 @@ Row { anchors.verticalCenter: parent.verticalCenter width: 14 height: 14 - source: _pill.modelData.resolvedIcon || "" + source: { + const icon = _pill.modelData.icon; + if (!icon) + return ""; + if (icon.startsWith("/")) + return icon; + return Quickshell.iconPath(icon) ?? ""; + } sourceSize: Qt.size(14, 14) visible: source !== "" } diff --git a/shell/modules/DockModule.qml b/shell/modules/DockModule.qml index 016ee0f..49fc28f 100644 --- a/shell/modules/DockModule.qml +++ b/shell/modules/DockModule.qml @@ -4,7 +4,7 @@ import "../services" as S M.BarModule { id: root - active: S.Modules.dock.enable && S.DockState.mode !== "pinned" + active: S.Modules.dock.enable tooltip: S.DockState.open ? "Close dock" : "Open dock" onTapped: S.DockState.toggle() diff --git a/shell/modules/NotifCard.qml b/shell/modules/NotifCard.qml index 10e7630..df988b7 100644 --- a/shell/modules/NotifCard.qml +++ b/shell/modules/NotifCard.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell import Quickshell.Services.Notifications import "." as M import "../services" as S @@ -86,7 +87,15 @@ Item { anchors.topMargin: 8 width: root.iconSize height: root.iconSize - source: root.notif?.resolvedIcon ?? "" + source: { + const img = root.notif?.image; + if (img) + return img; + const ic = root.notif?.appIcon; + if (!ic) + return ""; + return (ic.startsWith("/") || ic.startsWith("file://")) ? ic : Quickshell.iconPath(ic, "dialog-information"); + } visible: status === Image.Ready fillMode: Image.PreserveAspectFit sourceSize: Qt.size(root.iconSize, root.iconSize) diff --git a/shell/modules/ThemedIcon.qml b/shell/modules/ThemedIcon.qml index 5379f11..066cae2 100644 --- a/shell/modules/ThemedIcon.qml +++ b/shell/modules/ThemedIcon.qml @@ -7,7 +7,6 @@ Image { required property color tint fillMode: Image.PreserveAspectFit - sourceSize: Qt.size(width, height) layer.enabled: true layer.effect: MultiEffect { diff --git a/shell/modules/TrayMenu.qml b/shell/modules/TrayMenu.qml index 6f1f390..d663661 100644 --- a/shell/modules/TrayMenu.qml +++ b/shell/modules/TrayMenu.qml @@ -6,9 +6,7 @@ import "../services" as S M.HoverPanel { id: menuWindow - // Deferred: anchorItem must be set before _show() computes position. - // Static `showPanel: true` would race with anchorItem initialization. - Component.onCompleted: showPanel = true + showPanel: true required property var handle diff --git a/shell/services/BatteryService.qml b/shell/services/BatteryService.qml index e8e115f..2794f09 100644 --- a/shell/services/BatteryService.qml +++ b/shell/services/BatteryService.qml @@ -1,6 +1,8 @@ pragma Singleton import QtQuick +import Quickshell +import Quickshell.Io import Quickshell.Services.UPower import "." as S @@ -45,6 +47,34 @@ QtObject { } } + // Low battery notifications + property bool _warnSent: false + property bool _critSent: false + + property var _chargingWatcher: Connections { + target: root + function onChargingChanged() { + root._warnSent = false; + root._critSent = false; + } + function onPercentChanged() { + if (root.charging) + return; + if (root.percent < root.critThresh && !root._critSent) { + root._critSent = true; + root._warnSent = true; + _notifProc.command = ["notify-send", "--urgency=critical", "--icon=battery-low", "--category=device", "Very Low Battery", "Connect to power now!"]; + _notifProc.running = true; + } else if (root.percent < root.warnThresh && !root._warnSent) { + root._warnSent = true; + _notifProc.command = ["notify-send", "--icon=battery-caution", "--category=device", "Low Battery"]; + _notifProc.running = true; + } + } + } + + property var _notifProc: Process {} + function fmtTime(secs) { if (!secs || secs <= 0) return ""; diff --git a/shell/services/NotifItem.qml b/shell/services/NotifItem.qml index 191170e..488f48a 100644 --- a/shell/services/NotifItem.qml +++ b/shell/services/NotifItem.qml @@ -1,5 +1,4 @@ import QtQuick -import Quickshell import Quickshell.Services.Notifications import "." as S @@ -21,17 +20,6 @@ QtObject { property var actions: [] property real time: Date.now() - // Resolved icon URL - checks image first, then resolves appIcon via XDG lookup - readonly property string resolvedIcon: { - if (image) - return image; - if (!appIcon) - return ""; - if (appIcon.startsWith("/") || appIcon.startsWith("file://")) - return appIcon; - return Quickshell.iconPath(appIcon, "dialog-information"); - } - // Expire timer — owned by this item, not dynamically created readonly property Timer _expireTimer: Timer { running: false diff --git a/test/qmllint-baseline.txt b/test/qmllint-baseline.txt index f3c778e..ff9ca29 100644 --- a/test/qmllint-baseline.txt +++ b/test/qmllint-baseline.txt @@ -84,6 +84,7 @@ shell/modules/WeatherModule.qml: Unqualified access [unqualified] shell/modules/WindowTitleModule.qml: Unqualified access [unqualified] shell/modules/WorkspacesModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/WorkspacesModule.qml: Unqualified access [unqualified] +shell/services/BatteryService.qml: Unqualified access [unqualified] shell/services/BluetoothService.qml: Unqualified access [unqualified] shell/services/LockService.qml: Type QProcess::ExitStatus of parameter exitStatus in signal called exited was not found, but is required to compile onExited. Did you add all imports and dependencies? [signal-handler-parameters] shell/services/LockService.qml: Unqualified access [unqualified]