pipewire service, lock-aware applets, cpu overall chart, security comments

This commit is contained in:
Damocles 2026-04-27 18:36:07 +02:00
parent e7bf175169
commit 8628b4b27b
13 changed files with 183 additions and 208 deletions

View file

@ -1,6 +1,7 @@
import QtQuick import QtQuick
import "../services" as S import "../services" as S
// NOT safe for lock screen - can toggle bluetooth power and connect/disconnect devices
Column { Column {
id: root id: root

View file

@ -11,7 +11,6 @@ Column {
required property color accentColor required property color accentColor
property bool active: true property bool active: true
property bool locked: false
property bool _coreActive: false property bool _coreActive: false
onActiveChanged: { onActiveChanged: {
@ -141,36 +140,25 @@ Column {
} }
} }
// Overall CPU utilization chart Separator {}
Item {
width: root.width
height: 44
Text { // Overall CPU utilization
id: _totalLabel InfoRow {
anchors.left: parent.left label: "Total"
anchors.leftMargin: 12 value: S.SystemStats.cpuUsage + "% @ " + S.SystemStats.cpuFreqGhz.toFixed(2) + " GHz"
anchors.verticalCenter: parent.verticalCenter valueColor: S.Theme.loadColor(S.SystemStats.cpuUsage)
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
}
SparklineCanvas { SparklineCanvas {
anchors.left: _totalLabel.right anchors.left: parent.left
anchors.leftMargin: 6 anchors.leftMargin: 12
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 12 anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter height: 32
height: 32 history: root._cpuHistory
history: root._cpuHistory strokeColor: root.accentColor
strokeColor: root.accentColor colorAt: v => S.Theme.loadColor(v)
colorAt: v => S.Theme.loadColor(v) active: root.active
active: root.active
}
} }
property var _cpuHistory: [] property var _cpuHistory: []
@ -184,72 +172,77 @@ Column {
} }
} }
Separator {} // Process list - hidden on lock screen (exposes running process names)
Column {
Item { visible: !S.LockService.locked
width: root.width width: root.width
height: 18
Text { Separator {}
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
}
Text { Item {
anchors.right: parent.right width: parent.width
anchors.rightMargin: 12 height: 18
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
Text { Text {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 12 anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: modelData.cmd text: "PROCESS"
color: S.Theme.base05 color: S.Theme.base03
font.pixelSize: S.Theme.fontSize - 2 font.pixelSize: S.Theme.fontSize - 3
font.family: S.Theme.fontFamily font.family: S.Theme.fontFamily
elide: Text.ElideRight font.letterSpacing: 1
width: parent.width - 80
} }
Text { Text {
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 12 anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: modelData.cpu.toFixed(1) + "%" text: "CPU"
color: S.Theme.loadColor(modelData.cpu) color: S.Theme.base03
font.pixelSize: S.Theme.fontSize - 2 font.pixelSize: S.Theme.fontSize - 3
font.family: S.Theme.fontFamily font.family: S.Theme.fontFamily
width: 36 font.letterSpacing: 1
horizontalAlignment: Text.AlignRight
} }
} }
}
Item { Repeater {
width: 1 model: root.processes
height: 4
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
}
} }
} }

View file

@ -101,72 +101,77 @@ Column {
active: root.active active: root.active
} }
Separator {} // Process list - hidden on lock screen (exposes running process names)
Column {
Item { visible: !S.LockService.locked
width: root.width width: root.width
height: 18
Text { Separator {}
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
}
Text { Item {
anchors.right: parent.right width: parent.width
anchors.rightMargin: 12 height: 18
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
Text { Text {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 12 anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: modelData.cmd text: "PROCESS"
color: S.Theme.base05 color: S.Theme.base03
font.pixelSize: S.Theme.fontSize - 2 font.pixelSize: S.Theme.fontSize - 3
font.family: S.Theme.fontFamily font.family: S.Theme.fontFamily
elide: Text.ElideRight font.letterSpacing: 1
width: parent.width - 80
} }
Text { Text {
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 12 anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: modelData.mem.toFixed(1) + "%" text: "MEM"
color: S.Theme.base04 color: S.Theme.base03
font.pixelSize: S.Theme.fontSize - 2 font.pixelSize: S.Theme.fontSize - 3
font.family: S.Theme.fontFamily font.family: S.Theme.fontFamily
width: 36 font.letterSpacing: 1
horizontalAlignment: Text.AlignRight
} }
} }
}
Item { Repeater {
width: 1 model: root.processes
height: 4
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
}
} }
} }

View file

@ -1,6 +1,7 @@
import QtQuick import QtQuick
import "../services" as S import "../services" as S
// NOT safe for lock screen - can toggle wifi and connect/disconnect networks
Column { Column {
id: root id: root

View file

@ -3,6 +3,9 @@ import Quickshell
import "../services" as S import "../services" as S
import "../modules" as M import "../modules" as M
// NOT safe for lock screen - notification contents may be sensitive,
// can dismiss/clear notifications and toggle DND
Column { Column {
id: root id: root

View file

@ -2,6 +2,7 @@ import QtQuick
import Quickshell import Quickshell
import "../services" as S import "../services" as S
// NOT safe for lock screen - executes system commands (shutdown, reboot, logout, suspend)
Column { Column {
id: root id: root

View file

@ -5,11 +5,9 @@ import "../services" as S
Column { Column {
id: root id: root
required property var sink
required property var sinkList
required property var streamList
required property color accentColor required property color accentColor
readonly property var sink: S.PipewireService.sink
property real volume: sink?.audio?.volume ?? 0 property real volume: sink?.audio?.volume ?? 0
property bool muted: sink?.audio?.muted ?? false property bool muted: sink?.audio?.muted ?? false
readonly property string volumeIcon: muted ? "\uF026" : (volume > 0.5 ? "\uF028" : (volume > 0 ? "\uF027" : "\uF026")) 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 { Column {
id: deviceList id: deviceList
visible: !S.LockService.locked
width: root.width width: root.width
// Output devices - only shown when more than one exists // Output devices - only shown when more than one exists
Column { Column {
visible: root.sinkList.length > 1 visible: S.PipewireService.sinks.length > 1
width: parent.width width: parent.width
Separator {} Separator {}
@ -119,7 +118,7 @@ Column {
} }
Repeater { Repeater {
model: root.sinkList model: S.PipewireService.sinks
delegate: HoverableListItem { delegate: HoverableListItem {
required property var modelData required property var modelData
@ -149,11 +148,11 @@ Column {
// Streams section // Streams section
Separator { Separator {
visible: root.streamList.length > 0 visible: S.PipewireService.streams.length > 0
} }
Text { Text {
visible: root.streamList.length > 0 visible: S.PipewireService.streams.length > 0
width: parent.width width: parent.width
height: visible ? 24 : 0 height: visible ? 24 : 0
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@ -165,7 +164,7 @@ Column {
} }
Repeater { Repeater {
model: root.streamList model: S.PipewireService.streams
delegate: Item { delegate: Item {
id: streamEntry id: streamEntry

View file

@ -3,7 +3,6 @@ import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Io import Quickshell.Io
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
import Quickshell.Services.Pipewire
import "." as D import "." as D
import "../services" as S import "../services" as S
import "../applets" as C import "../applets" as C
@ -325,9 +324,6 @@ PanelWindow {
C.VolumeApplet { C.VolumeApplet {
width: parent.width width: parent.width
sink: Pipewire.defaultAudioSink
sinkList: root._sinkList
streamList: root._streamList
accentColor: root._accent accentColor: root._accent
} }
} }
@ -430,30 +426,6 @@ PanelWindow {
active: _memCard.expanded && S.DockState.open 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 { Process {
id: _runner id: _runner
} }

View file

@ -1,5 +1,4 @@
import QtQuick import QtQuick
import Quickshell.Services.Pipewire
import "../services" as S import "../services" as S
import "../applets" as C 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) 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.color: Qt.rgba(S.Theme.base03.r, S.Theme.base03.g, S.Theme.base03.b, 0.3)
border.width: 1 border.width: 1
visible: (S.Modules.lock.volume ?? true) && Pipewire.defaultAudioSink !== null visible: (S.Modules.lock.volume ?? true) && S.PipewireService.sink !== null
PwObjectTracker {
objects: [Pipewire.defaultAudioSink]
}
C.VolumeApplet { C.VolumeApplet {
id: _volumeContent id: _volumeContent
@ -103,9 +98,6 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 8 anchors.topMargin: 8
sink: Pipewire.defaultAudioSink
sinkList: []
streamList: []
accentColor: S.Theme.base0E accentColor: S.Theme.base0E
} }
} }

View file

@ -1,6 +1,5 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Services.Pipewire
import "." as M import "." as M
import "../services" as S import "../services" as S
import "../applets" as C import "../applets" as C
@ -15,43 +14,15 @@ M.BarModule {
panelComponent: Component { panelComponent: Component {
C.VolumeApplet { C.VolumeApplet {
width: parent.width width: parent.width
sink: root.sink
sinkList: root._sinkList
streamList: root._streamList
accentColor: root.accentColor accentColor: root.accentColor
} }
} }
PwObjectTracker { readonly property real volume: S.PipewireService.volume
objects: [Pipewire.defaultAudioSink, ...root._streamList] readonly property bool muted: S.PipewireService.muted
}
readonly property var sink: Pipewire.defaultAudioSink
readonly property real volume: sink?.audio?.volume ?? 0
readonly property bool muted: sink?.audio?.muted ?? false
readonly property string _volumeIcon: muted ? "\uF026" : (volume > 0.5 ? "\uF028" : (volume > 0 ? "\uF027" : "\uF026")) 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 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 _volumeInit: false
property bool _mutedInit: false property bool _mutedInit: false
@ -85,9 +56,9 @@ M.BarModule {
WheelHandler { WheelHandler {
onWheel: event => { onWheel: event => {
if (!root.sink?.audio) if (!S.PipewireService.sink?.audio)
return; 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));
} }
} }
} }

View file

@ -8,6 +8,7 @@ QtObject {
id: root id: root
readonly property bool enabled: S.Modules.lock.enable readonly property bool enabled: S.Modules.lock.enable
property bool locked: false
property string sessionPath: "" property string sessionPath: ""
// Lock/unlock requests from logind // Lock/unlock requests from logind
@ -15,10 +16,10 @@ QtObject {
signal unlockRequested signal unlockRequested
// Set logind LockedHint // Set logind LockedHint
function setLockedHint(locked) { function setLockedHint(isLocked) {
if (!sessionPath) if (!sessionPath)
return; return;
_lockedHint._locked = locked; root.locked = isLocked;
_lockedHint.running = true; _lockedHint.running = true;
} }
@ -82,7 +83,6 @@ QtObject {
// Set logind LockedHint // Set logind LockedHint
property Process _lockedHint: Process { 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", root.locked ? "true" : "false"]
command: ["busctl", "call", "--system", "org.freedesktop.login1", root.sessionPath || "/org/freedesktop/login1/session/auto", "org.freedesktop.login1.Session", "SetLockedHint", "b", _locked ? "true" : "false"]
} }
} }

View file

@ -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]
}
}

View file

@ -12,6 +12,7 @@ singleton MprisService 1.0 MprisService.qml
singleton NetworkService 1.0 NetworkService.qml singleton NetworkService 1.0 NetworkService.qml
singleton NiriIpc 1.0 NiriIpc.qml singleton NiriIpc 1.0 NiriIpc.qml
singleton NotifService 1.0 NotifService.qml singleton NotifService 1.0 NotifService.qml
singleton PipewireService 1.0 PipewireService.qml
singleton PowerProfileService 1.0 PowerProfileService.qml singleton PowerProfileService 1.0 PowerProfileService.qml
singleton ScreenshotService 1.0 ScreenshotService.qml singleton ScreenshotService 1.0 ScreenshotService.qml
singleton SleepService 1.0 SleepService.qml singleton SleepService 1.0 SleepService.qml