230 lines
9.6 KiB
QML
230 lines
9.6 KiB
QML
pragma Singleton
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import "../../../../config"
|
|
|
|
QtObject {
|
|
id: root
|
|
|
|
property string status: "..."
|
|
|
|
property string interfaceName: ""
|
|
property string interfaceType: ""
|
|
property string connectionName: ""
|
|
property string ipv4: ""
|
|
property string wifiName: ""
|
|
property string signalStrength: ""
|
|
property string wifiChannel: ""
|
|
property string wifiFrequency: ""
|
|
property string linkSpeed: ""
|
|
property string downloadSpeed: ""
|
|
property string uploadSpeed: ""
|
|
property string ping: ""
|
|
property bool internetAvailable: false
|
|
property bool vpnConnected: false
|
|
property string vpnName: ""
|
|
property string vpnIp: ""
|
|
property bool tailscaleConnected: false
|
|
property string tailscaleIp: ""
|
|
|
|
property bool running: false
|
|
|
|
property string _activeDevice: ""
|
|
property real _prevRxBytes: 0
|
|
property real _prevTxBytes: 0
|
|
property var _prevUpdateTime: 0
|
|
|
|
property Timer networkTimer: Timer {
|
|
interval: 10000
|
|
repeat: true
|
|
running: true
|
|
onTriggered: networkScript.running = true
|
|
}
|
|
|
|
property Process networkScript: Process {
|
|
command: ["sh", "-c",
|
|
"echo '===SHOW==='; " +
|
|
"ACTIVE=$(nmcli -t device status 2>/dev/null | awk -F: '$3==\"connected\"{print $1; exit}'); " +
|
|
"echo \"ACTIVE_DEVICE=$ACTIVE\"; " +
|
|
"if [ -n \"$ACTIVE\" ]; then " +
|
|
" nmcli -t -f ALL device show \"$ACTIVE\" 2>/dev/null; " +
|
|
"fi; " +
|
|
"echo '===TRAFFIC==='; " +
|
|
"if [ -n \"$ACTIVE\" ]; then " +
|
|
" echo \"RX_BYTES=$(cat /sys/class/net/$ACTIVE/statistics/rx_bytes 2>/dev/null || echo 0)\"; " +
|
|
" echo \"TX_BYTES=$(cat /sys/class/net/$ACTIVE/statistics/tx_bytes 2>/dev/null || echo 0)\"; " +
|
|
"fi; " +
|
|
"echo '===WIFI==='; " +
|
|
"nmcli -t -f active,ssid,signal,chan,freq,rate dev wifi | awk -F: '$1==\"yes\" { $1=\"\"; sub(/^:/,\"\"); print }'; " +
|
|
"echo '===PING==='; " +
|
|
"ping -c 1 -W 2 1.1.1.1 2>/dev/null | awk -F/ '/rtt/{print $5}' || echo ''; " +
|
|
"echo '===INTERNET==='; " +
|
|
"ping -c 1 -W 1 1.1.1.1 2>/dev/null >/dev/null && echo 1 || echo 0; " +
|
|
"echo '===VPN==='; " +
|
|
"nmcli -t -f NAME,TYPE connection show --active 2>/dev/null | grep -i vpn || true; " +
|
|
"echo '===TAILSCALE==='; " +
|
|
"TAIL_IP=$(ip -4 addr show tailscale0 2>/dev/null | grep -oP 'inet \\K[\\d.]+'); " +
|
|
"if [ -n \"$TAIL_IP\" ]; then echo \"TAILSCALE_IP=$TAIL_IP\"; else echo \"\"; fi"
|
|
]
|
|
|
|
stdout: StdioCollector {
|
|
onStreamFinished: root.parseNetworkOutput(text.trim())
|
|
}
|
|
}
|
|
|
|
function formatSpeed(bytesPerSec) {
|
|
if (bytesPerSec >= 1073741824) return (bytesPerSec / 1073741824).toFixed(1) + " GB/s"
|
|
if (bytesPerSec >= 1048576) return (bytesPerSec / 1048576).toFixed(1) + " MB/s"
|
|
if (bytesPerSec >= 1024) return (bytesPerSec / 1024).toFixed(1) + " KB/s"
|
|
return bytesPerSec.toFixed(0) + " B/s"
|
|
}
|
|
|
|
function parseNetworkOutput(output) {
|
|
var sections = output.split("===TAILSCALE===")
|
|
var tailscaleSection = sections.length > 1 ? sections[1].trim() : ""
|
|
|
|
var beforeTailscale = sections[0]
|
|
sections = beforeTailscale.split("===VPN===")
|
|
var vpnSection = sections.length > 1 ? sections[1].trim() : ""
|
|
|
|
var beforeVpn = sections[0]
|
|
sections = beforeVpn.split("===INTERNET===")
|
|
root.internetAvailable = sections.length > 1 ? sections[1].trim() === "1" : false
|
|
|
|
var beforeInternet = sections[0]
|
|
sections = beforeInternet.split("===PING===")
|
|
root.ping = sections.length > 1 ? sections[1].trim() : ""
|
|
|
|
var beforePing = sections[0]
|
|
sections = beforePing.split("===WIFI===")
|
|
var wifiSection = sections.length > 1 ? sections[1].trim() : ""
|
|
|
|
var beforeWifi = sections[0]
|
|
sections = beforeWifi.split("===TRAFFIC===")
|
|
var trafficSection = sections.length > 1 ? sections[1].trim() : ""
|
|
|
|
var beforeTraffic = sections[0]
|
|
sections = beforeTraffic.split("===SHOW===")
|
|
var showSection = sections.length > 1 ? sections[1].trim() : ""
|
|
|
|
// Parse SHOW section
|
|
var activeDevice = ""
|
|
var lines = showSection.split("\n")
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i].trim()
|
|
if (line.indexOf("ACTIVE_DEVICE=") === 0) {
|
|
activeDevice = line.substring("ACTIVE_DEVICE=".length)
|
|
} else if (line.indexOf("GENERAL.DEVICE:") === 0) {
|
|
root.interfaceName = line.substring("GENERAL.DEVICE:".length)
|
|
} else if (line.indexOf("GENERAL.TYPE:") === 0) {
|
|
root.interfaceType = line.substring("GENERAL.TYPE:".length)
|
|
} else if (line.indexOf("GENERAL.CONNECTION:") === 0) {
|
|
root.connectionName = line.substring("GENERAL.CONNECTION:".length)
|
|
} else if (line.indexOf("IP4.ADDRESS") >= 0) {
|
|
var ipPart = line.substring(line.indexOf(":") + 1)
|
|
root.ipv4 = ipPart.split("/")[0]
|
|
}
|
|
}
|
|
|
|
// Parse TRAFFIC section
|
|
if (trafficSection) {
|
|
var trafficLines = trafficSection.split("\n")
|
|
var rxBytes = 0
|
|
var txBytes = 0
|
|
for (var t = 0; t < trafficLines.length; t++) {
|
|
var tline = trafficLines[t].trim()
|
|
if (tline.indexOf("RX_BYTES=") === 0) rxBytes = parseFloat(tline.substring("RX_BYTES=".length))
|
|
else if (tline.indexOf("TX_BYTES=") === 0) txBytes = parseFloat(tline.substring("TX_BYTES=".length))
|
|
}
|
|
|
|
// console.log("[Network] rxBytes:", rxBytes, "txBytes:", txBytes)
|
|
// console.log("[Network] activeDevice:", activeDevice, "| prevActiveDevice:", root._activeDevice)
|
|
// console.log("[Network] prevRxBytes:", root._prevRxBytes, "prevTxBytes:", root._prevTxBytes, "prevTime:", root._prevUpdateTime)
|
|
|
|
if (activeDevice === root._activeDevice && root._prevRxBytes > 0 && root._prevUpdateTime > 0) {
|
|
var now = Date.now()
|
|
var interval = (now - root._prevUpdateTime) / 1000
|
|
// console.log("[Network] Same device, interval:", interval, "s")
|
|
// console.log("[Network] rxDelta:", (rxBytes - root._prevRxBytes), "txDelta:", (txBytes - root._prevTxBytes))
|
|
if (interval > 0) {
|
|
var downBps = (rxBytes - root._prevRxBytes) / interval
|
|
var upBps = (txBytes - root._prevTxBytes) / interval
|
|
// console.log("[Network] downBps:", downBps, "upBps:", upBps)
|
|
root.downloadSpeed = root.formatSpeed(downBps)
|
|
root.uploadSpeed = root.formatSpeed(upBps)
|
|
// console.log("[Network] downloadSpeed:", root.downloadSpeed, "uploadSpeed:", root.uploadSpeed)
|
|
} else {
|
|
console.log("[Network] Interval zero or negative, skipping")
|
|
}
|
|
} else {
|
|
// console.log("[Network] First run or device changed, storing baseline")
|
|
root.downloadSpeed = ""
|
|
root.uploadSpeed = ""
|
|
}
|
|
|
|
root._activeDevice = activeDevice
|
|
root._prevRxBytes = rxBytes
|
|
root._prevTxBytes = txBytes
|
|
root._prevUpdateTime = Date.now()
|
|
// console.log("[Network] Stored baseline: rxBytes:", root._prevRxBytes, "txBytes:", root._prevTxBytes)
|
|
}
|
|
|
|
// Parse WIFI section
|
|
if (wifiSection) {
|
|
var wifiParts = wifiSection.split(" ")
|
|
if (wifiParts.length >= 2) {
|
|
root.wifiName = wifiParts[0] || ""
|
|
root.signalStrength = wifiParts[1] || ""
|
|
if (wifiParts.length >= 3) root.wifiChannel = wifiParts[2] || ""
|
|
if (wifiParts.length >= 4) root.wifiFrequency = wifiParts[3] + " " + wifiParts[4] || ""
|
|
if (wifiParts.length >= 5) root.linkSpeed = wifiParts[5] + " " + wifiParts[6] || ""
|
|
}
|
|
}
|
|
|
|
// Determine status icon
|
|
if (root.interfaceType === "ethernet") {
|
|
root.status = ""
|
|
} else if (root.interfaceType === "wifi") {
|
|
if (!root.connectionName) {
|
|
root.status = ""
|
|
} else {
|
|
var sig = parseInt(root.signalStrength)
|
|
if (sig > 80) root.status = ""
|
|
else if (sig > 60) root.status = ""
|
|
else if (sig > 40) root.status = ""
|
|
else if (sig > 20) root.status = ""
|
|
else root.status = ""
|
|
}
|
|
} else if (!activeDevice) {
|
|
root.status = "/"
|
|
}
|
|
|
|
// Parse VPN section
|
|
if (vpnSection) {
|
|
root.vpnConnected = true
|
|
var vpnLines = vpnSection.split("\n")
|
|
if (vpnLines.length > 0) {
|
|
var vpnParts = vpnLines[0].split(":")
|
|
root.vpnName = vpnParts[0] || ""
|
|
}
|
|
} else {
|
|
root.vpnConnected = false
|
|
root.vpnName = ""
|
|
root.vpnIp = ""
|
|
}
|
|
|
|
// Parse TAILSCALE section
|
|
if (tailscaleSection && tailscaleSection.indexOf("TAILSCALE_IP=") === 0) {
|
|
root.tailscaleConnected = true
|
|
root.tailscaleIp = tailscaleSection.substring("TAILSCALE_IP=".length)
|
|
} else {
|
|
root.tailscaleConnected = false
|
|
root.tailscaleIp = ""
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
networkScript.running = true
|
|
}
|
|
} |