pragma Singleton import QtQuick import Quickshell import Quickshell.Io import Quickshell.Networking QtObject { id: root property string status: "..." property ListModel activeConnections: ListModel {} property string downloadSpeed: "" property string uploadSpeed: "" property string ping: "" property var downloadHistory: [] property var uploadHistory: [] property var pingHistory: [] property int _maxHistory: 60 property bool internetAvailable: Networking.connectivity === NetworkConnectivity.Full property bool vpnConnected: false property bool vpnExists: false property string vpnName: "" property string vpnIp: "" property bool tailscaleConnected: false property bool tailscaleExists: false property string tailscaleIp: "" property bool running: false property real _prevRxBytes: 0 property real _prevTxBytes: 0 property var _prevUpdateTime: 0 property string _trafficActiveDevice: "" readonly property var networking: Networking function findAllConnectedDevices() { var devs = networking.devices.values var result = [] for (var i = 0; i < devs.length; i++) { if (devs[i].connected) result.push(devs[i]) } return result } function getNmConnectionName(net) { if (!net) return "" var settings = net.nmSettings if (settings.length > 0) return settings[0].id return "" } property var _pendingDevices: [] function updateAll() { var connected = findAllConnectedDevices() _pendingDevices = connected activeConnections.clear() var hasWifi = false var hasWired = false var bestWifiSignal = 0 var trafficDev = connected.length > 0 ? connected[0] : null for (var i = 0; i < connected.length; i++) { var dev = connected[i] var entry = { interfaceName: dev.name, interfaceType: "", connectionName: "", wifiName: "", signalStrength: 0, linkSpeed: "", ipv4: "" } if (dev.type === DeviceType.Wifi) { entry.interfaceType = "wifi" hasWifi = true var networks = dev.networks.values for (var j = 0; j < networks.length; j++) { var net = networks[j] if (net.connected) { entry.wifiName = net.name entry.signalStrength = Math.round(net.signalStrength * 100) entry.connectionName = root.getNmConnectionName(net) if (net.signalStrength > bestWifiSignal) bestWifiSignal = net.signalStrength break } } } else if (dev.type === DeviceType.Wired) { entry.interfaceType = "ethernet" hasWired = true entry.linkSpeed = dev.linkSpeed + " Mbps" if (dev.network) { entry.connectionName = root.getNmConnectionName(dev.network) } } activeConnections.append(entry) if (dev.type === DeviceType.Wired && trafficDev !== dev) { trafficDev = dev } } if (trafficDev) { _trafficActiveDevice = trafficDev.name trafficScript.running = true } root.computeStatus(hasWired, hasWifi, bestWifiSignal) if (connected.length > 0) { ipv4Script.running = true } } property Timer networkTimer: Timer { interval: 10000 repeat: true running: true onTriggered: root.updateAll() } property Process ipv4Script: Process { command: ["sh", "-c", "ip -4 addr show 2>/dev/null | awk '/^[0-9]+:/{iface=$2; gsub(/:.*/, \"\", iface)} /inet /{print iface\" \"$2}' | " + "grep -v '\\blo\\b' | grep -v '\\bvirbr' | grep -v '\\btailscale' | grep -v '\\bdocker' | grep -v '\\bveth' | " + "sed 's|/[0-9]*||'" ] stdout: StdioCollector { onStreamFinished: root.parseIpv4Output(text.trim()) } } function parseIpv4Output(output) { var ipMap = {} if (!output) { root.applyIpv4Map(ipMap) return } var lines = output.split("\n") for (var i = 0; i < lines.length; i++) { var parts = lines[i].trim().split(" ") if (parts.length >= 2) { ipMap[parts[0]] = parts[1] } } root.applyIpv4Map(ipMap) } function applyIpv4Map(ipMap) { for (var i = 0; i < activeConnections.count; i++) { var iface = activeConnections.get(i).interfaceName var ip = ipMap[iface] || "" activeConnections.setProperty(i, "ipv4", ip) } } property Process trafficScript: Process { command: ["sh", "-c", "echo '===TRAFFIC==='; " + "DEV=\"" + root._trafficActiveDevice + "\"; " + "if [ -n \"$DEV\" ]; then " + " echo \"RX_BYTES=$(cat /sys/class/net/$DEV/statistics/rx_bytes 2>/dev/null || echo 0)\"; " + " echo \"TX_BYTES=$(cat /sys/class/net/$DEV/statistics/tx_bytes 2>/dev/null || echo 0)\"; " + "fi; " + "echo '===PING==='; " + "ping -c 1 -W 2 1.1.1.1 2>/dev/null | awk -F/ '/rtt/{print $5}' || echo ''; " + "echo '===VPN_ALL==='; " + "nmcli -t -f NAME,TYPE connection show 2>/dev/null | grep -i vpn || true; " + "echo '===VPN_ACTIVE==='; " + "nmcli -t -f NAME,TYPE connection show --active 2>/dev/null | grep -i vpn || true; " + "echo '===TAILSCALE_EXISTS==='; " + "ip link show tailscale0 >/dev/null 2>&1 && echo EXISTS || echo ''; " + "echo '===TAILSCALE_IP==='; " + "ip -4 addr show tailscale0 2>/dev/null | grep -oP 'inet \\K[\\d.]+' || echo ''" ] stdout: StdioCollector { onStreamFinished: root.parseOutput(text.trim()) } } function computeStatus(hasWired, hasWifi, bestSignal) { if (!hasWired && !hasWifi) { root.status = "󰤭/󱐤" return } var parts = [] if (hasWired) parts.push("󰈀") if (hasWifi) { var sig = Math.round(bestSignal * 100) if (sig > 80) parts.push("󰤨") else if (sig > 60) parts.push("󰤥") else if (sig > 40) parts.push("󰤢") else if (sig > 20) parts.push("󰤟") else if (sig > 0) parts.push("󰤯") else parts.push("󰤭") } root.status = parts.join(" ") } 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 parseOutput(output) { var sections = output.split("===TAILSCALE_IP===") var tailscaleIpSection = sections.length > 1 ? sections[1].trim() : "" var beforeTsIp = sections[0] sections = beforeTsIp.split("===TAILSCALE_EXISTS===") var tailscaleExistsSection = sections.length > 1 ? sections[1].trim() : "" var beforeTsExist = sections[0] sections = beforeTsExist.split("===VPN_ACTIVE===") var vpnActiveSection = sections.length > 1 ? sections[1].trim() : "" var beforeVpnActive = sections[0] sections = beforeVpnActive.split("===VPN_ALL===") var vpnAllSection = sections.length > 1 ? sections[1].trim() : "" var beforeVpnAll = sections[0] sections = beforeVpnAll.split("===PING===") root.ping = sections.length > 1 ? sections[1].trim() : "" var pingVal = parseFloat(root.ping) if (!isNaN(pingVal) && pingVal > 0) { var pingHist = root.pingHistory.slice() pingHist.push(pingVal) if (pingHist.length > root._maxHistory) pingHist.shift() root.pingHistory = pingHist } var beforePing = sections[0] sections = beforePing.split("===TRAFFIC===") var trafficSection = sections.length > 1 ? sections[1].trim() : "" 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)) } if (root._prevRxBytes > 0 && root._prevUpdateTime > 0) { var now = Date.now() var interval = (now - root._prevUpdateTime) / 1000 if (interval > 0) { var dlBytes = (rxBytes - root._prevRxBytes) / interval var ulBytes = (txBytes - root._prevTxBytes) / interval root.downloadSpeed = root.formatSpeed(dlBytes) root.uploadSpeed = root.formatSpeed(ulBytes) var dlHist = root.downloadHistory.slice() dlHist.push(dlBytes) if (dlHist.length > root._maxHistory) dlHist.shift() root.downloadHistory = dlHist var ulHist = root.uploadHistory.slice() ulHist.push(ulBytes) if (ulHist.length > root._maxHistory) ulHist.shift() root.uploadHistory = ulHist } } else { root.downloadSpeed = "" root.uploadSpeed = "" } root._prevRxBytes = rxBytes root._prevTxBytes = txBytes root._prevUpdateTime = Date.now() } root.vpnExists = vpnAllSection.length > 0 if (vpnActiveSection) { root.vpnConnected = true var vpnLines = vpnActiveSection.split("\n") if (vpnLines.length > 0) { root.vpnName = vpnLines[0].split(":")[0] || "" } } else { root.vpnConnected = false root.vpnName = "" root.vpnIp = "" } root.tailscaleExists = tailscaleExistsSection.indexOf("EXISTS") >= 0 if (tailscaleIpSection) { root.tailscaleConnected = true root.tailscaleIp = tailscaleIpSection } else { root.tailscaleConnected = false root.tailscaleIp = "" } } Component.onCompleted: { root.updateAll() } }