1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 05:55:37 -05:00

anotehr qmlfmt round and extra GPU data

This commit is contained in:
bbedward
2025-08-08 19:17:53 -04:00
parent 6c8e6568dc
commit 0a22565cbd
5 changed files with 804 additions and 499 deletions

View File

@@ -17,6 +17,7 @@ Singleton {
property string profileLastPath: "" property string profileLastPath: ""
property bool doNotDisturb: false property bool doNotDisturb: false
property var pinnedApps: [] property var pinnedApps: []
property int selectedGpuIndex: 0
Component.onCompleted: { Component.onCompleted: {
loadSettings() loadSettings()
@@ -37,6 +38,7 @@ Singleton {
profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : "" profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : ""
doNotDisturb = settings.doNotDisturb !== undefined ? settings.doNotDisturb : false doNotDisturb = settings.doNotDisturb !== undefined ? settings.doNotDisturb : false
pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : [] pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : []
selectedGpuIndex = settings.selectedGpuIndex !== undefined ? settings.selectedGpuIndex : 0
} }
} catch (e) { } catch (e) {
@@ -50,7 +52,8 @@ Singleton {
"wallpaperLastPath": wallpaperLastPath, "wallpaperLastPath": wallpaperLastPath,
"profileLastPath": profileLastPath, "profileLastPath": profileLastPath,
"doNotDisturb": doNotDisturb, "doNotDisturb": doNotDisturb,
"pinnedApps": pinnedApps "pinnedApps": pinnedApps,
"selectedGpuIndex": selectedGpuIndex
}, null, 2)) }, null, 2))
} }
@@ -115,6 +118,11 @@ Singleton {
return appId && pinnedApps.indexOf(appId) !== -1 return appId && pinnedApps.indexOf(appId) !== -1
} }
function setSelectedGpuIndex(index) {
selectedGpuIndex = index
saveSettings()
}
FileView { FileView {
id: settingsFile id: settingsFile

View File

@@ -237,12 +237,34 @@ Row {
width: (parent.width - Theme.spacingM * 2) / 3 width: (parent.width - Theme.spacingM * 2) / 3
height: 80 height: 80
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, color: {
if (gpuCardMouseArea.containsMouse
&& SysMonitorService.availableGpus.length > 1)
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.16)
else
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.08) Theme.surfaceVariant.b, 0.08)
}
border.color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, border.color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.2) Theme.surfaceVariant.b, 0.2)
border.width: 1 border.width: 1
MouseArea {
id: gpuCardMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: SysMonitorService.availableGpus.length
> 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
if (SysMonitorService.availableGpus.length > 1) {
var nextIndex = (SessionData.selectedGpuIndex + 1)
% SysMonitorService.availableGpus.length
SessionData.setSelectedGpuIndex(nextIndex)
}
}
}
Column { Column {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
@@ -250,7 +272,7 @@ Row {
spacing: 2 spacing: 2
StyledText { StyledText {
text: "Graphics" text: "GPU"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.secondary color: Theme.secondary
@@ -261,30 +283,16 @@ Row {
text: { text: {
if (!SysMonitorService.availableGpus if (!SysMonitorService.availableGpus
|| SysMonitorService.availableGpus.length === 0) { || SysMonitorService.availableGpus.length === 0) {
return "None"
}
if (SysMonitorService.availableGpus.length === 1) {
var gpu = SysMonitorService.availableGpus[0]
var temp = gpu.temperature
var tempText = (temp === undefined || temp === null
|| temp === 0) ? "--°" : Math.round(temp) + "°"
return tempText
}
// Multiple GPUs - show average temp
var totalTemp = 0
var validTemps = 0
for (var i = 0; i < SysMonitorService.availableGpus.length; i++) {
var temp = SysMonitorService.availableGpus[i].temperature
if (temp !== undefined && temp !== null && temp > 0) {
totalTemp += temp
validTemps++
}
}
if (validTemps > 0) {
return Math.round(totalTemp / validTemps) + "°"
}
return "--°" return "--°"
} }
var gpu = SysMonitorService.availableGpus[Math.min(
SessionData.selectedGpuIndex,
SysMonitorService.availableGpus.length - 1)]
var temp = gpu.temperature
return (temp === undefined || temp === null
|| temp === 0) ? "--°" : Math.round(temp) + "°"
}
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.family: SettingsData.monoFontFamily font.family: SettingsData.monoFontFamily
font.weight: Font.Bold font.weight: Font.Bold
@@ -293,25 +301,15 @@ Row {
|| SysMonitorService.availableGpus.length === 0) { || SysMonitorService.availableGpus.length === 0) {
return Theme.surfaceText return Theme.surfaceText
} }
if (SysMonitorService.availableGpus.length === 1) {
var temp = SysMonitorService.availableGpus[0].temperature || 0 var gpu = SysMonitorService.availableGpus[Math.min(
SessionData.selectedGpuIndex,
SysMonitorService.availableGpus.length - 1)]
var temp = gpu.temperature || 0
if (temp > 80) if (temp > 80)
return Theme.tempDanger return Theme.error
if (temp > 60) if (temp > 60)
return Theme.tempWarning return Theme.warning
return Theme.surfaceText
}
// Multiple GPUs - get max temp for coloring
var maxTemp = 0
for (var i = 0; i < SysMonitorService.availableGpus.length; i++) {
var temp = SysMonitorService.availableGpus[i].temperature || 0
if (temp > maxTemp)
maxTemp = temp
}
if (maxTemp > 80)
return Theme.tempDanger
if (maxTemp > 60)
return Theme.tempWarning
return Theme.surfaceText return Theme.surfaceText
} }
} }
@@ -322,15 +320,25 @@ Row {
|| SysMonitorService.availableGpus.length === 0) { || SysMonitorService.availableGpus.length === 0) {
return "No GPUs detected" return "No GPUs detected"
} }
if (SysMonitorService.availableGpus.length === 1) {
return SysMonitorService.availableGpus[0].driver.toUpperCase() var gpu = SysMonitorService.availableGpus[Math.min(
} SessionData.selectedGpuIndex,
return SysMonitorService.availableGpus.length + " GPUs detected" SysMonitorService.availableGpus.length - 1)]
return gpu.vendor + " " + gpu.displayName
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily font.family: SettingsData.monoFontFamily
color: Theme.surfaceText color: Theme.surfaceText
opacity: 0.7 opacity: 0.7
width: parent.parent.width - Theme.spacingM * 2
elide: Text.ElideRight
maximumLineCount: 1
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
} }
} }
} }

View File

@@ -104,8 +104,7 @@ ScrollView {
Rectangle { Rectangle {
width: (parent.width - Theme.spacingXL) / 2 width: (parent.width - Theme.spacingXL) / 2
height: Math.max(hardwareColumn.implicitHeight, height: hardwareColumn.implicitHeight + Theme.spacingL
memoryColumn.implicitHeight) + Theme.spacingM
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainerHigh.r, color: Qt.rgba(Theme.surfaceContainerHigh.r,
Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.g,
@@ -135,7 +134,7 @@ ScrollView {
} }
StyledText { StyledText {
text: "Hardware" text: "System"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily font.family: SettingsData.monoFontFamily
font.weight: Font.Bold font.weight: Font.Bold
@@ -180,23 +179,57 @@ ScrollView {
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
StyledText {
text: SysMonitorService.formatSystemMemory(
SysMonitorService.totalMemoryKB) + " RAM"
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
Theme.surfaceText.b, 0.8)
width: parent.width
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
} }
} }
Rectangle { Rectangle {
width: (parent.width - Theme.spacingXL) / 2 width: (parent.width - Theme.spacingXL) / 2
height: Math.max(hardwareColumn.implicitHeight, height: gpuColumn.implicitHeight + Theme.spacingL
memoryColumn.implicitHeight) + Theme.spacingM
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainerHigh.r, color: {
if (gpuCardMouseArea.containsMouse
&& SysMonitorService.availableGpus.length > 1)
return Qt.rgba(Theme.surfaceContainerHigh.r,
Theme.surfaceContainerHigh.g,
Theme.surfaceContainerHigh.b, 0.6)
else
return Qt.rgba(Theme.surfaceContainerHigh.r,
Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.g,
Theme.surfaceContainerHigh.b, 0.4) Theme.surfaceContainerHigh.b, 0.4)
}
border.width: 1 border.width: 1
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.1) Theme.outline.b, 0.1)
MouseArea {
id: gpuCardMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: SysMonitorService.availableGpus.length
> 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
if (SysMonitorService.availableGpus.length > 1) {
var nextIndex = (SessionData.selectedGpuIndex + 1)
% SysMonitorService.availableGpus.length
SessionData.setSelectedGpuIndex(nextIndex)
}
}
}
Column { Column {
id: memoryColumn id: gpuColumn
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@@ -209,14 +242,14 @@ ScrollView {
spacing: Theme.spacingS spacing: Theme.spacingS
DankIcon { DankIcon {
name: "developer_board" name: "auto_awesome_mosaic"
size: Theme.iconSizeSmall size: Theme.iconSizeSmall
color: Theme.secondary color: Theme.secondary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
StyledText { StyledText {
text: "Memory" text: "GPU"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily font.family: SettingsData.monoFontFamily
font.weight: Font.Bold font.weight: Font.Bold
@@ -226,35 +259,113 @@ ScrollView {
} }
StyledText { StyledText {
text: SysMonitorService.formatSystemMemory( text: {
SysMonitorService.totalMemoryKB) + " Total" if (!SysMonitorService.availableGpus
|| SysMonitorService.availableGpus.length === 0) {
return "No GPUs detected"
}
var gpu = SysMonitorService.availableGpus[Math.min(
SessionData.selectedGpuIndex,
SysMonitorService.availableGpus.length
- 1)]
return gpu.fullName
}
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily font.family: SettingsData.monoFontFamily
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
width: parent.width width: parent.width
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
StyledText { StyledText {
text: SysMonitorService.formatSystemMemory( text: {
SysMonitorService.usedMemoryKB) + " Used • " if (!SysMonitorService.availableGpus
+ SysMonitorService.formatSystemMemory( || SysMonitorService.availableGpus.length === 0) {
SysMonitorService.totalMemoryKB return "Vendor: N/A"
- SysMonitorService.usedMemoryKB) + " Available" }
var gpu = SysMonitorService.availableGpus[Math.min(
SessionData.selectedGpuIndex,
SysMonitorService.availableGpus.length
- 1)]
return "Vendor: " + gpu.vendor
}
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily font.family: SettingsData.monoFontFamily
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
Theme.surfaceText.b, 0.7) Theme.surfaceText.b, 0.8)
width: parent.width width: parent.width
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
Item { StyledText {
text: {
if (!SysMonitorService.availableGpus
|| SysMonitorService.availableGpus.length === 0) {
return "Driver: N/A"
}
var gpu = SysMonitorService.availableGpus[Math.min(
SessionData.selectedGpuIndex,
SysMonitorService.availableGpus.length
- 1)]
return "Driver: " + gpu.driver
}
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
Theme.surfaceText.b, 0.8)
width: parent.width width: parent.width
height: Theme.fontSizeSmall + Theme.spacingXS elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: {
if (!SysMonitorService.availableGpus
|| SysMonitorService.availableGpus.length === 0) {
return "Temp: --°"
}
var gpu = SysMonitorService.availableGpus[Math.min(
SessionData.selectedGpuIndex,
SysMonitorService.availableGpus.length
- 1)]
var temp = gpu.temperature
return "Temp: " + ((temp === undefined || temp === null
|| temp === 0) ? "--°" : Math.round(
temp) + "°C")
}
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: {
if (!SysMonitorService.availableGpus
|| SysMonitorService.availableGpus.length === 0) {
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
Theme.surfaceText.b, 0.7)
}
var gpu = SysMonitorService.availableGpus[Math.min(
SessionData.selectedGpuIndex,
SysMonitorService.availableGpus.length
- 1)]
var temp = gpu.temperature || 0
if (temp > 80)
return Theme.error
if (temp > 60)
return Theme.warning
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g,
Theme.surfaceText.b, 0.7)
}
width: parent.width
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
} }
} }
} }

View File

@@ -87,7 +87,8 @@ Rectangle {
SequentialAnimation on opacity { SequentialAnimation on opacity {
id: pulseAnimation id: pulseAnimation
running: parent.visible && hasActivePrivacy && PrivacyService.cameraActive running: parent.visible && hasActivePrivacy
&& PrivacyService.cameraActive
loops: Animation.Infinite loops: Animation.Infinite
NumberAnimation { NumberAnimation {

View File

@@ -1,4 +1,5 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior pragma ComponentBehavior
import QtQuick import QtQuick
@@ -75,324 +76,335 @@ Singleton {
property var availableGpus: [] property var availableGpus: []
function addRef() { function addRef() {
refCount++; refCount++
if (refCount === 1) { if (refCount === 1) {
updateAllStats(); updateAllStats()
} }
} }
function removeRef() { function removeRef() {
refCount = Math.max(0, refCount - 1); refCount = Math.max(0, refCount - 1)
} }
function updateAllStats() { function updateAllStats() {
if (refCount > 0) { if (refCount > 0) {
isUpdating = true; isUpdating = true
unifiedStatsProcess.running = true; unifiedStatsProcess.running = true
} }
} }
function setSortBy(newSortBy) { function setSortBy(newSortBy) {
if (newSortBy !== sortBy) { if (newSortBy !== sortBy) {
sortBy = newSortBy; sortBy = newSortBy
sortProcessesInPlace(); sortProcessesInPlace()
} }
} }
function toggleSortOrder() { function toggleSortOrder() {
sortDescending = !sortDescending; sortDescending = !sortDescending
sortProcessesInPlace(); sortProcessesInPlace()
} }
function sortProcessesInPlace() { function sortProcessesInPlace() {
if (processes.length === 0) if (processes.length === 0)
return; return
const sortedProcesses = [...processes]; const sortedProcesses = [...processes]
sortedProcesses.sort((a, b) => { sortedProcesses.sort((a, b) => {
let aVal, bVal; let aVal, bVal
switch (sortBy) { switch (sortBy) {
case "cpu": case "cpu":
aVal = parseFloat(a.cpu) || 0; aVal = parseFloat(a.cpu) || 0
bVal = parseFloat(b.cpu) || 0; bVal = parseFloat(b.cpu) || 0
break; break
case "memory": case "memory":
aVal = parseFloat(a.memoryPercent) || 0; aVal = parseFloat(a.memoryPercent) || 0
bVal = parseFloat(b.memoryPercent) || 0; bVal = parseFloat(b.memoryPercent) || 0
break; break
case "name": case "name":
aVal = a.command || ""; aVal = a.command || ""
bVal = b.command || ""; bVal = b.command || ""
break; break
case "pid": case "pid":
aVal = parseInt(a.pid) || 0; aVal = parseInt(a.pid) || 0
bVal = parseInt(b.pid) || 0; bVal = parseInt(b.pid) || 0
break; break
default: default:
aVal = parseFloat(a.cpu) || 0; aVal = parseFloat(a.cpu) || 0
bVal = parseFloat(b.cpu) || 0; bVal = parseFloat(b.cpu) || 0
} }
if (typeof aVal === "string") { if (typeof aVal === "string") {
return sortDescending ? bVal.localeCompare(aVal) : aVal.localeCompare(bVal); return sortDescending ? bVal.localeCompare(
aVal) : aVal.localeCompare(
bVal)
} else { } else {
return sortDescending ? bVal - aVal : aVal - bVal; return sortDescending ? bVal - aVal : aVal - bVal
} }
}); })
processes = sortedProcesses; processes = sortedProcesses
} }
function killProcess(pid) { function killProcess(pid) {
if (pid > 0) { if (pid > 0) {
Quickshell.execDetached("kill", [pid.toString()]); Quickshell.execDetached("kill", [pid.toString()])
} }
} }
function addToHistory(array, value) { function addToHistory(array, value) {
array.push(value); array.push(value)
if (array.length > historySize) if (array.length > historySize)
array.shift(); array.shift()
} }
function calculateCpuUsage(currentStats, lastStats) { function calculateCpuUsage(currentStats, lastStats) {
if (!lastStats || !currentStats || currentStats.length < 4) { if (!lastStats || !currentStats || currentStats.length < 4) {
return 0; return 0
} }
const currentTotal = currentStats.reduce((sum, val) => sum + val, 0); const currentTotal = currentStats.reduce((sum, val) => sum + val, 0)
const lastTotal = lastStats.reduce((sum, val) => sum + val, 0); const lastTotal = lastStats.reduce((sum, val) => sum + val, 0)
const totalDiff = currentTotal - lastTotal; const totalDiff = currentTotal - lastTotal
if (totalDiff <= 0) if (totalDiff <= 0)
return 0; return 0
const currentIdle = currentStats[3]; const currentIdle = currentStats[3]
const lastIdle = lastStats[3]; const lastIdle = lastStats[3]
const idleDiff = currentIdle - lastIdle; const idleDiff = currentIdle - lastIdle
const usedDiff = totalDiff - idleDiff; const usedDiff = totalDiff - idleDiff
return Math.max(0, Math.min(100, (usedDiff / totalDiff) * 100)); return Math.max(0, Math.min(100, (usedDiff / totalDiff) * 100))
} }
function parseUnifiedStats(text) { function parseUnifiedStats(text) {
function num(x) { function num(x) {
return (typeof x === "number" && !isNaN(x)) ? x : 0; return (typeof x === "number" && !isNaN(x)) ? x : 0
} }
let data; let data
try { try {
data = JSON.parse(text); data = JSON.parse(text)
} catch (error) { } catch (error) {
isUpdating = false; isUpdating = false
return; return
} }
if (data.memory) { if (data.memory) {
const m = data.memory; const m = data.memory
totalMemoryKB = num(m.total); totalMemoryKB = num(m.total)
const free = num(m.free); const free = num(m.free)
const buf = num(m.buffers); const buf = num(m.buffers)
const cached = num(m.cached); const cached = num(m.cached)
const shared = num(m.shared); const shared = num(m.shared)
usedMemoryKB = totalMemoryKB - free - buf - cached; usedMemoryKB = totalMemoryKB - free - buf - cached
totalSwapKB = num(m.swaptotal); totalSwapKB = num(m.swaptotal)
usedSwapKB = num(m.swaptotal) - num(m.swapfree); usedSwapKB = num(m.swaptotal) - num(m.swapfree)
totalMemoryMB = totalMemoryKB / 1024; totalMemoryMB = totalMemoryKB / 1024
usedMemoryMB = usedMemoryKB / 1024; usedMemoryMB = usedMemoryKB / 1024
freeMemoryMB = (totalMemoryKB - usedMemoryKB) / 1024; freeMemoryMB = (totalMemoryKB - usedMemoryKB) / 1024
availableMemoryMB = num(m.available) ? num(m.available) / 1024 : (free + buf + cached) / 1024; availableMemoryMB = num(
memoryUsage = totalMemoryKB > 0 ? (usedMemoryKB / totalMemoryKB) * 100 : 0; m.available) ? num(
m.available) / 1024 : (free + buf + cached) / 1024
memoryUsage = totalMemoryKB > 0 ? (usedMemoryKB / totalMemoryKB) * 100 : 0
} }
if (data.cpu) { if (data.cpu) {
cpuCores = data.cpu.count || 1; cpuCores = data.cpu.count || 1
cpuCount = data.cpu.count || 1; cpuCount = data.cpu.count || 1
cpuModel = data.cpu.model || ""; cpuModel = data.cpu.model || ""
cpuFrequency = data.cpu.frequency || 0; cpuFrequency = data.cpu.frequency || 0
cpuTemperature = data.cpu.temperature || 0; cpuTemperature = data.cpu.temperature || 0
if (data.cpu.total && data.cpu.total.length >= 8) { if (data.cpu.total && data.cpu.total.length >= 8) {
const currentStats = data.cpu.total; const currentStats = data.cpu.total
const usage = calculateCpuUsage(currentStats, lastCpuStats); const usage = calculateCpuUsage(currentStats, lastCpuStats)
cpuUsage = usage; cpuUsage = usage
totalCpuUsage = usage; totalCpuUsage = usage
lastCpuStats = [...currentStats]; lastCpuStats = [...currentStats]
} }
if (data.cpu.cores) { if (data.cpu.cores) {
const coreUsages = []; const coreUsages = []
for (var i = 0; i < data.cpu.cores.length; i++) { for (var i = 0; i < data.cpu.cores.length; i++) {
const currentCoreStats = data.cpu.cores[i]; const currentCoreStats = data.cpu.cores[i]
if (currentCoreStats && currentCoreStats.length >= 8) { if (currentCoreStats && currentCoreStats.length >= 8) {
let lastCoreStats = null; let lastCoreStats = null
if (lastPerCoreStats && lastPerCoreStats[i]) { if (lastPerCoreStats && lastPerCoreStats[i]) {
lastCoreStats = lastPerCoreStats[i]; lastCoreStats = lastPerCoreStats[i]
} }
const usage = calculateCpuUsage(currentCoreStats, lastCoreStats); const usage = calculateCpuUsage(currentCoreStats, lastCoreStats)
coreUsages.push(usage); coreUsages.push(usage)
} }
} }
if (JSON.stringify(perCoreCpuUsage) !== JSON.stringify(coreUsages)) { if (JSON.stringify(perCoreCpuUsage) !== JSON.stringify(coreUsages)) {
perCoreCpuUsage = coreUsages; perCoreCpuUsage = coreUsages
} }
lastPerCoreStats = data.cpu.cores.map(core => [...core]); lastPerCoreStats = data.cpu.cores.map(core => [...core])
} }
} }
if (data.network) { if (data.network) {
let totalRx = 0; let totalRx = 0
let totalTx = 0; let totalTx = 0
for (const iface of data.network) { for (const iface of data.network) {
totalRx += iface.rx; totalRx += iface.rx
totalTx += iface.tx; totalTx += iface.tx
} }
if (lastNetworkStats) { if (lastNetworkStats) {
const timeDiff = updateInterval / 1000; const timeDiff = updateInterval / 1000
const rxDiff = totalRx - lastNetworkStats.rx; const rxDiff = totalRx - lastNetworkStats.rx
const txDiff = totalTx - lastNetworkStats.tx; const txDiff = totalTx - lastNetworkStats.tx
networkRxRate = Math.max(0, rxDiff / timeDiff); networkRxRate = Math.max(0, rxDiff / timeDiff)
networkTxRate = Math.max(0, txDiff / timeDiff); networkTxRate = Math.max(0, txDiff / timeDiff)
addToHistory(networkHistory.rx, networkRxRate / 1024); addToHistory(networkHistory.rx, networkRxRate / 1024)
addToHistory(networkHistory.tx, networkTxRate / 1024); addToHistory(networkHistory.tx, networkTxRate / 1024)
} }
lastNetworkStats = { lastNetworkStats = {
"rx": totalRx, "rx": totalRx,
"tx": totalTx "tx": totalTx
}; }
} }
if (data.disk) { if (data.disk) {
let totalRead = 0; let totalRead = 0
let totalWrite = 0; let totalWrite = 0
for (const disk of data.disk) { for (const disk of data.disk) {
totalRead += disk.read * 512; totalRead += disk.read * 512
totalWrite += disk.write * 512; totalWrite += disk.write * 512
} }
if (lastDiskStats) { if (lastDiskStats) {
const timeDiff = updateInterval / 1000; const timeDiff = updateInterval / 1000
const readDiff = totalRead - lastDiskStats.read; const readDiff = totalRead - lastDiskStats.read
const writeDiff = totalWrite - lastDiskStats.write; const writeDiff = totalWrite - lastDiskStats.write
diskReadRate = Math.max(0, readDiff / timeDiff); diskReadRate = Math.max(0, readDiff / timeDiff)
diskWriteRate = Math.max(0, writeDiff / timeDiff); diskWriteRate = Math.max(0, writeDiff / timeDiff)
addToHistory(diskHistory.read, diskReadRate / (1024 * 1024)); addToHistory(diskHistory.read, diskReadRate / (1024 * 1024))
addToHistory(diskHistory.write, diskWriteRate / (1024 * 1024)); addToHistory(diskHistory.write, diskWriteRate / (1024 * 1024))
} }
lastDiskStats = { lastDiskStats = {
"read": totalRead, "read": totalRead,
"write": totalWrite "write": totalWrite
}; }
} }
let totalDiff = 0; let totalDiff = 0
if (data.cpu && data.cpu.total && data.cpu.total.length >= 4) { if (data.cpu && data.cpu.total && data.cpu.total.length >= 4) {
const currentTotal = data.cpu.total.reduce((s, v) => s + v, 0); const currentTotal = data.cpu.total.reduce((s, v) => s + v, 0)
if (lastTotalJiffies > 0) if (lastTotalJiffies > 0)
totalDiff = currentTotal - lastTotalJiffies; totalDiff = currentTotal - lastTotalJiffies
lastTotalJiffies = currentTotal; lastTotalJiffies = currentTotal
} }
if (data.processes) { if (data.processes) {
const newProcesses = []; const newProcesses = []
for (const proc of data.processes) { for (const proc of data.processes) {
const pid = proc.pid; const pid = proc.pid
const pticks = Number(proc.pticks) || 0; const pticks = Number(proc.pticks) || 0
const prev = lastProcTicks[pid] ?? null; const prev = lastProcTicks[pid] ?? null
let cpuShare = 0; let cpuShare = 0
if (prev !== null && totalDiff > 0) { if (prev !== null && totalDiff > 0) {
// Per share all CPUs (matches gnome system monitor) // Per share all CPUs (matches gnome system monitor)
//cpuShare = 100 * Math.max(0, pticks - prev) / totalDiff //cpuShare = 100 * Math.max(0, pticks - prev) / totalDiff
// per-share per-core // per-share per-core
cpuShare = 100 * cpuCores * Math.max(0, pticks - prev) / totalDiff; cpuShare = 100 * cpuCores * Math.max(0, pticks - prev) / totalDiff
} }
lastProcTicks[pid] = pticks; // update cache lastProcTicks[pid] = pticks // update cache
newProcesses.push({ newProcesses.push({
"pid": pid, "pid": pid,
"ppid": proc.ppid, "ppid": proc.ppid,
"cpu": cpuShare, "cpu": cpuShare,
"memoryPercent": proc.pssPercent ?? proc.memoryPercent, "memoryPercent": proc.pssPercent
?? proc.memoryPercent,
"memoryKB": proc.pssKB ?? proc.memoryKB, "memoryKB": proc.pssKB ?? proc.memoryKB,
"command": proc.command, "command": proc.command,
"fullCommand": proc.fullCommand, "fullCommand": proc.fullCommand,
"displayName": (proc.command && proc.command.length > 15) ? proc.command.substring(0, 15) + "..." : proc.command "displayName": (proc.command && proc.command.length
}); > 15) ? proc.command.substring(
0,
15) + "..." : proc.command
})
} }
processes = newProcesses; processes = newProcesses
sortProcessesInPlace(); sortProcessesInPlace()
} }
if (data.system) { if (data.system) {
kernelVersion = data.system.kernel || ""; kernelVersion = data.system.kernel || ""
distribution = data.system.distro || ""; distribution = data.system.distro || ""
hostname = data.system.hostname || ""; hostname = data.system.hostname || ""
architecture = data.system.arch || ""; architecture = data.system.arch || ""
loadAverage = data.system.loadavg || ""; loadAverage = data.system.loadavg || ""
processCount = data.system.processes || 0; processCount = data.system.processes || 0
threadCount = data.system.threads || 0; threadCount = data.system.threads || 0
bootTime = data.system.boottime || ""; bootTime = data.system.boottime || ""
motherboard = data.system.motherboard || ""; motherboard = data.system.motherboard || ""
biosVersion = data.system.bios || ""; biosVersion = data.system.bios || ""
} }
if (data.diskmounts) { if (data.diskmounts) {
diskMounts = data.diskmounts; diskMounts = data.diskmounts
} }
if (data.gpus) { if (data.gpus) {
availableGpus = data.gpus; availableGpus = data.gpus
} }
addToHistory(cpuHistory, cpuUsage); addToHistory(cpuHistory, cpuUsage)
addToHistory(memoryHistory, memoryUsage); addToHistory(memoryHistory, memoryUsage)
isUpdating = false; isUpdating = false
} }
function getProcessIcon(command) { function getProcessIcon(command) {
const cmd = command.toLowerCase(); const cmd = command.toLowerCase()
if (cmd.includes("firefox") || cmd.includes("chrome") || cmd.includes("browser")) if (cmd.includes("firefox") || cmd.includes("chrome") || cmd.includes(
return "web"; "browser"))
return "web"
if (cmd.includes("code") || cmd.includes("editor") || cmd.includes("vim")) if (cmd.includes("code") || cmd.includes("editor") || cmd.includes("vim"))
return "code"; return "code"
if (cmd.includes("terminal") || cmd.includes("bash") || cmd.includes("zsh")) if (cmd.includes("terminal") || cmd.includes("bash") || cmd.includes("zsh"))
return "terminal"; return "terminal"
if (cmd.includes("music") || cmd.includes("audio") || cmd.includes("spotify")) if (cmd.includes("music") || cmd.includes("audio") || cmd.includes(
return "music_note"; "spotify"))
return "music_note"
if (cmd.includes("video") || cmd.includes("vlc") || cmd.includes("mpv")) if (cmd.includes("video") || cmd.includes("vlc") || cmd.includes("mpv"))
return "play_circle"; return "play_circle"
if (cmd.includes("systemd") || cmd.includes("kernel") || cmd.includes("kthread")) if (cmd.includes("systemd") || cmd.includes("kernel") || cmd.includes(
return "settings"; "kthread"))
return "memory"; return "settings"
return "memory"
} }
function formatCpuUsage(cpu) { function formatCpuUsage(cpu) {
return (cpu || 0).toFixed(1) + "%"; return (cpu || 0).toFixed(1) + "%"
} }
function formatMemoryUsage(memoryKB) { function formatMemoryUsage(memoryKB) {
const mem = memoryKB || 0; const mem = memoryKB || 0
if (mem < 1024) if (mem < 1024)
return mem.toFixed(0) + " KB"; return mem.toFixed(0) + " KB"
else if (mem < 1024 * 1024) else if (mem < 1024 * 1024)
return (mem / 1024).toFixed(1) + " MB"; return (mem / 1024).toFixed(1) + " MB"
else else
return (mem / (1024 * 1024)).toFixed(1) + " GB"; return (mem / (1024 * 1024)).toFixed(1) + " GB"
} }
function formatSystemMemory(memoryKB) { function formatSystemMemory(memoryKB) {
const mem = memoryKB || 0; const mem = memoryKB || 0
if (mem < 1024 * 1024) if (mem < 1024 * 1024)
return (mem / 1024).toFixed(0) + " MB"; return (mem / 1024).toFixed(0) + " MB"
else else
return (mem / (1024 * 1024)).toFixed(1) + " GB"; return (mem / (1024 * 1024)).toFixed(1) + " GB"
} }
Timer { Timer {
@@ -612,30 +624,195 @@ Singleton {
gfirst=1 gfirst=1
tmp_gpu=$(mktemp) tmp_gpu=$(mktemp)
# Gather cards via DRM (much cheaper than lspci each tick) # Function to extract display name from full GPU name
extract_display_name() {
local full_name="$1"
local drv="$2"
local display_name=""
# NVIDIA patterns
if [[ "$full_name" =~ GeForce|Quadro|Tesla|NVIDIA ]]; then
# Extract model like "RTX 4070", "GTX 1080", "RTX 4090", etc.
display_name=$(echo "$full_name" | grep -oE '(RTX|GTX|GT|MX|Tesla|Quadro|TITAN) [0-9]{3,4}( Ti| SUPER| Ti SUPER| Max-Q| Mobile)?' | head -1)
# If not found, try alternate patterns
if [ -z "$display_name" ]; then
display_name=$(echo "$full_name" | grep -oE 'GeForce [0-9]{3,4}' | sed 's/GeForce //')
fi
# AMD patterns
elif [[ "$full_name" =~ AMD|Radeon|ATI|Navi|Raphael|Rembrandt|Phoenix|Strix ]]; then
# Check for our special integrated graphics format
if [[ "$full_name" =~ "AMD Raphael (Integrated Graphics)" ]]; then
display_name="Raphael"
elif [[ "$full_name" =~ "AMD Phoenix (Integrated Graphics)" ]]; then
display_name="Phoenix"
elif [[ "$full_name" =~ "AMD Rembrandt (Integrated Graphics)" ]]; then
display_name="Rembrandt"
else
# Check if it contains actual Radeon model numbers
display_name=$(echo "$full_name" | grep -oE 'Radeon [0-9]{3,4}[A-Z]*( / [0-9]{3,4}[A-Z]*)?' | head -1)
# If not, try RX/R series cards
if [ -z "$display_name" ]; then
display_name=$(echo "$full_name" | grep -oE '(RX|R9|R7|R5|Vega|VII) [0-9]{3,4}( XT| XTX)?' | head -1)
fi
# For Strix with Radeon model in brackets
if [ -z "$display_name" ] && [[ "$full_name" =~ "Strix" ]]; then
if [[ "$full_name" =~ Radeon ]]; then
# Extract everything after "Strix "
display_name=$(echo "$full_name" | sed 's/.*Strix //')
# Remove brackets if present
display_name=$(echo "$display_name" | tr -d '[]')
else
display_name="Strix"
fi
fi
# Check for Navi
if [ -z "$display_name" ] && [[ "$full_name" =~ Navi ]]; then
display_name=$(echo "$full_name" | grep -oE 'Navi [0-9]+' | head -1)
fi
fi
# Intel patterns
elif [[ "$full_name" =~ Intel ]]; then
# Extract Arc models
display_name=$(echo "$full_name" | grep -oE 'Arc A[0-9]{3,4}' | head -1)
# If not Arc, try Iris/UHD
if [ -z "$display_name" ]; then
display_name=$(echo "$full_name" | grep -oE '(Iris Xe|UHD|HD) Graphics( [0-9]+)?' | head -1)
fi
# Generic Intel graphics
if [ -z "$display_name" ]; then
display_name="Intel Graphics"
fi
fi
# Fallback - but don't cut off at 3 words if it's our special format
if [ -z "$display_name" ]; then
if [[ "$full_name" =~ "(Integrated Graphics)" ]]; then
# Just use the codename part
display_name=$(echo "$full_name" | sed 's/AMD //' | sed 's/ (Integrated Graphics)//')
else
display_name="$full_name"
fi
fi
echo "$display_name"
}
# Gather cards via DRM
for card in /sys/class/drm/card*; do for card in /sys/class/drm/card*; do
[ -e "$card/device/driver" ] || continue [ -e "$card/device/driver" ] || continue
drv=$(basename "$(readlink -f "$card/device/driver")") # e.g. nvidia, amdgpu, i915 drv=$(basename "$(readlink -f "$card/device/driver")")
drv=\${drv##*/} drv=\${drv##*/}
# Determine PCI secondary bus to help identify integrated vs discrete # Get PCI path and info
pci_path=$(readlink -f "$card/device") # .../0000:01:00.0 pci_path=$(readlink -f "$card/device")
func=\${pci_path##*/} # 0000:01:00.0 func=\${pci_path##*/}
sec=\${func#*:}; sec=\${sec%%:*} # "01" from 0000:01:00.0 sec=\${func#*:}; sec=\${sec%%:*}
# Priority: higher = more likely dedicated # Determine vendor
vendor="Unknown"
case "$drv" in
nvidia) vendor="NVIDIA" ;;
amdgpu|radeon) vendor="AMD" ;;
i915|xe) vendor="Intel" ;;
esac
# Priority
prio=0 prio=0
case "$drv" in case "$drv" in
nvidia) prio=3 ;; # dGPU nvidia) prio=3 ;;
amdgpu|radeon) amdgpu|radeon)
if [ "$sec" = "00" ]; then prio=1; else prio=2; fi # APU often on bus 00 if [ "$sec" = "00" ]; then prio=1; else prio=2; fi
;; ;;
i915) prio=0 ;; # iGPU i915|xe) prio=0 ;;
*) prio=0 ;; *) prio=0 ;;
esac esac
# Temperature via per-device hwmon if available # Get full GPU name
full_name=""
display_name=""
# Special handling for NVIDIA with nvidia-smi
if [ "$drv" = "nvidia" ] && command -v nvidia-smi >/dev/null 2>&1; then
# Get the GPU index for this card
gpu_name=$(nvidia-smi --query-gpu=name --format=csv,noheader,nounits 2>/dev/null | head -1 | sed 's/^ *//;s/ *$//' | json_escape)
if [ -n "$gpu_name" ]; then
full_name="$gpu_name"
# Extract display name from nvidia-smi output
display_name=$(echo "$gpu_name" | grep -oE '(RTX|GTX|GT|MX|Tesla|Quadro|TITAN) [0-9]{3,4}( Ti| SUPER| Ti SUPER)?' | head -1)
if [ -z "$display_name" ]; then
display_name="$gpu_name"
fi
fi
fi
# AMD specific detection
if [ -z "$full_name" ] && [ "$drv" = "amdgpu" -o "$drv" = "radeon" ]; then
# Try lspci with proper parsing for AMD GPUs
if command -v lspci >/dev/null 2>&1; then
pci_addr="\${func}"
# Get the full device name
lspci_out=$(lspci -s "$pci_addr" 2>/dev/null)
# Remove PCI address and device class prefix (everything before the colon)
temp_name=$(echo "$lspci_out" | cut -d: -f2- | sed 's/^ *//')
# Check for AMD/ATI format and extract the GPU name
if echo "$temp_name" | grep -q 'AMD/ATI'; then
# Extract everything after "] " which comes after [AMD/ATI]
# This handles both "Raphael" and "Strix [Radeon 880M / 890M]" formats
gpu_name=$(echo "$temp_name" | sed 's/.*\] //' | sed 's/ (rev .*)$//')
# For codenames like Raphael, add context for full name
if [[ "$gpu_name" == "Raphael" ]] || [[ "$gpu_name" == "Phoenix" ]] || [[ "$gpu_name" == "Rembrandt" ]]; then
full_name="AMD $gpu_name (Integrated Graphics)"
else
full_name="$gpu_name"
fi
else
# Fallback: just clean up vendor name
full_name=$(echo "$temp_name" | sed 's/Advanced Micro Devices, Inc\. //' | sed 's/ (rev .*)$//')
fi
# Clean up and escape for JSON
full_name=$(echo "$full_name" | json_escape)
fi
fi
# Intel and generic fallback
if [ -z "$full_name" ]; then
if command -v lspci >/dev/null 2>&1; then
pci_addr="\${func}"
lspci_out=$(lspci -s "$pci_addr" 2>/dev/null)
# Extract device name after the colon
full_name=$(echo "$lspci_out" | sed 's/^[0-9a-f:.]* [^:]*: //' | sed 's/ (rev .*)$//' | json_escape)
fi
fi
# Final fallback - use driver name
if [ -z "$full_name" ] || [[ "$full_name" =~ ^[0-9a-f] ]]; then
case "$drv" in
nvidia) full_name="NVIDIA GPU" ;;
amdgpu|radeon) full_name="AMD GPU" ;;
i915|xe) full_name="Intel GPU" ;;
*) full_name="Unknown GPU" ;;
esac
fi
# Extract display name
display_name=$(extract_display_name "$full_name" "$drv")
# If display name is still empty, use a simplified version of full name
if [ -z "$display_name" ]; then
display_name="$full_name"
fi
# Temperature
hw=""; temp="0" hw=""; temp="0"
for h in "$card/device"/hwmon/hwmon*; do for h in "$card/device"/hwmon/hwmon*; do
[ -e "$h/temp1_input" ] || continue [ -e "$h/temp1_input" ] || continue
@@ -644,36 +821,35 @@ Singleton {
break break
done done
# NVIDIA fallback: use nvidia-smi (first GPU) if temp still 0 # NVIDIA temperature fallback
if [ "$drv" = "nvidia" ] && command -v nvidia-smi >/dev/null 2>&1; then if [ "$drv" = "nvidia" ] && [ "$temp" = "0" ] && command -v nvidia-smi >/dev/null 2>&1; then
t=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1) t=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1)
[ -n "$t" ] && { temp="$t"; hw="\${hw:-nvidia}"; } [ -n "$t" ] && { temp="$t"; hw="\${hw:-nvidia}"; }
fi fi
printf '%s|%s|%s|%s\n' "$prio" "$drv" "\${hw:-unknown}" "\${temp:-0}" >> "$tmp_gpu" printf '%s|%s|%s|%s|%s|%s|%s\n' "$prio" "$drv" "\${hw:-unknown}" "\${temp:-0}" "$vendor" "$display_name" "$full_name" >> "$tmp_gpu"
done done
# Fallback if no DRM cards found (keep drivers but still sort) # Fallback if no DRM cards found but nvidia-smi is available
if [ ! -s "$tmp_gpu" ]; then if [ ! -s "$tmp_gpu" ]; then
for drv in nvidia amdgpu radeon i915; do if command -v nvidia-smi >/dev/null 2>&1; then
command -v "$drv" >/dev/null 2>&1 || true gpu_info=$(nvidia-smi --query-gpu=name,temperature.gpu --format=csv,noheader 2>/dev/null | head -1)
prio=0; [ "$drv" = "nvidia" ] && prio=3 if [ -n "$gpu_info" ]; then
[ "$drv" = "amdgpu" ] && prio=2 IFS=',' read -r gpu_name temp <<< "$gpu_info"
[ "$drv" = "radeon" ] && prio=2 gpu_name=$(echo "$gpu_name" | sed 's/^ *//;s/ *$//' | json_escape)
[ "$drv" = "i915" ] && prio=0 temp=$(echo "$temp" | sed 's/^ *//;s/ *$//')
temp="0"; hw="$drv" display_name=$(echo "$gpu_name" | grep -oE '(RTX|GTX|GT|MX|Tesla|Quadro) [0-9]{3,4}( Ti| SUPER| Ti SUPER)?' | head -1)
if [ "$drv" = "nvidia" ] && command -v nvidia-smi >/dev/null 2>&1; then [ -z "$display_name" ] && display_name="$gpu_name"
t=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1) printf '3|nvidia|nvidia|%s|NVIDIA|%s|%s\n' "\${temp:-0}" "$display_name" "$gpu_name" >> "$tmp_gpu"
[ -n "$t" ] && temp="$t" fi
fi fi
printf '%s|%s|%s|%s\n' "$prio" "$drv" "$hw" "$temp" >> "$tmp_gpu"
done
fi fi
# Sort by priority (desc), then driver name for stability, and emit SAME JSON shape # Sort and output JSON
while IFS='|' read -r pr drv hw temp; do while IFS='|' read -r pr drv hw temp vendor display_name full_name; do
[ $gfirst -eq 1 ] || printf "," [ $gfirst -eq 1 ] || printf ","
printf '{"driver":"%s","hwmon":"%s","temperature":%s}' "$drv" "$hw" "$temp" printf '{"driver":"%s","hwmon":"%s","temperature":%s,"vendor":"%s","displayName":"%s","fullName":"%s"}' \\
"$drv" "$hw" "$temp" "$vendor" "$display_name" "$full_name"
gfirst=0 gfirst=0
done < <(sort -t'|' -k1,1nr -k2,2 "$tmp_gpu") done < <(sort -t'|' -k1,1nr -k2,2 "$tmp_gpu")
@@ -684,30 +860,31 @@ Singleton {
Process { Process {
id: unifiedStatsProcess id: unifiedStatsProcess
command: ["bash", "-c", "bash -s \"$1\" \"$2\" <<'QS_EOF'\n" + root.scriptBody + "\nQS_EOF\n", root.sortBy, String(root.maxProcesses)] command: ["bash", "-c", "bash -s \"$1\" \"$2\" <<'QS_EOF'\n"
+ root.scriptBody + "\nQS_EOF\n", root.sortBy, String(root.maxProcesses)]
running: false running: false
onExited: exitCode => { onExited: exitCode => {
if (exitCode !== 0) { if (exitCode !== 0) {
isUpdating = false; isUpdating = false
} }
} }
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
if (text.trim()) { if (text.trim()) {
const fullText = text.trim(); const fullText = text.trim()
const lastBraceIndex = fullText.lastIndexOf('}'); const lastBraceIndex = fullText.lastIndexOf('}')
if (lastBraceIndex === -1) { if (lastBraceIndex === -1) {
isUpdating = false; isUpdating = false
return; return
} }
const jsonText = fullText.substring(0, lastBraceIndex + 1); const jsonText = fullText.substring(0, lastBraceIndex + 1)
try { try {
const data = JSON.parse(jsonText); const data = JSON.parse(jsonText)
parseUnifiedStats(jsonText); parseUnifiedStats(jsonText)
} catch (e) { } catch (e) {
isUpdating = false; isUpdating = false
return; return
} }
} }
} }