import QtQuick import Quickshell import Quickshell.Io import qs.Common pragma Singleton pragma ComponentBehavior: Bound Singleton { id: root property int refCount: 0 property int updateInterval: refCount > 0 ? 3000 : 30000 property bool isUpdating: false property bool dankgopAvailable: false property var moduleRefCounts: ({}) property var enabledModules: [] property var gpuPciIds: [] property var gpuPciIdRefCounts: ({}) property int processLimit: 20 property string processSort: "cpu" property bool noCpu: false // Sampling data property var cpuSampleData: null property var procSampleData: null property real cpuUsage: 0 property real cpuFrequency: 0 property real cpuTemperature: 0 property int cpuCores: 1 property string cpuModel: "" property var perCoreCpuUsage: [] property real memoryUsage: 0 property real totalMemoryMB: 0 property real usedMemoryMB: 0 property real freeMemoryMB: 0 property real availableMemoryMB: 0 property int totalMemoryKB: 0 property int usedMemoryKB: 0 property int totalSwapKB: 0 property int usedSwapKB: 0 property real networkRxRate: 0 property real networkTxRate: 0 property var lastNetworkStats: null property var networkInterfaces: [] property real diskReadRate: 0 property real diskWriteRate: 0 property var lastDiskStats: null property var diskMounts: [] property var diskDevices: [] property var processes: [] property var availableGpus: [] property string kernelVersion: "" property string distribution: "" property string hostname: "" property string architecture: "" property string loadAverage: "" property int processCount: 0 property int threadCount: 0 property string bootTime: "" property string motherboard: "" property string biosVersion: "" property int historySize: 60 property var cpuHistory: [] property var memoryHistory: [] property var networkHistory: ({ "rx": [], "tx": [] }) property var diskHistory: ({ "read": [], "write": [] }) function addRef(modules = null) { refCount++ let modulesChanged = false if (modules) { const modulesToAdd = Array.isArray(modules) ? modules : [modules] for (const module of modulesToAdd) { // Increment reference count for this module const currentCount = moduleRefCounts[module] || 0 moduleRefCounts[module] = currentCount + 1 console.log("Adding ref for module:", module, "count:", moduleRefCounts[module]) // Add to enabled modules if not already there if (enabledModules.indexOf(module) === -1) { enabledModules.push(module) modulesChanged = true } } } if (modulesChanged || refCount === 1) { enabledModules = enabledModules.slice() // Force property change moduleRefCounts = Object.assign({}, moduleRefCounts) // Force property change updateAllStats() } else if (gpuPciIds.length > 0 && refCount > 0) { // If we have GPU PCI IDs and active modules, make sure to update // This handles the case where PCI IDs were loaded after modules were added updateAllStats() } } function removeRef(modules = null) { refCount = Math.max(0, refCount - 1) let modulesChanged = false if (modules) { const modulesToRemove = Array.isArray(modules) ? modules : [modules] for (const module of modulesToRemove) { const currentCount = moduleRefCounts[module] || 0 if (currentCount > 1) { // Decrement reference count moduleRefCounts[module] = currentCount - 1 console.log("Removing ref for module:", module, "count:", moduleRefCounts[module]) } else if (currentCount === 1) { // Remove completely when count reaches 0 delete moduleRefCounts[module] const index = enabledModules.indexOf(module) if (index > -1) { enabledModules.splice(index, 1) modulesChanged = true console.log("Disabling module:", module, "(no more refs)") } } } } if (modulesChanged) { enabledModules = enabledModules.slice() // Force property change moduleRefCounts = Object.assign({}, moduleRefCounts) // Force property change } } function setGpuPciIds(pciIds) { gpuPciIds = Array.isArray(pciIds) ? pciIds : [] } function addGpuPciId(pciId) { const currentCount = gpuPciIdRefCounts[pciId] || 0 gpuPciIdRefCounts[pciId] = currentCount + 1 // Add to gpuPciIds array if not already there if (!gpuPciIds.includes(pciId)) { gpuPciIds = gpuPciIds.concat([pciId]) } console.log("Adding GPU PCI ID ref:", pciId, "count:", gpuPciIdRefCounts[pciId]) // Force property change notification gpuPciIdRefCounts = Object.assign({}, gpuPciIdRefCounts) } function removeGpuPciId(pciId) { const currentCount = gpuPciIdRefCounts[pciId] || 0 if (currentCount > 1) { // Decrement reference count gpuPciIdRefCounts[pciId] = currentCount - 1 console.log("Removing GPU PCI ID ref:", pciId, "count:", gpuPciIdRefCounts[pciId]) } else if (currentCount === 1) { // Remove completely when count reaches 0 delete gpuPciIdRefCounts[pciId] const index = gpuPciIds.indexOf(pciId) if (index > -1) { gpuPciIds = gpuPciIds.slice() gpuPciIds.splice(index, 1) } // Clear temperature data for this GPU when no longer monitored if (availableGpus && availableGpus.length > 0) { const updatedGpus = availableGpus.slice() for (let i = 0; i < updatedGpus.length; i++) { if (updatedGpus[i].pciId === pciId) { updatedGpus[i] = Object.assign({}, updatedGpus[i], { temperature: 0 }) } } availableGpus = updatedGpus } console.log("Removing GPU PCI ID completely:", pciId) } // Force property change notification gpuPciIdRefCounts = Object.assign({}, gpuPciIdRefCounts) } function setProcessOptions(limit = 20, sort = "cpu", disableCpu = false) { processLimit = limit processSort = sort noCpu = disableCpu } function updateAllStats() { if (dankgopAvailable && refCount > 0 && enabledModules.length > 0) { isUpdating = true dankgopProcess.running = true } else { isUpdating = false } } function initializeGpuMetadata() { if (!dankgopAvailable) return // Load GPU metadata once at startup for basic info gpuInitProcess.running = true } function buildDankgopCommand() { const cmd = ["dankgop", "meta", "--json"] if (enabledModules.length === 0) { // Don't run if no modules are needed return [] } // Replace 'gpu' with 'gpu-temp' when we have PCI IDs to monitor const finalModules = [] for (const module of enabledModules) { if (module === "gpu" && gpuPciIds.length > 0) { finalModules.push("gpu-temp") } else if (module !== "gpu") { finalModules.push(module) } } // Add gpu-temp module automatically when we have PCI IDs to monitor if (gpuPciIds.length > 0 && finalModules.indexOf("gpu-temp") === -1) { finalModules.push("gpu-temp") } if (enabledModules.indexOf("all") !== -1) { cmd.push("--modules", "all") } else if (finalModules.length > 0) { const moduleList = finalModules.join(",") cmd.push("--modules", moduleList) } else { return [] } // Add sampling data if available if ((enabledModules.includes("cpu") || enabledModules.includes("all")) && cpuSampleData) { cmd.push("--cpu-sample", JSON.stringify(cpuSampleData)) } if ((enabledModules.includes("processes") || enabledModules.includes("all")) && procSampleData) { cmd.push("--proc-sample", JSON.stringify(procSampleData)) } if (gpuPciIds.length > 0) { cmd.push("--gpu-pci-ids", gpuPciIds.join(",")) } if (enabledModules.indexOf("processes") !== -1 || enabledModules.indexOf("all") !== -1) { if (processLimit > 0) { cmd.push("--limit", processLimit.toString()) } cmd.push("--sort", processSort) if (noCpu) { cmd.push("--no-cpu") } } return cmd } function parseData(data) { if (data.cpu) { const cpu = data.cpu cpuUsage = cpu.usage || 0 cpuFrequency = cpu.frequency || 0 cpuTemperature = cpu.temperature || 0 cpuCores = cpu.count || 1 cpuModel = cpu.model || "" perCoreCpuUsage = cpu.coreUsage || [] addToHistory(cpuHistory, cpuUsage) // Update CPU sample data for the next run if (cpu.total && cpu.cores) { cpuSampleData = { previousTotal: cpu.total, previousCores: cpu.cores, sampleTime: new Date().getTime() } } } if (data.memory) { const mem = data.memory const totalKB = mem.total || 0 const availableKB = mem.available || 0 const freeKB = mem.free || 0 // Update MB properties totalMemoryMB = totalKB / 1024 availableMemoryMB = availableKB / 1024 freeMemoryMB = freeKB / 1024 usedMemoryMB = totalMemoryMB - availableMemoryMB memoryUsage = totalKB > 0 ? ((totalKB - availableKB) / totalKB) * 100 : 0 // Update KB properties for compatibility totalMemoryKB = totalKB usedMemoryKB = totalKB - availableKB totalSwapKB = mem.swaptotal || 0 usedSwapKB = (mem.swaptotal || 0) - (mem.swapfree || 0) addToHistory(memoryHistory, memoryUsage) } if (data.network && Array.isArray(data.network)) { // Store raw network interface data networkInterfaces = data.network let totalRx = 0 let totalTx = 0 for (const iface of data.network) { totalRx += iface.rx || 0 totalTx += iface.tx || 0 } if (lastNetworkStats) { const timeDiff = updateInterval / 1000 const rxDiff = totalRx - lastNetworkStats.rx const txDiff = totalTx - lastNetworkStats.tx networkRxRate = Math.max(0, rxDiff / timeDiff) networkTxRate = Math.max(0, txDiff / timeDiff) addToHistory(networkHistory.rx, networkRxRate / 1024) addToHistory(networkHistory.tx, networkTxRate / 1024) } lastNetworkStats = { "rx": totalRx, "tx": totalTx } } if (data.disk && Array.isArray(data.disk)) { // Store raw disk device data diskDevices = data.disk let totalRead = 0 let totalWrite = 0 for (const disk of data.disk) { totalRead += (disk.read || 0) * 512 totalWrite += (disk.write || 0) * 512 } if (lastDiskStats) { const timeDiff = updateInterval / 1000 const readDiff = totalRead - lastDiskStats.read const writeDiff = totalWrite - lastDiskStats.write diskReadRate = Math.max(0, readDiff / timeDiff) diskWriteRate = Math.max(0, writeDiff / timeDiff) addToHistory(diskHistory.read, diskReadRate / (1024 * 1024)) addToHistory(diskHistory.write, diskWriteRate / (1024 * 1024)) } lastDiskStats = { "read": totalRead, "write": totalWrite } } if (data.diskmounts) { diskMounts = data.diskmounts || [] } if (data.processes && Array.isArray(data.processes)) { const newProcesses = [] const newProcSample = [] for (const proc of data.processes) { newProcesses.push({ "pid": proc.pid || 0, "ppid": proc.ppid || 0, "cpu": proc.cpu || 0, "memoryPercent": proc.memoryPercent || proc.pssPercent || 0, "memoryKB": proc.memoryKB || proc.pssKB || 0, "command": proc.command || "", "fullCommand": proc.fullCommand || "", "displayName": (proc.command && proc.command.length > 15) ? proc.command.substring(0, 15) + "..." : (proc.command || "") }) if (proc.pid && typeof proc.pticks !== 'undefined') { newProcSample.push({ pid: proc.pid, previousTicks: proc.pticks, sampleTime: new Date().getTime() }) } } processes = newProcesses if (newProcSample.length > 0) { procSampleData = newProcSample } } // Handle both gpu and gpu-temp module data const gpuData = (data.gpu && data.gpu.gpus) || data.gpus // Handle both meta format and direct gpu command format if (gpuData && Array.isArray(gpuData)) { // Check if this is temperature update data (has PCI IDs being monitored) if (gpuPciIds.length > 0 && availableGpus && availableGpus.length > 0) { // This is temperature data - merge with existing GPU metadata const updatedGpus = availableGpus.slice() for (let i = 0; i < updatedGpus.length; i++) { const existingGpu = updatedGpus[i] const tempGpu = gpuData.find(g => g.pciId === existingGpu.pciId) // Only update temperature if this GPU's PCI ID is being monitored if (tempGpu && gpuPciIds.includes(existingGpu.pciId)) { updatedGpus[i] = Object.assign({}, existingGpu, { temperature: tempGpu.temperature || 0 }) } } availableGpus = updatedGpus } else { // This is initial GPU metadata - set the full list const gpuList = [] for (const gpu of gpuData) { let displayName = gpu.displayName || gpu.name || "Unknown GPU" let fullName = gpu.fullName || gpu.name || "Unknown GPU" gpuList.push({ "driver": gpu.driver || "", "vendor": gpu.vendor || "", "displayName": displayName, "fullName": fullName, "pciId": gpu.pciId || "", "temperature": gpu.temperature || 0 }) } availableGpus = gpuList } } if (data.system) { const sys = data.system loadAverage = sys.loadavg || "" processCount = sys.processes || 0 threadCount = sys.threads || 0 bootTime = sys.boottime || "" } if (data.hardware) { const hw = data.hardware hostname = hw.hostname || "" kernelVersion = hw.kernel || "" distribution = hw.distro || "" architecture = hw.arch || "" motherboard = (hw.bios && hw.bios.motherboard) || "" biosVersion = (hw.bios && hw.bios.version) || "" } isUpdating = false } function addToHistory(array, value) { array.push(value) if (array.length > historySize) { array.shift() } } function getProcessIcon(command) { const cmd = command.toLowerCase() if (cmd.includes("firefox") || cmd.includes("chrome") || cmd.includes("browser")) return "web" if (cmd.includes("code") || cmd.includes("editor") || cmd.includes("vim")) return "code" if (cmd.includes("terminal") || cmd.includes("bash") || cmd.includes("zsh")) return "terminal" if (cmd.includes("music") || cmd.includes("audio") || cmd.includes("spotify")) return "music_note" if (cmd.includes("video") || cmd.includes("vlc") || cmd.includes("mpv")) return "play_circle" if (cmd.includes("systemd") || cmd.includes("kernel") || cmd.includes("kthread")) return "settings" return "memory" } function formatCpuUsage(cpu) { return (cpu || 0).toFixed(1) + "%" } function formatMemoryUsage(memoryKB) { const mem = memoryKB || 0 if (mem < 1024) return mem.toFixed(0) + " KB" else if (mem < 1024 * 1024) return (mem / 1024).toFixed(1) + " MB" else return (mem / (1024 * 1024)).toFixed(1) + " GB" } function formatSystemMemory(memoryKB) { const mem = memoryKB || 0 if (mem === 0) return "--" if (mem < 1024 * 1024) return (mem / 1024).toFixed(0) + " MB" else return (mem / (1024 * 1024)).toFixed(1) + " GB" } function killProcess(pid) { if (pid > 0) { Quickshell.execDetached("kill", [pid.toString()]) } } function setSortBy(newSortBy) { if (newSortBy !== processSort) { processSort = newSortBy } } Timer { id: updateTimer interval: root.updateInterval running: root.dankgopAvailable && root.refCount > 0 && root.enabledModules.length > 0 repeat: true triggeredOnStart: true onTriggered: root.updateAllStats() } Process { id: dankgopProcess command: root.buildDankgopCommand() running: false onCommandChanged: { //console.log("DankgopService command:", JSON.stringify(command)) } onExited: exitCode => { if (exitCode !== 0) { console.warn("Dankgop process failed with exit code:", exitCode) isUpdating = false } } stdout: StdioCollector { onStreamFinished: { if (text.trim()) { try { const data = JSON.parse(text.trim()) parseData(data) } catch (e) { console.warn("Failed to parse dankgop JSON:", e) console.warn("Raw text was:", text.substring(0, 200)) isUpdating = false } } } } } Process { id: gpuInitProcess command: ["dankgop", "gpu", "--json"] running: false onExited: exitCode => { if (exitCode !== 0) { console.warn("GPU init process failed with exit code:", exitCode) } } stdout: StdioCollector { onStreamFinished: { if (text.trim()) { try { const data = JSON.parse(text.trim()) parseData(data) } catch (e) { console.warn("Failed to parse GPU init JSON:", e) } } } } } Process { id: dankgopCheckProcess command: ["which", "dankgop"] running: false onExited: exitCode => { dankgopAvailable = (exitCode === 0) if (dankgopAvailable) { initializeGpuMetadata() // Load persisted GPU PCI IDs from session state if (SessionData.enabledGpuPciIds && SessionData.enabledGpuPciIds.length > 0) { for (const pciId of SessionData.enabledGpuPciIds) { addGpuPciId(pciId) } // Trigger update if we already have active modules if (refCount > 0 && enabledModules.length > 0) { updateAllStats() } } } else { console.warn("dankgop is not installed or not in PATH") } } } Component.onCompleted: { dankgopCheckProcess.running = true } }