pragma Singleton import qs.Config import Quickshell import Quickshell.Io import QtQuick Singleton { id: root property var _downloadHistory: [] // Private properties property real _downloadSpeed: 0 property real _downloadTotal: 0 // Initial readings for calculating totals property real _initialRxBytes: 0 property real _initialTxBytes: 0 property bool _initialized: false // Previous readings for calculating speed property real _prevRxBytes: 0 property real _prevTimestamp: 0 property real _prevTxBytes: 0 property var _uploadHistory: [] property real _uploadSpeed: 0 property real _uploadTotal: 0 // History of speeds for sparkline (most recent at end) readonly property var downloadHistory: _downloadHistory // Current speeds in bytes per second readonly property real downloadSpeed: _downloadSpeed // Total bytes transferred since tracking started readonly property real downloadTotal: _downloadTotal readonly property int historyLength: 30 property int refCount: 0 readonly property var uploadHistory: _uploadHistory readonly property real uploadSpeed: _uploadSpeed readonly property real uploadTotal: _uploadTotal function formatBytes(bytes: real): var { // Handle negative or invalid values if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) { return { value: 0, unit: "B/s" }; } if (bytes < 1024) { return { value: bytes, unit: "B/s" }; } else if (bytes < 1024 * 1024) { return { value: bytes / 1024, unit: "KB/s" }; } else if (bytes < 1024 * 1024 * 1024) { return { value: bytes / (1024 * 1024), unit: "MB/s" }; } else { return { value: bytes / (1024 * 1024 * 1024), unit: "GB/s" }; } } function formatBytesTotal(bytes: real): var { // Handle negative or invalid values if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) { return { value: 0, unit: "B" }; } if (bytes < 1024) { return { value: bytes, unit: "B" }; } else if (bytes < 1024 * 1024) { return { value: bytes / 1024, unit: "KB" }; } else if (bytes < 1024 * 1024 * 1024) { return { value: bytes / (1024 * 1024), unit: "MB" }; } else { return { value: bytes / (1024 * 1024 * 1024), unit: "GB" }; } } function parseNetDev(content: string): var { const lines = content.split("\n"); let totalRx = 0; let totalTx = 0; for (let i = 2; i < lines.length; i++) { const line = lines[i].trim(); if (!line) continue; const parts = line.split(/\s+/); if (parts.length < 10) continue; const iface = parts[0].replace(":", ""); // Skip loopback interface if (iface === "lo") continue; const rxBytes = parseFloat(parts[1]) || 0; const txBytes = parseFloat(parts[9]) || 0; totalRx += rxBytes; totalTx += txBytes; } return { rx: totalRx, tx: totalTx }; } FileView { id: netDevFile path: "/proc/net/dev" } Timer { interval: Config.dashboard.resourceUpdateInterval repeat: true running: root.refCount > 0 triggeredOnStart: true onTriggered: { netDevFile.reload(); const content = netDevFile.text(); if (!content) return; const data = root.parseNetDev(content); const now = Date.now(); if (!root._initialized) { root._initialRxBytes = data.rx; root._initialTxBytes = data.tx; root._prevRxBytes = data.rx; root._prevTxBytes = data.tx; root._prevTimestamp = now; root._initialized = true; return; } const timeDelta = (now - root._prevTimestamp) / 1000; // seconds if (timeDelta > 0) { // Calculate byte deltas let rxDelta = data.rx - root._prevRxBytes; let txDelta = data.tx - root._prevTxBytes; // Handle counter overflow (when counters wrap around from max to 0) // This happens when counters exceed 32-bit or 64-bit limits if (rxDelta < 0) { // Counter wrapped around - assume 64-bit counter rxDelta += Math.pow(2, 64); } if (txDelta < 0) { txDelta += Math.pow(2, 64); } // Calculate speeds root._downloadSpeed = rxDelta / timeDelta; root._uploadSpeed = txDelta / timeDelta; const maxHistory = root.historyLength + 1; if (root._downloadSpeed >= 0 && isFinite(root._downloadSpeed)) { let newDownHist = root._downloadHistory.slice(); newDownHist.push(root._downloadSpeed); if (newDownHist.length > maxHistory) { newDownHist.shift(); } root._downloadHistory = newDownHist; } if (root._uploadSpeed >= 0 && isFinite(root._uploadSpeed)) { let newUpHist = root._uploadHistory.slice(); newUpHist.push(root._uploadSpeed); if (newUpHist.length > maxHistory) { newUpHist.shift(); } root._uploadHistory = newUpHist; } } // Calculate totals with overflow handling let downTotal = data.rx - root._initialRxBytes; let upTotal = data.tx - root._initialTxBytes; // Handle counter overflow for totals if (downTotal < 0) { downTotal += Math.pow(2, 64); } if (upTotal < 0) { upTotal += Math.pow(2, 64); } root._downloadTotal = downTotal; root._uploadTotal = upTotal; root._prevRxBytes = data.rx; root._prevTxBytes = data.tx; root._prevTimestamp = now; } } }