609 lines
20 KiB
QML
609 lines
20 KiB
QML
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
|
|
}
|
|
}
|
|
}
|