import 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() } }