SystemUsage.qml
1 pragma Singleton 2 3 import Quickshell 4 import Quickshell.Io 5 import QtQuick 6 7 Singleton { 8 id: root 9 10 property real cpuPerc 11 property real cpuTemp 12 property real gpuPerc 13 property real gpuTemp 14 property int memUsed 15 property int memTotal 16 readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0 17 property int storageUsed 18 property int storageTotal 19 property real storagePerc: storageTotal > 0 ? storageUsed / storageTotal : 0 20 21 property int lastCpuIdle 22 property int lastCpuTotal 23 24 function formatKib(kib: int): var { 25 const mib = 1024; 26 const gib = 1024 ** 2; 27 const tib = 1024 ** 3; 28 29 if (kib >= tib) 30 return { 31 value: kib / tib, 32 unit: "TiB" 33 }; 34 if (kib >= gib) 35 return { 36 value: kib / gib, 37 unit: "GiB" 38 }; 39 if (kib >= mib) 40 return { 41 value: kib / mib, 42 unit: "MiB" 43 }; 44 return { 45 value: kib, 46 unit: "KiB" 47 }; 48 } 49 50 Timer { 51 running: true 52 interval: 3000 53 repeat: true 54 onTriggered: { 55 stat.reload(); 56 meminfo.reload(); 57 storage.running = true; 58 cpuTemp.running = true; 59 gpuUsage.running = true; 60 gpuTemp.running = true; 61 } 62 } 63 64 FileView { 65 id: stat 66 67 path: "/proc/stat" 68 onLoaded: { 69 const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/); 70 if (data) { 71 const stats = data.slice(1).map(n => parseInt(n, 10)); 72 const total = stats.reduce((a, b) => a + b, 0); 73 const idle = stats[3]; 74 75 const totalDiff = total - root.lastCpuTotal; 76 const idleDiff = idle - root.lastCpuIdle; 77 root.cpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0; 78 79 root.lastCpuTotal = total; 80 root.lastCpuIdle = idle; 81 } 82 } 83 } 84 85 FileView { 86 id: meminfo 87 88 path: "/proc/meminfo" 89 onLoaded: { 90 const data = text(); 91 root.memTotal = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1; 92 root.memUsed = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0; 93 } 94 } 95 96 Process { 97 id: storage 98 99 running: true 100 command: ["sh", "-c", "df | grep '^/dev/' | awk '{print $3, $4}'"] 101 stdout: SplitParser { 102 splitMarker: "" 103 onRead: data => { 104 let used = 0; 105 let avail = 0; 106 for (const line of data.trim().split("\n")) { 107 const [u, a] = line.split(" "); 108 used += parseInt(u, 10); 109 avail += parseInt(a, 10); 110 } 111 root.storageUsed = used; 112 root.storageTotal = used + avail; 113 } 114 } 115 } 116 117 Process { 118 id: cpuTemp 119 120 running: true 121 command: ["fish", "-c", "cat /sys/class/thermal/thermal_zone*/temp | string join ' '"] 122 stdout: SplitParser { 123 onRead: data => { 124 const temps = data.trim().split(" "); 125 const sum = temps.reduce((acc, d) => acc + parseInt(d, 10), 0); 126 root.cpuTemp = sum / temps.length / 1000; 127 } 128 } 129 } 130 131 Process { 132 id: gpuUsage 133 134 running: true 135 command: ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] 136 stdout: SplitParser { 137 splitMarker: "" 138 onRead: data => { 139 const percs = data.trim().split("\n"); 140 const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0); 141 root.gpuPerc = sum / percs.length / 100; 142 } 143 } 144 } 145 146 Process { 147 id: gpuTemp 148 149 running: true 150 command: ["sh", "-c", "sensors | jq -nRc '[inputs]'"] 151 stdout: SplitParser { 152 onRead: data => { 153 let eligible = false; 154 let sum = 0; 155 let count = 0; 156 for (const line of JSON.parse(data)) { 157 if (line === "Adapter: PCI adapter") 158 eligible = true; 159 else if (line === "") 160 eligible = false; 161 else if (eligible) { 162 const match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)°C/); 163 if (match) { 164 sum += parseFloat(match[2]); 165 count++; 166 } 167 } 168 } 169 root.gpuTemp = count > 0 ? sum / count : 0; 170 } 171 } 172 } 173 }