import QtQuick import QtQuick.Layouts import Quickshell.Io import "../../config" as Cfg import "../../components" ModuleChip { id: root width: statsLayout.implicitWidth + 20 chipColor: t.statsBg chipBorderColor: t.statsBorder Behavior on width { NumberAnimation { duration: 250; easing.type: Easing.OutCubic } } property bool expanded: Cfg.Config.statsStartExpanded TapHandler { onTapped: { root.expanded = !root.expanded if (!root.expanded) root.hovered = false } } property real cpuVal: 0 property real memVal: 0 property real diskVal: 0 property real cpuTemp: -1 property real gpuTemp: -1 Timer { interval: 2000 running: true repeat: true triggeredOnStart: true onTriggered: { statsProc.running = true } } Process { id: statsProc command: ["sh", "-c", "(head -n 1 /proc/stat; sleep 1; head -n 1 /proc/stat) | awk " + "'{u=$2+$3;s=$4;i=$5+$6;x=$7+$8;t=u+s+i+x;if(NR==1){p=t;q=i;next}" + "{dt=t-p;di=i-q;print (dt>0?100*(dt-di)/dt:0)}}'; " + "awk '/MemTotal/{t=$2}/MemAvailable/{a=$2}END" + "{print (t>0?(t-a)/t*100:0)}' /proc/meminfo; " + "df / | awk 'NR==2{sub(/%/,\"\",$5); print $5}'; " + "ct=-1; for d in /sys/class/hwmon/hwmon*; do " + "n=$(cat $d/name 2>/dev/null); " + "if echo \"$n\" | grep -qiE " + "'coretemp|k10temp|cpu_thermal|zenpower|acpitz|x86_pkg_temp|thinkpad|nct6775'; then " + "ct=$(( $(cat $d/temp1_input 2>/dev/null) / 1000 )); break; " + "fi; done; echo $ct; " + "gt=-1; for d in /sys/class/hwmon/hwmon*; do " + "n=$(cat $d/name 2>/dev/null); " + "if echo \"$n\" | grep -qiE 'amdgpu|radeon|nouveau'; then " + "gt=$(( $(cat $d/temp1_input 2>/dev/null) / 1000 )); break; " + "fi; done; " + "[ \"$gt\" = \"-1\" ] && " + "gt=$(nvidia-smi --query-gpu=temperature.gpu " + "--format=csv,noheader 2>/dev/null | head -n 1); " + "echo $gt"] property string rawOutput: "" stdout: SplitParser { onRead: data => { statsProc.rawOutput += data + "\n" } } onRunningChanged: { if (!statsProc.running && statsProc.rawOutput) { const lines = statsProc.rawOutput.trim().split("\n") if (lines.length >= 5) { let v = parseFloat(lines[0]); root.cpuVal = isNaN(v) ? 0 : v v = parseFloat(lines[1]); root.memVal = isNaN(v) ? 0 : v v = parseFloat(lines[2]); root.diskVal = isNaN(v) ? 0 : v v = parseFloat(lines[3]); root.cpuTemp = isNaN(v) ? -1 : v v = parseFloat(lines[4]); root.gpuTemp = isNaN(v) ? -1 : v } statsProc.rawOutput = "" } } } component StatRing: RowLayout { id: ring spacing: 5 property real value: 0.0 property string icon: "" property string label: "" property real iconXOff: 0 property color ringColor: Cfg.Config.theme.accent property color trackColor: Cfg.Config.theme.statsTrackColor property string labelSuffix:"%" Behavior on value { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } readonly property real fraction: Math.max(0, Math.min(value, 100)) / 100 Text { id: leftPct text: Math.round(ring.value) + ring.labelSuffix color: ring.ringColor font.pixelSize: Cfg.Config.statsRingFontSize font.bold: true font.family: "monospace" Layout.alignment: Qt.AlignVCenter clip: true readonly property bool active: Cfg.Config.statsLabelPosition === "left" && (root.expanded || root.hovered) opacity: active ? 1 : 0 Layout.preferredWidth: active ? implicitWidth : 0 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } Behavior on Layout.preferredWidth { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } } Item { id: ringCore Layout.preferredWidth: Cfg.Config.statsRingSize Layout.preferredHeight: Cfg.Config.statsRingSize onVisibleChanged: if (visible) arc.requestPaint() Canvas { id: arc anchors.fill: parent antialiasing: true Connections { target: ring function onFractionChanged() { arc.requestPaint() } function onRingColorChanged() { arc.requestPaint() } } onPaint: { var ctx = getContext("2d") ctx.reset() var cx = width / 2 var cy = height / 2 var lw = 2.8 var r = Math.min(cx, cy) - lw / 2 - 0.5 ctx.beginPath() ctx.arc(cx, cy, r, 0, Math.PI * 2) ctx.strokeStyle = ring.trackColor ctx.lineWidth = lw ctx.stroke() if (ring.fraction > 0) { var start = -Math.PI / 2 var end = start + (ring.fraction * Math.PI * 2) ctx.beginPath() ctx.arc(cx, cy, r, start, end) ctx.strokeStyle = ring.ringColor ctx.lineWidth = lw ctx.lineCap = "round" ctx.stroke() } } } Text { anchors.centerIn: parent anchors.horizontalCenterOffset: ring.iconXOff text: ring.icon color: ring.ringColor font.pixelSize: Cfg.Config.statsIconSize } } Text { id: rightPct text: Math.round(ring.value) + ring.labelSuffix color: ring.ringColor font.pixelSize: Cfg.Config.statsRingFontSize font.bold: true font.family: "monospace" Layout.alignment: Qt.AlignVCenter clip: true readonly property bool active: Cfg.Config.statsLabelPosition === "right" && (root.expanded || root.hovered) opacity: active ? 1 : 0 Layout.preferredWidth: active ? implicitWidth : 0 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } Behavior on Layout.preferredWidth { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } } } RowLayout { id: statsLayout anchors.centerIn: parent spacing: 8 StatRing { visible: Cfg.Config.statsShowCpu value: root.cpuVal icon: "󰍛" label: "CPU" iconXOff: 0.3 ringColor: root.t.statsCpuColor trackColor: root.t.statsTrackColor } StatRing { visible: Cfg.Config.statsShowMem value: root.memVal icon: "󰑭" label: "RAM" ringColor: root.t.statsMemColor trackColor: root.t.statsTrackColor } StatRing { visible: Cfg.Config.statsShowDisk value: root.diskVal icon: "󰒋" label: "DISK" ringColor: root.t.statsDiskColor trackColor: root.t.statsTrackColor } StatRing { visible: Cfg.Config.statsShowCpuTemp && root.cpuTemp >= 0 value: root.cpuTemp icon: "󰔄" label: "CPU°C" ringColor: root.t.statsCpuTempColor trackColor: root.t.statsTrackColor labelSuffix: "°C" } StatRing { visible: Cfg.Config.statsShowGpuTemp && root.gpuTemp >= 0 value: root.gpuTemp icon: "󰾲" label: "GPU°C" ringColor: root.t.statsGpuColor trackColor: root.t.statsTrackColor labelSuffix: "°C" } } }