fix(qs,fluxo) performance fix for repaints
This commit is contained in:
+24
-2
@@ -5,6 +5,13 @@
|
|||||||
# strings. The Quickshell config (~/.config/quickshell/services/Sys.qml) parses
|
# strings. The Quickshell config (~/.config/quickshell/services/Sys.qml) parses
|
||||||
# them and does its own formatting. The previous human-readable Waybar formats
|
# them and does its own formatting. The previous human-readable Waybar formats
|
||||||
# are preserved in config.toml.waybar-bak.
|
# are preserved in config.toml.waybar-bak.
|
||||||
|
#
|
||||||
|
# Quickshell only consumes cpu, mem, gpu, disk, net, sys and power from fluxo.
|
||||||
|
# Everything else it gets natively — Quickshell.Services.Mpris, .Pipewire,
|
||||||
|
# .UPower, .Notifications, Quickshell.Bluetooth — and Brightness.qml reads
|
||||||
|
# sysfs directly. Those fluxo modules are therefore switched off below: left on,
|
||||||
|
# they held a libpulse context, a BlueZ D-Bus poll every 2 s and an MPRIS
|
||||||
|
# marquee ticker firing twice a second, all feeding a bar that does not exist.
|
||||||
|
|
||||||
[general]
|
[general]
|
||||||
menu_command = "fuzzel --dmenu --prompt \"$FLUXO_PROMPT\""
|
menu_command = "fuzzel --dmenu --prompt \"$FLUXO_PROMPT\""
|
||||||
@@ -44,12 +51,18 @@ format = "{mount}|{used:.1}|{total:.1}"
|
|||||||
format = "{percentage}"
|
format = "{percentage}"
|
||||||
|
|
||||||
[audio]
|
[audio]
|
||||||
|
# Quickshell.Services.Pipewire drives Audio.qml; leaving this on kept a
|
||||||
|
# libpulse context and its callbacks alive for nothing.
|
||||||
|
enabled = false
|
||||||
format_sink_unmuted = "{name} {volume:>3}% <span size='large'>{icon}</span>"
|
format_sink_unmuted = "{name} {volume:>3}% <span size='large'>{icon}</span>"
|
||||||
format_sink_muted = "{name} <span size='large'> {icon}</span>"
|
format_sink_muted = "{name} <span size='large'> {icon}</span>"
|
||||||
format_source_unmuted = "{name} {volume:>3}% <span size='large'>{icon}</span>"
|
format_source_unmuted = "{name} {volume:>3}% <span size='large'>{icon}</span>"
|
||||||
format_source_muted = "{name} <span size='large'>{icon}</span>"
|
format_source_muted = "{name} <span size='large'>{icon}</span>"
|
||||||
|
|
||||||
[bt]
|
[bt]
|
||||||
|
# Quickshell.Bluetooth drives Bt.qml. This module polled BlueZ over D-Bus every
|
||||||
|
# 2 seconds regardless.
|
||||||
|
enabled = false
|
||||||
format_plugin = "{alias} [{left}|{right}] {anc} <span size='large'></span>"
|
format_plugin = "{alias} [{left}|{right}] {anc} <span size='large'></span>"
|
||||||
format_connected = "{alias} <span size='large'></span>"
|
format_connected = "{alias} <span size='large'></span>"
|
||||||
format_disconnected = "Disconnected <span size='large'></span>"
|
format_disconnected = "Disconnected <span size='large'></span>"
|
||||||
@@ -60,6 +73,11 @@ format_active = "<span size='large'></span>"
|
|||||||
format_inactive = "<span size='large'></span>"
|
format_inactive = "<span size='large'></span>"
|
||||||
|
|
||||||
[mpris]
|
[mpris]
|
||||||
|
# Quickshell.Services.Mpris drives Media.qml, and MediaPill/MediaPopout do their
|
||||||
|
# own eliding and scrolling. This module was the single most expensive thing in
|
||||||
|
# the daemon: `scroll` woke a ticker every `scroll_speed` ms to advance a marquee
|
||||||
|
# offset for a Waybar that is not running, and each tick drove the signaler.
|
||||||
|
enabled = false
|
||||||
format = "{artist} - {title}"
|
format = "{artist} - {title}"
|
||||||
max_length = 20
|
max_length = 20
|
||||||
scroll = true
|
scroll = true
|
||||||
@@ -81,10 +99,14 @@ enabled = false
|
|||||||
format = "{layout}"
|
format = "{layout}"
|
||||||
|
|
||||||
[backlight]
|
[backlight]
|
||||||
enable = true
|
# Was `enable = true` — not a key fluxo reads, so this section was silently
|
||||||
|
# running on its default of enabled. Brightness.qml reads
|
||||||
|
# /sys/class/backlight directly, so the D-Bus watcher here is redundant.
|
||||||
|
enabled = false
|
||||||
format = "{percentage}"
|
format = "{percentage}"
|
||||||
|
|
||||||
[dnd]
|
[dnd]
|
||||||
enabled = true
|
# Notifs.qml owns do-not-disturb via Quickshell.Services.Notifications.
|
||||||
|
enabled = false
|
||||||
format_dnd = "<span size='large'></span>"
|
format_dnd = "<span size='large'></span>"
|
||||||
format_normal = "<span size='large'></span>"
|
format_normal = "<span size='large'></span>"
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ Item {
|
|||||||
readonly property string incomingTitle: mine && toplevel.title ? toplevel.title : "—"
|
readonly property string incomingTitle: mine && toplevel.title ? toplevel.title : "—"
|
||||||
property string displayedTitle: "—"
|
property string displayedTitle: "—"
|
||||||
|
|
||||||
|
// Which toplevel the landing transition last played for. The landing is a
|
||||||
|
// focus-change stamp, so it has to key off window identity rather than off
|
||||||
|
// the caption text — see commitTitle().
|
||||||
|
property var landedToplevel: null
|
||||||
|
|
||||||
// The bar shrinks this when the three clusters would otherwise collide.
|
// The bar shrinks this when the three clusters would otherwise collide.
|
||||||
property real maxWidth: 240
|
property real maxWidth: 240
|
||||||
|
|
||||||
@@ -67,11 +72,28 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adopt the settled caption, and stamp it only when focus actually moved.
|
||||||
|
//
|
||||||
|
// The landing used to restart on any title change, which is not the same
|
||||||
|
// thing: plenty of windows rewrite their own title on a timer — a terminal
|
||||||
|
// running a task with a spinner or a percentage, a browser tab with a live
|
||||||
|
// clock — and each rewrite restarted seven overlapping animations totalling
|
||||||
|
// over half a second. At a title churning twice a second on a 240 Hz screen
|
||||||
|
// the bar never stopped animating, and repainting it that hard measured at
|
||||||
|
// roughly 18% of a CPU core with nothing else happening. Keying the stamp to
|
||||||
|
// window identity restores what the effect was described as doing.
|
||||||
function commitTitle(): void {
|
function commitTitle(): void {
|
||||||
if (root.displayedTitle === root.incomingTitle)
|
const focusMoved = root.toplevel !== root.landedToplevel;
|
||||||
|
|
||||||
|
if (root.displayedTitle !== root.incomingTitle)
|
||||||
|
root.displayedTitle = root.incomingTitle;
|
||||||
|
else if (!focusMoved)
|
||||||
return;
|
return;
|
||||||
root.displayedTitle = root.incomingTitle;
|
|
||||||
titleLanding.restart();
|
if (focusMoved) {
|
||||||
|
root.landedToplevel = root.toplevel;
|
||||||
|
titleLanding.restart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Browser tabs, terminals and editors can update titles several times in a
|
// Browser tabs, terminals and editors can update titles several times in a
|
||||||
@@ -85,7 +107,17 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onIncomingTitleChanged: titleSettle.restart()
|
onIncomingTitleChanged: titleSettle.restart()
|
||||||
Component.onCompleted: displayedTitle = incomingTitle
|
|
||||||
|
// Focus can move to a window whose caption happens to match the outgoing
|
||||||
|
// one, which changes no title and so would otherwise never land.
|
||||||
|
onToplevelChanged: titleSettle.restart()
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
displayedTitle = incomingTitle;
|
||||||
|
// Adopt the current window silently, so a config reload does not play a
|
||||||
|
// focus-change stamp for a focus that did not change.
|
||||||
|
landedToplevel = toplevel;
|
||||||
|
}
|
||||||
|
|
||||||
// A quick crimson wipe under the title on focus change.
|
// A quick crimson wipe under the title on focus change.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|||||||
@@ -109,15 +109,20 @@ Item {
|
|||||||
|
|
||||||
// Track progress, shown until the pointer arrives and the hover rule
|
// Track progress, shown until the pointer arrives and the hover rule
|
||||||
// takes the band over.
|
// takes the band over.
|
||||||
|
//
|
||||||
|
// Deliberately untweened. MPRIS position is polled once a second, and a
|
||||||
|
// 480 ms ease on a 1 Hz input meant this rule was mid-animation roughly
|
||||||
|
// half of every second — about 115 animated frames per second on a
|
||||||
|
// 240 Hz screen, each one rebuilding this Skew's Shape geometry through
|
||||||
|
// the curve renderer and re-rendering the pill's layer textures with it.
|
||||||
|
// Stepping once per second costs 1 frame instead of 115, matches the rate
|
||||||
|
// the underlying data actually arrives at, and makes a seek land exactly
|
||||||
|
// where it was dropped instead of gliding there.
|
||||||
Skew {
|
Skew {
|
||||||
height: parent.height
|
height: parent.height
|
||||||
width: parent.width * Math.max(0, Math.min(1, root.progress))
|
width: parent.width * Math.max(0, Math.min(1, root.progress))
|
||||||
color: Theme.accent
|
color: Theme.accent
|
||||||
visible: root.progress >= 0 && !root.hovered && !root.active
|
visible: root.progress >= 0 && !root.hovered && !root.active
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
NumberAnimation { duration: 480; easing.type: Easing.OutQuad }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Skew {
|
Skew {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
|
import QtQuick.Window
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import "root:/config"
|
import "root:/config"
|
||||||
import "root:/components"
|
import "root:/components"
|
||||||
@@ -126,35 +127,77 @@ BarPill {
|
|||||||
|
|
||||||
// Compact play-state equaliser. It moves only while audio is playing
|
// Compact play-state equaliser. It moves only while audio is playing
|
||||||
// and freezes into three quiet bars when paused.
|
// and freezes into three quiet bars when paused.
|
||||||
|
//
|
||||||
|
// Sampled from a timer rather than tweened by an infinite
|
||||||
|
// NumberAnimation. DP-1 runs at 240 Hz, and a frame-synced animation
|
||||||
|
// here repainted the entire bar 240 times a second: this pill sits
|
||||||
|
// inside ancestors that use layer.enabled together with
|
||||||
|
// Shape.CurveRenderer, and the album art carries a MultiEffect pass, so
|
||||||
|
// every one of those frames re-rendered several layer textures. Measured
|
||||||
|
// on its own, this one animation cost about two thirds of a CPU core
|
||||||
|
// whenever anything was playing.
|
||||||
|
//
|
||||||
|
// The timer samples a sine, it does not step through arbitrary levels: a
|
||||||
|
// low tick rate only looks like the original if consecutive ticks stay
|
||||||
|
// near each other. Each bar keeps the eased breathing motion and the
|
||||||
|
// period it had before — the curve is just read 25 times a second
|
||||||
|
// instead of 240.
|
||||||
Row {
|
Row {
|
||||||
|
id: eq
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: 2
|
spacing: 2
|
||||||
|
|
||||||
|
// Sampling interval — the one knob trading smoothness against cost.
|
||||||
|
// Frames are the whole expense: a full-bar repaint runs about 0.3% of
|
||||||
|
// a core (the bar spans 2544x40 on a 240 Hz output), so cost scales
|
||||||
|
// linearly with this rate. 16 ms holds 60 Hz, which is visually
|
||||||
|
// indistinguishable from the original frame-synced tween at a quarter
|
||||||
|
// of its ~75%.
|
||||||
|
readonly property int tickMs: 16
|
||||||
|
property int phase: 0
|
||||||
|
|
||||||
|
// Reproduces the original per-bar tween exactly, sampled instead of
|
||||||
|
// frame-synced. Each bar eased between two heights with its own pair
|
||||||
|
// of durations, so the three differed in both swing and period — the
|
||||||
|
// middle bar moved barely a pixel while the outer two ran in
|
||||||
|
// opposition. Giving all three the full swing, as a single symmetric
|
||||||
|
// sine would, is three times the motion and reads far busier than
|
||||||
|
// this meter is supposed to.
|
||||||
|
function level(i: int): real {
|
||||||
|
const lo = 4 + (i * 5) % 13; // old first NumberAnimation `to`
|
||||||
|
const hi = 14 - (i * 4) % 9; // old second NumberAnimation `to`
|
||||||
|
const d1 = 320 + i * 90; // ...and their durations
|
||||||
|
const d2 = 280 + i * 70;
|
||||||
|
|
||||||
|
const t = (eq.phase * eq.tickMs) % (d1 + d2);
|
||||||
|
// Easing.InOutSine, which is what both halves used.
|
||||||
|
const ease = x => (1 - Math.cos(Math.PI * x)) / 2;
|
||||||
|
|
||||||
|
return t < d1
|
||||||
|
? hi + (lo - hi) * ease(t / d1)
|
||||||
|
: lo + (hi - lo) * ease((t - d1) / d2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: eq.tickMs
|
||||||
|
running: Media.playing && eq.visible && (eq.Window.window?.visible ?? true)
|
||||||
|
repeat: true
|
||||||
|
onTriggered: eq.phase++
|
||||||
|
// Restart the cycle from its trough, so playback always begins
|
||||||
|
// from a settled meter rather than mid-swing.
|
||||||
|
onRunningChanged: if (!running) eq.phase = 0
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: 3
|
model: 3
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
required property int index
|
required property int index
|
||||||
width: 3
|
width: 3
|
||||||
height: 6
|
height: Media.playing ? eq.level(index) : 6
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
color: Media.playing ? Theme.primary : Theme.muted
|
color: Media.playing ? Theme.primary : Theme.muted
|
||||||
|
|
||||||
SequentialAnimation on height {
|
|
||||||
running: Media.playing
|
|
||||||
loops: Animation.Infinite
|
|
||||||
NumberAnimation {
|
|
||||||
to: 4 + (index * 5) % 13
|
|
||||||
duration: 320 + index * 90
|
|
||||||
easing.type: Easing.InOutSine
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
to: 14 - (index * 4) % 9
|
|
||||||
duration: 280 + index * 70
|
|
||||||
easing.type: Easing.InOutSine
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation { duration: Theme.durBase }
|
ColorAnimation { duration: Theme.durBase }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,8 +132,13 @@ Item {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hyprland evaluates IPC `dispatch` payloads as Lua now that the
|
||||||
|
// config is Lua (hyprland.lua rather than hyprland.conf), so the
|
||||||
|
// old `dispatch workspace 3` string is a Lua syntax error and the
|
||||||
|
// click silently does nothing. Dispatchers have to be called the
|
||||||
|
// same way lua/binds/workspaces.lua calls them.
|
||||||
TapHandler {
|
TapHandler {
|
||||||
onTapped: Hyprland.dispatch("workspace " + pip.wsId)
|
onTapped: Hyprland.dispatch("hl.dsp.focus({workspace = " + pip.wsId + "})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Window
|
||||||
import "root:/config"
|
import "root:/config"
|
||||||
|
|
||||||
// Horizontally scrolling text that only moves when it actually overflows, and
|
// Horizontally scrolling text that only moves when it actually overflows, and
|
||||||
@@ -18,6 +19,13 @@ Item {
|
|||||||
|
|
||||||
readonly property bool overflowing: label.implicitWidth > width + 1
|
readonly property bool overflowing: label.implicitWidth > width + 1
|
||||||
|
|
||||||
|
// An Item inside an unmapped PopupWindow still reports itself visible, so a
|
||||||
|
// marquee in a closed popout would keep its infinite animation alive. That
|
||||||
|
// animation writes QML properties on every frame — at 240 Hz, three closed
|
||||||
|
// popout marquees are enough to keep the whole animation driver spinning and
|
||||||
|
// burn a fifth of a core with nothing on screen. Follow the window instead.
|
||||||
|
readonly property bool onScreen: Window.window?.visible ?? true
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
implicitHeight: label.implicitHeight
|
implicitHeight: label.implicitHeight
|
||||||
implicitWidth: label.implicitWidth
|
implicitWidth: label.implicitWidth
|
||||||
@@ -38,7 +46,7 @@ Item {
|
|||||||
|
|
||||||
SequentialAnimation {
|
SequentialAnimation {
|
||||||
id: scroll
|
id: scroll
|
||||||
running: root.running && root.overflowing && root.visible
|
running: root.running && root.overflowing && root.visible && root.onScreen
|
||||||
loops: Animation.Infinite
|
loops: Animation.Infinite
|
||||||
|
|
||||||
PauseAnimation { duration: root.pause }
|
PauseAnimation { duration: root.pause }
|
||||||
|
|||||||
+101
-140
@@ -7,12 +7,18 @@ import Quickshell.Io
|
|||||||
// Hardware telemetry, sourced from the user's `fluxo` daemon. fluxo's hardware
|
// Hardware telemetry, sourced from the user's `fluxo` daemon. fluxo's hardware
|
||||||
// modules are configured to emit raw pipe-delimited values (see
|
// modules are configured to emit raw pipe-delimited values (see
|
||||||
// ~/.config/fluxo/config.toml); everything here is parsing and formatting.
|
// ~/.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 {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// Poll interval. The bar reads these continuously, so keep it modest.
|
|
||||||
property int interval: 2000
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------ cpu
|
// ------------------------------------------------------------------ cpu
|
||||||
property real cpuUsage: 0
|
property real cpuUsage: 0
|
||||||
property real cpuTemp: 0
|
property real cpuTemp: 0
|
||||||
@@ -56,157 +62,112 @@ Singleton {
|
|||||||
// UPower drives the battery UI; fluxo supplies the TLP/AC detail line.
|
// UPower drives the battery UI; fluxo supplies the TLP/AC detail line.
|
||||||
property string powerDetail: ""
|
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 {
|
function _clean(s: string): string {
|
||||||
return (s || "").replace(//g, "").trim();
|
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 {
|
function _num(v): real {
|
||||||
const n = parseFloat(v);
|
const n = parseFloat(v);
|
||||||
return isNaN(n) ? 0 : n;
|
return isNaN(n) ? 0 : n;
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
// ------------------------------------------------------------------ feed
|
||||||
interval: root.interval
|
// 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
|
running: true
|
||||||
repeat: true
|
|
||||||
triggeredOnStart: true
|
stdout: SplitParser {
|
||||||
onTriggered: {
|
onRead: data => root._ingest(data)
|
||||||
cpuReader.running = true;
|
|
||||||
memReader.running = true;
|
|
||||||
gpuReader.running = true;
|
|
||||||
netReader.running = true;
|
|
||||||
sysReader.running = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
Timer {
|
||||||
interval: root.interval * 10
|
id: retry
|
||||||
running: true
|
interval: 2000
|
||||||
repeat: true
|
repeat: false
|
||||||
triggeredOnStart: true
|
onTriggered: source.running = 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) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------ formatting
|
// ------------------------------------------------------------ formatting
|
||||||
|
|||||||
Reference in New Issue
Block a user