i3status_rs/blocks/net.rs
1//! Network information
2//!
3//! This block uses `sysfs` and `netlink` and thus does not require any external dependencies.
4//!
5//! # Configuration
6//!
7//! Key | Values | Default
8//! ----|--------|--------
9//! `device` | Network interface to monitor (as specified in `/sys/class/net/`). Supports regex. | If not set, device will be automatically selected every `interval`
10//! `interval` | Update interval in seconds | `2`
11//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon ^icon_net_down $speed_down.eng(prefix:K) ^icon_net_up $speed_up.eng(prefix:K) "`
12//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None`
13//! `inactive_format` | Same as `format` but for when the interface is inactive | `" $icon Down "`
14//! `missing_format` | Same as `format` but for when the device is missing | `" × "`
15//!
16//! Action | Description | Default button
17//! ----------------|-------------------------------------------|---------------
18//! `toggle_format` | Toggles between `format` and `format_alt` | Left
19//!
20//! Placeholder | Value | Type | Unit
21//! ------------------|-----------------------------|--------|---------------
22//! `icon` | Icon based on device's type | Icon | -
23//! `speed_down` | Download speed | Number | Bytes per second
24//! `speed_up` | Upload speed | Number | Bytes per second
25//! `graph_down` | Download speed graph | Text | -
26//! `graph_up` | Upload speed graph | Text | -
27//! `device` | The name of device | Text | -
28//! `ssid` | Netfork SSID (WiFi only) | Text | -
29//! `frequency` | WiFi frequency | Number | Hz
30//! `signal_strength` | WiFi signal | Number | %
31//! `bitrate` | WiFi connection bitrate | Number | Bits per second
32//! `ip` | IPv4 address of the iface | Text | -
33//! `ipv6` | IPv6 address of the iface | Text | -
34//! `nameserver` | Nameserver | Text | -
35//!
36//! # Example
37//!
38//! Display WiFi info if available
39//!
40//! ```toml
41//! [[block]]
42//! block = "net"
43//! format = " $icon {$signal_strength $ssid $frequency|Wired connection} via $device "
44//! ```
45//!
46//! Display exact device
47//!
48//! ```toml
49//! [[block]]
50//! block = "net"
51//! device = "^wlo0$"
52//! ```
53//!
54//! # Icons Used
55//! - `net_loopback`
56//! - `net_vpn`
57//! - `net_wired`
58//! - `net_wireless` (as a progression)
59//! - `net_up`
60//! - `net_down`
61
62use super::prelude::*;
63use crate::netlink::NetDevice;
64use crate::util;
65use itertools::Itertools as _;
66use regex::Regex;
67use std::time::Instant;
68
69#[derive(Deserialize, Debug, SmartDefault)]
70#[serde(deny_unknown_fields, default)]
71pub struct Config {
72 pub device: Option<String>,
73 #[default(2.into())]
74 pub interval: Seconds,
75 pub format: FormatConfig,
76 pub format_alt: Option<FormatConfig>,
77 pub inactive_format: FormatConfig,
78 pub missing_format: FormatConfig,
79}
80
81pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
82 let mut actions = api.get_actions()?;
83 api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
84
85 let mut format = config.format.with_default(
86 " $icon ^icon_net_down $speed_down.eng(prefix:K) ^icon_net_up $speed_up.eng(prefix:K) ",
87 )?;
88 let missing_format = config.missing_format.with_default(" × ")?;
89 let inactive_format = config.inactive_format.with_default(" $icon Down ")?;
90 let mut format_alt = match &config.format_alt {
91 Some(f) => Some(f.with_default("")?),
92 None => None,
93 };
94
95 let mut timer = config.interval.timer();
96
97 let device_re = config
98 .device
99 .as_deref()
100 .map(Regex::new)
101 .transpose()
102 .error("Failed to parse device regex")?;
103
104 // Stats
105 let mut stats = None;
106 let mut stats_timer = Instant::now();
107 let mut tx_hist = [0f64; 8];
108 let mut rx_hist = [0f64; 8];
109
110 loop {
111 match NetDevice::new(device_re.as_ref()).await? {
112 None => {
113 api.set_widget(Widget::new().with_format(missing_format.clone()))?;
114 }
115 Some(device) => {
116 let mut widget = Widget::new();
117
118 if device.is_up() {
119 widget.set_format(format.clone());
120 } else {
121 widget.set_format(inactive_format.clone());
122 }
123
124 let mut speed_down: f64 = 0.0;
125 let mut speed_up: f64 = 0.0;
126
127 // Calculate speed
128 match (stats, device.iface.stats) {
129 // No previous stats available
130 (None, new_stats) => stats = new_stats,
131 // No new stats available
132 (Some(_), None) => stats = None,
133 // All stats available
134 (Some(old_stats), Some(new_stats)) => {
135 let diff = new_stats - old_stats;
136 let elapsed = stats_timer.elapsed().as_secs_f64();
137 stats_timer = Instant::now();
138 speed_down = diff.rx_bytes as f64 / elapsed;
139 speed_up = diff.tx_bytes as f64 / elapsed;
140 stats = Some(new_stats);
141 }
142 }
143 push_to_hist(&mut rx_hist, speed_down);
144 push_to_hist(&mut tx_hist, speed_up);
145
146 let icon = if let Some(signal) = device.signal() {
147 Value::icon_progression(device.icon, signal / 100.0)
148 } else {
149 Value::icon(device.icon)
150 };
151
152 widget.set_values(map! {
153 "icon" => icon,
154 "speed_down" => Value::bytes(speed_down),
155 "speed_up" => Value::bytes(speed_up),
156 "graph_down" => Value::text(util::format_bar_graph(&rx_hist)),
157 "graph_up" => Value::text(util::format_bar_graph(&tx_hist)),
158 [if let Some(v) = device.ip] "ip" => Value::text(v.to_string()),
159 [if let Some(v) = device.ipv6] "ipv6" => Value::text(v.to_string()),
160 [if let Some(v) = device.ssid()] "ssid" => Value::text(v),
161 [if let Some(v) = device.frequency()] "frequency" => Value::hertz(v),
162 [if let Some(v) = device.bitrate()] "bitrate" => Value::bits(v),
163 [if let Some(v) = device.signal()] "signal_strength" => Value::percents(v),
164 [if !device.nameservers.is_empty()] "nameserver" => Value::text(
165 device
166 .nameservers
167 .into_iter()
168 .map(|s| s.to_string())
169 .join(" "),
170 ),
171 "device" => Value::text(device.iface.name),
172 });
173
174 api.set_widget(widget)?;
175 }
176 }
177
178 loop {
179 select! {
180 _ = timer.tick() => break,
181 _ = api.wait_for_update_request() => break,
182 Some(action) = actions.recv() => match action.as_ref() {
183 "toggle_format" => {
184 if let Some(format_alt) = &mut format_alt {
185 std::mem::swap(format_alt, &mut format);
186 break;
187 }
188 }
189 _ => ()
190 }
191 }
192 }
193 }
194}
195
196fn push_to_hist<T>(hist: &mut [T], elem: T) {
197 hist[0] = elem;
198 hist.rotate_left(1);
199}
200
201#[cfg(test)]
202mod tests {
203 use super::push_to_hist;
204
205 #[test]
206 fn test_push_to_hist() {
207 let mut hist = [0; 4];
208 assert_eq!(&hist, &[0, 0, 0, 0]);
209 push_to_hist(&mut hist, 1);
210 assert_eq!(&hist, &[0, 0, 0, 1]);
211 push_to_hist(&mut hist, 3);
212 assert_eq!(&hist, &[0, 0, 1, 3]);
213 push_to_hist(&mut hist, 0);
214 assert_eq!(&hist, &[0, 1, 3, 0]);
215 push_to_hist(&mut hist, 10);
216 assert_eq!(&hist, &[1, 3, 0, 10]);
217 push_to_hist(&mut hist, 2);
218 assert_eq!(&hist, &[3, 0, 10, 2]);
219 }
220}