1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 05:55:37 -05:00
Files
DankMaterialShell/Services/DankgopService.qml
2025-08-09 20:01:26 -04:00

595 lines
21 KiB
QML

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
}
}