pragma Singleton // ───────────────────────────────────────────────────────────────────────────── // System vitals — CPU, memory and GPU utilisation. // // Replaces the GNOME Vitals extension, which showed exactly these three in // exactly this order. Everything comes from procfs/sysfs, so there are no // subprocesses: one timer kicks three async re-reads of very small files and // the parsing happens in the `loaded` handlers. // ───────────────────────────────────────────────────────────────────────────── import Quickshell import Quickshell.Io import QtQuick import qs.config Singleton { id: root // All three are percentages, 0-100. property real cpu: 0 property real memory: 0 property real gpu: 0 // False when Settings.gpuBusyPath is missing or unreadable (no amdgpu, or a // different card index). The widget hides the GPU field rather than // reporting a permanent 0%. property bool gpuAvailable: false // /proc/stat is cumulative since boot, so utilisation is only meaningful as // a delta between two samples. These hold the previous one. property real _prevTotal: 0 property real _prevIdle: 0 Timer { interval: Settings.vitalsIntervalMs running: true repeat: true triggeredOnStart: true onTriggered: { cpuFile.reload(); memFile.reload(); if (Settings.showGpu) gpuFile.reload(); } } // ── CPU ───────────────────────────────────────────────────────────────── FileView { id: cpuFile path: "/proc/stat" printErrors: false onLoaded: root._parseCpu(text()) } // ── Memory ────────────────────────────────────────────────────────────── FileView { id: memFile path: "/proc/meminfo" printErrors: false onLoaded: root._parseMemory(text()) } // ── GPU ───────────────────────────────────────────────────────────────── // amdgpu exposes a bare integer 0-100 here. FileView { id: gpuFile path: Settings.gpuBusyPath printErrors: false onLoaded: root._parseGpu(text()) onLoadFailed: root.gpuAvailable = false } function _parseCpu(text: string): void { if (!text) return; // First line is the aggregate: "cpu user nice system idle iowait ..." const fields = text.slice(0, text.indexOf("\n")).split(/\s+/).slice(1).map(parseFloat).filter(n => !isNaN(n)); if (fields.length < 5) return; // idle + iowait — the kernel counts both as "not doing work". const idle = fields[3] + fields[4]; let total = 0; for (let i = 0; i < fields.length; i++) total += fields[i]; const dTotal = total - root._prevTotal; const dIdle = idle - root._prevIdle; root._prevTotal = total; root._prevIdle = idle; // The very first sample has no predecessor; reporting it would show the // since-boot average, which is never what you want. if (dTotal <= 0) return; root.cpu = Math.max(0, Math.min(100, (1 - dIdle / dTotal) * 100)); } function _parseMemory(text: string): void { // MemAvailable is the kernel's own estimate of what a new allocation // could get; a much better "used" basis than MemFree. const total = root._meminfoField(text, "MemTotal"); const available = root._meminfoField(text, "MemAvailable"); if (total <= 0 || available < 0) return; root.memory = Math.max(0, Math.min(100, (1 - available / total) * 100)); } function _meminfoField(text: string, key: string): real { const match = text.match(new RegExp("^" + key + ":\\s+(\\d+)", "m")); return match ? parseFloat(match[1]) : -1; } function _parseGpu(text: string): void { const value = parseInt(text, 10); if (isNaN(value)) { root.gpuAvailable = false; return; } root.gpuAvailable = true; root.gpu = Math.max(0, Math.min(100, value)); } }