From 561312c2342b82013bee981d4add419f8b1b3d93 Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 22 Oct 2026 07:21:09 +0100 Subject: [PATCH] Feature: Show public IP in network panel Co-Authored-By: Claude Opus 5.5 --- .../modules/bar/NetworkIndicator.qml | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/dotfiles/.config/quickshell/modules/bar/NetworkIndicator.qml b/dotfiles/.config/quickshell/modules/bar/NetworkIndicator.qml index 28eb448..66f856d 100644 --- a/dotfiles/.config/quickshell/modules/bar/NetworkIndicator.qml +++ b/dotfiles/.config/quickshell/modules/bar/NetworkIndicator.qml @@ -575,6 +575,40 @@ Item { } } + // Public (WAN-side) IP — nothing local knows this, the address on the + // interface is the LAN one behind NAT, so it has to come from an + // outside service. Polled much more slowly than the local stats since + // it's a real HTTP request and rarely changes. + property string publicIp: "—" + + Process { + id: publicIpProc + command: ["curl", "-s", "--max-time", "5", "https://api.ipify.org"] + stdout: StdioCollector { + id: publicIpCollector + onStreamFinished: { + const ip = publicIpCollector.text.trim() + root.publicIp = /^[\d.]+$|^[0-9a-fA-F:]+$/.test(ip) ? ip : "—" + } + } + } + + // Switching networks almost always changes the public IP, so don't + // leave the old one up until the next slow poll. + onConnectedNetworkNameChanged: { + root.publicIp = "—" + if (panel.visible && root.connectedDevice !== null && !publicIpProc.running) + publicIpProc.running = true + } + + Timer { + interval: 60000 + repeat: true + triggeredOnStart: true + running: panel.visible && root.connectedDevice !== null + onTriggered: if (!publicIpProc.running) publicIpProc.running = true + } + Process { id: bytesProc command: root.ifaceName @@ -994,6 +1028,27 @@ Item { } + Row { + width: parent.width + spacing: 4 + visible: root.connectedDevice !== null + + Text { + text: "Public IP:" + font.family: Appearance.fontFamily + font.pixelSize: Appearance.fontSizeSmall + color: Colors.muted + } + + Text { + text: root.publicIp + font.family: Appearance.fontFamily + font.pixelSize: Appearance.fontSizeSmall + color: Colors.foreground + font.bold: true + } + } + // Captive portal banner — shown when the connectivity probe // above finds either a confirmed sign-in page or a // "limited" result standing between this network and the