Skip to main content

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}