add systemd and machinectl bar modules with applets

This commit is contained in:
Damocles 2026-05-01 18:43:35 +02:00
parent 7ab784e101
commit 8ab3fc5f6b
12 changed files with 1117 additions and 1 deletions

View file

@ -0,0 +1,149 @@
pragma Singleton
import QtQuick
import Quickshell.Io
import "." as S
QtObject {
id: root
property var machines: []
// cache: machineName -> {state, units, loading}
property var _cache: ({})
readonly property bool anyUnhealthy: {
for (const k of Object.keys(_cache)) {
if ((_cache[k]?.units?.length ?? 0) > 0)
return true;
}
return false;
}
function machineState(name) {
return _cache[name]?.state ?? "unknown";
}
function machineUnits(name) {
return _cache[name]?.units ?? [];
}
function machineLoading(name) {
return _cache[name]?.loading ?? false;
}
signal machineReady(string machineName, string state, var units)
signal machineJournalReady(string machineName, string unitName, string text)
function fetchMachine(name) {
const c = Object.assign({}, _cache);
c[name] = Object.assign({}, c[name] ?? {}, {
loading: true
});
_cache = c;
_machineProc._name = name;
_machineProc.command = ["sh", "-c", "busctl get-property --json=short --machine=" + name + " org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager SystemState 2>/dev/null || echo '{}'; " + "busctl call --json=short --machine=" + name + " org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager ListUnitsFiltered as 1 failed 2>/dev/null || echo '{}'"];
if (_machineProc.running)
_machineProc.running = false;
_machineProc.running = true;
}
function fetchMachineJournal(machineName, unitName) {
_machineJournalProc._machine = machineName;
_machineJournalProc._unit = unitName;
_machineJournalProc.command = ["journalctl", "-M", machineName, "-u", unitName, "-n", "80", "--no-pager", "--output=short-precise"];
if (_machineJournalProc.running)
_machineJournalProc.running = false;
_machineJournalProc.running = true;
}
function restartMachineUnit(machineName, unitName) {
_machineRestartProc._machine = machineName;
_machineRestartProc.command = ["pkexec", "systemctl", "-M", machineName, "restart", unitName];
if (_machineRestartProc.running)
_machineRestartProc.running = false;
_machineRestartProc.running = true;
}
property Timer _poll: Timer {
interval: S.Modules.machinectl.interval ?? 15000
running: S.Modules.machinectl.enable
repeat: true
triggeredOnStart: true
onTriggered: if (!_listProc.running)
_listProc.running = true
}
property Process _listProc: Process {
command: ["busctl", "call", "--json=short", "org.freedesktop.machine1", "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", "ListMachines"]
stdout: StdioCollector {
onStreamFinished: {
try {
const parsed = JSON.parse(text.trim());
const newMachines = (parsed.data || []).map(m => ({
name: m[0],
class: m[1],
service: m[2]
}));
root.machines = newMachines;
// drop stale cache entries
const names = new Set(newMachines.map(m => m.name));
const c = {};
for (const k of Object.keys(root._cache)) {
if (names.has(k))
c[k] = root._cache[k];
}
root._cache = c;
} catch (e) {
root.machines = [];
}
}
}
}
// 2 lines of output: state, failed-units
property Process _machineProc: Process {
property string _name: ""
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().split("\n");
let state = "unknown";
let units = [];
try {
state = JSON.parse(lines[0] ?? "").data || "unknown";
} catch (e) {}
try {
const parsed = JSON.parse(lines[1] ?? "");
units = (parsed.data || []).map(u => ({
name: u[0],
description: u[1],
loadState: u[2],
activeState: u[3],
subState: u[4]
}));
} catch (e) {}
const c = Object.assign({}, root._cache);
c[root._machineProc._name] = {
state: state,
units: units,
loading: false
};
root._cache = c;
root.machineReady(root._machineProc._name, state, units);
}
}
}
property Process _machineJournalProc: Process {
property string _machine: ""
property string _unit: ""
stdout: StdioCollector {
onStreamFinished: root.machineJournalReady(root._machineJournalProc._machine, root._machineJournalProc._unit, text)
}
}
property Process _machineRestartProc: Process {
property string _machine: ""
onRunningChanged: if (!running && _machine !== "")
root.fetchMachine(_machine)
}
}

View file

@ -125,13 +125,21 @@ QtObject {
power: true
}
})
property var systemd: ({
enable: true,
interval: 15000
})
property var machinectl: ({
enable: true,
interval: 15000
})
property var statsDaemon: ({
interval: -1
})
// All module keys that have an enable flag used to default-enable anything
// not explicitly mentioned in modules.json
readonly property var _moduleKeys: ["workspaces", "tray", "windowTitle", "clock", "notifications", "mpris", "volume", "bluetooth", "backlight", "network", "powerProfile", "idleInhibitor", "weather", "temperature", "gpu", "cpu", "memory", "disk", "battery", "privacy", "screenCorners", "power", "backgroundOverlay", "overviewBackdrop", "lock", "dock"]
readonly property var _moduleKeys: ["workspaces", "tray", "windowTitle", "clock", "notifications", "mpris", "volume", "bluetooth", "backlight", "network", "powerProfile", "idleInhibitor", "weather", "temperature", "gpu", "cpu", "memory", "disk", "battery", "privacy", "screenCorners", "power", "backgroundOverlay", "overviewBackdrop", "lock", "dock", "systemd", "machinectl"]
// Fallback: if modules.json doesn't exist, enable everything
Component.onCompleted: _apply("{}")

View file

@ -0,0 +1,97 @@
pragma Singleton
import QtQuick
import Quickshell.Io
import "." as S
QtObject {
id: root
property string systemState: "unknown"
property string userState: "unknown"
property var systemUnits: []
property var userUnits: []
readonly property int totalFailedCount: systemUnits.length + userUnits.length
signal journalReady(string unitName, bool isUser, string text)
function refresh() {
if (!_pollProc.running)
_pollProc.running = true;
}
function fetchJournal(unitName, isUser) {
_journalProc.command = isUser ? ["journalctl", "--user", "-u", unitName, "-n", "80", "--no-pager", "--output=short-precise"] : ["journalctl", "-u", unitName, "-n", "80", "--no-pager", "--output=short-precise"];
_journalProc._unitName = unitName;
_journalProc._isUser = isUser;
if (_journalProc.running)
_journalProc.running = false;
_journalProc.running = true;
}
function restartUnit(unitName, isUser) {
_restartProc.command = isUser ? ["systemctl", "--user", "restart", unitName] : ["pkexec", "systemctl", "restart", unitName];
if (_restartProc.running)
_restartProc.running = false;
_restartProc.running = true;
}
function _parseState(json) {
try {
return JSON.parse(json).data || "unknown";
} catch (e) {
return "unknown";
}
}
function _parseUnits(json) {
try {
const parsed = JSON.parse(json);
return (parsed.data || []).map(u => ({
name: u[0],
description: u[1],
loadState: u[2],
activeState: u[3],
subState: u[4]
}));
} catch (e) {
return [];
}
}
property Timer _poll: Timer {
interval: S.Modules.systemd.interval ?? 15000
running: S.Modules.systemd.enable
repeat: true
triggeredOnStart: true
onTriggered: if (!_pollProc.running)
_pollProc.running = true
}
// 4 lines of output: systemState, systemUnits, userState, userUnits
property Process _pollProc: Process {
command: ["sh", "-c", "busctl get-property --json=short org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager SystemState 2>/dev/null || echo '{}'; " + "busctl call --json=short org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager ListUnitsFiltered as 1 failed 2>/dev/null || echo '{}'; " + "busctl --user get-property --json=short org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager SystemState 2>/dev/null || echo '{}'; " + "busctl --user call --json=short org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager ListUnitsFiltered as 1 failed 2>/dev/null || echo '{}'"]
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().split("\n");
root.systemState = root._parseState(lines[0] ?? "");
root.systemUnits = root._parseUnits(lines[1] ?? "");
root.userState = root._parseState(lines[2] ?? "");
root.userUnits = root._parseUnits(lines[3] ?? "");
}
}
}
property Process _journalProc: Process {
property string _unitName: ""
property bool _isUser: false
stdout: StdioCollector {
onStreamFinished: root.journalReady(root._journalProc._unitName, root._journalProc._isUser, text)
}
}
property Process _restartProc: Process {
onRunningChanged: if (!running)
root.refresh()
}
}

View file

@ -8,6 +8,7 @@ singleton CpuService 1.0 CpuService.qml
singleton DockState 1.0 DockState.qml
singleton IdleInhibitService 1.0 IdleInhibitService.qml
singleton LockService 1.0 LockService.qml
singleton MachinectlService 1.0 MachinectlService.qml
singleton Modules 1.0 Modules.qml
singleton MprisService 1.0 MprisService.qml
singleton NetworkService 1.0 NetworkService.qml
@ -18,6 +19,7 @@ singleton PowerProfileService 1.0 PowerProfileService.qml
singleton ScreenshotService 1.0 ScreenshotService.qml
singleton SleepService 1.0 SleepService.qml
singleton SystemStats 1.0 SystemStats.qml
singleton SystemdService 1.0 SystemdService.qml
singleton Theme 1.0 Theme.qml
singleton WeatherService 1.0 WeatherService.qml
# keep-sorted end