Skip to main content

i3status_rs/blocks/
uptime.rs

1//! System's uptime
2//!
3//! This block displays system uptime in terms of two biggest units, so minutes and seconds, or
4//! hours and minutes or days and hours or weeks and days.
5//!
6//! # Configuration
7//!
8//! Key        | Values                     | Default
9//! -----------|----------------------------|--------
10//! `format` | A string to customise the output of this block. See below for available placeholders | `" $icon $uptime "`
11//! `interval` | Update interval in seconds | `60`
12//!
13//! Placeholder         | Value                   | Type     | Unit
14//! --------------------|-------------------------|----------|-----
15//! `icon`              | A static icon           | Icon     | -
16//! `text` *DEPRECATED* | Current uptime          | Text     | -
17//! `uptime`            | Current uptime          | Duration | -
18//!
19//! `text` has been deprecated in favor of `uptime`.
20//!
21//! # Example
22//!
23//! ```toml
24//! [[block]]
25//! block = "uptime"
26//! interval = 3600 # update every hour
27//! ```
28//!
29//! # Used Icons
30//! - `uptime`
31
32use super::prelude::*;
33use tokio::fs::read_to_string;
34
35#[derive(Deserialize, Debug, SmartDefault)]
36#[serde(deny_unknown_fields, default)]
37pub struct Config {
38    pub format: FormatConfig,
39    #[default(60.into())]
40    pub interval: Seconds,
41}
42
43pub(crate) fn prepare(config: &Config) -> Result<Arc<BlockPlan>> {
44    BlockPlan::new(vec![
45        OutputPlan::new("main", config.format.with_default(" $icon $uptime ")?)
46            .icon("icon", IconChoices::one(icons::UPTIME)),
47    ])
48}
49
50pub(crate) async fn run(config: &Config, api: &CommonApi, plan: &Arc<BlockPlan>) -> Result<()> {
51    let output_main = plan.output("main")?;
52
53    loop {
54        let uptime = read_to_string("/proc/uptime")
55            .await
56            .error("Failed to read /proc/uptime")?;
57        let mut seconds: u64 = uptime
58            .split('.')
59            .next()
60            .and_then(|u| u.parse().ok())
61            .error("/proc/uptime has invalid content")?;
62
63        let uptime = Duration::from_secs(seconds);
64
65        let weeks = seconds / 604_800;
66        seconds %= 604_800;
67        let days = seconds / 86_400;
68        seconds %= 86_400;
69        let hours = seconds / 3_600;
70        seconds %= 3_600;
71        let minutes = seconds / 60;
72        seconds %= 60;
73
74        let text = if weeks > 0 {
75            format!("{weeks}w {days}d")
76        } else if days > 0 {
77            format!("{days}d {hours}h")
78        } else if hours > 0 {
79            format!("{hours}h {minutes}m")
80        } else {
81            format!("{minutes}m {seconds}s")
82        };
83
84        let mut widget = output_main.new_widget();
85        widget.set_values(map! {
86          "icon" => Value::icon(icons::UPTIME),
87          "text" => Value::text(text),
88          "uptime" => Value::duration(uptime)
89        });
90        api.set_widget(widget)?;
91
92        select! {
93            _ = sleep(config.interval.0) => (),
94            _ = api.wait_for_update_request() => (),
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn plan_declares_main_output_with_uptime_icon() {
105        let plan = prepare(&Config::default()).unwrap();
106        let declared: Vec<_> = plan.outputs().map(|o| o.id()).collect();
107        assert_eq!(declared, ["main"]);
108        let output = plan.output("main").unwrap();
109        assert_eq!(output.single_icon("icon").unwrap(), "uptime");
110    }
111
112    #[test]
113    fn custom_format_is_respected() {
114        let config = Config {
115            format: " $uptime ".parse().unwrap(),
116            ..Config::default()
117        };
118        let plan = prepare(&config).unwrap();
119        let output = plan.output("main").unwrap();
120        assert!(output.format().contains_key("uptime"));
121        assert!(!output.format().contains_key("icon"));
122    }
123}