use super::prelude::*;
use crate::netlink::NetDevice;
use crate::util;
use itertools::Itertools as _;
use regex::Regex;
use std::time::Instant;
#[derive(Deserialize, Debug, SmartDefault)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
pub device: Option<String>,
#[default(2.into())]
pub interval: Seconds,
pub format: FormatConfig,
pub format_alt: Option<FormatConfig>,
pub inactive_format: FormatConfig,
pub missing_format: FormatConfig,
}
pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
let mut actions = api.get_actions()?;
api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
let mut format = config.format.with_default(
" $icon ^icon_net_down $speed_down.eng(prefix:K) ^icon_net_up $speed_up.eng(prefix:K) ",
)?;
let missing_format = config.missing_format.with_default(" × ")?;
let inactive_format = config.inactive_format.with_default(" $icon Down ")?;
let mut format_alt = match &config.format_alt {
Some(f) => Some(f.with_default("")?),
None => None,
};
let mut timer = config.interval.timer();
let device_re = config
.device
.as_deref()
.map(Regex::new)
.transpose()
.error("Failed to parse device regex")?;
let mut stats = None;
let mut stats_timer = Instant::now();
let mut tx_hist = [0f64; 8];
let mut rx_hist = [0f64; 8];
loop {
match NetDevice::new(device_re.as_ref()).await? {
None => {
api.set_widget(Widget::new().with_format(missing_format.clone()))?;
}
Some(device) => {
let mut widget = Widget::new();
if device.is_up() {
widget.set_format(format.clone());
} else {
widget.set_format(inactive_format.clone());
}
let mut speed_down: f64 = 0.0;
let mut speed_up: f64 = 0.0;
match (stats, device.iface.stats) {
(None, new_stats) => stats = new_stats,
(Some(_), None) => stats = None,
(Some(old_stats), Some(new_stats)) => {
let diff = new_stats - old_stats;
let elapsed = stats_timer.elapsed().as_secs_f64();
stats_timer = Instant::now();
speed_down = diff.rx_bytes as f64 / elapsed;
speed_up = diff.tx_bytes as f64 / elapsed;
stats = Some(new_stats);
}
}
push_to_hist(&mut rx_hist, speed_down);
push_to_hist(&mut tx_hist, speed_up);
let icon = if let Some(signal) = device.signal() {
Value::icon_progression(device.icon, signal / 100.0)
} else {
Value::icon(device.icon)
};
widget.set_values(map! {
"icon" => icon,
"speed_down" => Value::bytes(speed_down),
"speed_up" => Value::bytes(speed_up),
"graph_down" => Value::text(util::format_bar_graph(&rx_hist)),
"graph_up" => Value::text(util::format_bar_graph(&tx_hist)),
[if let Some(v) = device.ip] "ip" => Value::text(v.to_string()),
[if let Some(v) = device.ipv6] "ipv6" => Value::text(v.to_string()),
[if let Some(v) = device.ssid()] "ssid" => Value::text(v),
[if let Some(v) = device.frequency()] "frequency" => Value::hertz(v),
[if let Some(v) = device.bitrate()] "bitrate" => Value::bits(v),
[if let Some(v) = device.signal()] "signal_strength" => Value::percents(v),
[if !device.nameservers.is_empty()] "nameserver" => Value::text(
device
.nameservers
.into_iter()
.map(|s| s.to_string())
.join(" "),
),
"device" => Value::text(device.iface.name),
});
api.set_widget(widget)?;
}
}
loop {
select! {
_ = timer.tick() => break,
_ = api.wait_for_update_request() => break,
Some(action) = actions.recv() => match action.as_ref() {
"toggle_format" => {
if let Some(format_alt) = &mut format_alt {
std::mem::swap(format_alt, &mut format);
break;
}
}
_ => ()
}
}
}
}
}
fn push_to_hist<T>(hist: &mut [T], elem: T) {
hist[0] = elem;
hist.rotate_left(1);
}
#[cfg(test)]
mod tests {
use super::push_to_hist;
#[test]
fn test_push_to_hist() {
let mut hist = [0; 4];
assert_eq!(&hist, &[0, 0, 0, 0]);
push_to_hist(&mut hist, 1);
assert_eq!(&hist, &[0, 0, 0, 1]);
push_to_hist(&mut hist, 3);
assert_eq!(&hist, &[0, 0, 1, 3]);
push_to_hist(&mut hist, 0);
assert_eq!(&hist, &[0, 1, 3, 0]);
push_to_hist(&mut hist, 10);
assert_eq!(&hist, &[1, 3, 0, 10]);
push_to_hist(&mut hist, 2);
assert_eq!(&hist, &[3, 0, 10, 2]);
}
}