aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/network
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-14 18:13:11 +0200
committerDanilo M. <danix@danix.xyz>2026-09-14 18:13:11 +0200
commit26e6f28b15a8f2ed7664c5c268b4065ae62668d0 (patch)
tree62f087b0047652f70e362606f3c0925099190204 /desktop/modules/network
parent4eaf3bdce7b4e7663c6ba5c206d68eacbd24949e (diff)
downloadquickshell-26e6f28b15a8f2ed7664c5c268b4065ae62668d0.tar.gz
quickshell-26e6f28b15a8f2ed7664c5c268b4065ae62668d0.zip
feat(desktop): globe network tile with per-link IPs
Diffstat (limited to 'desktop/modules/network')
-rw-r--r--desktop/modules/network/NetworkModule.qml45
-rw-r--r--desktop/modules/network/NetworkPage.qml30
-rw-r--r--desktop/modules/network/NetworkTile.qml70
-rw-r--r--desktop/modules/network/README.md7
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