initial commit
This commit is contained in:
233
bar/SystemStats.qml
Normal file
233
bar/SystemStats.qml
Normal file
@@ -0,0 +1,233 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
// System stats strip: Volume · Network · CPU load · Memory · Temperature · Brightness · Battery
|
||||
// All stats refreshed every 5 s via a single bash process.
|
||||
// Requires: pactl, ip, awk, nproc, acpi, coreutils. Battery/backlight items hide when unavailable.
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
spacing: 16 // space between stat groups
|
||||
|
||||
// ── State ─────────────────────────────────────────────────────────────────
|
||||
property int volPct: 0
|
||||
property bool volMuted: false
|
||||
property real loadAvg: 0
|
||||
property int cpuCores: 1
|
||||
property real memUsedGB: 0
|
||||
property real memTotalGB: 0
|
||||
property int tempC: 0
|
||||
property int brightPct: 100
|
||||
property int batPct: -1 // -1 means no battery detected
|
||||
property string batStatus: ""
|
||||
property string batTime: "" // "H:MM" remaining, empty when unknown
|
||||
property string netIp: ""
|
||||
|
||||
// ── Line parser ───────────────────────────────────────────────────────────
|
||||
function parseLine(line) {
|
||||
if (line.startsWith("vol:")) {
|
||||
const p = line.split(":");
|
||||
root.volPct = parseInt(p[1]) || 0;
|
||||
root.volMuted = p[2] === "1";
|
||||
|
||||
} else if (line.startsWith("cpu:")) {
|
||||
const p = line.split(":");
|
||||
root.loadAvg = parseFloat(p[1]) || 0;
|
||||
root.cpuCores = parseInt(p[2]) || 1;
|
||||
|
||||
} else if (line.startsWith("mem:")) {
|
||||
const p = line.split(":");
|
||||
root.memUsedGB = parseFloat(p[1]) || 0;
|
||||
root.memTotalGB = parseFloat(p[2]) || 0;
|
||||
|
||||
} else if (line.startsWith("temp:")) {
|
||||
root.tempC = parseInt(line.slice(5)) || 0;
|
||||
|
||||
} else if (line.startsWith("bright:")) {
|
||||
root.brightPct = parseInt(line.slice(7)) || 100;
|
||||
|
||||
} else if (line.startsWith("bat:")) {
|
||||
const p = line.split(":");
|
||||
const cap = parseInt(p[1]);
|
||||
root.batPct = isNaN(cap) ? -1 : cap;
|
||||
root.batStatus = p[2] || "";
|
||||
root.batTime = p.slice(3).join(":") || ""; // rejoin H:MM split by delimiter
|
||||
|
||||
} else if (line.startsWith("net:")) {
|
||||
root.netIp = line.slice(4).trim();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Process ───────────────────────────────────────────────────────────────
|
||||
Process {
|
||||
id: statsProc
|
||||
command: [
|
||||
"bash", "-c",
|
||||
// Volume
|
||||
"vol=$(pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | grep -oP '\\d+(?=%)' | head -1); " +
|
||||
"muted=$(pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null | grep -q yes && echo 1 || echo 0); " +
|
||||
// CPU: 1-min load average + core count
|
||||
"load=$(cut -d' ' -f1 /proc/loadavg); " +
|
||||
"cores=$(nproc); " +
|
||||
// Memory: used and total in GB
|
||||
"mem=$(awk '/MemTotal/{t=$2}/MemAvailable/{a=$2}END{printf \"%.2f:%.2f\",(t-a)/1048576,t/1048576}' /proc/meminfo); " +
|
||||
// Temperature (highest thermal zone; falls back to hwmon)
|
||||
"temps=$(cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | sort -n | tail -1); " +
|
||||
"[ -z \"$temps\" ] && temps=$(cat /sys/class/hwmon/hwmon*/temp1_input 2>/dev/null | sort -n | tail -1); " +
|
||||
// Brightness
|
||||
"bright_cur=$(cat /sys/class/backlight/*/brightness 2>/dev/null | head -1); " +
|
||||
"bright_max=$(cat /sys/class/backlight/*/max_brightness 2>/dev/null | head -1); " +
|
||||
"if [ -n \"$bright_cur\" ] && [ -n \"$bright_max\" ] && [ \"$bright_max\" -gt 0 ]; " +
|
||||
"then bright=$((bright_cur*100/bright_max)); else bright=100; fi; " +
|
||||
// Battery capacity + status + estimated time (via acpi, H:MM only)
|
||||
"bat=$(cat /sys/class/power_supply/BAT*/capacity 2>/dev/null | head -1); " +
|
||||
"bat_st=$(cat /sys/class/power_supply/BAT*/status 2>/dev/null | head -1); " +
|
||||
"bat_time=$(acpi -b 2>/dev/null | grep -oP '\\d+:\\d+:\\d+' | head -1 | cut -d: -f1-2); " +
|
||||
// Network (outbound IP)
|
||||
"net=$(ip route get 1.1.1.1 2>/dev/null | grep -oP 'src \\K[\\d.]+' | head -1); " +
|
||||
// Normalise
|
||||
"temp=$([ -n \"$temps\" ] && echo $((temps/1000)) || echo 0); " +
|
||||
"[ -z \"$vol\" ] && vol=0; [ -z \"$bat\" ] && bat=-1; " +
|
||||
// Output
|
||||
"printf \"vol:%s:%s\\ncpu:%s:%s\\nmem:%s\\ntemp:%s\\nbright:%s\\nbat:%s:%s:%s\\nnet:%s\\n\" " +
|
||||
"\"$vol\" \"$muted\" \"$load\" \"$cores\" \"$mem\" \"$temp\" \"$bright\" \"$bat\" \"$bat_st\" \"$bat_time\" \"$net\""
|
||||
]
|
||||
stdout: SplitParser {
|
||||
onRead: function(line) { root.parseLine(line) }
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 5000
|
||||
running: true
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
statsProc.running = false
|
||||
statsProc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
// ── Volume ────────────────────────────────────────────────────────────────
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
|
||||
Process { id: pavucontrolProc; command: ["pavucontrol"] }
|
||||
Process { id: volUpProc; command: ["pactl", "set-sink-volume", "@DEFAULT_SINK@", "+5%"] }
|
||||
Process { id: volDownProc; command: ["pactl", "set-sink-volume", "@DEFAULT_SINK@", "-5%"] }
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: if (!pavucontrolProc.running) pavucontrolProc.running = true
|
||||
onWheel: function(wheel) {
|
||||
if (wheel.angleDelta.y > 0) {
|
||||
volUpProc.running = false
|
||||
volUpProc.running = true
|
||||
root.volPct = Math.min(100, root.volPct + 5)
|
||||
} else {
|
||||
volDownProc.running = false
|
||||
volDownProc.running = true
|
||||
root.volPct = Math.max(0, root.volPct - 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.volMuted ? "" : (root.volPct > 50 ? "" : root.volPct > 0 ? "" : "")
|
||||
color: root.volMuted ? "#585b70" : "#cdd6f4"
|
||||
font.pixelSize: 15
|
||||
}
|
||||
Text {
|
||||
text: root.volMuted ? "mute" : root.volPct + "%"
|
||||
color: "#a6adc8"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
|
||||
// ── Network ───────────────────────────────────────────────────────────────
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
Text {
|
||||
text: root.netIp ? "" : ""
|
||||
color: root.netIp ? "#cdd6f4" : "#585b70"
|
||||
font.pixelSize: 15
|
||||
}
|
||||
Text {
|
||||
visible: root.netIp !== ""
|
||||
text: root.netIp
|
||||
color: "#a6adc8"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
|
||||
// ── CPU load average ──────────────────────────────────────────────────────
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
Text { text: ""; color: "#cdd6f4"; font.pixelSize: 15 }
|
||||
Text {
|
||||
text: root.loadAvg.toFixed(2)
|
||||
color: root.loadAvg > root.cpuCores ? "#f38ba8" :
|
||||
root.loadAvg > root.cpuCores * 0.7 ? "#fab387" : "#a6adc8"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
|
||||
// ── Memory ────────────────────────────────────────────────────────────────
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
Text { text: ""; color: "#cdd6f4"; font.pixelSize: 15 }
|
||||
Text {
|
||||
text: root.memUsedGB.toFixed(1) + "/" + root.memTotalGB.toFixed(1) + "G"
|
||||
color: root.memTotalGB > 0 && (root.memUsedGB / root.memTotalGB) > 0.8 ? "#f38ba8" :
|
||||
root.memTotalGB > 0 && (root.memUsedGB / root.memTotalGB) > 0.6 ? "#fab387" : "#a6adc8"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
|
||||
// ── Temperature ───────────────────────────────────────────────────────────
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
Text { text: ""; color: "#cdd6f4"; font.pixelSize: 15 }
|
||||
Text {
|
||||
text: root.tempC + "°"
|
||||
color: root.tempC > 85 ? "#f38ba8" : root.tempC > 65 ? "#fab387" : "#a6adc8"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
|
||||
// ── Brightness ────────────────────────────────────────────────────────────
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
Text { text: ""; color: "#cdd6f4"; font.pixelSize: 15 }
|
||||
Text {
|
||||
text: root.brightPct + "%"
|
||||
color: "#a6adc8"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
|
||||
// ── Battery (hidden on desktops without a battery) ────────────────────────
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
visible: root.batPct >= 0
|
||||
Text {
|
||||
text: root.batStatus === "Charging" ? "" :
|
||||
root.batPct > 80 ? "" :
|
||||
root.batPct > 60 ? "" :
|
||||
root.batPct > 40 ? "" :
|
||||
root.batPct > 20 ? "" : ""
|
||||
color: root.batPct < 20 && root.batStatus !== "Charging" ? "#f38ba8" : "#cdd6f4"
|
||||
font.pixelSize: 15
|
||||
}
|
||||
Text {
|
||||
text: root.batTime ? root.batPct + "% · " + root.batTime : root.batPct + "%"
|
||||
color: root.batPct < 20 && root.batStatus !== "Charging" ? "#f38ba8" : "#a6adc8"
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user