120 lines
3.5 KiB
Lua
120 lines
3.5 KiB
Lua
-- 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
|