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
|
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
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -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" = {
|
"quickshell/Commands.qml" = {
|
||||||
onChange = qsRestart;
|
onChange = qsRestart;
|
||||||
text = ''
|
text = ''
|
||||||
|
|
@ -1483,14 +1868,6 @@ in
|
||||||
bottom: true
|
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
|
implicitWidth: bar.screen.width
|
||||||
exclusiveZone: Theme.barWidth
|
exclusiveZone: Theme.barWidth
|
||||||
color: "transparent"
|
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
|
// ── 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 server glance sits just above it and
|
||||||
|
|
@ -5002,6 +5580,199 @@ 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
|
||||||
|
|
@ -6846,16 +7617,14 @@ in
|
||||||
property real value: 0
|
property real value: 0
|
||||||
property color fill: Theme.accent
|
property color fill: Theme.accent
|
||||||
|
|
||||||
// Sits in the gap the server glance used to occupy:
|
// Takes over the server glance's slot: fixed position, no
|
||||||
// bottom edge 20px above the bottom cluster, which is
|
// dependence on how tall the workspace column has grown.
|
||||||
// the same margin that widget anchored with, so the
|
|
||||||
// OSD lands exactly where it always has.
|
|
||||||
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: barBottom.y - 20 - height
|
y: srvWidget.y + srvWidget.height / 2 - height / 2
|
||||||
opacity: shown ? 1 : 0
|
opacity: shown ? 1 : 0
|
||||||
visible: opacity > 0.01
|
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