repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/Resources.qml
Rawimport QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.UPower
import "sysinfo.js" as Sys
import "resources.js" as Res
import "system-problems.js" as Problems
// Thin I/O + cadence service over the pure sysinfo.js core.
// All parsing / issue-detection logic lives in sysinfo.js; this file only does
// file reads, process spawns, timers, and exposes reactive properties.
Item {
id: root
// --- injected services (DI via shell.qml; never singletons here) ---
required property var upower // UPower singleton (displayDevice bindings)
required property var powerProfiles // PowerProfiles singleton (degradationReason)
required property Brightness brightnessService // Brightness.qml (identity row reuse)
// --- view-bound live properties (fast loop) ---
property real cpuUsage: 0
property real memoryUsage: 0
property real swapUsage: 0
property real memTotal: 0
property real memAvailable: 0
property real swapTotal: 0
property real swapFree: 0
property real networkRxRate: 0
property real networkTxRate: 0
property real diskReadRate: 0
property real diskWriteRate: 0
// nullable: no sensor -> null (view shows n/a, thermal check skipped)
property var cpuTemperatureC: null
property var fanRpm: null
property real loadOne: 0
property real loadFive: 0
property real loadFifteen: 0
property int cores: 1
// --- ring buffers for sparklines (cap = max samples a wide spark shows;
// 96 × 2s ≈ 3 min of history) ---
readonly property int ringCap: 96
property var cpuRing: []
property var memRing: []
property var tempRing: []
property var netRing: []
property var netRxRing: [] // download, for the directional net spark
property var netTxRing: [] // upload, for the directional net spark
property var diskRing: []
property var diskReadRing: [] // read, for the directional disk spark
property var diskWriteRing: [] // write, for the directional disk spark
// --- issue inputs (slow / rare loops) ---
property var failedUnits: []
property var incidents: []
property string incidentStatus: "not checked"
property string agentIssueId: ""
property string agentError: ""
property var pendingAgent: ({})
signal agentDispatched
property bool agentPromptLoading: false
readonly property bool agentPending: agentPromptLoading || agentCheck.running
property var diskUsage: [] // [{source,target,usedPercent}]
property var updateCount: null // null = unknown / n/a
property bool swapGrowing: false
// thermal high threshold; resolved from sensor max/crit on start, default 87
property real tempHighC: 87
// aurora "firestorm" intensity, consumed by AuroraBackdrop.qml. A continuous
// 0..1 ramp as CPU temp climbs the last 12°C toward the thermal threshold —
// replaces the old hot/cool hysteresis counters. null temp => 0 (calm).
readonly property real firestormTarget: {
if (cpuTemperatureC === null || cpuTemperatureC === undefined)
return 0;
const t = Number(cpuTemperatureC);
if (!isFinite(t))
return 0;
const high = Math.max(1, tempHighC);
const cool = high - 12;
return Math.max(0, Math.min(1, (t - cool) / (high - cool)));
}
// --- top processes (slow loop, only while popout open) ---
property var processes: []
// --- identity facts (rare loop, cached) ---
property var identity: ({})
property bool identityLoaded: false
// --- popout visibility gate (set by the view) ---
property bool popoutOpen: false
// --- battery / throttle (event-driven UPower bindings) ---
// healthPercentage is null when unsupported / no battery (NOT -1) so the
// detectIssues battery check is skipped rather than firing a false error.
// UPower percentage/healthPercentage are 0..1 fractions (energy/energyCapacity);
// scale to 0..100 at this boundary so the view and detectIssues see whole percents.
readonly property var healthPercentage: {
const d = upower ? upower.displayDevice : null;
return (d && d.ready && d.isLaptopBattery && d.healthSupported) ? Number(d.healthPercentage) * 100 : null;
}
readonly property bool isLaptopBattery: {
const d = upower ? upower.displayDevice : null;
return !!(d && d.ready && d.isLaptopBattery);
}
// live charge 0..100 (null when no battery) + a short state word
readonly property var batteryPercent: {
const d = upower ? upower.displayDevice : null;
return (d && d.ready && d.isLaptopBattery) ? Math.round(Number(d.percentage) * 100) : null;
}
readonly property string batteryStatus: {
const d = upower ? upower.displayDevice : null;
if (!d || !d.ready || !d.isLaptopBattery)
return "";
switch (d.state) {
case UPowerDeviceState.Charging:
case UPowerDeviceState.PendingCharge:
return "charging";
case UPowerDeviceState.FullyCharged:
return "full";
case UPowerDeviceState.Discharging:
case UPowerDeviceState.PendingDischarge:
return "on battery";
default:
return "";
}
}
readonly property var degradationReason: {
if (!powerProfiles)
return null;
const r = powerProfiles.degradationReason;
return (r === null || r === undefined) ? null : String(r);
}
// --- last issue action record (for IPC introspection) ---
property var lastAction: ({})
// --- internal delta state ---
property var prevCpu: null
property real prevNetRx: 0
property real prevNetTx: 0
property real prevDiskRead: 0
property real prevDiskWrite: 0
property real prevRateTime: 0
property real prevSwapUsed: 0
property var failedSystemUnits: []
property var failedUserUnits: []
// --- network-widget compatibility shims (consumed by shell.qml
// networkWidgetState(); out of scope for the system redesign, kept so a
// working widget is not silently regressed). ---
property var networkHistorySamples: []
function networkRxLevel() {
return Sys.clamp01(networkRxRate / 262144);
}
function networkTxLevel() {
return Sys.clamp01(networkTxRate / 262144);
}
function shaderHistoryObject(samples) {
return {
"windowSeconds": 7200,
"sampleIntervalMs": 2000,
"sampleLimit": 3600,
"sampleCount": (samples || []).length
};
}
// --- fast loop: pure /proc + /sys reads, deltas, rings (always on, 2s) ---
function refreshFast() {
statFile.reload();
const statText = statFile.text() || "";
const cd = Sys.cpuDelta(prevCpu, statText);
cpuUsage = cd.usage;
prevCpu = cd.next;
const coreLines = statText.match(/^cpu[0-9]/gm);
if (coreLines && coreLines.length > 0)
cores = coreLines.length;
meminfoFile.reload();
const mem = Sys.parseMeminfo(meminfoFile.text() || "");
if (mem.memTotal > 0) {
memTotal = mem.memTotal;
memAvailable = mem.memAvailable;
swapTotal = mem.swapTotal;
swapFree = mem.swapFree;
memoryUsage = mem.memoryUsage;
swapUsage = mem.swapUsage;
const swapUsed = Math.max(0, mem.swapTotal - mem.swapFree);
swapGrowing = swapUsed > prevSwapUsed + 1;
prevSwapUsed = swapUsed;
}
netFile.reload();
diskFile.reload();
const net = Sys.parseNetDev(netFile.text() || "");
const disk = Sys.parseDiskstats(diskFile.text() || "");
const now = Date.now();
const seconds = prevRateTime > 0 ? Math.max(0.2, (now - prevRateTime) / 1000) : 0;
if (seconds > 0) {
networkRxRate = Sys.rate(prevNetRx, net.rx, seconds);
networkTxRate = Sys.rate(prevNetTx, net.tx, seconds);
diskReadRate = Sys.rate(prevDiskRead, disk.readBytes, seconds);
diskWriteRate = Sys.rate(prevDiskWrite, disk.writeBytes, seconds);
}
prevNetRx = net.rx;
prevNetTx = net.tx;
prevDiskRead = disk.readBytes;
prevDiskWrite = disk.writeBytes;
prevRateTime = now;
loadFile.reload();
const loadText = loadFile.text() || "";
if (loadText.trim().length > 0) {
const la = Sys.parseLoadavg(loadText);
loadOne = la.one;
loadFive = la.five;
loadFifteen = la.fifteen;
}
// hwmon temp + fan; keep last-good on null/parse failure
if (tempFile.path) {
tempFile.reload();
const t = Sys.parseTemp(tempFile.text() || "");
if (t !== null)
cpuTemperatureC = t;
}
if (fanFile.path) {
fanFile.reload();
const f = Sys.parseFan(fanFile.text() || "");
if (f !== null)
fanRpm = f;
}
cpuRing = Sys.pushRing(cpuRing, cpuUsage, ringCap);
memRing = Sys.pushRing(memRing, memoryUsage, ringCap);
if (cpuTemperatureC !== null)
tempRing = Sys.pushRing(tempRing, cpuTemperatureC, ringCap);
netRing = Sys.pushRing(netRing, networkRxRate + networkTxRate, ringCap);
netRxRing = Sys.pushRing(netRxRing, networkRxRate, ringCap);
netTxRing = Sys.pushRing(netTxRing, networkTxRate, ringCap);
diskRing = Sys.pushRing(diskRing, diskReadRate + diskWriteRate, ringCap);
diskReadRing = Sys.pushRing(diskReadRing, diskReadRate, ringCap);
diskWriteRing = Sys.pushRing(diskWriteRing, diskWriteRate, ringCap);
networkHistorySamples = Sys.pushRing(networkHistorySamples, Math.max(networkRxLevel(), networkTxLevel()), ringCap);
}
// --- slow loop: process spawns, only while popout open (5s) ---
function refreshSlow() {
if (!popoutOpen)
return;
if (!failedSystemProc.running)
failedSystemProc.exec(["systemctl", "--failed", "--no-legend", "--plain"]);
if (!failedUserProc.running)
failedUserProc.exec(["systemctl", "--user", "--failed", "--no-legend", "--plain"]);
if (!incidentProc.running)
incidentProc.exec(Problems.journalCommand());
if (!dfProc.running)
dfProc.exec(["df", "-P"]);
if (!psProc.running)
psProc.exec(["sh", "-c", "ps -eo pid=,comm=,pcpu=,pmem= --sort=-pcpu | head -n 5"]);
}
// --- rare loop: identity facts (cached) + checkupdates (async, on open) ---
function refreshRare() {
if (!identityLoaded && !identityProc.running)
identityProc.exec(["sh", "-c", "printf 'host=%s\\n' \"$(uname -n)\"; printf 'kernel=%s\\n' \"$(uname -r)\"; ( . /etc/os-release 2>/dev/null; printf 'distro=%s\\n' \"${PRETTY_NAME:-Linux}\" ); printf 'model=%s\\n' \"$(cat /sys/devices/virtual/dmi/id/product_version 2>/dev/null || cat /sys/devices/virtual/dmi/id/product_name 2>/dev/null)\"; printf 'shell=%s\\n' \"$(basename \"${SHELL:-sh}\")\"; printf 'uptime=%s\\n' \"$(uptime -p 2>/dev/null | sed 's/^up //')\"; printf 'packages=%s\\n' \"$(pacman -Qq 2>/dev/null | wc -l)\""]);
if (!updatesProc.running)
updatesProc.exec(["checkupdates"]);
}
function recomputeFailed() {
failedUnits = failedSystemUnits.concat(failedUserUnits);
}
// --- assemble detectIssues input (keys MUST match the sysinfo.js contract) ---
function buildIssueState() {
return {
"failedUnits": failedUnits,
"failedScopes": [
{
scope: "system",
units: failedSystemUnits
},
{
scope: "user",
units: failedUserUnits
}
],
"cpuTemperatureC": cpuTemperatureC,
"tempHighC": tempHighC,
"degradationReason": degradationReason,
"diskUsage": diskUsage,
"memoryUsage": memoryUsage,
"swapGrowing": swapGrowing,
"healthPercentage": healthPercentage,
"isLaptopBattery": isLaptopBattery,
"loadOne": loadOne,
"cores": cores,
"updateCount": updateCount
};
}
// returns the issue ARRAY (Repeater binds directly); not stringified.
function issues() {
return Sys.detectIssues(buildIssueState()).concat(Problems.groupIncidents(incidents));
}
// click-to-act: pure planner decides argv; QML only launches.
function runIssueAction(id) {
const result = Res.issueAction(issues(), id);
if (!result.ok)
return JSON.stringify(result);
lastAction = result;
Quickshell.execDetached(result.argv);
return JSON.stringify(lastAction);
}
function runIssueAgent(id) {
if (agentPending)
return;
const issue = issues().find(entry => entry.id === String(id));
if (!Res.agentAllowed(issue))
return;
agentIssueId = String(id);
agentError = "";
agentPromptLoading = true;
repairPrompt.reload();
}
function prepareIssueAgent(template: string): void {
const plan = Res.agentAction(issues(), agentIssueId, {
home: Quickshell.env("HOME"),
stateHome: Quickshell.env("XDG_STATE_HOME"),
observedAt: new Date().toISOString(),
identity: identity
}, template);
agentPromptLoading = false;
if (!plan.ok) {
agentError = plan.reason === "prompt-invalid" ? "repair prompt unavailable or invalid" : plan.reason === "home-unavailable" ? "home directory unavailable" : "issue no longer eligible";
return;
}
pendingAgent = plan;
// Fixed script only; no issue text ever enters shell syntax.
// File transport preserves all retained occurrences without Linux's
// per-argument size limit. Requests are private, immutable repair evidence.
agentCheck.stdinEnabled = true;
agentCheck.exec(["sh", "-c", "command -v pi && command -v wezterm || { cat >/dev/null; exit 127; }; umask 077; mkdir -p -- \"$1\" && test -d \"$1\" && test -w \"$1\" && test -x \"$1\" || { cat >/dev/null; exit 73; }; request=$(mktemp -- \"$1/.request-XXXXXX.md\") || { cat >/dev/null; exit 74; }; cat >\"$request\" || { rm -- \"$request\"; exit 74; }; printf '%s\\n' \"${request##*/}\"", "nuguland-repairs", plan.argv[3]]);
}
// --- trimmed IPC contract (KEEP — see nuguland-statejson-is-ipc-contract) ---
function stateJson() {
const memUsed = Math.max(0, memTotal - memAvailable);
const swapUsed = Math.max(0, swapTotal - swapFree);
return JSON.stringify({
"kind": "system",
"title": "system",
"popoutOpen": popoutOpen,
"cpu": {
"usage": cpuUsage,
"percent": Math.round(cpuUsage * 100)
},
"memory": {
"usage": memoryUsage,
"percent": Math.round(memoryUsage * 100),
"used": memUsed,
"total": memTotal,
"label": `${Sys.formatBytes(memUsed)} / ${Sys.formatBytes(memTotal)}`
},
"swap": {
"usage": swapUsage,
"percent": Math.round(swapUsage * 100),
"used": swapUsed,
"total": swapTotal,
"label": swapTotal > 0 ? `${Sys.formatBytes(swapUsed)} / ${Sys.formatBytes(swapTotal)}` : "off"
},
"network": {
"rxRate": networkRxRate,
"txRate": networkTxRate,
"label": `↓ ${Sys.formatRate(networkRxRate)} ↑ ${Sys.formatRate(networkTxRate)}`
},
"disk": {
"readRate": diskReadRate,
"writeRate": diskWriteRate,
"label": `R ${Sys.formatRate(diskReadRate)} W ${Sys.formatRate(diskWriteRate)}`
},
"temperature": {
"celsius": cpuTemperatureC,
"highThresholdC": tempHighC,
"label": cpuTemperatureC !== null ? `${Math.round(Number(cpuTemperatureC))}°C` : "n/a"
},
"fan": {
"rpm": fanRpm,
"label": fanRpm !== null ? `${Math.round(Number(fanRpm))} rpm` : "n/a"
},
"load": {
"one": loadOne,
"five": loadFive,
"fifteen": loadFifteen,
"cores": cores
},
"battery": {
"percent": batteryPercent,
"status": batteryStatus,
"healthPercentage": healthPercentage,
"isLaptopBattery": isLaptopBattery
},
"identity": identity,
"updateCount": updateCount,
"processes": processes,
"issues": issues(),
"lastAction": lastAction
});
}
// backward-compatible alias for the existing shell.qml "systemWidgetState" IPC
function systemWidgetState() {
return stateJson();
}
// --- integration selfTest: exercises the QML glue (buildIssueState keys,
// issues()/stateJson()), not sysinfo.js (already node-tested). ---
function selfTest() {
const cases = [];
function ok(name, cond) {
cases.push({
"name": name,
"pass": !!cond
});
}
function findIssue(list, id) {
for (let i = 0; i < list.length; i++) {
if (list[i].id === id)
return list[i];
}
return null;
}
const saved = {
"failedUnits": failedUnits,
"cpuTemperatureC": cpuTemperatureC,
"tempHighC": tempHighC,
"diskUsage": diskUsage,
"memoryUsage": memoryUsage,
"swapGrowing": swapGrowing,
"loadOne": loadOne,
"cores": cores,
"updateCount": updateCount
};
try {
const st = buildIssueState();
const keys = ["failedUnits", "cpuTemperatureC", "tempHighC", "degradationReason", "diskUsage", "memoryUsage", "swapGrowing", "healthPercentage", "isLaptopBattery", "loadOne", "cores", "updateCount"];
const missing = [];
for (let k = 0; k < keys.length; k++) {
if (!(keys[k] in st))
missing.push(keys[k]);
}
ok("state-keys-present:" + missing.join(","), missing.length === 0);
failedUnits = ["app-Handy@autostart.service"];
cpuTemperatureC = 95;
tempHighC = 87;
diskUsage = [
{
"source": "/dev/sda3",
"target": "/",
"usedPercent": 96
}
];
memoryUsage = 0.95;
swapGrowing = true;
loadOne = 9;
cores = 4;
updateCount = 3;
const list = root.issues();
ok("issues-is-array", Array.isArray(list));
ok("failed-services", !!findIssue(list, "failed-services"));
const th = findIssue(list, "thermal");
ok("thermal-warning", th && th.severity === "warning");
const dk = findIssue(list, "low-disk:/dev/sda3");
ok("low-disk-error", dk && dk.severity === "error");
ok("mem-pressure", !!findIssue(list, "mem-pressure"));
ok("high-load", !!findIssue(list, "high-load"));
const up = findIssue(list, "updates");
ok("updates-info", up && up.severity === "info");
// below threshold -> these checks clear (battery/throttle ignored: real device)
failedUnits = [];
cpuTemperatureC = 40;
diskUsage = [
{
"source": "/dev/sda3",
"target": "/",
"usedPercent": 50
}
];
memoryUsage = 0.5;
swapGrowing = false;
loadOne = 1;
updateCount = 0;
const clear = root.issues();
ok("failed-cleared", !findIssue(clear, "failed-services"));
ok("thermal-cleared", !findIssue(clear, "thermal"));
ok("disk-cleared", !findIssue(clear, "low-disk:/dev/sda3"));
ok("mem-cleared", !findIssue(clear, "mem-pressure"));
ok("load-cleared", !findIssue(clear, "high-load"));
ok("updates-cleared", !findIssue(clear, "updates"));
const sj = JSON.parse(root.stateJson());
ok("statejson-kind", sj.kind === "system");
ok("statejson-issues-array", Array.isArray(sj.issues));
} catch (e) {
ok("threw:" + e, false);
} finally {
failedUnits = saved.failedUnits;
cpuTemperatureC = saved.cpuTemperatureC;
tempHighC = saved.tempHighC;
diskUsage = saved.diskUsage;
memoryUsage = saved.memoryUsage;
swapGrowing = saved.swapGrowing;
loadOne = saved.loadOne;
cores = saved.cores;
updateCount = saved.updateCount;
}
const fail = cases.filter(c => {
return !c.pass;
}).length;
return JSON.stringify({
"pass": cases.length - fail,
"fail": fail,
"cases": cases
});
}
onPopoutOpenChanged: {
if (popoutOpen) {
refreshRare();
refreshSlow();
}
}
Component.onCompleted: {
// resolve hwmon temp (coretemp package) + fan (thinkpad) paths once.
hwmonResolve.exec(["sh", "-c", "for d in /sys/class/hwmon/hwmon*; do n=$(cat \"$d/name\" 2>/dev/null); if [ \"$n\" = coretemp ]; then for t in \"$d\"/temp*_label; do [ -r \"$t\" ] || continue; if grep -qi package \"$t\"; then b=${t%_label}; [ -r \"${b}_input\" ] && printf 'temp=%s\\n' \"${b}_input\"; if [ -r \"${b}_max\" ]; then printf 'high=%s\\n' \"$(cat ${b}_max)\"; elif [ -r \"${b}_crit\" ]; then printf 'high=%s\\n' \"$(cat ${b}_crit)\"; fi; break; fi; done; elif [ \"$n\" = thinkpad ]; then [ -r \"$d/fan1_input\" ] && printf 'fan=%s\\n' \"$d/fan1_input\"; fi; done"]);
}
// --- FileViews: pure reads, last-good on failure (printErrors off) ---
FileView {
id: statFile
path: "/proc/stat"
blockLoading: true
printErrors: false
}
FileView {
id: meminfoFile
path: "/proc/meminfo"
blockLoading: true
printErrors: false
}
FileView {
id: netFile
path: "/proc/net/dev"
blockLoading: true
printErrors: false
}
FileView {
id: diskFile
path: "/proc/diskstats"
blockLoading: true
printErrors: false
}
FileView {
id: loadFile
path: "/proc/loadavg"
blockLoading: true
printErrors: false
}
// paths resolved at runtime by hwmonResolve; empty until then.
FileView {
id: tempFile
path: ""
blockLoading: true
printErrors: false
}
FileView {
id: fanFile
path: ""
blockLoading: true
printErrors: false
}
// --- one-shot hwmon path resolution ---
Process {
id: hwmonResolve
running: false
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: {
const lines = String(this.text || "").split("\n");
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.indexOf("temp=") === 0)
tempFile.path = line.slice(5);
else if (line.indexOf("fan=") === 0)
fanFile.path = line.slice(4);
else if (line.indexOf("high=") === 0) {
const v = Number(line.slice(5));
if (Number.isFinite(v) && v > 0)
root.tempHighC = v / 1000;
}
}
}
}
}
FileView {
id: repairPrompt
path: Qt.resolvedUrl("prompts/system-repair.md")
printErrors: false
onLoaded: if (root.agentPromptLoading)
root.prepareIssueAgent(repairPrompt.text())
onLoadFailed: {
if (root.agentPromptLoading) {
root.agentPromptLoading = false;
root.agentError = "repair prompt unavailable or invalid";
}
}
}
Process {
id: agentCheck
stdout: StdioCollector {
id: agentPaths
waitForEnd: true
}
function handleExit(exitCode: int): void {
const paths = String(agentPaths.text).trim().split("\n");
if (exitCode === 73) {
root.agentError = "repair directory unavailable or not writable";
return;
}
if (exitCode === 74) {
root.agentError = "repair context could not be saved";
return;
}
if (exitCode !== 0 || paths.length !== 3 || paths[0][0] !== "/" || paths[1][0] !== "/" || !/^\.request-[A-Za-z0-9]+\.md$/.test(paths[2])) {
root.agentError = "pi or wezterm unavailable in session path";
return;
}
const argv = root.pendingAgent.argv.slice();
argv[0] = paths[1];
argv[5] = paths[0];
argv[argv.length - 1] = "@" + argv[3] + "/" + paths[2];
root.lastAction = {
id: root.pendingAgent.id,
kind: "agent",
status: "dispatched"
};
Quickshell.execDetached(argv);
root.pendingAgent = ({});
root.agentDispatched();
}
}
// --- slow-loop processes ---
Process {
id: incidentProc
stdout: StdioCollector {
id: incidentOut
waitForEnd: true
}
stderr: StdioCollector {
id: incidentErr
waitForEnd: true
}
function handleExit(exitCode: int): void {
// No-match with --grep exits 1. Diagnostics mean incomplete visibility.
const parsed = Problems.parseJournal(incidentOut.text);
const empty = !String(incidentOut.text).trim();
if ((exitCode === 0 || (exitCode === 1 && empty)) && !String(incidentErr.text).trim() && parsed.ok) {
root.incidents = parsed.issues;
root.incidentStatus = "ready";
} else {
root.incidentStatus = "history unavailable or incomplete";
}
}
}
Process {
id: failedSystemProc
running: false
stdout: StdioCollector {
id: failedSystemOut
waitForEnd: true
}
function handleExit(exitCode: int): void {
if (exitCode === 0) {
root.failedSystemUnits = Sys.parseFailedUnits(failedSystemOut.text);
root.recomputeFailed();
}
}
}
Process {
id: failedUserProc
running: false
stdout: StdioCollector {
id: failedUserOut
waitForEnd: true
}
function handleExit(exitCode: int): void {
if (exitCode === 0) {
root.failedUserUnits = Sys.parseFailedUnits(failedUserOut.text);
root.recomputeFailed();
}
}
}
Process {
id: dfProc
running: false
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: {
const parsed = Sys.parseDf(this.text);
if (parsed.length > 0)
root.diskUsage = parsed;
}
}
}
Process {
id: psProc
running: false
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: {
const parsed = Sys.parsePsTop(this.text);
if (parsed.length > 0)
root.processes = parsed;
}
}
}
// --- rare-loop processes ---
Process {
id: identityProc
running: false
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: {
const out = {
"wm": "niri"
};
const lines = String(this.text || "").split("\n");
for (let i = 0; i < lines.length; i++) {
const eq = lines[i].indexOf("=");
if (eq <= 0)
continue;
out[lines[i].slice(0, eq)] = lines[i].slice(eq + 1).trim();
}
if (out.host) {
root.identity = out;
root.identityLoaded = true;
}
}
}
}
Process {
id: updatesProc
running: false
stdout: StdioCollector {
id: updatesOut
waitForEnd: true
}
// checkupdates: 0 = updates listed, 2 = none, other = error / no network.
function handleExit(exitCode: int): void {
if (exitCode === 2) {
root.updateCount = 0;
return;
}
if (exitCode !== 0) {
root.updateCount = null;
return;
}
const lines = String(updatesOut.text || "").split("\n").filter(l => {
return l.trim().length > 0;
});
root.updateCount = lines.length;
}
}
// Explicit Connections register real handlers without requiring the missing
// QProcess::ExitStatus enum in Quickshell's installed qmltypes metadata.
Connections {
target: agentCheck
function onStarted(): void {
const argv = root.pendingAgent.argv;
agentCheck.write(argv[argv.length - 1]);
agentCheck.stdinEnabled = false;
}
function onExited(exitCode: int): void {
agentCheck.handleExit(exitCode);
}
}
Connections {
target: incidentProc
function onExited(exitCode: int): void {
incidentProc.handleExit(exitCode);
}
}
Connections {
target: failedSystemProc
function onExited(exitCode: int): void {
failedSystemProc.handleExit(exitCode);
}
}
Connections {
target: failedUserProc
function onExited(exitCode: int): void {
failedUserProc.handleExit(exitCode);
}
}
Connections {
target: updatesProc
function onExited(exitCode: int): void {
updatesProc.handleExit(exitCode);
}
}
// --- cadence timers ---
Timer {
id: fastTimer
interval: 2000
repeat: true
running: true
triggeredOnStart: true
onTriggered: root.refreshFast()
}
Timer {
id: slowTimer
interval: 5000
repeat: true
running: root.popoutOpen
triggeredOnStart: true
onTriggered: root.refreshSlow()
}
Timer {
id: rareTimer
interval: 300000
repeat: true
running: root.popoutOpen
triggeredOnStart: false
onTriggered: root.refreshRare()
}
}