The Problem
A network widget must show Wi-Fi SSID, signal strength, connection state, and maybe IP address. Wired Ethernet should also be detected. The state changes frequently — you connect to a new network, drop signal, or plug in an Ethernet cable.
The Naive Approach
Run iwconfig or nmcli in a polling Timer. These tools have inconsistent output formats and spawning processes every few seconds is wasteful. Worse, polling misses transient states (connecting, authenticating, DHCP timeout).
Timer {
interval: 5000
onTriggered: {
// "nmcli -t -f active,ssid,signal dev wifi" or "iwconfig wlan0"
// Parse messy output
}
}Think of network status like a phone's cellular indicator. You don't check your data plan every second — the phone tells you when you connect, disconnect, or switch towers. Similarly, your widget should react to events, not poll for changes.
The Idea
Quickshell provides NetworkManager integration via the NetworkManager singleton. It exposes active connections, device status, signal strength, and SSID through reactive properties. No polling, no shell commands.
Let's Build It
A network widget with Wi-Fi SSID and signal strength:
import Quickshell
import Quickshell.Services.NetworkManager
Row {
spacing: 6
property var primaryDevice: {
for (var i = 0; i < NetworkManager.devices.length; i++) {
var dev = NetworkManager.devices[i]
if (dev.activeConnection) return dev
}
return null
}
property var connection: primaryDevice ? primaryDevice.activeConnection : null
property string ssid: connection ? connection.id : ""
property int signalStrength: primaryDevice && primaryDevice.type === "wifi"
? primaryDevice.wirelessStrength : -1
property bool connected: connection !== null
Text {
text: {
if (!connected) return ""
if (primaryDevice && primaryDevice.type === "ethernet") return ""
if (signalStrength >= 80) return ""
if (signalStrength >= 50) return ""
if (signalStrength >= 25) return ""
return ""
}
font.pixelSize: 14
color: connected ? "#a6e3a1" : "#585b70"
}
Text {
text: ssid
font.pixelSize: 12
color: "#cdd6f4"
visible: ssid !== ""
}
Text {
text: signalStrength >= 0 ? signalStrength + "%" : ""
font.pixelSize: 10
color: "#585b70"
visible: primaryDevice && primaryDevice.type === "wifi"
}
}Let's Improve It
Show download/upload speed and IP address:
Row {
spacing: 8
property var prevRx: 0
property var prevTx: 0
property real speedRx: 0
property real speedTx: 0
function updateSpeed() {
var data = readFile("/sys/class/net/" + iface + "/statistics/rx_bytes")
var rx = parseInt(data.trim())
data = readFile("/sys/class/net/" + iface + "/statistics/tx_bytes")
var tx = parseInt(data.trim())
speedRx = (rx - prevRx) / 1024 / 2 // KB/s over 2s window
speedTx = (tx - prevTx) / 1024 / 2
prevRx = rx
prevTx = tx
}
Text {
text: " " + speedTx.toFixed(1) + "K"
font.pixelSize: 10
color: "#585b70"
}
Text {
text: " " + speedRx.toFixed(1) + "K"
font.pixelSize: 10
color: "#585b70"
}
}Assuming Wi-Fi is the only interface. Desktops use Ethernet. Laptops may have both. Check device.type === "wifi" vs "ethernet". Handle the case where no connection is active.
Polling signal strength. NetworkManager properties are reactive. Just bind to primaryDevice.wirelessStrength — the UI updates when the kernel reports a signal change to NetworkManager.
Exposing raw IP addresses. If you show your local IP in the panel, consider privacy implications for screenshots or streaming. Make it opt-in.
Under the Hood
NetworkManager is a Quickshell module wrapping the org.freedesktop.NetworkManager D-Bus interface. It subscribes to StateChanged, DeviceAdded, DeviceRemoved, and PropertiesChanged signals. The wirelessStrength property updates when NetworkManager receives a scan update from wpa_supplicant (typically every 5-15 seconds).
Network speed is computed by reading /sys/class/net/<iface>/statistics/rx_bytes and tx_bytes. These are kernel counters incremented by the NIC driver on every packet. By taking deltas over a 2-second window, you get a smoothed throughput rate. The filesystem path must match the active interface name.
Exercises
- NetworkManager singleton provides reactive network state
- Device type (wifi vs ethernet) determines the icon
- Signal strength is reactive — no polling needed
- Network speed comes from sysfs byte counters
- Always handle the disconnected state gracefully