Files
quickdots/widgets/sidebar/sysinfo/SysInfo.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
}
}
}