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}