diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-14 18:13:11 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-14 18:13:11 +0200 |
| commit | 26e6f28b15a8f2ed7664c5c268b4065ae62668d0 (patch) | |
| tree | 62f087b0047652f70e362606f3c0925099190204 | |
| parent | 4eaf3bdce7b4e7663c6ba5c206d68eacbd24949e (diff) | |
| download | quickshell-26e6f28b15a8f2ed7664c5c268b4065ae62668d0.tar.gz quickshell-26e6f28b15a8f2ed7664c5c268b4065ae62668d0.zip | |
feat(desktop): globe network tile with per-link IPs
| -rw-r--r-- | desktop/modules/network/NetworkModule.qml | 45 | ||||
| -rw-r--r-- | desktop/modules/network/NetworkPage.qml | 30 | ||||
| -rw-r--r-- | desktop/modules/network/NetworkTile.qml | 70 | ||||
| -rw-r--r-- | desktop/modules/network/README.md | 7 |
4 files changed, 122 insertions, 30 deletions
diff --git a/desktop/modules/network/NetworkModule.qml b/desktop/modules/network/NetworkModule.qml index 6ff98b5..0310475 100644 --- a/desktop/modules/network/NetworkModule.qml +++ b/desktop/modules/network/NetworkModule.qml @@ -45,6 +45,17 @@ Module { return d ? d.name : ""; } + // Local IPs, keyed by interface. The Networking API exposes only the MAC + // (NetworkDevice.address), so this is the module's one shell-out. It is + // refreshed when a link comes up and on a slow timer while one is up, to + // catch a DHCP renew. `ip -j` is JSON, so no text parsing. + property var ipByIface: ({}) + function ipFor(iface) { return ipByIface[iface] ?? ""; } + function refreshIps() { + ipProc.running = false; + ipProc.running = true; + } + // Connected first, then known, then strongest. Kept here so both the // tile and the page read the same order. readonly property var wifiNetworksSorted: { @@ -56,9 +67,9 @@ Module { return arr; } - // The icon follows the active link, live, with no contract change: the - // drawer reads this property like any other. - icon: mod.wiredUp ? "\uf796" : (mod.wifiUp ? "\uf1eb" : "\uf05e") + // The tile is a globe: the wired/wifi distinction belongs to the page's + // section headers, not the tile. + icon: "\uf0ac" // Active while any link is up. active: mod.wiredUp || mod.wifiUp @@ -93,6 +104,30 @@ Module { property Process notifyProc: Process {} + property Process ipProc: Process { + command: ["ip", "-j", "-4", "addr", "show"] + stdout: StdioCollector { + onStreamFinished: { + const map = {}; + try { + for (const iface of JSON.parse(text)) { + const a = (iface.addr_info ?? []).find(x => x.family === "inet"); + if (a) map[iface.ifname] = a.local; + } + } catch (e) {} + mod.ipByIface = map; + } + } + } + + // Only poll while something is actually up. + property Timer ipTimer: Timer { + interval: 15000 + repeat: true + running: mod.wiredUp || mod.wifiUp + onTriggered: mod.refreshIps() + } + property Connections conn: Connections { target: mod.pendingNetwork function onConnectionFailed(reason) { @@ -106,6 +141,10 @@ Module { } } + onWiredUpChanged: mod.refreshIps() + onWifiUpChanged: mod.refreshIps() + Component.onCompleted: mod.refreshIps() + tileContent: Component { NetworkTile { net: mod } } page: Component { diff --git a/desktop/modules/network/NetworkPage.qml b/desktop/modules/network/NetworkPage.qml index 0951231..22bffa0 100644 --- a/desktop/modules/network/NetworkPage.qml +++ b/desktop/modules/network/NetworkPage.qml @@ -31,11 +31,23 @@ Column { // --- Wired --- - Text { + Row { visible: page.net.wiredDevices.length > 0 - text: "Wired" - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true } - color: Theme.subtext + spacing: 6 + + Text { + anchors.verticalCenter: parent.verticalCenter + text: "\uf796" + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } + color: Theme.subtext + } + + Text { + anchors.verticalCenter: parent.verticalCenter + text: "Wired" + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true } + color: Theme.subtext + } } Repeater { @@ -76,12 +88,20 @@ Column { Text { id: wifiLabel - anchors.verticalCenter: parent.verticalCenter + anchors { left: wifiGlyph.right; leftMargin: 6; verticalCenter: parent.verticalCenter } text: "Wi-Fi" font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1; bold: true } color: Theme.text } + Text { + id: wifiGlyph + anchors.verticalCenter: parent.verticalCenter + text: "\uf1eb" + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } + color: Theme.subtext + } + Switch { id: wifiSwitch anchors { right: parent.right; verticalCenter: parent.verticalCenter } diff --git a/desktop/modules/network/NetworkTile.qml b/desktop/modules/network/NetworkTile.qml index a9e5dd0..d88ad35 100644 --- a/desktop/modules/network/NetworkTile.qml +++ b/desktop/modules/network/NetworkTile.qml @@ -12,38 +12,68 @@ import QtQuick import "../.." -// The state line under the tile label. One line per active link: the API -// exposes no route metric, so when both wired and wifi are up the tile shows -// both rather than guessing which one carries traffic. +// The state line under the tile label: a green dot, the link's name, and its +// local IP. One row per active link, because the API exposes no route metric +// and the tile will not guess which link carries traffic. Column { + id: tile + required property var net width: parent ? parent.width : implicitWidth spacing: 2 - Text { - width: parent.width - elide: Text.ElideRight - visible: net.wiredUp - text: net.wiredName - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } - color: Theme.text + Row { + visible: tile.net.wiredUp + width: tile.width + spacing: 5 + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 7; height: 7; radius: 4 + color: Theme.green + } + + Text { + width: parent.width - 12 + elide: Text.ElideRight + text: { + const ip = tile.net.ipFor(tile.net.wiredName); + return tile.net.wiredName + (ip !== "" ? " " + ip : ""); + } + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + color: Theme.text + } } - Text { - width: parent.width - elide: Text.ElideRight - visible: net.wifiUp - text: net.wifiSsid - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } - color: Theme.text + Row { + visible: tile.net.wifiUp + width: tile.width + spacing: 5 + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 7; height: 7; radius: 4 + color: Theme.green + } + + Text { + width: parent.width - 12 + elide: Text.ElideRight + text: { + const ip = tile.net.ipFor(tile.net.wifiDevice ? tile.net.wifiDevice.name : ""); + return tile.net.wifiSsid + (ip !== "" ? " " + ip : ""); + } + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + color: Theme.text + } } Text { - width: parent.width + width: tile.width elide: Text.ElideRight - visible: !net.wiredUp && !net.wifiUp - text: net.wifiOn ? "Disconnected" : "Off" + visible: !tile.net.wiredUp && !tile.net.wifiUp + text: tile.net.wifiOn ? "Disconnected" : "Off" font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } color: Theme.subtext } diff --git a/desktop/modules/network/README.md b/desktop/modules/network/README.md index 394b356..a806488 100644 --- a/desktop/modules/network/README.md +++ b/desktop/modules/network/README.md @@ -3,8 +3,11 @@ Wired and wifi in one module, because a connected `eth0` must not mask the wifi state. -The tile shows one line per active link. When both wired and wifi are up it -shows both: the API exposes no route metric, so it does not guess which link +The tile is a globe, and each active link shows a green dot, its name and its +local IPv4 address. The address comes from `ip -j -4 addr show`, because the +Networking API exposes only the MAC; it is refreshed on link change and on a +slow timer while a link is up. When both wired and wifi are up the tile shows +both: the API exposes no route metric, so it does not guess which link carries traffic. The page lists managed wired devices, then a wifi radio switch and list. A |
