pragma Singleton import QtQuick import Quickshell.Io import "." as S import NovaStats as NS 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) // Per-call state: kept on root (not on the inline Process objects) so qmllint can see them. property string _machineName: "" property string _journalMachine: "" property string _journalUnit: "" property string _restartMachine: "" function fetchMachine(name) { const c = Object.assign({}, _cache); c[name] = Object.assign({}, c[name] ?? {}, { loading: true }); _cache = c; root._machineName = 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) { root._journalMachine = machineName; root._journalUnit = 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) { root._restartMachine = machineName; _machineRestartProc.command = ["pkexec", "systemctl", "-M", machineName, "restart", unitName]; if (_machineRestartProc.running) _machineRestartProc.running = false; _machineRestartProc.running = true; } property Timer _poll: Timer { interval: NS.ModulesService.machinectlInterval ?? 15000 running: NS.ModulesService.machinectlEnable repeat: true triggeredOnStart: true onTriggered: if (!root._listProc.running) root._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?.[0] || []).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 { 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?.[0] || []).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._machineName] = { state: state, units: units, loading: false }; root._cache = c; root.machineReady(root._machineName, state, units); } } } property Process _machineJournalProc: Process { stdout: StdioCollector { onStreamFinished: root.machineJournalReady(root._journalMachine, root._journalUnit, text) } } property Process _machineRestartProc: Process { onRunningChanged: if (!running && root._restartMachine !== "") root.fetchMachine(root._restartMachine) } }