Restore sysmon bar widget (CPU/RAM/disk/GPU temp/net) on current plugin API
swadhinbiswas committed 2 months ago
f5910ef
1 parent 12362d0
commit f5910ef
3 files changed
+192 -0
Showing 3 changed files+192-0
▼shell/plugins/sysmon/Sysmon.qml
+112-0
| @@ -0,0 +1,112 @@ | ||
1 | +import QtQuick | |
2 | +import Quickshell | |
3 | +import Quickshell.Io | |
4 | +import qs.Commons | |
5 | +import qs.Ui | |
6 | + | |
7 | +BarWidget { | |
8 | + id: root | |
9 | + moduleName: "omarchy.sysmon" | |
10 | + | |
11 | + property int cpuPct: 0 | |
12 | + property int memPct: 0 | |
13 | + property int diskPct: 0 | |
14 | + property int gpuTemp: 0 | |
15 | + property int cpuTemp: 0 | |
16 | + property string netDown: "0.0" | |
17 | + property string netUp: "0.0" | |
18 | + property bool sampled: false | |
19 | + | |
20 | + // Poll interval in ms. The sampler itself needs ~0.6s inside each tick to | |
21 | + // measure its delta window, so don't push this below ~1500. | |
22 | + readonly property int intervalMs: setting("intervalMs", 2000) | |
23 | + | |
24 | + readonly property string scriptPath: Qt.resolvedUrl("sysmon-stats.sh").toString().replace("file://", "") | |
25 | + | |
26 | + visible: !root.vertical && sampled | |
27 | + implicitWidth: row.implicitWidth + Style.spacing.controlPaddingX * 2 | |
28 | + implicitHeight: barSize | |
29 | + | |
30 | + function refresh() { | |
31 | + if (!statsProc.running) statsProc.running = true | |
32 | + } | |
33 | + | |
34 | + function applyStats(line) { | |
35 | + var m = String(line).match(/C=(\d+) M=(\d+) D=(\d+) G=(\d+) T=(-?\d+) RX=([\d.]+) TX=([\d.]+)/) | |
36 | + if (!m) return | |
37 | + root.cpuPct = parseInt(m[1]) | |
38 | + root.memPct = parseInt(m[2]) | |
39 | + root.diskPct = parseInt(m[3]) | |
40 | + root.gpuTemp = parseInt(m[4]) | |
41 | + root.cpuTemp = parseInt(m[5]) | |
42 | + root.netDown = m[6] | |
43 | + root.netUp = m[7] | |
44 | + root.sampled = true | |
45 | + } | |
46 | + | |
47 | + Component.onCompleted: refresh() | |
48 | + | |
49 | + Process { | |
50 | + id: statsProc | |
51 | + command: ["bash", root.scriptPath] | |
52 | + stdout: StdioCollector { | |
53 | + waitForEnd: true | |
54 | + onStreamFinished: root.applyStats(text) | |
55 | + } | |
56 | + } | |
57 | + | |
58 | + Timer { | |
59 | + interval: root.intervalMs | |
60 | + running: true | |
61 | + repeat: true | |
62 | + onTriggered: root.broadcast("refresh") | |
63 | + } | |
64 | + | |
65 | + IpcHandler { | |
66 | + target: "omarchy.sysmon" | |
67 | + function refresh(): void { root.broadcast("refresh") } | |
68 | + } | |
69 | + | |
70 | + Row { | |
71 | + id: row | |
72 | + anchors.verticalCenter: parent.verticalCenter | |
73 | + spacing: 10 | |
74 | + | |
75 | + Repeater { | |
76 | + model: [ | |
77 | + { icon: "", value: root.cpuPct + "%" }, | |
78 | + { icon: "", value: root.cpuTemp + "°" }, | |
79 | + { icon: "", value: root.memPct + "%" }, | |
80 | + { icon: "", value: root.diskPct + "%" }, | |
81 | + { icon: "", value: root.gpuTemp + "°" }, | |
82 | + { icon: "", value: root.netDown + "↓" }, | |
83 | + { icon: "\uf093", value: root.netUp + "↑" } | |
84 | + ] | |
85 | + delegate: Text { | |
86 | + text: modelData.icon + " " + modelData.value | |
87 | + color: root.bar ? root.bar.barForeground : Color.foreground | |
88 | + font.family: root.bar ? root.bar.fontFamily : Style.font.family | |
89 | + font.pixelSize: Style.font.body | |
90 | + opacity: 0.85 | |
91 | + } | |
92 | + } | |
93 | + } | |
94 | + | |
95 | + MouseArea { | |
96 | + anchors.fill: parent | |
97 | + hoverEnabled: true | |
98 | + cursorShape: Qt.PointingHandCursor | |
99 | + onClicked: { | |
100 | + root.bar.run("uwsm-app -- kitty -e btop") | |
101 | + } | |
102 | + onEntered: { | |
103 | + if (root.bar) root.bar.showTooltip(root, | |
104 | + "CPU " + root.cpuPct + "% @ " + root.cpuTemp + "°\n" + | |
105 | + "RAM " + root.memPct + "%\n" + | |
106 | + "Disk " + root.diskPct + "%\n" + | |
107 | + "GPU " + root.gpuTemp + "°\n" + | |
108 | + "Net ↓ " + root.netDown + " MB/s ↑ " + root.netUp + " MB/s\n" + | |
109 | + "Click for btop") | |
110 | + } | |
111 | + } | |
112 | +} | |
0 | 113 | |
▼shell/plugins/sysmon/manifest.json
+21-0
| @@ -0,0 +1,21 @@ | ||
1 | +{ | |
2 | + "schemaVersion": 1, | |
3 | + "id": "omarchy.sysmon", | |
4 | + "name": "System monitor", | |
5 | + "version": "1.1.0", | |
6 | + "author": "de-omarchy", | |
7 | + "description": "CPU, RAM, disk and GPU temperature usage in the bar", | |
8 | + "kinds": [ | |
9 | + "bar-widget" | |
10 | + ], | |
11 | + "entryPoints": { | |
12 | + "barWidget": "Sysmon.qml" | |
13 | + }, | |
14 | + "barWidget": { | |
15 | + "displayName": "System monitor", | |
16 | + "description": "CPU, RAM, disk, GPU temp readouts", | |
17 | + "category": "System", | |
18 | + "allowMultiple": false, | |
19 | + "defaultSection": "right" | |
20 | + } | |
21 | +} | |
0 | 22 | |
▼shell/plugins/sysmon/sysmon-stats.sh
+59-0
| @@ -0,0 +1,59 @@ | ||
1 | +#!/bin/bash | |
2 | +# One-shot system stats sampler for the omarchy.sysmon bar widget. | |
3 | +# Prints a single line: | |
4 | +# C=<cpu%> M=<mem%> D=<disk%> G=<gpu-temp> T=<cpu-temp> RX=<MB/s down> TX=<MB/s up> | |
5 | +# Stateless: each invocation takes two samples 0.6s apart and derives rates | |
6 | +# from that window, so the QML side owns no previous-counter state and a dead | |
7 | +# sample never poisons the next one. | |
8 | + | |
9 | +read -r t0 i0 <<< "$(awk '/^cpu /{print $2+$3+$4+$5+$6+$7+$8, $5}' /proc/stat 2>/dev/null)" | |
10 | +read -r rx0 tx0 <<< "$(awk 'NR>2{rx+=$2; tx+=$10} END{print rx+0, tx+0}' /proc/net/dev 2>/dev/null)" | |
11 | + | |
12 | +sleep 0.6 | |
13 | + | |
14 | +read -r t1 i1 <<< "$(awk '/^cpu /{print $2+$3+$4+$5+$6+$7+$8, $5}' /proc/stat 2>/dev/null)" | |
15 | +read -r rx1 tx1 <<< "$(awk 'NR>2{rx+=$2; tx+=$10} END{print rx+0, tx+0}' /proc/net/dev 2>/dev/null)" | |
16 | + | |
17 | +dt=$(( t1 - t0 )) | |
18 | +di=$(( i1 - i0 )) | |
19 | +cpu=0 | |
20 | +(( dt > 0 )) && cpu=$(( 100 * (dt - di) / dt )) | |
21 | + | |
22 | +mem=$(awk '/^MemTotal:/{t=$2} /^MemAvailable:/{a=$2} END{if (t > 0) printf "%d", (t-a)*100/t; else print 0}' /proc/meminfo 2>/dev/null) | |
23 | +mem=${mem:-0} | |
24 | + | |
25 | +dsk=$(df --output=pcent / 2>/dev/null | tail -1 | tr -dc '0-9') | |
26 | +dsk=${dsk:-0} | |
27 | + | |
28 | +# GPU temperature: hwmon first (no subprocess cost), nvidia-smi as fallback. | |
29 | +gpu=0 | |
30 | +for hm in /sys/class/hwmon/hwmon*/name; do | |
31 | + hm_name=$(cat "$hm" 2>/dev/null) || continue | |
32 | + case $hm_name in | |
33 | + nvidia*|amdgpu|nouveau|radeon) | |
34 | + hm_dir=${hm%/name} | |
35 | + hm_temp=$(cat "$hm_dir/temp1_input" 2>/dev/null) || continue | |
36 | + [[ -n $hm_temp ]] && { gpu=$(( hm_temp / 1000 )); break; } | |
37 | + ;; | |
38 | + esac | |
39 | +done | |
40 | +if (( gpu == 0 )) && command -v nvidia-smi >/dev/null 2>&1; then | |
41 | + gpu=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1) | |
42 | + gpu=${gpu:-0} | |
43 | +fi | |
44 | + | |
45 | +ct=0 | |
46 | +for z in /sys/class/thermal/thermal_zone*/temp; do | |
47 | + zdir=${z%/temp} | |
48 | + ztype=$(cat "$zdir/type" 2>/dev/null) || continue | |
49 | + if [[ $ztype == x86_pkg_temp || $ztype == coretemp ]]; then | |
50 | + ct=$(awk '{printf "%d", $1/1000}' "$z" 2>/dev/null) | |
51 | + break | |
52 | + fi | |
53 | +done | |
54 | +ct=${ct:-0} | |
55 | + | |
56 | +down=$(awk "BEGIN{printf \"%.1f\", ($rx1-$rx0)/1048576/0.6}") | |
57 | +up=$(awk "BEGIN{printf \"%.1f\", ($tx1-$tx0)/1048576/0.6}") | |
58 | + | |
59 | +echo "C=$cpu M=$mem D=$dsk G=$gpu T=$ct RX=$down TX=$up" | |
0 | 60 | |