diff --git a/Greeter/Content.qml b/Greeter/Content.qml index 6ebf790..f2f314a 100644 --- a/Greeter/Content.qml +++ b/Greeter/Content.qml @@ -30,18 +30,6 @@ RowLayout { } } - // CustomRect { - // Layout.fillWidth: true - // color: DynamicColors.tPalette.m3surfaceContainer - // implicitHeight: resources.implicitHeight - // radius: Appearance.rounding.small - // - // Resources { - // id: resources - // - // } - // } - CustomClippingRect { Layout.fillHeight: true Layout.fillWidth: true diff --git a/Greeter/Helpers/SystemUsage.qml b/Greeter/Helpers/SystemUsage.qml deleted file mode 100644 index 11813f3..0000000 --- a/Greeter/Helpers/SystemUsage.qml +++ /dev/null @@ -1,409 +0,0 @@ -pragma Singleton - -import Quickshell -import Quickshell.Io -import QtQuick -import qs.Config - -Singleton { - id: root - - property string autoGpuType: "NONE" - property string cpuName: "" - property real cpuPerc - property real cpuTemp - - // Individual disks: Array of { mount, used, total, free, perc } - property var disks: [] - property real gpuMemTotal: 0 - property real gpuMemUsed - property real gpuPerc - property real gpuTemp - readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType - property real lastCpuIdle - property real lastCpuTotal - readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0 - property real memTotal - property real memUsed - property int refCount - readonly property real storagePerc: { - let totalUsed = 0; - let totalSize = 0; - for (const disk of disks) { - totalUsed += disk.used; - totalSize += disk.total; - } - return totalSize > 0 ? totalUsed / totalSize : 0; - } - - function cleanCpuName(name: string): string { - return name.replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/CPU/gi, "").replace(/\d+th Gen /gi, "").replace(/\d+nd Gen /gi, "").replace(/\d+rd Gen /gi, "").replace(/\d+st Gen /gi, "").replace(/Core /gi, "").replace(/Processor/gi, "").replace(/\s+/g, " ").trim(); - } - - function cleanGpuName(name: string): string { - return name.replace(/NVIDIA GeForce /gi, "").replace(/NVIDIA /gi, "").replace(/AMD Radeon /gi, "").replace(/AMD /gi, "").replace(/Intel /gi, "").replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/Graphics/gi, "").replace(/\s+/g, " ").trim(); - } - - function formatKib(kib: real): var { - const mib = 1024; - const gib = 1024 ** 2; - const tib = 1024 ** 3; - - if (kib >= tib) - return { - value: kib / tib, - unit: "TiB" - }; - if (kib >= gib) - return { - value: kib / gib, - unit: "GiB" - }; - if (kib >= mib) - return { - value: kib / mib, - unit: "MiB" - }; - return { - value: kib, - unit: "KiB" - }; - } - - Timer { - interval: Config.dashboard.resourceUpdateInterval - repeat: true - running: root.refCount > 0 - triggeredOnStart: true - - onTriggered: { - stat.reload(); - meminfo.reload(); - if (root.gpuType === "GENERIC") - gpuUsage.running = true; - } - } - - Timer { - interval: 60000 * 120 - repeat: true - running: true - triggeredOnStart: true - - onTriggered: { - storage.running = true; - } - } - - Timer { - interval: Config.dashboard.resourceUpdateInterval * 5 - repeat: true - running: root.refCount > 0 - triggeredOnStart: true - - onTriggered: { - sensors.running = true; - } - } - - FileView { - id: cpuinfoInit - - path: "/proc/cpuinfo" - - onLoaded: { - const nameMatch = text().match(/model name\s*:\s*(.+)/); - if (nameMatch) - root.cpuName = root.cleanCpuName(nameMatch[1]); - } - } - - FileView { - id: stat - - path: "/proc/stat" - - onLoaded: { - const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/); - if (data) { - const stats = data.slice(1).map(n => parseInt(n, 10)); - const total = stats.reduce((a, b) => a + b, 0); - const idle = stats[3] + (stats[4] ?? 0); - - const totalDiff = total - root.lastCpuTotal; - const idleDiff = idle - root.lastCpuIdle; - const newCpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0; - - root.lastCpuTotal = total; - root.lastCpuIdle = idle; - - if (Math.abs(newCpuPerc - root.cpuPerc) >= 0.01) - root.cpuPerc = newCpuPerc; - } - } - } - - FileView { - id: meminfo - - path: "/proc/meminfo" - - onLoaded: { - const data = text(); - const total = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1; - const used = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0; - - if (root.memTotal !== total) - root.memTotal = total; - - if (Math.abs(used - root.memUsed) >= 16384) - root.memUsed = used; - } - } - - Process { - id: storage - - command: ["lsblk", "-b", "-o", "NAME,SIZE,TYPE,FSUSED,FSSIZE", "-P"] - - stdout: StdioCollector { - onStreamFinished: { - const diskMap = {}; // Map disk name -> { name, totalSize, used, fsTotal } - const lines = text.trim().split("\n"); - - for (const line of lines) { - if (line.trim() === "") - continue; - const nameMatch = line.match(/NAME="([^"]+)"/); - const sizeMatch = line.match(/SIZE="([^"]+)"/); - const typeMatch = line.match(/TYPE="([^"]+)"/); - const fsusedMatch = line.match(/FSUSED="([^"]*)"/); - const fssizeMatch = line.match(/FSSIZE="([^"]*)"/); - - if (!nameMatch || !typeMatch) - continue; - - const name = nameMatch[1]; - const type = typeMatch[1]; - const size = parseInt(sizeMatch?.[1] || "0", 10); - const fsused = parseInt(fsusedMatch?.[1] || "0", 10); - const fssize = parseInt(fssizeMatch?.[1] || "0", 10); - - if (type === "disk") { - // Skip zram (swap) devices - if (name.startsWith("zram")) - continue; - - // Initialize disk entry - if (!diskMap[name]) { - diskMap[name] = { - name: name, - totalSize: size, - used: 0, - fsTotal: 0 - }; - } - } else if (type === "part") { - // Find parent disk (remove trailing numbers/p+numbers) - let parentDisk = name.replace(/p?\d+$/, ""); - // For nvme devices like nvme0n1p1, parent is nvme0n1 - if (name.match(/nvme\d+n\d+p\d+/)) - parentDisk = name.replace(/p\d+$/, ""); - - // Aggregate partition usage to parent disk - if (diskMap[parentDisk]) { - diskMap[parentDisk].used += fsused; - diskMap[parentDisk].fsTotal += fssize; - } - } - } - - const diskList = []; - let totalUsed = 0; - let totalSize = 0; - - for (const diskName of Object.keys(diskMap).sort()) { - const disk = diskMap[diskName]; - // Use filesystem total if available, otherwise use disk size - const total = disk.fsTotal > 0 ? disk.fsTotal : disk.totalSize; - const used = disk.used; - const perc = total > 0 ? used / total : 0; - - // Convert bytes to KiB for consistency with formatKib - diskList.push({ - mount: disk.name // Using 'mount' property for compatibility - , - used: used / 1024, - total: total / 1024, - free: (total - used) / 1024, - perc: perc - }); - - totalUsed += used; - totalSize += total; - } - - root.disks = diskList; - } - } - } - - Process { - id: gpuNameDetect - - command: ["sh", "-c", "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || lspci 2>/dev/null | grep -i 'vga\\|3d\\|display' | head -1"] - running: true - - stdout: StdioCollector { - onStreamFinished: { - const output = text.trim(); - if (!output) - return; - - // Check if it's from nvidia-smi (clean GPU name) - if (output.toLowerCase().includes("nvidia") || output.toLowerCase().includes("geforce") || output.toLowerCase().includes("rtx") || output.toLowerCase().includes("gtx")) { - root.gpuName = root.cleanGpuName(output); - } else { - // Parse lspci output: extract name from brackets or after colon - const bracketMatch = output.match(/\[([^\]]+)\]/); - if (bracketMatch) { - root.gpuName = root.cleanGpuName(bracketMatch[1]); - } else { - const colonMatch = output.match(/:\s*(.+)/); - if (colonMatch) - root.gpuName = root.cleanGpuName(colonMatch[1]); - } - } - } - } - } - - Process { - id: gpuTypeCheck - - command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"] - running: !Config.services.gpuType - - stdout: StdioCollector { - onStreamFinished: root.autoGpuType = text.trim() - } - } - - Process { - id: oneshotMem - - command: ["nvidia-smi", "--query-gpu=memory.total", "--format=csv,noheader,nounits"] - running: root.gpuType === "NVIDIA" && root.gpuMemTotal === 0 - - stdout: StdioCollector { - onStreamFinished: { - root.gpuMemTotal = Number(this.text.trim()); - oneshotMem.running = false; - } - } - } - - Process { - id: gpuUsageNvidia - - command: ["/usr/bin/nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu,memory.used", "--format=csv,noheader,nounits", "-lms", "1000"] - running: root.refCount > 0 && root.gpuType === "NVIDIA" - - stdout: SplitParser { - onRead: data => { - const parts = String(data).trim().split(/\s*,\s*/); - if (parts.length < 3) - return; - - const usageRaw = parseInt(parts[0], 10); - const tempRaw = parseInt(parts[1], 10); - const memRaw = parseInt(parts[2], 10); - - if (!Number.isFinite(usageRaw) || !Number.isFinite(tempRaw) || !Number.isFinite(memRaw)) - return; - - const newGpuPerc = Math.max(0, Math.min(1, usageRaw / 100)); - const newGpuTemp = tempRaw; - const newGpuMemUsed = root.gpuMemTotal > 0 ? Math.max(0, Math.min(1, memRaw / root.gpuMemTotal)) : 0; - - // Only publish meaningful changes to avoid needless binding churn / repaints - if (Math.abs(root.gpuPerc - newGpuPerc) >= 0.01) - root.gpuPerc = newGpuPerc; - - if (Math.abs(root.gpuTemp - newGpuTemp) >= 1) - root.gpuTemp = newGpuTemp; - - if (Math.abs(root.gpuMemUsed - newGpuMemUsed) >= 0.01) - root.gpuMemUsed = newGpuMemUsed; - } - } - } - - Process { - id: gpuUsage - - command: root.gpuType === "GENERIC" ? ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] : ["echo"] - - stdout: StdioCollector { - onStreamFinished: { - if (root.gpuType === "GENERIC") { - const percs = text.trim().split("\n"); - const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0); - root.gpuPerc = sum / percs.length / 100; - } else { - root.gpuPerc = 0; - root.gpuTemp = 0; - } - } - } - } - - Process { - id: sensors - - command: ["sensors"] - environment: ({ - LANG: "C.UTF-8", - LC_ALL: "C.UTF-8" - }) - - stdout: StdioCollector { - onStreamFinished: { - let cpuTemp = text.match(/(?:Package id [0-9]+|Tdie):\s+((\+|-)[0-9.]+)(°| )C/); - if (!cpuTemp) - // If AMD Tdie pattern failed, try fallback on Tctl - cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/); - - if (cpuTemp && Math.abs(parseFloat(cpuTemp[1]) - root.cpuTemp) >= 0.5) - root.cpuTemp = parseFloat(cpuTemp[1]); - - if (root.gpuType !== "GENERIC") - return; - - let eligible = false; - let sum = 0; - let count = 0; - - for (const line of text.trim().split("\n")) { - if (line === "Adapter: PCI adapter") - eligible = true; - else if (line === "") - eligible = false; - else if (eligible) { - let match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)(°| )C/); - if (!match) - // Fall back to junction/mem if GPU doesn't have edge temp (for AMD GPUs) - match = line.match(/^(junction|mem)+:\s+\+([0-9]+\.[0-9]+)(°| )C/); - - if (match) { - sum += parseFloat(match[2]); - count++; - } - } - } - - root.gpuTemp = count > 0 ? sum / count : 0; - } - } - } -} diff --git a/Greeter/Resources.qml b/Greeter/Resources.qml deleted file mode 100644 index c2f5c62..0000000 --- a/Greeter/Resources.qml +++ /dev/null @@ -1,77 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Helpers -import qs.Config - -GridLayout { - id: root - - anchors.left: parent.left - anchors.margins: Appearance.padding.large - anchors.right: parent.right - columnSpacing: Appearance.spacing.large - columns: 2 - rowSpacing: Appearance.spacing.large - rows: 1 - - Ref { - service: SystemUsage - } - - Resource { - Layout.bottomMargin: Appearance.padding.large - Layout.topMargin: Appearance.padding.large - colour: DynamicColors.palette.m3primary - icon: "memory" - value: SystemUsage.cpuPerc - } - - Resource { - Layout.bottomMargin: Appearance.padding.large - Layout.topMargin: Appearance.padding.large - colour: DynamicColors.palette.m3secondary - icon: "memory_alt" - value: SystemUsage.memPerc - } - - component Resource: CustomRect { - id: res - - required property color colour - required property string icon - required property real value - - Layout.fillWidth: true - color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2) - implicitHeight: width - radius: Appearance.rounding.large - - Behavior on value { - Anim { - duration: Appearance.anim.durations.large - } - } - - CircularProgress { - id: circ - - anchors.fill: parent - bgColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3) - fgColour: res.colour - padding: Appearance.padding.large * 3 - strokeWidth: width < 200 ? Appearance.padding.smaller : Appearance.padding.normal - value: res.value - } - - MaterialIcon { - id: icon - - anchors.centerIn: parent - color: res.colour - font.pointSize: (circ.arcRadius * 0.7) || 1 - font.weight: 600 - text: res.icon - } - } -}