import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Io import "../../../config" as Cfg ColumnLayout { id: root spacing: 0 readonly property var t: Cfg.Config.theme property string osName: "Linux" property string kernelVer: "" property real cpuPct: 0 property real memPct: 0 property real memUsedGb: 0 property real swapPct: 0 property real swapUsedGb: 0 property bool hasSwap: false property real diskPct: 0 property real diskUsedGb: 0 property string uptimeStr: "—" property real battPct: -1 property bool battCharging: false property real cpuTemp: -1 property real gpuTemp: -1 property bool netOnline: false property var _cpuPrev: ({ total: 0, idle: 0 }) Behavior on cpuPct { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } Behavior on memPct { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } Behavior on memUsedGb { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } Behavior on swapPct { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } Behavior on swapUsedGb { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } Behavior on diskPct { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } Behavior on diskUsedGb { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } Behavior on battPct { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } Behavior on cpuTemp { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } Behavior on gpuTemp { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } Process { running: true command: ["sh", "-c", "grep '^PRETTY_NAME=' /etc/os-release 2>/dev/null | cut -d'\"' -f2 || echo Linux"] stdout: SplitParser { onRead: d => { root.osName = d.trim() } } } Process { running: true command: ["uname", "-r"] stdout: SplitParser { onRead: d => { root.kernelVer = d.trim() } } } Process { id: cpuTempProc property string _buf: "" command: ["sh", "-c", "t=-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 " + "t=$(( $(cat $d/temp1_input 2>/dev/null) / 1000 )); break; " + "fi; done; echo $t"] stdout: SplitParser { onRead: d => { cpuTempProc._buf = d } } onRunningChanged: { if (running || !_buf) return root.cpuTemp = parseInt(_buf) _buf = "" } } Process { id: gpuTempProc property string _buf: "" command: ["sh", "-c", "t=-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 " + "t=$(( $(cat $d/temp1_input 2>/dev/null) / 1000 )); break; " + "fi; done; " + "[ \"$t\" = \"-1\" ] && " + "t=$(nvidia-smi --query-gpu=temperature.gpu " + "--format=csv,noheader 2>/dev/null | head -n 1); " + "echo $t"] stdout: SplitParser { onRead: d => { gpuTempProc._buf = d } } onRunningChanged: { if (running || !_buf) return root.gpuTemp = parseInt(_buf) _buf = "" } } Process { id: cpuProc property string _buf: "" command: ["sh", "-c", "grep '^cpu ' /proc/stat | head -1"] stdout: SplitParser { onRead: d => { cpuProc._buf = d } } onRunningChanged: { if (running || !_buf) return var p = _buf.trim().split(/\s+/) var user = +p[1], nice = +p[2], sys = +p[3] var idle = +p[4], iow = +p[5], irq = +p[6], sirq = +p[7] var total = user + nice + sys + idle + iow + irq + sirq var idleSum = idle + iow if (root._cpuPrev.total > 0) { var dt = total - root._cpuPrev.total var di = idleSum - root._cpuPrev.idle root.cpuPct = dt > 0 ? Math.round((dt - di) / dt * 100) : 0 } root._cpuPrev = { total: total, idle: idleSum } _buf = "" } } Process { id: memProc property string _buf: "" command: ["sh", "-c", "awk '/MemTotal/{t=$2}/MemAvailable/{a=$2}END" + "{print t*1024, (t-a)*1024, a*1024}' /proc/meminfo"] stdout: SplitParser { onRead: d => { memProc._buf = d } } onRunningChanged: { if (running || !_buf) return var p = _buf.trim().split(/\s+/) var total = +p[0], avail = +p[2] var used = total - avail root.memUsedGb = used / 1073741824 root.memPct = total > 0 ? (used / total * 100) : 0 _buf = "" } } Process { id: swapProc property string _buf: "" command: ["sh", "-c", "awk '/SwapTotal/{t=$2}/SwapFree/{f=$2}END" + "{print t*1024, (t-f)*1024}' /proc/meminfo"] stdout: SplitParser { onRead: d => { swapProc._buf = d } } onRunningChanged: { if (running || !_buf) return var p = _buf.trim().split(/\s+/) var total = +p[0], used = +p[1] root.hasSwap = total > 0 root.swapUsedGb = total > 0 ? used / 1073741824 : 0 root.swapPct = total > 0 ? (used / total * 100) : 0 _buf = "" } } Process { id: uptimeProc property string _buf: "" command: ["sh", "-c", "u=$(awk '{print int($1)}' /proc/uptime); " + "d=$((u/86400)); h=$(((u%86400)/3600)); m=$(((u%3600)/60)); " + "[ $d -gt 0 ] && echo ${d}d${h}h || [ $h -gt 0 ] && echo ${h}h${m}m || echo ${m}m"] stdout: SplitParser { onRead: d => { uptimeProc._buf = d } } onRunningChanged: { if (running || !_buf) return root.uptimeStr = _buf.trim() _buf = "" } } Process { id: diskProc property string _buf: "" command: ["sh", "-c", "df -B1 / | awk 'NR==2{print $3,$2}'"] stdout: SplitParser { onRead: d => { diskProc._buf = d } } onRunningChanged: { if (running || !_buf) return var p = _buf.trim().split(/\s+/) var used = +p[0], total = +p[1] root.diskUsedGb = used / 1073741824 root.diskPct = total > 0 ? (used / total * 100) : 0 _buf = "" } } Process { id: battProc property string _buf: "" command: ["sh", "-c", "p=/sys/class/power_supply/BAT0; " + "[ -f $p/capacity ] && echo $(cat $p/capacity) $(cat $p/status) || echo -1 Unknown"] stdout: SplitParser { onRead: d => { battProc._buf = d } } onRunningChanged: { if (running || !_buf) return var p = _buf.trim().split(/\s+/) root.battPct = parseInt(p[0]) root.battCharging = (p[1] === "Charging" || p[1] === "Full") _buf = "" } } Process { id: netProc property string _buf: "" command: ["sh", "-c", "ip route show default 2>/dev/null | grep -c default || echo 0"] stdout: SplitParser { onRead: d => { netProc._buf = d } } onRunningChanged: { if (running) return root.netOnline = parseInt(netProc._buf.trim()) > 0 _buf = "" } } function refresh() { if (!cpuProc.running) cpuProc.running = true if (!memProc.running) memProc.running = true if (!swapProc.running) swapProc.running = true if (!diskProc.running) diskProc.running = true if (!uptimeProc.running) uptimeProc.running = true if (!battProc.running) battProc.running = true if (!netProc.running) netProc.running = true if (!cpuTempProc.running) cpuTempProc.running = true if (!gpuTempProc.running) gpuTempProc.running = true } Timer { interval: 5000 running: true repeat: true triggeredOnStart: true onTriggered: root.refresh() } RowLayout { Layout.fillWidth: true spacing: 10 Rectangle { width: 32; height: 32 radius: 8 color: Qt.alpha(root.t.sysInfoAccent, 0.12) border.color: Qt.alpha(root.t.sysInfoAccent, 0.22) border.width: 1 Layout.alignment: Qt.AlignVCenter Text { anchors.centerIn: parent text: "󰌽" color: root.t.sysInfoAccent font.pixelSize: 20 font.weight: Font.Bold } } ColumnLayout { spacing: 2 Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter Text { text: root.osName color: root.t.sysInfoTextMain font.pixelSize: 13 font.weight: Font.Bold elide: Text.ElideRight Layout.fillWidth: true } Text { text: root.kernelVer color: root.t.sysInfoTextDim font.pixelSize: 10 font.weight: Font.Bold opacity: 0.75 elide: Text.ElideRight Layout.fillWidth: true } } } Item { Layout.preferredHeight: 12 } Rectangle { Layout.fillWidth: true height: 1 gradient: Gradient { orientation: Gradient.Horizontal GradientStop { position: 0.0; color: Qt.alpha(root.t.sysInfoAccent, 0.5) } GradientStop { position: 1.0; color: "transparent" } } } Item { Layout.preferredHeight: 10 } SysInfoRow { Layout.fillWidth: true icon: "󰻠" label: "CPU" value: Math.round(root.cpuPct) + "%" iconColor: root.t.sysInfoCpuColor dimColor: root.t.sysInfoTextDim textColor: root.t.sysInfoTextMain showBar: true barPct: root.cpuPct barColor: root.t.sysInfoCpuColor trackColor: root.t.sysInfoTrackColor } Item { Layout.preferredHeight: 9 } SysInfoRow { Layout.fillWidth: true visible: root.cpuTemp >= 0 icon: "󰔄" label: "CPU Temp" value: root.cpuTemp >= 0 ? Math.round(root.cpuTemp) + "°C" : "—" iconColor: root.cpuTemp > 80 ? root.t.statusErr : root.cpuTemp > 60 ? root.t.statusWarn : root.t.sysInfoCpuTempColor dimColor: root.t.sysInfoTextDim textColor: root.t.sysInfoTextMain showBar: true barPct: Math.min(100, Math.round(root.cpuTemp * 100 / 95)) barColor: root.cpuTemp > 80 ? root.t.statusErr : root.cpuTemp > 60 ? root.t.statusWarn : root.t.sysInfoCpuTempColor trackColor: root.t.sysInfoTrackColor } Item { Layout.fillWidth: true implicitHeight: root.gpuTemp >= 0 ? 9 : 0 visible: root.gpuTemp >= 0 } SysInfoRow { Layout.fillWidth: true visible: root.gpuTemp >= 0 icon: "󰾲" label: "GPU Temp" value: Math.round(root.gpuTemp) + "°C" iconColor: root.gpuTemp > 80 ? root.t.statusErr : root.gpuTemp > 65 ? root.t.statusWarn : root.t.sysInfoGpuColor dimColor: root.t.sysInfoTextDim textColor: root.t.sysInfoTextMain showBar: true barPct: Math.min(100, Math.round(root.gpuTemp * 100 / 95)) barColor: root.gpuTemp > 80 ? root.t.statusErr : root.gpuTemp > 65 ? root.t.statusWarn : root.t.sysInfoGpuColor trackColor: root.t.sysInfoTrackColor } Item { Layout.preferredHeight: 9 } SysInfoRow { Layout.fillWidth: true icon: "󰍛" label: "Memory" value: root.memUsedGb.toFixed(2) + " GB" iconColor: root.t.sysInfoMemColor dimColor: root.t.sysInfoTextDim textColor: root.t.sysInfoTextMain showBar: true barPct: root.memPct barColor: root.t.sysInfoMemColor trackColor: root.t.sysInfoTrackColor } Item { Layout.preferredHeight: 9 } SysInfoRow { Layout.fillWidth: true icon: "󰋊" label: "Disk (/)" value: root.diskUsedGb.toFixed(1) + " GB" iconColor: root.t.sysInfoDiskColor dimColor: root.t.sysInfoTextDim textColor: root.t.sysInfoTextMain showBar: true barPct: root.diskPct barColor: root.t.sysInfoDiskColor trackColor: root.t.sysInfoTrackColor } Item { Layout.fillWidth: true implicitHeight: root.hasSwap ? swapItem.implicitHeight + 9 : 0 visible: root.hasSwap clip: true ColumnLayout { id: swapItem anchors { top: parent.top; left: parent.left; right: parent.right } anchors.topMargin: 9 spacing: 5 SysInfoRow { Layout.fillWidth: true icon: "󰾫" label: "Swap" value: root.swapUsedGb.toFixed(1) + " GB" iconColor: root.t.sysInfoMemColor dimColor: root.t.sysInfoTextDim textColor: root.t.sysInfoTextMain showBar: true barPct: root.swapPct barColor: root.t.sysInfoMemColor trackColor: root.t.sysInfoTrackColor } } } Item { Layout.preferredHeight: 9 } RowLayout { Layout.fillWidth: true spacing: 10 Rectangle { width: 18; height: 18 radius: 4 color: Qt.alpha(root.t.sysInfoAccent, 0.15) Layout.alignment: Qt.AlignVCenter Text { anchors.centerIn: parent text: "󰅐" color: root.t.sysInfoAccent font.pixelSize: 12 font.weight: Font.Bold } } Text { text: "Uptime" color: root.t.sysInfoTextDim font.pixelSize: 12 font.weight: Font.Bold Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter } Text { text: root.uptimeStr color: root.t.sysInfoTextMain font.pixelSize: 12 font.weight: Font.Bold Layout.alignment: Qt.AlignVCenter } } Item { Layout.preferredHeight: 9 } RowLayout { Layout.fillWidth: true spacing: 10 Rectangle { width: 18; height: 18 radius: 4 color: Qt.alpha(root.t.sysInfoAccent, 0.15) Layout.alignment: Qt.AlignVCenter Text { anchors.centerIn: parent text: "" color: root.t.sysInfoAccent font.pixelSize: 12 font.weight: Font.Bold } } Text { text: "Kernel" color: root.t.sysInfoTextDim font.pixelSize: 12 font.weight: Font.Bold Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter } Text { text: root.kernelVer color: root.t.sysInfoTextMain font.pixelSize: 12 font.weight: Font.Bold Layout.alignment: Qt.AlignVCenter } } Item { Layout.fillWidth: true implicitHeight: root.battPct >= 0 ? battItem.implicitHeight + 9 : 0 visible: root.battPct >= 0 clip: true ColumnLayout { id: battItem anchors { top: parent.top; left: parent.left; right: parent.right } anchors.topMargin: 9 spacing: 5 RowLayout { Layout.fillWidth: true spacing: 10 Rectangle { width: 18; height: 18 radius: 4 color: Qt.alpha(battIconColor, 0.15) Layout.alignment: Qt.AlignVCenter readonly property color battIconColor: root.battPct <= 20 ? root.t.statusErr : root.battPct <= 40 ? root.t.statusWarn : root.battCharging ? root.t.statusOk : root.t.sysInfoAccent Text { anchors.centerIn: parent text: root.battCharging ? "󰂄" : root.battPct > 80 ? "󰁹" : root.battPct > 60 ? "󰂀" : root.battPct > 40 ? "󰁿" : root.battPct > 20 ? "󰁾" : "󰂃" color: parent.battIconColor font.pixelSize: 12 font.weight: Font.Bold } } Text { text: "Battery" color: root.t.sysInfoTextDim font.pixelSize: 12 font.weight: Font.Bold Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter } Text { text: Math.round(root.battPct) + "%" color: root.t.sysInfoTextMain font.pixelSize: 12 font.weight: Font.Bold Layout.alignment: Qt.AlignVCenter } } Rectangle { Layout.fillWidth: true height: 3 radius: 2 color: root.t.sysInfoTrackColor readonly property color fillColor: root.battPct <= 20 ? root.t.statusErr : root.battPct <= 40 ? root.t.statusWarn : root.battCharging ? root.t.statusOk : root.t.sysInfoAccent Rectangle { width: parent.width * Math.max(0, Math.min(root.battPct, 100)) / 100 height: parent.height radius: parent.radius color: parent.fillColor Behavior on width { NumberAnimation { duration: 800; easing.type: Easing.OutCubic } } Behavior on color { ColorAnimation { duration: 400 } } } } } } Item { Layout.preferredHeight: 9 } RowLayout { Layout.fillWidth: true spacing: 10 Rectangle { width: 18; height: 18 radius: 4 color: Qt.alpha(root.netOnline ? root.t.statusOk : root.t.sysInfoTextDim, 0.15) Layout.alignment: Qt.AlignVCenter Text { anchors.centerIn: parent text: root.netOnline ? "󰤨" : "󰤭" color: root.netOnline ? root.t.statusOk : root.t.sysInfoTextDim font.pixelSize: 12 font.weight: Font.Bold } } Text { text: "Network" color: root.t.sysInfoTextDim font.pixelSize: 12 font.weight: Font.Bold Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter } Text { text: root.netOnline ? "Connected" : "Offline" color: root.netOnline ? root.t.statusOk : root.t.statusErr font.pixelSize: 12 font.weight: Font.Bold Layout.alignment: Qt.AlignVCenter } } }