diff --git a/shell/applets/MprisApplet.qml b/shell/applets/MprisApplet.qml index 9cc349c..c4c6ea9 100644 --- a/shell/applets/MprisApplet.qml +++ b/shell/applets/MprisApplet.qml @@ -23,6 +23,7 @@ Column { id: _artImgPrev anchors.fill: parent fillMode: Image.PreserveAspectCrop + sourceSize: Qt.size(width, height) asynchronous: true opacity: 0 @@ -41,6 +42,7 @@ 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 0864da5..5280810 100644 --- a/shell/applets/NotifApplet.qml +++ b/shell/applets/NotifApplet.qml @@ -1,5 +1,4 @@ import QtQuick -import Quickshell import "../services" as S import "../modules" as M @@ -76,12 +75,14 @@ Column { if (!map[key]) map[key] = { appName: key, - appIcon: n.appIcon, + resolvedIcon: "", 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) @@ -101,7 +102,7 @@ Column { arr.push({ type: "header", appName: g.appName, - appIcon: g.appIcon, + resolvedIcon: g.resolvedIcon, count: g.notifs.length, collapsed: collapsed, summaries: g.notifs.map(n => n.summary || "") @@ -299,14 +300,7 @@ Column { anchors.topMargin: (28 - height) / 2 width: S.Theme.fontSize + 2 height: S.Theme.fontSize + 2 - 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"); - } + source: notifDelegate._type === "header" ? (notifDelegate.modelData.resolvedIcon || "") : "" 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 c245147..6febda3 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.Top : WlrLayer.Overlay + WlrLayershell.layer: S.DockState.mode === "pinned" ? WlrLayer.Bottom : WlrLayer.Overlay WlrLayershell.exclusiveZone: S.DockState.mode === "pinned" ? _dockWidth : 0 WlrLayershell.namespace: "nova-dock" @@ -136,10 +136,55 @@ 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.fill: parent + anchors.top: _dockBar.visible ? _dockBar.bottom : parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom anchors.leftMargin: S.Theme.groupSpacing + 1 contentHeight: _column.height clip: true diff --git a/shell/lock/LockNotifPills.qml b/shell/lock/LockNotifPills.qml index 35cf4ad..82025bd 100644 --- a/shell/lock/LockNotifPills.qml +++ b/shell/lock/LockNotifPills.qml @@ -1,5 +1,4 @@ import QtQuick -import Quickshell import "../services" as S Row { @@ -12,13 +11,15 @@ Row { const notifs = S.NotifService.list.filter(n => n.state !== "dismissed"); const groups = {}; for (const n of notifs) { - const key = n.appIcon || n.appName || "unknown"; + const key = n.appName || "unknown"; if (!groups[key]) groups[key] = { - icon: n.appIcon, + resolvedIcon: "", name: n.appName, count: 0 }; + if (!groups[key].resolvedIcon && n.resolvedIcon) + groups[key].resolvedIcon = n.resolvedIcon; groups[key].count++; } return Object.values(groups); @@ -62,14 +63,7 @@ Row { anchors.verticalCenter: parent.verticalCenter width: 14 height: 14 - source: { - const icon = _pill.modelData.icon; - if (!icon) - return ""; - if (icon.startsWith("/")) - return icon; - return Quickshell.iconPath(icon) ?? ""; - } + source: _pill.modelData.resolvedIcon || "" sourceSize: Qt.size(14, 14) visible: source !== "" } diff --git a/shell/modules/DockModule.qml b/shell/modules/DockModule.qml index 49fc28f..016ee0f 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 + active: S.Modules.dock.enable && S.DockState.mode !== "pinned" 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 df988b7..10e7630 100644 --- a/shell/modules/NotifCard.qml +++ b/shell/modules/NotifCard.qml @@ -1,5 +1,4 @@ import QtQuick -import Quickshell import Quickshell.Services.Notifications import "." as M import "../services" as S @@ -87,15 +86,7 @@ Item { anchors.topMargin: 8 width: root.iconSize height: root.iconSize - 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"); - } + source: root.notif?.resolvedIcon ?? "" 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 066cae2..5379f11 100644 --- a/shell/modules/ThemedIcon.qml +++ b/shell/modules/ThemedIcon.qml @@ -7,6 +7,7 @@ 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 d663661..6f1f390 100644 --- a/shell/modules/TrayMenu.qml +++ b/shell/modules/TrayMenu.qml @@ -6,7 +6,9 @@ import "../services" as S M.HoverPanel { id: menuWindow - showPanel: true + // Deferred: anchorItem must be set before _show() computes position. + // Static `showPanel: true` would race with anchorItem initialization. + Component.onCompleted: showPanel = true required property var handle diff --git a/shell/services/BatteryService.qml b/shell/services/BatteryService.qml index 2794f09..e8e115f 100644 --- a/shell/services/BatteryService.qml +++ b/shell/services/BatteryService.qml @@ -1,8 +1,6 @@ pragma Singleton import QtQuick -import Quickshell -import Quickshell.Io import Quickshell.Services.UPower import "." as S @@ -47,34 +45,6 @@ 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 488f48a..191170e 100644 --- a/shell/services/NotifItem.qml +++ b/shell/services/NotifItem.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell import Quickshell.Services.Notifications import "." as S @@ -20,6 +21,17 @@ 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 ff9ca29..f3c778e 100644 --- a/test/qmllint-baseline.txt +++ b/test/qmllint-baseline.txt @@ -84,7 +84,6 @@ 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]