xidlehook_core/
timers.rs

1//! The timer trait and some useful implementations
2
3use crate::Result;
4use std::{
5    process::{Child, Command},
6    time::Duration,
7};
8
9/// The timer trait is used to tell xidlehook after how much idle time
10/// your timer should activate (relatively), and what activation
11/// actually means. It also provides you with the ability to implement
12/// what happens when the next timer is activated, and also to disable
13/// the timer.
14pub trait Timer {
15    /// Return the time left based on the relative idle time
16    fn time_left(&mut self, idle_time: Duration) -> Result<Option<Duration>>;
17    /// How urgent this timer wants to be notified on abort (when the user is no longer idle).
18    /// Return as slow of a duration as you think is acceptable to be nice to the CPU - preferrably
19    /// return `None` which basically means infinity.
20    fn abort_urgency(&self) -> Option<Duration> {
21        None
22    }
23
24    /// Called when the timer was activated
25    fn activate(&mut self) -> Result<()> {
26        Ok(())
27    }
28    /// Called when the timer was aborted early - such as when the
29    /// user moves their mouse or otherwise stops being idle.
30    fn abort(&mut self) -> Result<()> {
31        Ok(())
32    }
33    /// Called when another timer was activated after this one
34    fn deactivate(&mut self) -> Result<()> {
35        Ok(())
36    }
37    /// Return true if the timer is disabled and should be skipped. Changes to this value are
38    /// reflected - you may enable a timer that was previously disabled, and xidlehook will call it
39    /// as soon as the timer is passed - or immediately if the timer has already passed.
40    fn disabled(&mut self) -> bool {
41        false
42    }
43}
44
45/// A simple timer that runs a binary executable after a certain
46/// amount of time
47#[derive(Debug, Default)]
48pub struct CmdTimer {
49    /// The idle time required for this timer to activate
50    pub time: Duration,
51    /// The command, if any, to run upon activation
52    pub activation: Option<Command>,
53    /// The command, if any, to run upon abortion
54    pub abortion: Option<Command>,
55    /// The command, if any, to run upon deactivation
56    pub deactivation: Option<Command>,
57    /// Whether or not to disable this timer
58    pub disabled: bool,
59
60    /// The child process that is currently running
61    pub activation_child: Option<Child>,
62}
63impl Timer for CmdTimer {
64    fn time_left(&mut self, idle_time: Duration) -> Result<Option<Duration>> {
65        Ok(self
66            .time
67            .checked_sub(idle_time)
68            .filter(|&dur| dur != Duration::default()))
69    }
70
71    fn abort_urgency(&self) -> Option<Duration> {
72        self.abortion.as_ref().map(|_| Duration::from_secs(1))
73    }
74
75    fn activate(&mut self) -> Result<()> {
76        if let Some(ref mut activation) = self.activation {
77            self.activation_child = Some(activation.spawn()?);
78        }
79        Ok(())
80    }
81    fn abort(&mut self) -> Result<()> {
82        if let Some(ref mut abortion) = self.abortion {
83            abortion.spawn()?;
84        }
85        Ok(())
86    }
87    fn deactivate(&mut self) -> Result<()> {
88        if let Some(ref mut deactivation) = self.deactivation {
89            deactivation.spawn()?;
90        }
91        Ok(())
92    }
93    fn disabled(&mut self) -> bool {
94        if let Some(Ok(None)) = self.activation_child.as_mut().map(|child| child.try_wait()) {
95            // We temporarily disable this timer while the child is still running
96            true
97        } else {
98            // Whether or not this command is disabled
99            self.disabled
100        }
101    }
102}
103
104/// A timer that lets you easily execute a rust callback on
105/// activation
106#[derive(Debug)]
107pub struct CallbackTimer<F>
108where
109    F: FnMut(),
110{
111    time: Duration,
112    f: F,
113
114    /// Whether or not to disable this timer
115    pub disabled: bool,
116}
117impl<'a> CallbackTimer<Box<dyn FnMut() + 'a>> {
118    /// Create a new instance, which boxes the closure to a dynamic
119    /// type. Use `new_unboxed` to use static dispatch, although keep
120    /// in mind this will potentially make you unable to register more
121    /// than one type of callback timer due to its static type.
122    pub fn new<F>(time: Duration, f: F) -> Self
123    where
124        F: FnMut() + 'a,
125    {
126        Self::new_unboxed(time, Box::new(f))
127    }
128}
129impl<F> CallbackTimer<F>
130where
131    F: FnMut(),
132{
133    /// Create a new unboxed instance. Due to it's static type, only
134    /// one type can be used. This means that registering 2 timers
135    /// with 2 different callbacks will conflict. An easy way to
136    /// bypass this is using the `new` function, which behind the
137    /// scenes just wraps the callback in a Box.
138    ///
139    /// TL;DR: Don't use this unless you're planning on using another
140    /// means of dynamic dispatch (an enum?) or if you're a masochist.
141    pub fn new_unboxed(time: Duration, f: F) -> Self {
142        Self {
143            time,
144            f,
145            disabled: false,
146        }
147    }
148}
149impl<F> Timer for CallbackTimer<F>
150where
151    F: FnMut(),
152{
153    fn time_left(&mut self, idle_time: Duration) -> Result<Option<Duration>> {
154        Ok(self
155            .time
156            .checked_sub(idle_time)
157            .filter(|&d| d != Duration::default()))
158    }
159    fn activate(&mut self) -> Result<()> {
160        (self.f)();
161        Ok(())
162    }
163    fn disabled(&mut self) -> bool {
164        self.disabled
165    }
166}