diff --git a/shell/applets/BluetoothApplet.qml b/shell/applets/BluetoothApplet.qml index 4fb5c43..dfc6f18 100644 --- a/shell/applets/BluetoothApplet.qml +++ b/shell/applets/BluetoothApplet.qml @@ -1,6 +1,7 @@ import QtQuick import "../services" as S +// NOT safe for lock screen - can toggle bluetooth power and connect/disconnect devices Column { id: root diff --git a/shell/applets/CpuApplet.qml b/shell/applets/CpuApplet.qml index 427f251..76b3ab3 100644 --- a/shell/applets/CpuApplet.qml +++ b/shell/applets/CpuApplet.qml @@ -11,7 +11,6 @@ Column { required property color accentColor property bool active: true - property bool locked: false property bool _coreActive: false onActiveChanged: { @@ -141,36 +140,25 @@ Column { } } - // Overall CPU utilization chart - Item { - width: root.width - height: 44 + Separator {} - Text { - id: _totalLabel - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: S.SystemStats.cpuUsage + "%" - color: S.Theme.loadColor(S.SystemStats.cpuUsage) - font.pixelSize: S.Theme.fontSize - 1 - font.family: S.Theme.fontFamily - font.bold: true - width: 32 - } + // Overall CPU utilization + InfoRow { + label: "Total" + value: S.SystemStats.cpuUsage + "% @ " + S.SystemStats.cpuFreqGhz.toFixed(2) + " GHz" + valueColor: S.Theme.loadColor(S.SystemStats.cpuUsage) + } - SparklineCanvas { - anchors.left: _totalLabel.right - anchors.leftMargin: 6 - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.verticalCenter: parent.verticalCenter - height: 32 - history: root._cpuHistory - strokeColor: root.accentColor - colorAt: v => S.Theme.loadColor(v) - active: root.active - } + SparklineCanvas { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.right: parent.right + anchors.rightMargin: 12 + height: 32 + history: root._cpuHistory + strokeColor: root.accentColor + colorAt: v => S.Theme.loadColor(v) + active: root.active } property var _cpuHistory: [] @@ -184,72 +172,77 @@ Column { } } - Separator {} - - Item { + // Process list - hidden on lock screen (exposes running process names) + Column { + visible: !S.LockService.locked width: root.width - height: 18 - Text { - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: "PROCESS" - color: S.Theme.base03 - font.pixelSize: S.Theme.fontSize - 3 - font.family: S.Theme.fontFamily - font.letterSpacing: 1 - } + Separator {} - Text { - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: "CPU" - color: S.Theme.base03 - font.pixelSize: S.Theme.fontSize - 3 - font.family: S.Theme.fontFamily - font.letterSpacing: 1 - } - } - - // Top processes by CPU - Repeater { - model: root.processes - - delegate: Item { - required property var modelData - width: root.width - height: 20 + Item { + width: parent.width + height: 18 Text { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter - text: modelData.cmd - color: S.Theme.base05 - font.pixelSize: S.Theme.fontSize - 2 + text: "PROCESS" + color: S.Theme.base03 + font.pixelSize: S.Theme.fontSize - 3 font.family: S.Theme.fontFamily - elide: Text.ElideRight - width: parent.width - 80 + font.letterSpacing: 1 } Text { anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter - text: modelData.cpu.toFixed(1) + "%" - color: S.Theme.loadColor(modelData.cpu) - font.pixelSize: S.Theme.fontSize - 2 + text: "CPU" + color: S.Theme.base03 + font.pixelSize: S.Theme.fontSize - 3 font.family: S.Theme.fontFamily - width: 36 - horizontalAlignment: Text.AlignRight + font.letterSpacing: 1 } } - } - Item { - width: 1 - height: 4 + Repeater { + model: root.processes + + delegate: Item { + required property var modelData + width: root.width + height: 20 + + Text { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.verticalCenter: parent.verticalCenter + text: modelData.cmd + color: S.Theme.base05 + font.pixelSize: S.Theme.fontSize - 2 + font.family: S.Theme.fontFamily + elide: Text.ElideRight + width: parent.width - 80 + } + + Text { + anchors.right: parent.right + anchors.rightMargin: 12 + anchors.verticalCenter: parent.verticalCenter + text: modelData.cpu.toFixed(1) + "%" + color: S.Theme.loadColor(modelData.cpu) + font.pixelSize: S.Theme.fontSize - 2 + font.family: S.Theme.fontFamily + width: 36 + horizontalAlignment: Text.AlignRight + } + } + } + + Item { + width: 1 + height: 4 + } } } diff --git a/shell/applets/MemoryApplet.qml b/shell/applets/MemoryApplet.qml index b9b2f15..bb9af55 100644 --- a/shell/applets/MemoryApplet.qml +++ b/shell/applets/MemoryApplet.qml @@ -101,72 +101,77 @@ Column { active: root.active } - Separator {} - - Item { + // Process list - hidden on lock screen (exposes running process names) + Column { + visible: !S.LockService.locked width: root.width - height: 18 - Text { - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: "PROCESS" - color: S.Theme.base03 - font.pixelSize: S.Theme.fontSize - 3 - font.family: S.Theme.fontFamily - font.letterSpacing: 1 - } + Separator {} - Text { - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: "MEM" - color: S.Theme.base03 - font.pixelSize: S.Theme.fontSize - 3 - font.family: S.Theme.fontFamily - font.letterSpacing: 1 - } - } - - // Top processes by memory - Repeater { - model: root.processes - - delegate: Item { - required property var modelData - width: root.width - height: 20 + Item { + width: parent.width + height: 18 Text { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter - text: modelData.cmd - color: S.Theme.base05 - font.pixelSize: S.Theme.fontSize - 2 + text: "PROCESS" + color: S.Theme.base03 + font.pixelSize: S.Theme.fontSize - 3 font.family: S.Theme.fontFamily - elide: Text.ElideRight - width: parent.width - 80 + font.letterSpacing: 1 } Text { anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter - text: modelData.mem.toFixed(1) + "%" - color: S.Theme.base04 - font.pixelSize: S.Theme.fontSize - 2 + text: "MEM" + color: S.Theme.base03 + font.pixelSize: S.Theme.fontSize - 3 font.family: S.Theme.fontFamily - width: 36 - horizontalAlignment: Text.AlignRight + font.letterSpacing: 1 } } - } - Item { - width: 1 - height: 4 + Repeater { + model: root.processes + + delegate: Item { + required property var modelData + width: root.width + height: 20 + + Text { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.verticalCenter: parent.verticalCenter + text: modelData.cmd + color: S.Theme.base05 + font.pixelSize: S.Theme.fontSize - 2 + font.family: S.Theme.fontFamily + elide: Text.ElideRight + width: parent.width - 80 + } + + Text { + anchors.right: parent.right + anchors.rightMargin: 12 + anchors.verticalCenter: parent.verticalCenter + text: modelData.mem.toFixed(1) + "%" + color: S.Theme.base04 + font.pixelSize: S.Theme.fontSize - 2 + font.family: S.Theme.fontFamily + width: 36 + horizontalAlignment: Text.AlignRight + } + } + } + + Item { + width: 1 + height: 4 + } } } diff --git a/shell/applets/NetworkApplet.qml b/shell/applets/NetworkApplet.qml index 3849f42..47453ef 100644 --- a/shell/applets/NetworkApplet.qml +++ b/shell/applets/NetworkApplet.qml @@ -1,6 +1,7 @@ import QtQuick import "../services" as S +// NOT safe for lock screen - can toggle wifi and connect/disconnect networks Column { id: root diff --git a/shell/applets/NotifApplet.qml b/shell/applets/NotifApplet.qml index 4973434..0864da5 100644 --- a/shell/applets/NotifApplet.qml +++ b/shell/applets/NotifApplet.qml @@ -3,6 +3,9 @@ import Quickshell import "../services" as S import "../modules" as M +// NOT safe for lock screen - notification contents may be sensitive, +// can dismiss/clear notifications and toggle DND + Column { id: root diff --git a/shell/applets/PowerApplet.qml b/shell/applets/PowerApplet.qml index e444691..efb6fa3 100644 --- a/shell/applets/PowerApplet.qml +++ b/shell/applets/PowerApplet.qml @@ -2,6 +2,7 @@ import QtQuick import Quickshell import "../services" as S +// NOT safe for lock screen - executes system commands (shutdown, reboot, logout, suspend) Column { id: root diff --git a/shell/applets/VolumeApplet.qml b/shell/applets/VolumeApplet.qml index ec06e85..eb9b58a 100644 --- a/shell/applets/VolumeApplet.qml +++ b/shell/applets/VolumeApplet.qml @@ -5,11 +5,9 @@ import "../services" as S Column { id: root - required property var sink - required property var sinkList - required property var streamList required property color accentColor + readonly property var sink: S.PipewireService.sink property real volume: sink?.audio?.volume ?? 0 property bool muted: sink?.audio?.muted ?? false readonly property string volumeIcon: muted ? "\uF026" : (volume > 0.5 ? "\uF028" : (volume > 0 ? "\uF027" : "\uF026")) @@ -95,14 +93,15 @@ Column { } } - // Device + stream list + // Device + stream list (hidden on lock screen) Column { id: deviceList + visible: !S.LockService.locked width: root.width // Output devices - only shown when more than one exists Column { - visible: root.sinkList.length > 1 + visible: S.PipewireService.sinks.length > 1 width: parent.width Separator {} @@ -119,7 +118,7 @@ Column { } Repeater { - model: root.sinkList + model: S.PipewireService.sinks delegate: HoverableListItem { required property var modelData @@ -149,11 +148,11 @@ Column { // Streams section Separator { - visible: root.streamList.length > 0 + visible: S.PipewireService.streams.length > 0 } Text { - visible: root.streamList.length > 0 + visible: S.PipewireService.streams.length > 0 width: parent.width height: visible ? 24 : 0 verticalAlignment: Text.AlignVCenter @@ -165,7 +164,7 @@ Column { } Repeater { - model: root.streamList + model: S.PipewireService.streams delegate: Item { id: streamEntry diff --git a/shell/dock/AppletDock.qml b/shell/dock/AppletDock.qml index d8184a1..0601279 100644 --- a/shell/dock/AppletDock.qml +++ b/shell/dock/AppletDock.qml @@ -3,7 +3,6 @@ import Quickshell import Quickshell.Wayland import Quickshell.Io import Quickshell.Services.Mpris -import Quickshell.Services.Pipewire import "." as D import "../services" as S import "../applets" as C @@ -325,9 +324,6 @@ PanelWindow { C.VolumeApplet { width: parent.width - sink: Pipewire.defaultAudioSink - sinkList: root._sinkList - streamList: root._streamList accentColor: root._accent } } @@ -430,30 +426,6 @@ PanelWindow { active: _memCard.expanded && S.DockState.open } - PwObjectTracker { - objects: [Pipewire.defaultAudioSink, ...root._streamList] - } - - readonly property var _sinkList: { - const sinks = []; - if (Pipewire.nodes) { - for (const node of Pipewire.nodes.values) - if (!node.isStream && node.isSink) - sinks.push(node); - } - return sinks; - } - - readonly property var _streamList: { - const streams = []; - if (Pipewire.nodes) { - for (const node of Pipewire.nodes.values) - if (node.isStream && node.audio) - streams.push(node); - } - return streams; - } - Process { id: _runner } diff --git a/shell/lock/LockWidgets.qml b/shell/lock/LockWidgets.qml index 1836d71..e934d53 100644 --- a/shell/lock/LockWidgets.qml +++ b/shell/lock/LockWidgets.qml @@ -1,5 +1,4 @@ import QtQuick -import Quickshell.Services.Pipewire import "../services" as S import "../applets" as C @@ -91,11 +90,7 @@ Item { 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 - visible: (S.Modules.lock.volume ?? true) && Pipewire.defaultAudioSink !== null - - PwObjectTracker { - objects: [Pipewire.defaultAudioSink] - } + visible: (S.Modules.lock.volume ?? true) && S.PipewireService.sink !== null C.VolumeApplet { id: _volumeContent @@ -103,9 +98,6 @@ Item { anchors.right: parent.right anchors.top: parent.top anchors.topMargin: 8 - sink: Pipewire.defaultAudioSink - sinkList: [] - streamList: [] accentColor: S.Theme.base0E } } diff --git a/shell/modules/VolumeModule.qml b/shell/modules/VolumeModule.qml index de11e0a..32f46a4 100644 --- a/shell/modules/VolumeModule.qml +++ b/shell/modules/VolumeModule.qml @@ -1,6 +1,5 @@ import QtQuick import Quickshell -import Quickshell.Services.Pipewire import "." as M import "../services" as S import "../applets" as C @@ -15,43 +14,15 @@ M.BarModule { panelComponent: Component { C.VolumeApplet { width: parent.width - sink: root.sink - sinkList: root._sinkList - streamList: root._streamList accentColor: root.accentColor } } - PwObjectTracker { - objects: [Pipewire.defaultAudioSink, ...root._streamList] - } - - readonly property var sink: Pipewire.defaultAudioSink - readonly property real volume: sink?.audio?.volume ?? 0 - readonly property bool muted: sink?.audio?.muted ?? false + readonly property real volume: S.PipewireService.volume + readonly property bool muted: S.PipewireService.muted readonly property string _volumeIcon: muted ? "\uF026" : (volume > 0.5 ? "\uF028" : (volume > 0 ? "\uF027" : "\uF026")) readonly property color _volumeColor: muted ? S.Theme.base04 : root.accentColor - readonly property var _sinkList: { - const sinks = []; - if (Pipewire.nodes) { - for (const node of Pipewire.nodes.values) - if (!node.isStream && node.isSink) - sinks.push(node); - } - return sinks; - } - - readonly property var _streamList: { - const streams = []; - if (Pipewire.nodes) { - for (const node of Pipewire.nodes.values) - if (node.isStream && node.audio) - streams.push(node); - } - return streams; - } - property bool _volumeInit: false property bool _mutedInit: false @@ -85,9 +56,9 @@ M.BarModule { WheelHandler { onWheel: event => { - if (!root.sink?.audio) + if (!S.PipewireService.sink?.audio) return; - root.sink.audio.volume = Math.max(0, root.sink.audio.volume + (event.angleDelta.y > 0 ? 0.05 : -0.05)); + S.PipewireService.sink.audio.volume = Math.max(0, S.PipewireService.sink.audio.volume + (event.angleDelta.y > 0 ? 0.05 : -0.05)); } } } diff --git a/shell/services/LockService.qml b/shell/services/LockService.qml index 1b55916..6dadafc 100644 --- a/shell/services/LockService.qml +++ b/shell/services/LockService.qml @@ -8,6 +8,7 @@ QtObject { id: root readonly property bool enabled: S.Modules.lock.enable + property bool locked: false property string sessionPath: "" // Lock/unlock requests from logind @@ -15,10 +16,10 @@ QtObject { signal unlockRequested // Set logind LockedHint - function setLockedHint(locked) { + function setLockedHint(isLocked) { if (!sessionPath) return; - _lockedHint._locked = locked; + root.locked = isLocked; _lockedHint.running = true; } @@ -82,7 +83,6 @@ QtObject { // Set logind LockedHint property Process _lockedHint: Process { - property bool _locked: false - command: ["busctl", "call", "--system", "org.freedesktop.login1", root.sessionPath || "/org/freedesktop/login1/session/auto", "org.freedesktop.login1.Session", "SetLockedHint", "b", _locked ? "true" : "false"] + command: ["busctl", "call", "--system", "org.freedesktop.login1", root.sessionPath || "/org/freedesktop/login1/session/auto", "org.freedesktop.login1.Session", "SetLockedHint", "b", root.locked ? "true" : "false"] } } diff --git a/shell/services/PipewireService.qml b/shell/services/PipewireService.qml new file mode 100644 index 0000000..63295f9 --- /dev/null +++ b/shell/services/PipewireService.qml @@ -0,0 +1,36 @@ +pragma Singleton + +import QtQuick +import Quickshell.Services.Pipewire + +QtObject { + id: root + + readonly property var sink: Pipewire.defaultAudioSink + readonly property real volume: sink?.audio?.volume ?? 0 + readonly property bool muted: sink?.audio?.muted ?? false + + readonly property var sinks: { + const list = []; + if (Pipewire.nodes) { + for (const node of Pipewire.nodes.values) + if (!node.isStream && node.isSink) + list.push(node); + } + return list; + } + + readonly property var streams: { + const list = []; + if (Pipewire.nodes) { + for (const node of Pipewire.nodes.values) + if (node.isStream && node.audio) + list.push(node); + } + return list; + } + + property PwObjectTracker _tracker: PwObjectTracker { + objects: [Pipewire.defaultAudioSink, ...root.streams] + } +} diff --git a/shell/services/qmldir b/shell/services/qmldir index 9a06401..38e7fd6 100644 --- a/shell/services/qmldir +++ b/shell/services/qmldir @@ -12,6 +12,7 @@ singleton MprisService 1.0 MprisService.qml singleton NetworkService 1.0 NetworkService.qml singleton NiriIpc 1.0 NiriIpc.qml singleton NotifService 1.0 NotifService.qml +singleton PipewireService 1.0 PipewireService.qml singleton PowerProfileService 1.0 PowerProfileService.qml singleton ScreenshotService 1.0 ScreenshotService.qml singleton SleepService 1.0 SleepService.qml