i3status_rs/blocks/
tea_timer.rs

1//! Timer
2//!
3//! # Configuration
4//!
5//! Key | Values | Default
6//! ----|--------|--------
7//! `format` | A string to customise the output of this block. See below for available placeholders. | <code>\" $icon {$time.duration(hms:true) \|}\"</code>
8//! `increment` | The numbers of seconds to add each time the block is clicked. | 30
9//! `done_cmd` | A command to run in `sh` when timer finishes. | None
10//!
11//! Placeholder            | Value                                                          | Type     | Unit
12//! -----------------------|----------------------------------------------------------------|----------|---------------
13//! `icon`                 | A static icon                                                  | Icon     | -
14//! `time`                 | The time remaining on the timer                                | Duration | -
15//! `hours` *DEPRECATED*   | The hours remaining on the timer                               | Text     | h
16//! `minutes` *DEPRECATED* | The minutes remaining on the timer                             | Text     | mn
17//! `seconds` *DEPRECATED* | The seconds remaining on the timer                             | Text     | s
18//!
19//! `time`, `hours`, `minutes`, and `seconds` are unset when the timer is inactive.
20//!
21//! `hours`, `minutes`, and `seconds` have been deprecated in favor of `time`.
22//!
23//! Action      | Default button
24//! ------------|---------------
25//! `increment` | Left / Wheel Up
26//! `decrement` | Wheel Down
27//! `reset`     | Right
28//!
29//! # Example
30//!
31//! ```toml
32//! [[block]]
33//! block = "tea_timer"
34//! format = " $icon {$minutes:$seconds |}"
35//! done_cmd = "notify-send 'Timer Finished'"
36//! ```
37//!
38//! # Icons Used
39//! - `tea`
40
41use super::prelude::*;
42use crate::subprocess::spawn_shell;
43
44use std::time::{Duration, Instant};
45
46#[derive(Deserialize, Debug, SmartDefault)]
47#[serde(deny_unknown_fields, default)]
48pub struct Config {
49    pub format: FormatConfig,
50    pub increment: Option<u64>,
51    pub done_cmd: Option<String>,
52}
53
54pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
55    let mut actions = api.get_actions()?;
56    api.set_default_actions(&[
57        (MouseButton::Left, None, "increment"),
58        (MouseButton::WheelUp, None, "increment"),
59        (MouseButton::WheelDown, None, "decrement"),
60        (MouseButton::Right, None, "reset"),
61    ])?;
62
63    let interval: Seconds = 1.into();
64    let mut timer = interval.timer();
65
66    let format = config
67        .format
68        .with_default(" $icon {$time.duration(hms:true) |}")?;
69
70    let increment = Duration::from_secs(config.increment.unwrap_or(30));
71    let mut timer_end = Instant::now();
72
73    let mut timer_was_active = false;
74
75    loop {
76        let mut widget = Widget::new().with_format(format.clone());
77
78        let remaining_time = timer_end - Instant::now();
79        let is_timer_active = !remaining_time.is_zero();
80
81        if !is_timer_active
82            && timer_was_active
83            && let Some(cmd) = &config.done_cmd
84        {
85            spawn_shell(cmd).error("done_cmd error")?;
86        }
87        timer_was_active = is_timer_active;
88
89        let mut values = map!(
90            "icon" => Value::icon("tea"),
91        );
92
93        if is_timer_active {
94            values.insert("time".into(), Value::duration(remaining_time));
95            let mut seconds = remaining_time.as_secs();
96
97            if format.contains_key("hours") {
98                let hours = seconds / 3_600;
99                values.insert("hours".into(), Value::text(format!("{hours:02}")));
100                seconds %= 3_600;
101            }
102
103            if format.contains_key("minutes") {
104                let minutes = seconds / 60;
105                values.insert("minutes".into(), Value::text(format!("{minutes:02}")));
106                seconds %= 60;
107            }
108
109            values.insert("seconds".into(), Value::text(format!("{seconds:02}")));
110        }
111
112        widget.set_values(values);
113
114        api.set_widget(widget)?;
115
116        select! {
117            _ = timer.tick(), if is_timer_active => (),
118            _ = api.wait_for_update_request() => (),
119            Some(action) = actions.recv() => {
120                let now = Instant::now();
121                match action.as_ref() {
122                    "increment" if is_timer_active => timer_end += increment,
123                    "increment" => timer_end = now + increment,
124                    "decrement" if is_timer_active => timer_end -= increment,
125                    "reset" => timer_end = now,
126                    _ => (),
127                }
128            }
129        }
130    }
131}