summaryrefslogtreecommitdiff
path: root/services/NetworkUsage.qml
diff options
context:
space:
mode:
authorThanh Minh <112760114+tmih06@users.noreply.github.com>2026-02-19 18:53:22 +0700
committerGitHub <noreply@github.com>2026-02-19 22:53:22 +1100
commit46174d1934370b2f4a7da43a3dbc0289c14a5a2d (patch)
tree2f401649de42e204f9904ed7797a3600e4654b57 /services/NetworkUsage.qml
parentfeat: add wallpaperEnabled option (#1187) (diff)
downloadcaelestia-shell-46174d1934370b2f4a7da43a3dbc0289c14a5a2d.tar.gz
caelestia-shell-46174d1934370b2f4a7da43a3dbc0289c14a5a2d.tar.bz2
caelestia-shell-46174d1934370b2f4a7da43a3dbc0289c14a5a2d.zip
dashboard/performance: new design, configurable, controlcenter support (#975)
* feat(dashboard): add configurable performance resources - Add config options to show/hide Battery, GPU, CPU, Memory, Storage - Make dashboard responsive based on number of visible resources - Scale resource sizes and spacing dynamically for 3, 4, or 5 items - Battery shows charge status and time remaining/to full - Each resource can be individually toggled via config * fix(dashboard): add dynamic right margin for last visible resource Ensures the rightmost resource always has proper margin to prevent content from being cut off at the edge * fix(performance): comment out duplicated value2 properties for memory and storage resources * controlcenter: add settings for dashboard * feat: handle readonly properties and re-usable codes * Feature/performance tab rework (#5) * dashboard/performance: rework tab with card-based grid layout - Replace circular arc meters with card-based grid layout - CPU/GPU cards show hardware name, usage and temperature with horizontal bars - Memory card with 3/4 arc indicator and used/total at bottom - Storage card shows physical disks from lsblk with aggregated partition usage - Add cpuName, gpuName, cpuFreq, cpuMaxFreq, disks properties to SystemUsage - Clean hardware names (remove Intel/AMD/NVIDIA prefixes, TM/R symbols) * dashboard/performance: new hero card design * dashboard/performance: update storage indicators to be reponsive to the physical disks count * dashboard/performance: fix the overlay bounding issue * dashboard/perfromance: refactor code * dashboard/performance: add battery gauge * dashboard/performance: correct battery icon * dashboard/performance: configurable battery * dashboard/performance: update layout * dashboard/performance: move the "Usage" text on top and smaller the font size * dashboard/performance: add a lot of configurations * dashboard/performance: add network metrics * fix: issue with hot reload * chore: update default vaule for mainValueSpacing to 0 * chore: group settings into collapasible sections * chore: making GPU & Battery toggle not showing if not found * chore: fix network widget spacing & text * chore: remove old disk bars configs, add update interval * chore: remove old & unused value, functions * chore: network graph update smoothly when data points change * chore: refactor settings - de-flood settings, most of the font & size setting now follow the global Appearance config - Most of sliders are not needed anymore, only keep the update interval slider - clean up * chore: remove readonly properties from the controlcenter/dashboard. * chore: minor fix * fix: fix warning about onPercChange() * fix: network metrics negative number * fix: add minimal height & width, placeholder for none toggled * fix: network graph move smoothly (#6) * fix: network graph move smoothly * clean up * fix: graph animation even more smooth * fix: padding issue * chore: network icons short description * fix --------- Co-authored-by: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>
Diffstat (limited to 'services/NetworkUsage.qml')
-rw-r--r--services/NetworkUsage.qml233
1 files changed, 233 insertions, 0 deletions
diff --git a/services/NetworkUsage.qml b/services/NetworkUsage.qml
new file mode 100644
index 0000000..502ec3a
--- /dev/null
+++ b/services/NetworkUsage.qml
@@ -0,0 +1,233 @@
+pragma Singleton
+
+import qs.config
+
+import Quickshell
+import Quickshell.Io
+
+import QtQuick
+
+Singleton {
+ id: root
+
+ property int refCount: 0
+
+ // Current speeds in bytes per second
+ readonly property real downloadSpeed: _downloadSpeed
+ readonly property real uploadSpeed: _uploadSpeed
+
+ // Total bytes transferred since tracking started
+ readonly property real downloadTotal: _downloadTotal
+ readonly property real uploadTotal: _uploadTotal
+
+ // History of speeds for sparkline (most recent at end)
+ readonly property var downloadHistory: _downloadHistory
+ readonly property var uploadHistory: _uploadHistory
+ readonly property int historyLength: 30
+
+ // Private properties
+ property real _downloadSpeed: 0
+ property real _uploadSpeed: 0
+ property real _downloadTotal: 0
+ property real _uploadTotal: 0
+ property var _downloadHistory: []
+ property var _uploadHistory: []
+
+ // Previous readings for calculating speed
+ property real _prevRxBytes: 0
+ property real _prevTxBytes: 0
+ property real _prevTimestamp: 0
+
+ // Initial readings for calculating totals
+ property real _initialRxBytes: 0
+ property real _initialTxBytes: 0
+ property bool _initialized: false
+
+ 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
+ running: root.refCount > 0
+ repeat: true
+ 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;
+ }
+ }
+}