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 && timer_was_active {
82            if let Some(cmd) = &config.done_cmd {
83                spawn_shell(cmd).error("done_cmd error")?;
84            }
85        }
86        timer_was_active = is_timer_active;
87
88        let mut values = map!(
89            "icon" => Value::icon("tea"),
90        );
91
92        if is_timer_active {
93            values.insert("time".into(), Value::duration(remaining_time));
94            let mut seconds = remaining_time.as_secs();
95
96            if format.contains_key("hours") {
97                let hours = seconds / 3_600;
98                values.insert("hours".into(), Value::text(format!("{hours:02}")));
99                seconds %= 3_600;
100            }
101
102            if format.contains_key("minutes") {
103                let minutes = seconds / 60;
104                values.insert("minutes".into(), Value::text(format!("{minutes:02}")));
105                seconds %= 60;
106            }
107
108            values.insert("seconds".into(), Value::text(format!("{seconds:02}")));
109        }
110
111        widget.set_values(values);
112
113        api.set_widget(widget)?;
114
115        select! {
116            _ = timer.tick(), if is_timer_active => (),
117            _ = api.wait_for_update_request() => (),
118            Some(action) = actions.recv() => {
119                let now = Instant::now();
120                match action.as_ref() {
121                    "increment" if is_timer_active => timer_end += increment,
122                    "increment" => timer_end = now + increment,
123                    "decrement" if is_timer_active => timer_end -= increment,
124                    "reset" => timer_end = now,
125                    _ => (),
126                }
127            }
128        }
129    }
130}