#![warn(
// Harden built-in lints
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
unreachable_pub,
// Harden clippy lints
clippy::cargo_common_metadata,
clippy::clone_on_ref_ptr,
clippy::dbg_macro,
clippy::decimal_literal_representation,
clippy::float_cmp_const,
clippy::get_unwrap,
clippy::integer_arithmetic,
clippy::integer_division,
clippy::print_stdout,
)]
#![allow(
// I don't agree with this lint
clippy::must_use_candidate,
// The integer arithmetic here is mostly regarding indexes into Vecs, indexes where memory
// allocation will fail far, far earlier than the arithmetic will fail.
clippy::integer_arithmetic,
)]
use std::{
cmp,
convert::TryInto,
fmt, ptr,
time::{Duration, Instant},
};
use log::{info, trace, warn};
use nix::libc;
pub type Error = Box<dyn std::error::Error>;
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub mod modules;
pub mod timers;
pub use self::{
modules::{Module, Progress},
timers::Timer,
};
#[derive(Clone, Copy, Debug)]
pub struct TimerInfo {
pub index: usize,
pub length: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Action {
Sleep(Duration),
Forever,
Quit,
}
pub struct Xidlehook<T: Timer, M: Module>
where
T: Timer,
M: Module,
{
module: M,
detect_sleep: bool,
timers: Vec<T>,
next_index: usize,
base_idle_time: Duration,
previous_idle_time: Duration,
aborted: bool,
}
impl<T: Timer> Xidlehook<T, ()> {
pub fn new(timers: Vec<T>) -> Self {
Self {
module: (),
detect_sleep: false,
timers,
next_index: 0,
base_idle_time: Duration::default(),
previous_idle_time: Duration::default(),
aborted: false,
}
}
}
macro_rules! with_module {
($self:expr, $module:expr) => {
Xidlehook {
module: $module,
detect_sleep: $self.detect_sleep,
timers: $self.timers,
next_index: $self.next_index,
base_idle_time: $self.base_idle_time,
previous_idle_time: $self.previous_idle_time,
aborted: $self.aborted,
}
};
}
#[allow(clippy::use_self)]
impl<T, M> Xidlehook<T, M>
where
T: Timer,
M: Module,
{
pub fn with_module<N: Module>(self, other: N) -> Xidlehook<T, N> {
with_module!(self, other)
}
pub fn register<N: Module>(self, other: N) -> Xidlehook<T, (M, N)> {
with_module!(self, (self.module, other))
}
pub fn set_detect_sleep(&mut self, value: bool) {
self.detect_sleep = value;
}
pub fn detect_sleep(&self) -> bool {
self.detect_sleep
}
pub fn with_detect_sleep(mut self, value: bool) -> Self {
self.detect_sleep = value;
self
}
pub fn timers(&self) -> &Vec<T> {
&self.timers
}
pub fn timers_mut(&mut self) -> Result<&mut Vec<T>> {
self.abort()?;
Ok(&mut self.timers)
}
fn previous(&mut self) -> Option<&mut T> {
self.next_index
.checked_sub(1)
.map(move |i| &mut self.timers[i])
}
pub fn abort(&mut self) -> Result<()> {
if self.aborted {
return Ok(());
}
self.aborted = true;
if let Some(prev) = self.previous() {
prev.abort()?;
}
Ok(())
}
pub fn reset(&mut self, absolute_time: Duration) -> Result<()> {
self.abort()?;
trace!("Resetting");
if self.next_index > 0 {
if let Err(err) = self.module.reset() {
self.module.warning(&err)?;
}
self.next_index = 0;
}
self.base_idle_time = absolute_time;
self.previous_idle_time = absolute_time;
self.aborted = false;
Ok(())
}
pub fn trigger(
&mut self,
index: usize,
absolute_time: Duration,
force: bool,
) -> Result<Progress> {
macro_rules! handle {
($progress:expr) => {
match $progress {
Progress::Continue => (),
Progress::Abort => {
trace!("Module requested abort of chain.");
self.abort()?;
return Ok(Progress::Abort);
},
Progress::Reset => {
trace!("Module requested reset of chain.");
self.reset(absolute_time)?;
return Ok(Progress::Reset);
},
Progress::Stop => return Ok(Progress::Stop),
}
};
}
trace!("Activating timer {}", index);
let timer_info = TimerInfo {
index,
length: self.timers.len(),
};
let next = &mut self.timers[index];
match self.module.pre_timer(timer_info) {
Ok(_) if force => (),
Ok(progress) => handle!(progress),
Err(err) => {
self.module.warning(&err)?;
},
}
next.activate()?;
if let Some(previous) = self.previous() {
previous.deactivate()?;
}
self.base_idle_time = absolute_time;
match self.module.post_timer(timer_info) {
Ok(progress) => handle!(progress),
Err(err) => {
self.module.warning(&err)?;
},
}
self.next_index = index + 1;
Ok(Progress::Continue)
}
pub fn poll(&mut self, absolute_time: Duration) -> Result<Action> {
if absolute_time < self.previous_idle_time {
self.reset(Duration::from_millis(0))?;
}
self.previous_idle_time = absolute_time;
let mut max_sleep = Duration::from_nanos(u64::MAX);
let mut first_timer = 0;
while let Some(timer) = self.timers.get_mut(first_timer) {
if !timer.disabled() {
break;
}
if let Some(remaining) = timer.time_left(Duration::from_nanos(0))? {
trace!(
"Taking disabled first timer into account. Remaining: {:?}",
remaining
);
max_sleep = cmp::min(max_sleep, remaining);
}
first_timer += 1;
}
if let Some(timer) = self.timers.get_mut(first_timer) {
if let Some(remaining) = timer.time_left(Duration::from_nanos(0))? {
trace!(
"Taking first timer into account. Remaining: {:?}",
remaining
);
max_sleep = cmp::min(max_sleep, remaining)
}
} else {
return Ok(Action::Forever);
}
if self.aborted {
trace!("This chain was aborted, I won't pursue it");
return Ok(Action::Sleep(max_sleep));
}
let relative_time = absolute_time - self.base_idle_time;
trace!("Relative time: {:?}", relative_time);
let mut next_index = self.next_index;
while let Some(timer) = self.timers.get_mut(next_index) {
if !timer.disabled() {
break;
}
if let Some(remaining) = timer.time_left(relative_time)? {
trace!(
"Taking disabled timer into account. Remaining: {:?}",
remaining
);
max_sleep = cmp::min(max_sleep, remaining);
}
next_index += 1;
}
if let Some(next) = self.timers.get_mut(next_index) {
if let Some(remaining) = next.time_left(relative_time)? {
trace!(
"Taking next enabled timer into account. Remaining: {:?}",
remaining
);
max_sleep = cmp::min(max_sleep, remaining);
} else {
trace!("Triggering timer #{}", next_index);
match self.trigger(next_index, absolute_time, false)? {
Progress::Stop => return Ok(Action::Quit),
_ => (),
}
return self.poll(absolute_time);
}
}
if let Some(abort) = self.previous() {
if let Some(urgency) = abort.abort_urgency() {
trace!(
"Taking abort urgency into account. Remaining: {:?}",
urgency
);
max_sleep = cmp::min(max_sleep, urgency);
}
}
Ok(Action::Sleep(max_sleep))
}
pub fn main_sync<F>(mut self, xcb: &self::modules::Xcb, mut callback: F) -> Result<()>
where
F: FnMut() -> bool,
{
loop {
let idle = xcb.get_idle()?;
match self.poll(idle)? {
Action::Sleep(delay) => {
trace!("Sleeping for {:?}", delay);
let sleep_start = Instant::now();
unsafe {
libc::nanosleep(
&libc::timespec {
tv_sec: delay
.as_secs()
.try_into()
.expect("woah that's one large number"),
tv_nsec: delay
.subsec_nanos()
.try_into()
.expect("woah that's one large number"),
},
ptr::null_mut(),
);
}
if let Some(time_difference) = sleep_start.elapsed().checked_sub(delay) {
if time_difference >= Duration::from_secs(3) && self.detect_sleep {
info!(
"We slept {:?} longer than expected - has the computer been suspended?",
time_difference,
);
self.reset(xcb.get_idle()?)?;
}
}
},
Action::Forever => {
warn!("xidlehook has not, and will never get, anything to do");
break;
},
Action::Quit => break,
}
if callback() {
break;
}
}
Ok(())
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
pub async fn main_async(&mut self, xcb: &self::modules::Xcb) -> Result<()> {
loop {
let idle = xcb.get_idle()?;
match self.poll(idle)? {
Action::Sleep(delay) => {
trace!("Sleeping for {:?}", delay);
let sleep_start = Instant::now();
#[cfg(feature = "async-std")]
async_std::task::sleep(delay).await;
#[cfg(feature = "tokio")]
if cfg!(not(feature = "async-std")) {
tokio::time::delay_for(delay).await;
}
if let Some(time_difference) = sleep_start.elapsed().checked_sub(delay) {
if time_difference >= Duration::from_secs(3) && self.detect_sleep {
info!(
"We slept {:?} longer than expected - has the computer been suspended?",
time_difference,
);
self.reset(xcb.get_idle()?)?;
}
}
},
Action::Forever => {
trace!("Nothing to do");
#[cfg(feature = "async-std")]
async_std::future::pending::<()>().await;
#[cfg(feature = "tokio")]
if cfg!(not(feature = "async-std")) {
use tokio::stream::StreamExt;
tokio::stream::pending::<()>().next().await;
}
},
Action::Quit => break,
}
}
Ok(())
}
}
impl<T, M> fmt::Debug for Xidlehook<T, M>
where
T: Timer,
M: Module + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Modules: {:?}", self.module)
}
}