quickshell: drop the server monitor; restore Archie
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 <noreply@anthropic.com>
This commit is contained in:
parent
8b3b7c1117
commit
2450814b36
1 changed files with 10 additions and 405 deletions
|
|
@ -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] }
|
||||
''}
|
||||
}
|
||||
'';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue