fix(qs,fluxo) performance fix for repaints

This commit is contained in:
2026-08-12 20:18:56 +02:00
parent 86e90b0213
commit 9cf510e93b
7 changed files with 245 additions and 169 deletions
+101 -140
View File
@@ -7,12 +7,18 @@ import Quickshell.Io
// Hardware telemetry, sourced from the user's `fluxo` daemon. fluxo's hardware
// modules are configured to emit raw pipe-delimited values (see
// ~/.config/fluxo/config.toml); everything here is parsing and formatting.
//
// Fed by a single long-lived `fluxo stream` subscription. This used to be two
// Timers driving seven separate `Process` objects, each forking a `fluxo
// <module>` client — five forks every 2 s plus two more every 20 s, so about
// 2.5 process spawns a second, each one exec'ing and dynamically linking a 13 MB
// binary to read a number the daemon already had in memory, and each metric
// still up to one interval stale. `fluxo stream` keeps one connection open and
// pushes a JSON line only when a module's output actually changes: no forks, and
// values land as soon as the daemon has them.
Singleton {
id: root
// Poll interval. The bar reads these continuously, so keep it modest.
property int interval: 2000
// ------------------------------------------------------------------ cpu
property real cpuUsage: 0
property real cpuTemp: 0
@@ -56,157 +62,112 @@ Singleton {
// UPower drives the battery UI; fluxo supplies the TLP/AC detail line.
property string powerDetail: ""
// fluxo wraps values in zero-width spaces for Waybar's benefit.
// The one-shot `fluxo <module>` client wraps text in zero-width spaces so
// Waybar's proportional font stops reflowing. The stream deliberately does
// not, but stripping them costs nothing and keeps this parser usable against
// either source.
function _clean(s: string): string {
return (s || "").replace(//g, "").trim();
}
function _fields(json: string): var {
try {
const o = JSON.parse(json);
return { parts: root._clean(o.text).split("|"), obj: o };
} catch (e) {
return null;
}
}
function _num(v): real {
const n = parseFloat(v);
return isNaN(n) ? 0 : n;
}
Timer {
interval: root.interval
// ------------------------------------------------------------------ feed
// Modules fluxo pushes to us. `disk` streams the default mount, which fluxo
// resolves to "/" (see `signaler_default_args` in its registry).
readonly property var modules: ["cpu", "mem", "gpu", "net", "sys", "disk", "power"]
// One line of the stream: {"module":"cpu","text":"7.4|41.0","tooltip":...}.
function _ingest(line: string): void {
let ev;
try {
ev = JSON.parse(line);
} catch (e) {
return;
}
if (!ev || !ev.module)
return;
const parts = root._clean(ev.text).split("|");
const tip = ev.tooltip || "";
switch (ev.module) {
case "cpu":
root.cpuUsage = root._num(parts[0]);
root.cpuTemp = root._num(parts[1]);
root.cpuModel = tip;
break;
case "mem":
root.memUsed = root._num(parts[0]);
root.memTotal = root._num(parts[1]);
break;
case "gpu":
// fluxo emits a single non-numeric field ("No GPU") when it cannot
// find one, so field count is the availability test.
if (parts.length < 4) {
root.gpuAvailable = false;
break;
}
root.gpuAvailable = true;
root.gpuUsage = root._num(parts[0]);
root.gpuVramUsed = root._num(parts[1]);
root.gpuVramTotal = root._num(parts[2]);
root.gpuTemp = root._num(parts[3]);
const gm = /Model:\s*(.+)/.exec(tip);
if (gm)
root.gpuModel = gm[1];
break;
case "net":
root.netInterface = parts[0] || "";
root.netIp = parts[1] || "";
root.netRx = root._num(parts[2]);
root.netTx = root._num(parts[3]);
break;
case "sys":
root.uptime = parts[0] || "";
root.load1 = root._num(parts[1]);
root.load5 = root._num(parts[2]);
root.load15 = root._num(parts[3]);
const pm = /Processes:\s*(\d+)/.exec(tip);
if (pm)
root.procs = parseInt(pm[1]);
break;
case "disk":
root.diskUsed = root._num(parts[1]);
root.diskTotal = root._num(parts[2]);
root.diskPercent = ev.percentage || 0;
const fm = /Free:\s*(\S+)/.exec(tip);
root.diskFree = fm ? fm[1] : "";
break;
case "power":
root.powerDetail = tip;
break;
}
}
Process {
id: source
command: ["fluxo", "stream"].concat(root.modules)
running: true
repeat: true
triggeredOnStart: true
onTriggered: {
cpuReader.running = true;
memReader.running = true;
gpuReader.running = true;
netReader.running = true;
sysReader.running = true;
stdout: SplitParser {
onRead: data => root._ingest(data)
}
// The daemon restarting (or not being up yet at login) drops us. Retry
// on a slow cadence: this is a reconnect, not a poll, so it only ever
// fires while the stream is actually down.
onExited: retry.restart()
}
// Disk and battery move slowly; poll them a tenth as often.
Timer {
interval: root.interval * 10
running: true
repeat: true
triggeredOnStart: true
onTriggered: {
diskReader.running = true;
powerReader.running = true;
}
}
Process {
id: cpuReader
command: ["fluxo", "cpu"]
stdout: StdioCollector {
onStreamFinished: {
const f = root._fields(text);
if (!f) return;
root.cpuUsage = root._num(f.parts[0]);
root.cpuTemp = root._num(f.parts[1]);
root.cpuModel = f.obj.tooltip || "";
}
}
}
Process {
id: memReader
command: ["fluxo", "mem"]
stdout: StdioCollector {
onStreamFinished: {
const f = root._fields(text);
if (!f) return;
root.memUsed = root._num(f.parts[0]);
root.memTotal = root._num(f.parts[1]);
}
}
}
Process {
id: gpuReader
command: ["fluxo", "gpu"]
stdout: StdioCollector {
onStreamFinished: {
const f = root._fields(text);
if (!f || f.parts.length < 4) {
root.gpuAvailable = false;
return;
}
root.gpuAvailable = true;
root.gpuUsage = root._num(f.parts[0]);
root.gpuVramUsed = root._num(f.parts[1]);
root.gpuVramTotal = root._num(f.parts[2]);
root.gpuTemp = root._num(f.parts[3]);
const m = /Model:\s*(.+)/.exec(f.obj.tooltip || "");
if (m) root.gpuModel = m[1];
}
}
}
Process {
id: diskReader
command: ["fluxo", "disk", "/"]
stdout: StdioCollector {
onStreamFinished: {
const f = root._fields(text);
if (!f) return;
root.diskUsed = root._num(f.parts[1]);
root.diskTotal = root._num(f.parts[2]);
root.diskPercent = f.obj.percentage || 0;
const m = /Free:\s*(\S+)/.exec(f.obj.tooltip || "");
root.diskFree = m ? m[1] : "";
}
}
}
Process {
id: netReader
command: ["fluxo", "net"]
stdout: StdioCollector {
onStreamFinished: {
const f = root._fields(text);
if (!f) return;
root.netInterface = f.parts[0] || "";
root.netIp = f.parts[1] || "";
root.netRx = root._num(f.parts[2]);
root.netTx = root._num(f.parts[3]);
}
}
}
Process {
id: sysReader
command: ["fluxo", "sys"]
stdout: StdioCollector {
onStreamFinished: {
const f = root._fields(text);
if (!f) return;
root.uptime = f.parts[0] || "";
root.load1 = root._num(f.parts[1]);
root.load5 = root._num(f.parts[2]);
root.load15 = root._num(f.parts[3]);
const m = /Processes:\s*(\d+)/.exec(f.obj.tooltip || "");
if (m) root.procs = parseInt(m[1]);
}
}
}
Process {
id: powerReader
command: ["fluxo", "power"]
stdout: StdioCollector {
onStreamFinished: {
try {
root.powerDetail = JSON.parse(text).tooltip || "";
} catch (e) {}
}
}
id: retry
interval: 2000
repeat: false
onTriggered: source.running = true
}
// ------------------------------------------------------------ formatting