Build the Panama Hyprland desktop
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user