quickshell: revert today's performance work back to 7f858b2
Restores Archie, the server monitor and the full-width bar surface. None of today's changes fixed the stutter, and the surface refactor regressed it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
af9ca8f212
commit
ad1f50814f
1 changed files with 786 additions and 13 deletions
|
|
@ -478,6 +478,7 @@ in
|
|||
Bar 1.0 Bar.qml
|
||||
Lock 1.0 Lock.qml
|
||||
Cheatsheet 1.0 Cheatsheet.qml
|
||||
Archie 1.0 Archie.qml
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
@ -1102,6 +1103,390 @@ 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" = {
|
||||
onChange = qsRestart;
|
||||
text = ''
|
||||
|
|
@ -1483,14 +1868,6 @@ in
|
|||
bottom: true
|
||||
}
|
||||
|
||||
// Screen-sized: the frame band and every panel are drawn into
|
||||
// this one surface. That AREA is this shell's main cost —
|
||||
// Hyprland blurs this namespace, and blur is recomputed
|
||||
// whenever what's behind the surface changes, which with a
|
||||
// game underneath is every pixel every frame. Shrinking it to
|
||||
// the bar column measured 57% -> 38% GPU. Doing that for real
|
||||
// needs the frame band drawn somewhere that is NOT another
|
||||
// full-screen surface; see the wallpaper window.
|
||||
implicitWidth: bar.screen.width
|
||||
exclusiveZone: Theme.barWidth
|
||||
color: "transparent"
|
||||
|
|
@ -3908,6 +4285,207 @@ 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
|
||||
|
|
@ -5002,6 +5580,199 @@ 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
|
||||
|
|
@ -6846,16 +7617,14 @@ in
|
|||
property real value: 0
|
||||
property color fill: Theme.accent
|
||||
|
||||
// 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.
|
||||
// Takes over the server glance's slot: fixed position, no
|
||||
// dependence on how tall the workspace column has grown.
|
||||
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: barBottom.y - 20 - height
|
||||
y: srvWidget.y + srvWidget.height / 2 - height / 2
|
||||
opacity: shown ? 1 : 0
|
||||
visible: opacity > 0.01
|
||||
|
||||
|
|
@ -7167,6 +7936,10 @@ in
|
|||
}
|
||||
}
|
||||
|
||||
${lib.optionalString (!isMacbook) ''
|
||||
// Archie. One dog, primary screen only.
|
||||
Archie { id: archieDog; primary: bar.screen === Quickshell.screens[0] }
|
||||
''}
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue