Watch
1
0
Fork
You've already forked souveraine
0
souveraine/surfaces/quickshell/ii-base/services/NetworkTraffic.qml

163 lines
5 KiB
QML
Raw Normal View History

pragma Singleton
pragma ComponentBehavior: Bound
import qs
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property string interfaceName: ""
property real rxBytesPerSecond: 0
property real txBytesPerSecond: 0
property string downloadSpeedText: formatSpeed(rxBytesPerSecond)
property string uploadSpeedText: formatSpeed(txBytesPerSecond)
property string downloadSpeedCompactText: formatCompactSpeed(rxBytesPerSecond)
property string uploadSpeedCompactText: formatCompactSpeed(txBytesPerSecond)
readonly property bool available: interfaceName.length > 0
property real previousRxBytes: -1
property real previousTxBytes: -1
property double previousTimestampMs: 0
function formatSpeed(bytesPerSecond) {
const units = ["B/s", "K/s", "M/s", "G/s"];
let value = Math.max(0, bytesPerSecond);
let unitIndex = 0;
while (value >= 1024 && unitIndex < units.length - 1) {
value /= 1024;
unitIndex++;
}
const precision = value >= 100 ? 0 : value >= 10 ? 1 : 2;
return `${value.toFixed(precision)}${units[unitIndex]}`;
}
function formatCompactSpeed(bytesPerSecond) {
const units = ["B", "K", "M", "G"];
let value = Math.max(0, bytesPerSecond);
let unitIndex = 0;
while (value >= 1024 && unitIndex < units.length - 1) {
value /= 1024;
unitIndex++;
}
let textValue = "0";
if (value >= 100)
textValue = `${Math.round(value)}`;
else if (value >= 10)
textValue = `${Math.round(value)}`;
else
textValue = `${value.toFixed(1)}`.replace(/\.0$/, "");
return `${textValue}${units[unitIndex]}/s`;
}
function pickInterfaceName(routeText, devText) {
const routeLines = routeText.trim().split("\n").slice(1);
for (const line of routeLines) {
const columns = line.trim().split(/\s+/);
if (columns.length < 2)
continue;
const iface = columns[0];
const destination = columns[1];
if (destination === "00000000" && iface !== "lo")
return iface;
}
const devLines = devText.trim().split("\n").slice(2);
for (const line of devLines) {
const iface = line.split(":")[0]?.trim() ?? "";
if (iface.length > 0 && iface !== "lo")
return iface;
}
return "";
}
function parseCounters(devText, ifaceName) {
if (!ifaceName)
return null;
const escapedName = ifaceName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const lineMatch = devText.match(new RegExp(`^\\s*${escapedName}\\s*:\\s*(.+)$`, "m"));
if (!lineMatch)
return null;
const fields = lineMatch[1].trim().split(/\s+/);
if (fields.length < 9)
return null;
return {
rx: Number(fields[0]),
tx: Number(fields[8])
};
}
function update() {
routeFile.reload();
devFile.reload();
const routeText = routeFile.text();
const devText = devFile.text();
const detectedInterface = pickInterfaceName(routeText, devText);
if (detectedInterface !== interfaceName) {
interfaceName = detectedInterface;
previousRxBytes = -1;
previousTxBytes = -1;
previousTimestampMs = 0;
}
if (!interfaceName) {
rxBytesPerSecond = 0;
txBytesPerSecond = 0;
return;
}
const counters = parseCounters(devText, interfaceName);
if (!counters) {
rxBytesPerSecond = 0;
txBytesPerSecond = 0;
return;
}
const now = Date.now();
if (previousRxBytes < 0 || previousTxBytes < 0 || previousTimestampMs <= 0) {
previousRxBytes = counters.rx;
previousTxBytes = counters.tx;
previousTimestampMs = now;
rxBytesPerSecond = 0;
txBytesPerSecond = 0;
return;
}
const elapsedMs = Math.max(1, now - previousTimestampMs);
rxBytesPerSecond = Math.max(0, (counters.rx - previousRxBytes) * 1000 / elapsedMs);
txBytesPerSecond = Math.max(0, (counters.tx - previousTxBytes) * 1000 / elapsedMs);
previousRxBytes = counters.rx;
previousTxBytes = counters.tx;
previousTimestampMs = now;
}
Timer {
interval: 1000
// Souveraine: pause while the display is inactive (idle-power task 4).
// The rate math is elapsed-time based, so the first tick after resume
// yields one correctly averaged sample rather than a spike.
running: GlobalStates.displayActive
repeat: true
triggeredOnStart: true
onTriggered: root.update()
}
FileView { id: routeFile; path: "/proc/net/route" }
FileView { id: devFile; path: "/proc/net/dev" }
}