Skip to main content

whatawhat_lib/
idle.rs

1use chrono::{DateTime, TimeDelta, Utc};
2use std::cmp::max;
3use tracing::debug;
4
5pub struct Tracker {
6    last_input_time: DateTime<Utc>,
7    is_idle: bool,
8    is_changed: bool,
9    idle_timeout: TimeDelta,
10
11    idle_end: Option<DateTime<Utc>>,
12}
13
14#[derive(Debug)]
15pub enum Status {
16    Idle {
17        changed: bool,
18        last_input_time: DateTime<Utc>,
19        duration: TimeDelta,
20    },
21    Active {
22        changed: bool,
23        last_input_time: DateTime<Utc>,
24    },
25}
26
27impl Tracker {
28    pub fn new(now: DateTime<Utc>, idle_timeout: TimeDelta) -> Self {
29        Self {
30            last_input_time: now,
31            is_idle: false,
32            is_changed: false,
33            idle_timeout,
34            idle_end: None,
35        }
36    }
37
38    fn set_idle(&mut self, is_idle: bool) {
39        self.is_idle = is_idle;
40        self.is_changed = true;
41    }
42
43    pub fn mark_not_idle(&mut self, now: DateTime<Utc>) {
44        debug!("No longer idle");
45        self.last_input_time = now;
46        self.set_idle(false);
47
48        self.idle_end = Some(now);
49    }
50
51    pub fn mark_idle(&mut self, _: DateTime<Utc>) {
52        debug!("Idle again");
53        self.set_idle(true);
54    }
55
56    // The logic is rewritten from the original Python code:
57    // https://github.com/ActivityWatch/aw-watcher-afk/blob/ef531605cd8238e00138bbb980e5457054e05248/aw_watcher_afk/afk.py#L73
58    pub fn get_with_last_input(
59        &mut self,
60        now: DateTime<Utc>,
61        seconds_since_input: u32,
62    ) -> anyhow::Result<Status> {
63        let time_since_input = TimeDelta::seconds(i64::from(seconds_since_input));
64
65        self.last_input_time = now - time_since_input;
66
67        if self.is_idle
68            && u64::from(seconds_since_input) < self.idle_timeout.num_seconds().try_into().unwrap()
69        {
70            debug!("No longer idle");
71            self.set_idle(false);
72        } else if !self.is_idle
73            && u64::from(seconds_since_input) >= self.idle_timeout.num_seconds().try_into().unwrap()
74        {
75            debug!("Idle again");
76            self.set_idle(true);
77        }
78
79        Ok(self.get_status(now))
80    }
81
82    pub fn get_reactive(&mut self, now: DateTime<Utc>) -> anyhow::Result<Status> {
83        if !self.is_idle {
84            self.last_input_time = max(self.last_input_time, now - self.idle_timeout);
85
86            if let Some(idle_end) = self.idle_end {
87                if self.last_input_time < idle_end {
88                    self.last_input_time = idle_end;
89                }
90            }
91        }
92
93        Ok(self.get_status(now))
94    }
95
96    fn get_status(&mut self, now: DateTime<Utc>) -> Status {
97        let result = if self.is_changed {
98            if self.is_idle {
99                Status::Idle {
100                    changed: self.is_changed,
101                    last_input_time: self.last_input_time,
102                    duration: now - self.last_input_time,
103                }
104            } else {
105                Status::Active {
106                    changed: self.is_changed,
107                    last_input_time: self.last_input_time,
108                }
109            }
110        } else if self.is_idle {
111            Status::Idle {
112                changed: self.is_changed,
113                last_input_time: self.last_input_time,
114                duration: now - self.last_input_time,
115            }
116        } else {
117            Status::Active {
118                changed: self.is_changed,
119                last_input_time: self.last_input_time,
120            }
121        };
122        self.is_changed = false;
123
124        result
125    }
126}