From 2450814b36a4e0918836424df77cc110358bbae3 Mon Sep 17 00:00:00 2001 From: rope Date: Wed, 2 Sep 2026 11:53:22 +0100 Subject: [PATCH] quickshell: drop the server monitor; restore Archie MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SSH qs-stats stream and the router ping both tick every 2s, and each tick rewrote six Text items in the bar. That surface is the whole screen, so every tick repainted 3440x1440, reran the SDF shader and made Hyprland recompute the blur behind it — one late frame, twice a second. Suppressing both processes was the only change all day that held the frametime graph flat under deliberate provocation. Archie was measured innocent and comes back. The OSD anchored its y to the glance widget, so it now derives from barBottom instead. Co-Authored-By: Claude Opus 5 --- settings/quickshell.nix | 415 +--------------------------------------- 1 file changed, 10 insertions(+), 405 deletions(-) diff --git a/settings/quickshell.nix b/settings/quickshell.nix index 7e5a5b1..910bf14 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -4285,207 +4285,6 @@ in } } - // ── Mid cluster: the server glance, pinned just above the - // bottom cluster. Its own anchor (not a barTop child) is the - // whole point: workspace count then changes nothing here. - // - // Server monitor: one persistent SSH streaming `top -b` - // from the mediaserver (2s ticks, parsed line by line — - // no per-poll reconnects) plus a streaming ping to the - // router. Glance = CPU/RAM numbers + ping ms; click - // opens the btop-style dropdown. - Item { - id: srvWidget - width: Theme.barWidth - height: srvGlanceCol.height - anchors.horizontalCenter: barBgRect.horizontalCenter - anchors.bottom: barBottom.top - anchors.bottomMargin: 20 - // Fades out while the OSD takes this slot. - opacity: osdItem.shown ? 0 : 1 - Behavior on opacity { - NumberAnimation { duration: Theme.animContent } - } - - property bool connected: false - property real cpu: 0 - property real memUsed: 0 // GiB - property real memTotal: 0 - property real pingMs: -1 - property string uptime: "" - property string load: "" - property var procs: [] - property int tempC: -1 - property real rxBps: 0 - property real txBps: 0 - - function fmtRate(bps) { - const b = bps * 8; - if (b >= 1e9) return (b / 1e9).toFixed(2) + " Gb/s"; - if (b >= 1e6) return (b / 1e6).toFixed(1) + " Mb/s"; - if (b >= 1e3) return Math.round(b / 1e3) + " Kb/s"; - return Math.round(b) + " b/s"; - } - - // Split variants for the bar glance: number and unit - // live on separate lines, so both stay narrow. - function rateVal(bps) { - const b = bps * 8; - if (b >= 1e9) return (b / 1e9).toFixed(1); - if (b >= 1e7) return Math.round(b / 1e6).toString(); - if (b >= 1e6) return (b / 1e6).toFixed(1); - return Math.round(b / 1e3).toString(); - } - function rateUnit(bps) { - const b = bps * 8; - if (b >= 1e9) return "gb/s"; - if (b >= 1e6) return "mb/s"; - return "kb/s"; - } - - property var _procsPending: [] - property bool _collecting: false - - function _parseLine(line) { - if (line.indexOf("@STAT ") === 0) { - const t = line.match(/temp=(\d+)/); - if (t) tempC = parseInt(t[1]); - const rx = line.match(/rxbps=(-?\d+)/); - if (rx) rxBps = Math.max(0, parseInt(rx[1])); - const tx = line.match(/txbps=(-?\d+)/); - if (tx) txBps = Math.max(0, parseInt(tx[1])); - return; - } - if (line.indexOf("top - ") === 0) { - if (_collecting) procs = _procsPending; - _collecting = false; - const up = line.match(/up\s+(.*?),\s*\d+\s+users?,/); - if (up) uptime = up[1].replace(/\s+/g, " "); - const la = line.match(/load average:\s*(.*)$/); - if (la) load = la[1]; - return; - } - if (line.indexOf("%Cpu(s)") === 0) { - const m = line.match(/([\d.]+)\s+id/); - if (m) { cpu = Math.max(0, 100 - parseFloat(m[1])); connected = true; } - return; - } - const mem = line.match(/^(\S+) Mem\s*:\s*([\d.]+)\s+total,\s*[\d.]+\s+free,\s*([\d.]+)\s+used/); - if (mem) { - const div = mem[1] === "KiB" ? 1048576 : mem[1] === "MiB" ? 1024 : 1; - memTotal = parseFloat(mem[2]) / div; - memUsed = parseFloat(mem[3]) / div; - return; - } - if (/^\s*PID\s/.test(line)) { _collecting = true; _procsPending = []; return; } - if (_collecting) { - const f = line.trim().split(/\s+/); - if (f.length >= 12) { - _procsPending.push({ pid: f[0], cpu: parseFloat(f[8]), mem: parseFloat(f[9]), name: f.slice(11).join(" ") }); - // top sorts by %CPU; first 10 rows are the story - if (_procsPending.length >= 10) { procs = _procsPending; _collecting = false; } - } else { - procs = _procsPending; _collecting = false; - } - } - } - - Process { - id: srvTop - command: [Commands.ssh, "-o", "BatchMode=yes", "-o", "ConnectTimeout=3", - "-o", "StrictHostKeyChecking=accept-new", - "-o", "ServerAliveInterval=5", "-o", "ServerAliveCountMax=2", - "fred@10.0.0.1", "qs-stats"] - running: true - stdout: SplitParser { - onRead: data => srvWidget._parseLine(data) - } - onRunningChanged: if (!running) { srvWidget.connected = false; srvRestart.start(); } - } - Timer { id: srvRestart; interval: 10000; onTriggered: srvTop.running = true } - - // Ping is deliberately separate from the SSH stream: - // ping up + ssh down = box alive but wedged (seen it). - Process { - id: srvPing - command: [Commands.ping, "-n", "-i", "2", "10.0.0.1"] - running: true - stdout: SplitParser { - onRead: data => { - const m = data.match(/time=([\d.]+)\s*ms/); - if (m) { srvWidget.pingMs = parseFloat(m[1]); pingStale.restart(); } - } - } - onRunningChanged: if (!running) pingRestart.start() - } - Timer { id: pingRestart; interval: 10000; onTriggered: srvPing.running = true } - // No echo reply for 3 intervals -> show dashes, not a stale number. - Timer { id: pingStale; interval: 6000; onTriggered: srvWidget.pingMs = -1 } - - function openSrvDropdown() { - bar.toggleDropdown(srvDropdown, function() { - let pos = srvWidget.mapToItem(bar.contentItem, 0, srvWidget.height / 2); - srvDropdown.dropdownY = pos.y; - }); - } - - // Stat stack, clock-sized values over faded captions. - Column { - id: srvGlanceCol - anchors.centerIn: parent - spacing: 5 - - GlanceStat { - value: srvWidget.connected ? Math.round(srvWidget.cpu) + "%" : "--" - caption: "cpu" - // base05, like temp: the accent was the one fully - // saturated thing in the column, which made the - // stat the eye landed on first for no reason. - valueColor: srvWidget.connected ? Theme.base05 : Theme.base03 - } - GlanceStat { - value: srvWidget.connected && srvWidget.memTotal > 0 - ? Math.round(100 * srvWidget.memUsed / srvWidget.memTotal) + "%" : "--" - caption: "ram" - valueColor: srvWidget.connected ? Theme.info : Theme.base03 - } - GlanceStat { - value: srvWidget.connected && srvWidget.tempC >= 0 ? srvWidget.tempC + "°" : "--" - caption: "temp" - valueColor: !srvWidget.connected || srvWidget.tempC < 0 ? Theme.base03 - : srvWidget.tempC >= 85 ? Theme.err - : srvWidget.tempC >= 70 ? Theme.warn : Theme.base05 - } - GlanceStat { - value: srvWidget.connected ? srvWidget.rateVal(srvWidget.rxBps) : "--" - caption: "↓ " + srvWidget.rateUnit(srvWidget.rxBps) - valueColor: srvWidget.connected ? Theme.ok : Theme.base03 - } - GlanceStat { - value: srvWidget.connected ? srvWidget.rateVal(srvWidget.txBps) : "--" - caption: "↑ " + srvWidget.rateUnit(srvWidget.txBps) - valueColor: srvWidget.connected ? Theme.alt : Theme.base03 - } - GlanceStat { - value: srvWidget.pingMs < 0 ? "--" - : srvWidget.pingMs < 10 ? srvWidget.pingMs.toFixed(1) - : Math.round(srvWidget.pingMs).toString() - caption: "ms" - valueColor: srvWidget.pingMs < 0 ? Theme.base03 : Theme.base04 - } - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - onClicked: srvWidget.openSrvDropdown() - onEntered: { - if (bar.activeDropdown) { - if (bar.activeDropdown !== srvDropdown) srvWidget.openSrvDropdown(); - } - } - } - } // ── Bottom cluster: status icons, tray, clock, power button. Anchored // to the bottom edge; the server glance sits just above it and @@ -5580,199 +5379,6 @@ in } // Volume dropdown - // Server monitor dropdown — the btop-at-a-distance view. - BarDropdown { - id: srvDropdown - fullWidth: srvDropdownCol.width + 2 * Theme.panelGap - fullHeight: srvDropdownCol.height + 2 * Theme.panelGap - - Column { - id: srvDropdownCol - anchors.centerIn: parent - width: 340 - spacing: 8 - - Card { - width: parent.width - - Row { - spacing: 6 - SIcon { - anchors.verticalCenter: parent.verticalCenter - text: "server" - color: srvWidget.connected ? Theme.ok : Theme.err - } - SText { - anchors.verticalCenter: parent.verticalCenter - text: "FredOS-Mediaserver" - font.weight: Font.Medium - } - } - - SText { - width: parent.width - text: srvWidget.connected - ? "up " + srvWidget.uptime + " • load " + srvWidget.load - : "unreachable" - font.pixelSize: 12 - color: srvWidget.connected ? Theme.base04 : Theme.err - elide: Text.ElideRight - } - - Row { - width: parent.width - spacing: 10 - SText { width: 42; text: "cpu"; color: Theme.base04; anchors.verticalCenter: parent.verticalCenter } - MeterBar { - width: parent.width - 42 - 76 - 20 - anchors.verticalCenter: parent.verticalCenter - value: srvWidget.cpu / 100 - fillColor: srvWidget.cpu >= 90 ? Theme.err : Theme.accent - } - SText { - width: 76 - text: srvWidget.cpu.toFixed(1) + "%" - font.weight: Font.Medium - horizontalAlignment: Text.AlignRight - anchors.verticalCenter: parent.verticalCenter - } - } - - Row { - width: parent.width - spacing: 10 - SText { width: 42; text: "ram"; color: Theme.base04; anchors.verticalCenter: parent.verticalCenter } - MeterBar { - width: parent.width - 42 - 76 - 20 - anchors.verticalCenter: parent.verticalCenter - value: srvWidget.memTotal > 0 ? srvWidget.memUsed / srvWidget.memTotal : 0 - fillColor: Theme.info - } - SText { - width: 76 - text: srvWidget.memUsed.toFixed(1) + "/" + Math.round(srvWidget.memTotal) + "G" - font.weight: Font.Medium - horizontalAlignment: Text.AlignRight - anchors.verticalCenter: parent.verticalCenter - } - } - - Row { - width: parent.width - spacing: 10 - SText { width: 42; text: "temp"; color: Theme.base04 } - SText { - text: srvWidget.tempC < 0 ? "--" : srvWidget.tempC + "°C" - font.weight: Font.Medium - color: srvWidget.tempC < 0 ? Theme.base03 - : srvWidget.tempC >= 85 ? Theme.err - : srvWidget.tempC >= 70 ? Theme.warn : Theme.base05 - } - } - - Row { - width: parent.width - spacing: 10 - SText { width: 42; text: "wan"; color: Theme.base04 } - Row { - spacing: 14 - Row { - spacing: 4 - SIcon { text: "arrow-down"; font.pixelSize: 14; color: Theme.ok; anchors.verticalCenter: parent.verticalCenter } - SText { - text: srvWidget.fmtRate(srvWidget.rxBps) - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - } - Row { - spacing: 4 - SIcon { text: "arrow-up"; font.pixelSize: 14; color: Theme.alt; anchors.verticalCenter: parent.verticalCenter } - SText { - text: srvWidget.fmtRate(srvWidget.txBps) - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - } - } - } - - Row { - width: parent.width - spacing: 10 - SText { width: 42; text: "ping"; color: Theme.base04 } - SText { - text: srvWidget.pingMs < 0 ? "--" : srvWidget.pingMs.toFixed(1) + " ms" - font.weight: Font.Medium - color: srvWidget.pingMs < 0 ? Theme.base03 : Theme.base05 - } - } - } - - Card { - width: parent.width - - Row { - spacing: 6 - SIcon { anchors.verticalCenter: parent.verticalCenter; text: "activity" } - SText { - anchors.verticalCenter: parent.verticalCenter - text: "Processes" - font.weight: Font.Medium - } - } - - Row { - width: parent.width - SText { width: parent.width - 112; text: "name"; font.pixelSize: 11; color: Theme.base03 } - SText { width: 56; text: "cpu%"; font.pixelSize: 11; color: Theme.base03; horizontalAlignment: Text.AlignRight } - SText { width: 56; text: "mem%"; font.pixelSize: 11; color: Theme.base03; horizontalAlignment: Text.AlignRight } - } - - Column { - width: parent.width - spacing: 3 - - Repeater { - model: srvWidget.procs - - Row { - required property var modelData - width: parent.width - height: 21 - SText { - width: parent.width - 112 - text: modelData.name - elide: Text.ElideRight - anchors.verticalCenter: parent.verticalCenter - } - SText { - width: 56 - text: modelData.cpu.toFixed(1) - horizontalAlignment: Text.AlignRight - color: modelData.cpu >= 100 ? Theme.err - : modelData.cpu >= 25 ? Theme.warn : Theme.base05 - anchors.verticalCenter: parent.verticalCenter - } - SText { - width: 56 - text: modelData.mem.toFixed(1) - horizontalAlignment: Text.AlignRight - color: Theme.base04 - anchors.verticalCenter: parent.verticalCenter - } - } - } - - SText { - visible: srvWidget.procs.length === 0 - text: "no data" - color: Theme.base03 - } - } - } - } - } BarDropdown { id: volDropdown @@ -7617,14 +7223,16 @@ in property real value: 0 property color fill: Theme.accent - // Takes over the server glance's slot: fixed position, no - // dependence on how tall the workspace column has grown. + // Sits in the gap the server glance used to occupy: + // bottom edge 20px above the bottom cluster, which is + // the same margin that widget anchored with, so the + // OSD lands exactly where it always has. width: Theme.barWidth height: 170 anchors.horizontalCenter: parent.horizontalCenter // Both surfaces span the full screen height from y=0, so // the bar's coordinates carry over unchanged. - y: srvWidget.y + srvWidget.height / 2 - height / 2 + y: barBottom.y - 20 - height opacity: shown ? 1 : 0 visible: opacity > 0.01 @@ -7939,14 +7547,11 @@ in ${lib.optionalString (!isMacbook) '' // Archie. One dog, primary screen only. // - // DIAGNOSTIC, not a removal — put him back once measured. - // His 33ms timer is the only unconditional repaint driver left - // on this surface, and the surface is the whole screen: every - // tick reassigns `frame`, which re-evaluates ch/visible/colour - // on 836 Rectangles and dirties a 3440x1440 window, dragging - // the full-screen SDF shader and Hyprland's blur along with it - // 30 times a second. Measuring what that actually costs. - // Archie { id: archieDog; primary: bar.screen === Quickshell.screens[0] } + // Measured innocent: with blur on he looked like 22 points of + // GPU, but that was blur recomputing behind a screen-sized + // surface on every repaint, not his drawing. Blur off, his + // cost is 0. + Archie { id: archieDog; primary: bar.screen === Quickshell.screens[0] } ''} } '';