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}