pragma Singleton import QtQuick import Quickshell.Io import "." as M QtObject { id: root // ── CPU ────────────────────────────────────────────────────────────── property int cpuUsage: 0 property real cpuFreqGhz: 0 property var cpuCoreUsage: [] property var cpuCoreFreq: [] property var cpuCoreHistory: [] property var cpuCoreMaxFreq: [] property var cpuCoreTypes: [] // ── Memory ─────────────────────────────────────────────────────────── property int memPercent: 0 property real memUsedGb: 0 property real memTotalGb: 0 property real memAvailGb: 0 property real memCachedGb: 0 property real memBuffersGb: 0 // ── Disk ───────────────────────────────────────────────────────────── property var diskMounts: [] property int diskRootPct: 0 // nova-stats stream (cpu + mem) property var _statsProc: Process { running: true command: ["nova-stats"] stdout: SplitParser { splitMarker: "\n" onRead: line => { try { const ev = JSON.parse(line); if (ev.type === "cpu") { root.cpuUsage = ev.usage; root.cpuFreqGhz = ev.freq_ghz; root.cpuCoreUsage = ev.core_usage; root.cpuCoreFreq = ev.core_freq_ghz; const histLen = 16; const oldH = root.cpuCoreHistory; const newH = []; for (let i = 0; i < ev.core_usage.length; i++) { const prev = i < oldH.length ? oldH[i] : []; const next = prev.concat([ev.core_usage[i]]); newH.push(next.length > histLen ? next.slice(next.length - histLen) : next); } root.cpuCoreHistory = newH; } else if (ev.type === "mem") { root.memPercent = ev.percent; root.memUsedGb = ev.used_gb; root.memTotalGb = ev.total_gb; root.memAvailGb = ev.avail_gb; root.memCachedGb = ev.cached_gb; root.memBuffersGb = ev.buffers_gb; } } catch (e) {} } } } // One-time: per-core max freq property var _maxFreqProc: Process { running: true command: ["sh", "-c", "for f in /sys/devices/system/cpu/cpu[0-9]*/cpufreq/cpuinfo_max_freq; do [ -f \"$f\" ] && cat \"$f\" || echo 0; done 2>/dev/null"] stdout: StdioCollector { onStreamFinished: { root.cpuCoreMaxFreq = text.trim().split("\n").filter(l => l).map(l => parseInt(l) / 1e6); } } } // One-time: P/E-core topology property var _coreTypesProc: Process { running: true command: ["sh", "-c", "for d in /sys/devices/system/cpu/cpu[0-9]*/topology/core_type; do [ -f \"$d\" ] && cat \"$d\" || echo Performance; done 2>/dev/null"] stdout: StdioCollector { onStreamFinished: { root.cpuCoreTypes = text.trim().split("\n").filter(l => l).map(l => l.trim()); } } } // Disk via df property var _diskProc: Process { id: diskProc running: true command: ["sh", "-c", "df -x tmpfs -x devtmpfs -x squashfs -x efivarfs -x overlay -B1 --output=target,size,used 2>/dev/null | awk 'NR>1 && $2+0>0 {print $1\"|\"$2\"|\"$3}'"] stdout: StdioCollector { onStreamFinished: { const lines = text.trim().split("\n").filter(l => l); const mounts = []; for (const line of lines) { const parts = line.split("|"); if (parts.length < 3) continue; const total = parseInt(parts[1]); const used = parseInt(parts[2]); if (total <= 0) continue; mounts.push({ "target": parts[0], "pct": Math.round(used / total * 100), "usedBytes": used, "totalBytes": total }); } root.diskMounts = mounts; const rm = mounts.find(m => m.target === "/"); if (rm) root.diskRootPct = rm.pct; } } } property var _diskTimer: Timer { interval: M.Modules.disk.interval || 30000 running: true repeat: true onTriggered: diskProc.running = true } }