-- Compact, two-line network throughput widget. It samples the interface used -- by the default route so virtual and physical interfaces are not double-counted. local command = [[ iface=$(ip route show default 2>/dev/null | awk '$1 == "default" { for (i = 1; i <= NF; i++) if ($i == "dev") { print $(i + 1); exit } }') if [ -z "$iface" ]; then for path in /sys/class/net/*; do candidate=${path##*/} [ "$candidate" = "lo" ] && continue [ "$(cat "$path/carrier" 2>/dev/null)" = "1" ] || continue iface=$candidate break done fi [ -n "$iface" ] || exit 1 awk -v iface="$iface" '$1 ~ ("^" iface ":") { sub(":", "", $1); print iface "\t" $2 "\t" $10; exit }' /proc/net/dev ]] local previous = nil local request_in_flight = false -- 82px accommodates the longest fixed field ("1023 KiB/s") at 10px while -- removing the excess side padding from the original 112px capsule. local label_width = 82 local function formatRate(bytes_per_second) local units = { "B/s", "KiB/s", "MiB/s", "GiB/s" } local value = bytes_per_second local unit_index = 1 while value >= 1024 and unit_index < #units do value = value / 1024 unit_index = unit_index + 1 end if unit_index == 1 then return string.format("%d %s", math.floor(value + 0.5), units[unit_index]) end if value >= 100 then return string.format("%.0f %s", value, units[unit_index]) end return string.format("%.1f %s", value, units[unit_index]) end local function renderLines(download, upload) -- The explicit minimum width and fixed ten-character value field keep the -- capsule width stable while inheriting the bar font used by other modules. barWidget.render(ui.column({ gap = 0, align = "center", minWidth = label_width }, { ui.label({ text = string.format("↓ %10s", download), maxLines = 1, fontSize = 10, }), ui.label({ text = string.format("↑ %10s", upload), maxLines = 1, fontSize = 10, }), })) end function update() noctalia.setUpdateInterval(2000) if request_in_flight then return end request_in_flight = true noctalia.runAsync(command, function(result) request_in_flight = false if result.exitCode ~= 0 then previous = nil renderLines("—", "—") barWidget.setTooltip("Network throughput unavailable: no active interface") return end local iface, received, sent = string.match( result.stdout, "^([^\t]+)\t(%d+)\t(%d+)" ) received = tonumber(received) sent = tonumber(sent) if iface == nil or received == nil or sent == nil then previous = nil renderLines("—", "—") barWidget.setTooltip("Network throughput unavailable") return end local now = os.time() if previous == nil or previous.iface ~= iface or now <= previous.time then previous = { iface = iface, received = received, sent = sent, time = now } renderLines("Sampling…", "Sampling…") barWidget.setTooltip("Sampling network throughput on " .. iface) return end local elapsed = now - previous.time local download = math.max(0, (received - previous.received) / elapsed) local upload = math.max(0, (sent - previous.sent) / elapsed) previous = { iface = iface, received = received, sent = sent, time = now } renderLines(formatRate(download), formatRate(upload)) barWidget.setTooltip( string.format( "%s · Download %s · Upload %s", iface, formatRate(download), formatRate(upload) ) ) end) end