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 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.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 } }