add systemd and machinectl bar modules with applets
This commit is contained in:
parent
7ab784e101
commit
8ab3fc5f6b
12 changed files with 1117 additions and 1 deletions
149
shell/services/MachinectlService.qml
Normal file
149
shell/services/MachinectlService.qml
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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("{}")
|
||||
|
|
|
|||
97
shell/services/SystemdService.qml
Normal file
97
shell/services/SystemdService.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue