quickshell: drop server monitor and Archie

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-09-02 13:33:36 +01:00
parent ad1f50814f
commit 57e27aad52

View file

@ -478,7 +478,6 @@ in
Bar 1.0 Bar.qml Bar 1.0 Bar.qml
Lock 1.0 Lock.qml Lock 1.0 Lock.qml
Cheatsheet 1.0 Cheatsheet.qml Cheatsheet 1.0 Cheatsheet.qml
Archie 1.0 Archie.qml
''; '';
}; };
@ -1103,390 +1102,6 @@ in
''; '';
}; };
"quickshell/Archie.qml" = {
onChange = qsRestart;
text = ''
import QtQuick
// Archie — a black and tan dachshund with a red collar, living
// along the bottom of the screen. He trots about, wags, naps in
// his bed and visits his bowl.
//
// The art is plain text, one character per pixel, so he can be
// redrawn by editing the picture instead of editing code. He walks
// evenly on all fours and never limps; that was deliberate.
Item {
id: archie
anchors.fill: parent
// One dog. The bar is instantiated per monitor.
property bool primary: true
visible: primary
// 2px pixels: plenty of resolution for a proper pointed
// dachshund head, and he still lands smaller on screen than
// the chunkier drawing did.
readonly property int cell: 2
readonly property int cols: 44
readonly property int rows: 19
// Not themed — he looked how he looked.
readonly property var pal: ({
"K": "#2b2523", // black
"T": "#a86a30", // tan
"R": "#b3322c", // collar, and his bowl
"N": "#14100f", // nose and eye
"B": "#96602f", // kibble
"G": "#5a667a", // bed
"D": "#3a4454" // bed cushion
})
// Trotting: front leg forward, hind leg back, tail up. Paws
// point the way he is going — drawn trailing behind the leg
// they read as feet on backwards, which is exactly how the
// first pass looked.
readonly property var walkA: [
"...............................KKKKKKKK.....",
"KK............................KKKKKKKKKK....",
"KKKK..........................KKKKKKKKKKK...",
".KKKK.........................KKKKK.KKKKK...",
"..KKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKK..TTKKKKK.",
".KKKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKTTTTTTTTT.",
"..KKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKTTTTTTTT..",
"..KKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKTTTTT.....",
"...KKKKKKKKKKKKKKKKKKKKKKKKKTTKKKK..........",
"...KKKKKKKKKKKKKKKKKKKKKKKKKTTTKKK..........",
"...KKKKKKKKKKKKKKKKKKKKKKKKTTTT.............",
".....KKKKKKKKKKKKKKKKKKKKKTTTT..............",
"....TTKKKKKKKKKKKKKKKKKKKTTTT...............",
".....TTTTT..............TTTT................",
"......TTTT..............TTT.................",
".....TTT................TTT.................",
".....TTT................TTT.................",
".....TTT................TTT.................",
".....TTTTT..............TTTTT..............."
]
// Legs gathered under him, tail level.
readonly property var walkB: [
"...............................KKKKKKKK.....",
"..............................KKKKKKKKKK....",
"KKKK..........................KKKKKKKKKKK...",
"KKKK..........................KKKKK.KKKKK...",
"..KKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKK..TTKKKKK.",
".KKKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKTTTTTTTTT.",
"..KKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKTTTTTTTT..",
"..KKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKTTTTT.....",
"...KKKKKKKKKKKKKKKKKKKKKKKKKTTKKKK..........",
"...KKKKKKKKKKKKKKKKKKKKKKKKKTTTKKK..........",
"...KKKKKKKKKKKKKKKKKKKKKKKKTTTT.............",
".....KKKKKKKKKKKKKKKKKKKKKTTTT..............",
"....TTKKKKKKKKKKKKKKKKKKKTTTT...............",
"........TTTTT........TTTT...................",
".........TTTT........TTT....................",
"........TTT..........TTT....................",
"........TTT..........TTT....................",
"........TTT..........TTT....................",
"........TTTTT........TTTTT.................."
]
// Standing: walkA's planted legs, walkB's tail. Flipping
// between this and walkA is the wag.
readonly property var wag: [
"...............................KKKKKKKK.....",
"..............................KKKKKKKKKK....",
"KKKK..........................KKKKKKKKKKK...",
"KKKK..........................KKKKK.KKKKK...",
"..KKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKK..TTKKKKK.",
".KKKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKTTTTTTTTT.",
"..KKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKTTTTTTTT..",
"..KKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKTTTTT.....",
"...KKKKKKKKKKKKKKKKKKKKKKKKKTTKKKK..........",
"...KKKKKKKKKKKKKKKKKKKKKKKKKTTTKKK..........",
"...KKKKKKKKKKKKKKKKKKKKKKKKTTTT.............",
".....KKKKKKKKKKKKKKKKKKKKKTTTT..............",
"....TTKKKKKKKKKKKKKKKKKKKTTTT...............",
".....TTTTT..............TTTT................",
"......TTTT..............TTT.................",
".....TTT................TTT.................",
".....TTT................TTT.................",
".....TTT................TTT.................",
".....TTTTT..............TTTTT..............."
]
// Head down in the bowl.
readonly property var eat: [
"............................................",
"KK..........................................",
"KKKK........................................",
".KKKK.......................................",
"..KKKKKKKKKKKKKKKKKKKKKKKKKKKKKK............",
".KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK...........",
"..KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK..........",
"..KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK..........",
"...KKKKKKKKKKKKKKKKKKKKKKKKKTTKKKK..........",
"...KKKKKKKKKKKKKKKKKKKKKKKKKTTTKKKK.........",
"...KKKKKKKKKKKKKKKKKKKKKKKKTTTTKKKKKKKK.....",
".....KKKKKKKKKKKKKKKKKKKKKTTTTKKKKKKKKKK....",
"....TTKKKKKKKKKKKKKKKKKKKTTTT.KKKKK.KKKK....",
".....TTTTT..............TTTT..KKKKK.TTTKK...",
"......TTTT..............TTT...KKKKTTTTTTTTT.",
".....TTT................TTT...KKKKKTTTTTTT..",
".....TTT................TTT....KKKKTTTT.....",
".....TTT................TTT.................",
".....TTTTT..............TTTTT..............."
]
// Flat out in his bed, muzzle on the cushion.
readonly property var sleepArt: [
"............................................",
"............................................",
"............................................",
"............................................",
"............................................",
"............................................",
"............................................",
"............................................",
"............................................",
"..............................KKKKKKKKK.....",
".............................KKKKKKKKKKKK...",
"KKKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKKK.KKKKK...",
"..KKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKKTTTTTKKK..",
"..KKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKKTTTTTTTTT.",
"...KKKKKKKKKKKKKKKKKKKKKKKKRRKKKKKTTTTTTT...",
"...KKKKKKKKKKKKKKKKKKKKKKKKRRTTTT...........",
"....KKKKKKKKKKKKKKKKKKKKKKRRTTTT............",
".....TTTTTTTTKKKKKKKKKKKKTTTT...............",
"......TTTTTT..........TTTTTT................"
]
// Mid-breath: the same pose with his ribcage lifted one cell,
// just behind the collar where a dog's chest actually is. His
// belly stays put — that side is against the cushion.
readonly property var sleepIn: [
"............................................",
"............................................",
"............................................",
"............................................",
"............................................",
"............................................",
"............................................",
"............................................",
"............................................",
"..............................KKKKKKKKK.....",
"................KKKKKKKKKK...KKKKKKKKKKKK...",
"KKKKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKKK.KKKKK...",
"..KKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKKTTTTTKKK..",
"..KKKKKKKKKKKKKKKKKKKKKKKKKRRKKKKKTTTTTTTTT.",
"...KKKKKKKKKKKKKKKKKKKKKKKKRRKKKKKTTTTTTT...",
"...KKKKKKKKKKKKKKKKKKKKKKKKRRTTTT...........",
"....KKKKKKKKKKKKKKKKKKKKKKRRTTTT............",
".....TTTTTTTTKKKKKKKKKKKKTTTT...............",
"......TTTTTT..........TTTTTT................"
]
readonly property var bowlArt: [
".....BBBBBB.....",
"...BBBBBBBBBB...",
".BBBBBBBBBBBBBB.",
"RRRRRRRRRRRRRRRR",
".RRRRRRRRRRRRRR.",
"..RRRRRRRRRRRR..",
"...RRRRRRRRRR...",
"....RRRRRRRR....",
".....RRRRRR....."
]
readonly property var bedArt: [
"GGGGGGG........................................GGGGGGG",
"GGGGGGGGG....................................GGGGGGGGG",
"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG",
"..GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG.."
]
property var frame: walkA
// ── His patch of floor ──────────────────────────────────
readonly property real floorY: archie.height - Theme.frameWidth
readonly property real roamL: Theme.barWidth + 12
readonly property real roamR: Math.max(roamL,
archie.width - Theme.frameWidth - cols * cell - 12)
// Bed tucked into the bottom right corner, bowl beside it —
// his corner of the room. The bowl derives from the bed so
// they cannot drift into each other.
readonly property real bedX: Math.max(roamL,
archie.width - Theme.frameWidth - 54 * cell - 8)
readonly property real bowlX: Math.max(roamL, bedX - 16 * cell - 26)
// Stand so his muzzle lands in the bowl, not his shoulder.
readonly property real bowlStandX: bowlX - 32 * cell
// Lying down, centred on the cushion.
readonly property real bedLieX: bedX + 5 * cell
property real px: 0
property int facing: 1 // 1 = right, -1 = left
property string mode: "walk"
property real targetX: 0
property string errand: "" // "" | "bowl" | "bed"
property int hold: 0
// Ticks since he last got up. Past `tired` he takes himself
// off to bed.
property int awake: 0
readonly property int tired: 2700 // 90s on his feet
// Legs swap on distance covered, not on a tick count, so the
// gait stays in step. 3 cells is exactly how far the feet
// move in the art.
property real stride: 0
property bool stepped: false
// ── What he does next ───────────────────────────────────
function wander() {
mode = "walk";
errand = "";
// Bed is not a random errand any more, it is what he does
// when he has been up long enough. A dog sleeps most of
// the day; pottering about non-stop is the odd bit.
if (awake > tired) {
errand = "bed";
targetX = bedLieX;
return;
}
let dBowl = Math.abs(px - bowlStandX);
// Near enough to be worth it, and not already stood there
// — otherwise he marches half a minute for a snack, or
// rolls "go to the bowl" with his head already in it.
if (Math.random() < 0.3 && dBowl > 80 && dBowl < 900) {
errand = "bowl";
targetX = bowlStandX;
return;
}
let dir = Math.random() < 0.5 ? -1 : 1;
// Turn away from an end he is already near; clamping the
// target instead just parks him against the wall.
if (dir < 0 && px - roamL < 220) dir = 1;
if (dir > 0 && roamR - px < 220) dir = -1;
targetX = Math.max(roamL, Math.min(roamR,
px + dir * (150 + Math.random() * 550)));
}
function arrive() {
if (errand === "bowl") {
mode = "eat";
hold = 100 + Math.random() * 80;
facing = 1;
} else if (errand === "bed") {
mode = "sleep";
hold = 9000 + Math.random() * 9000; // 5-10 minutes
// Facing into the screen. His bed is in the right
// corner, so unmirrored he sleeps nose-to-the-wall.
facing = -1;
} else {
mode = "idle";
hold = 150 + Math.random() * 450; // 5-20s stood about
}
}
// Placed on the first real width: anchors have not resolved at
// Component.onCompleted, so roamR collapses to roamL and he
// spawns pinned to the left edge.
property bool placed: false
onWidthChanged: {
if (placed || width <= 0) return;
placed = true;
px = roamL + Math.random() * (roamR - roamL);
wander();
}
Timer {
interval: 33; running: archie.primary; repeat: true
onTriggered: {
archie.awake = archie.mode === "sleep" ? 0 : archie.awake + 1;
if (archie.mode === "walk") {
// Arrival first: checking after the step makes him
// stride backwards when he is already there.
if (Math.abs(archie.targetX - archie.px) < 2.5) {
archie.arrive();
} else {
let dir = archie.targetX > archie.px ? 1 : -1;
let sp = 1.5;
archie.facing = dir;
archie.px += dir * sp;
archie.stride += sp;
if (archie.stride >= 3 * archie.cell) {
archie.stride = 0;
archie.stepped = !archie.stepped;
}
archie.frame = archie.stepped ? archie.walkB : archie.walkA;
}
} else {
// Breathing: in for a third of the cycle, then
// back down and a pause. 91 ticks is about three
// seconds a breath, which is where a small dog
// asleep actually sits.
archie.frame = archie.mode === "eat" ? archie.eat
: archie.mode === "sleep"
? (archie.hold % 91 < 32 ? archie.sleepIn : archie.sleepArt)
: (archie.hold % 26 < 13 ? archie.walkA : archie.wag);
if (--archie.hold <= 0) archie.wander();
}
}
}
// ── Sprite: one Rectangle per pixel, colour looked up per
// frame, so switching frames just rebinds them.
component Sprite: Item {
id: sp
property var art: []
property int w: 0
property int h: 0
width: w * archie.cell
height: h * archie.cell
Repeater {
// Delegates parent to the Repeater's PARENT, so `sp`
// is the only reliable way back to the sprite here.
model: sp.w * sp.h
Rectangle {
required property int index
readonly property int col: index % sp.w
readonly property int row: Math.floor(index / sp.w)
readonly property string ch: sp.art[row][col]
visible: ch !== "."
color: archie.pal[ch] || "transparent"
x: col * archie.cell
y: row * archie.cell
width: archie.cell
height: archie.cell
antialiasing: false
}
}
}
// Bed first, so he lies in it rather than behind it.
Sprite {
art: archie.bedArt; w: 54; h: 7
x: Math.round(archie.bedX / archie.cell) * archie.cell
y: archie.floorY - height
}
Sprite {
art: archie.frame
w: archie.cols
h: archie.rows
// Snap x to the pixel grid or the art shimmers as he
// walks. y is exact: he never moves vertically, and
// rounding sank his paws through the floor.
x: Math.round(archie.px / archie.cell) * archie.cell
y: archie.floorY - height
// Mirror him when he heads back the other way.
transform: Scale { origin.x: archie.cols * archie.cell / 2; xScale: archie.facing }
}
// Bowl after him, so at dinner his nose goes behind it.
Sprite {
art: archie.bowlArt; w: 16; h: 9
x: Math.round(archie.bowlX / archie.cell) * archie.cell
y: archie.floorY - height
}
}
'';
};
"quickshell/Commands.qml" = { "quickshell/Commands.qml" = {
onChange = qsRestart; onChange = qsRestart;
text = '' text = ''
@ -4285,211 +3900,8 @@ 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 // ── Bottom cluster: status icons, tray, clock, power button. Anchored
// to the bottom edge; the server glance sits just above it and // to the bottom edge; the OSD sits just above it.
// the OSD borrows that same slot while it is on screen.
Column { Column {
id: barBottom id: barBottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@ -5580,200 +4992,6 @@ in
} }
// Volume dropdown // 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 { BarDropdown {
id: volDropdown id: volDropdown
fullWidth: volDropdownCol.width + 2 * Theme.panelGap fullWidth: volDropdownCol.width + 2 * Theme.panelGap
@ -7617,14 +6835,13 @@ in
property real value: 0 property real value: 0
property color fill: Theme.accent property color fill: Theme.accent
// Takes over the server glance's slot: fixed position, no // Fixed slot just above the bottom cluster.
// dependence on how tall the workspace column has grown.
width: Theme.barWidth width: Theme.barWidth
height: 170 height: 170
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
// Both surfaces span the full screen height from y=0, so // Both surfaces span the full screen height from y=0, so
// the bar's coordinates carry over unchanged. // the bar's coordinates carry over unchanged.
y: srvWidget.y + srvWidget.height / 2 - height / 2 y: barBottom.y - 20 - height
opacity: shown ? 1 : 0 opacity: shown ? 1 : 0
visible: opacity > 0.01 visible: opacity > 0.01
@ -7935,11 +7152,6 @@ in
} }
} }
} }
${lib.optionalString (!isMacbook) ''
// Archie. One dog, primary screen only.
Archie { id: archieDog; primary: bar.screen === Quickshell.screens[0] }
''}
} }
''; '';
}; };