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}