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] } ''} } '';