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 [MultiFormat][MaybeMultiFormatConfig] 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//! `inactive_format` | Same as `format` but for when the interface is inactive | `" $icon Down "`
13//! `missing_format` | Same as `format` but for when the device is missing | `" × "`
14//!
15//! Action | Description | Default button
16//! ----------------|-------------------------------------------|---------------
17//! `toggle_format` **DEPRECATED** | Toggles between `format` and `format_alt` | -
18//! `next_format` | Switches to the next format in the list | Left
19//! `prev_format` | Switches to the previous format in the list | Right
20//!
21//! Placeholder | Value | Type | Unit
22//! ------------------|-----------------------------|--------|---------------
23//! `icon` | Icon based on device's type | Icon | -
24//! `speed_down` | Download speed | Number | Bytes per second
25//! `speed_up` | Upload speed | Number | Bytes per second
26//! `graph_down` | Download speed graph | Text | -
27//! `graph_up` | Upload speed graph | Text | -
28//! `device` | The name of device | Text | -
29//! `ssid` | Network SSID (WiFi only) | Text | -
30//! `frequency` | WiFi frequency | Number | Hz
31//! `signal_strength` | WiFi signal | Number | %
32//! `bitrate` | WiFi connection bitrate | Number | Bits per second
33//! `ip` | IPv4 address of the iface | Text | -
34//! `ipv6` | IPv6 address of the iface | Text | -
35//! `nameserver` | Nameserver | Text | -
36//!
37//! # Example
38//!
39//! Display WiFi info if available
40//!
41//! ```toml
42//! [[block]]
43//! block = "net"
44//! format = " $icon {$signal_strength $ssid $frequency|Wired connection} via $device "
45//! ```
46//!
47//! Display exact device
48//!
49//! ```toml
50//! [[block]]
51//! block = "net"
52//! device = "^wlo0$"
53//! ```
54//!
55//! # Icons Used
56//! - `net_loopback`
57//! - `net_vpn`
58//! - `net_wired`
59//! - `net_wireless` (as a progression)
60//! - `net_up`
61//! - `net_down`
62
63use super::prelude::*;
64use crate::netlink::NetDevice;
65use crate::util;
66use itertools::Itertools as _;
67use regex::Regex;
68use std::time::Instant;
69
70#[derive(Deserialize, Debug, SmartDefault)]
71#[serde(default)]
72pub struct Config {
73 pub device: Option<String>,
74 #[default(2.into())]
75 pub interval: Seconds,
76 #[serde(flatten)]
77 pub formats: MaybeMultiFormatConfig,
78 pub inactive_format: FormatConfig,
79 pub missing_format: FormatConfig,
80}
81
82pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
83 let mut actions = api.get_actions()?;
84 api.set_default_actions(&[
85 (MouseButton::Left, None, "next_format"),
86 (MouseButton::Right, None, "prev_format"),
87 ])?;
88
89 let mut formats = config.formats.with_default(
90 " $icon ^icon_net_down $speed_down.eng(prefix:K) ^icon_net_up $speed_up.eng(prefix:K) ",
91 )?;
92 let missing_format = config.missing_format.with_default(" × ")?;
93 let inactive_format = config.inactive_format.with_default(" $icon Down ")?;
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(formats.get_format());
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 "next_format" | "toggle_format" => {
184 formats.next_format();
185 break;
186 }
187 "prev_format" => {
188 formats.prev_format();
189 break;
190 }
191 _ => ()
192 }
193 }
194 }
195 }
196}
197
198fn push_to_hist<T>(hist: &mut [T], elem: T) {
199 hist[0] = elem;
200 hist.rotate_left(1);
201}
202
203#[cfg(test)]
204mod tests {
205 use super::push_to_hist;
206
207 #[test]
208 fn test_push_to_hist() {
209 let mut hist = [0; 4];
210 assert_eq!(&hist, &[0, 0, 0, 0]);
211 push_to_hist(&mut hist, 1);
212 assert_eq!(&hist, &[0, 0, 0, 1]);
213 push_to_hist(&mut hist, 3);
214 assert_eq!(&hist, &[0, 0, 1, 3]);
215 push_to_hist(&mut hist, 0);
216 assert_eq!(&hist, &[0, 1, 3, 0]);
217 push_to_hist(&mut hist, 10);
218 assert_eq!(&hist, &[1, 3, 0, 10]);
219 push_to_hist(&mut hist, 2);
220 assert_eq!(&hist, &[3, 0, 10, 2]);
221 }
222}