i3status_rs/blocks/
speedtest.rs

1//! Ping, download, and upload speeds
2//!
3//! This block which requires [`speedtest-cli`](https://github.com/sivel/speedtest-cli).
4//!
5//! # Configuration
6//!
7//! Key | Values | Default
8//! ----|--------|--------
9//! `format` | A string to customise the output of this block. See below for available placeholders. | `" ^icon_ping $ping ^icon_net_down $speed_down ^icon_net_up $speed_up "`
10//! `interval` | Update interval in seconds | `1800`
11//!
12//! Placeholder  | Value          | Type   | Unit
13//! -------------|----------------|--------|---------------
14//! `ping`       | Ping delay     | Number | Seconds
15//! `speed_down` | Download speed | Number | Bits per second
16//! `speed_up`   | Upload speed   | Number | Bits per second
17//!
18//! # Example
19//!
20//! Show only ping (with an icon)
21//!
22//! ```toml
23//! [[block]]
24//! block = "speedtest"
25//! interval = 1800
26//! format = " ^icon_ping $ping "
27//! ```
28//!
29//! Hide ping and display speed in bytes per second each using 4 characters (without icons)
30//!
31//! ```toml
32//! [[block]]
33//! block = "speedtest"
34//! interval = 1800
35//! format = " $speed_down.eng(w:4,u:B) $speed_up(w:4,u:B) "
36//! ```
37//!
38//! # Icons Used
39//! - `ping`
40//! - `net_down`
41//! - `net_up`
42
43use super::prelude::*;
44use tokio::process::Command;
45
46#[derive(Deserialize, Debug, SmartDefault)]
47#[serde(deny_unknown_fields, default)]
48pub struct Config {
49    pub format: FormatConfig,
50    #[default(1800.into())]
51    pub interval: Seconds,
52}
53
54pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
55    let format = config
56        .format
57        .with_default(" ^icon_ping $ping ^icon_net_down $speed_down ^icon_net_up $speed_up ")?;
58
59    let mut command = Command::new("speedtest-cli");
60    command.arg("--json");
61
62    loop {
63        let output = command
64            .output()
65            .await
66            .error("failed to run 'speedtest-cli'")?
67            .stdout;
68        let output =
69            std::str::from_utf8(&output).error("'speedtest-cli' produced non-UTF8 output")?;
70        let output: SpeedtestCliOutput =
71            serde_json::from_str(output).error("'speedtest-cli' produced wrong JSON")?;
72
73        let mut widget = Widget::new().with_format(format.clone());
74        widget.set_values(map! {
75            "ping" => Value::seconds(output.ping * 1e-3),
76            "speed_down" => Value::bits(output.download),
77            "speed_up" => Value::bits(output.upload),
78        });
79        api.set_widget(widget)?;
80
81        select! {
82            _ = sleep(config.interval.0) => (),
83            _ = api.wait_for_update_request() => (),
84        }
85    }
86}
87
88#[derive(Deserialize, Debug, Clone, Copy)]
89struct SpeedtestCliOutput {
90    /// Download speed in bits per second
91    download: f64,
92    /// Upload speed in bits per second
93    upload: f64,
94    /// Ping time in ms
95    ping: f64,
96}