1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
//! Utilities for running scheduled updates to widgets
use crate::bar::widgets::Text;
use penrose::util::spawn_with_args;
use std::{
cmp::max,
fmt,
sync::{Arc, Mutex},
thread,
time::{Duration, Instant},
};
use tracing::trace;
/// The minimum allowed interval for an [UpdateSchedule].
pub const MIN_DURATION: Duration = Duration::from_secs(1);
/// For widgets that want to have their content updated periodically by the status bar by calling
/// an external function.
///
/// See [IntervalText] for a simple implementation of this behaviour
pub struct UpdateSchedule {
pub(crate) next: Instant,
pub(crate) interval: Duration,
pub(crate) get_text: Box<dyn Fn() -> Option<String> + Send + 'static>,
pub(crate) txt: Arc<Mutex<Text>>,
}
impl fmt::Debug for UpdateSchedule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UpdateSchedule")
.field("next", &self.next)
.field("interval", &self.interval)
.field("txt", &self.txt)
.finish()
}
}
impl UpdateSchedule {
/// Construct a new [UpdateSchedule] specifying the interval that the [Widget] content should
/// be updated on and an update function for producing the widget content.
///
/// The updated content will then be stored in the provided `Arc<Mutex<Text>>` for access
/// within your widget logic.
pub fn new(
interval: Duration,
get_text: Box<dyn Fn() -> Option<String> + Send + 'static>,
txt: Arc<Mutex<Text>>,
) -> Self {
if interval < MIN_DURATION {
panic!("UpdateSchedule interval is too small: {interval:?} < {MIN_DURATION:?}");
}
Self {
next: Instant::now(),
interval,
get_text,
txt,
}
}
/// Call our `get_text` function to update the contents of our paired [CronText] and then bump
/// our `next` time to the next interval point.
///
/// This is gives us behaviour of a consistent interval between invocation end/start but not
/// necessarily a consistent interval between start/start depending on how long `get_text`
/// takes to run.
fn update_text(&mut self) {
trace!("running UpdateSchedule get_text");
let s = (self.get_text)();
trace!(?s, "ouput from running get_text");
if let Some(s) = s {
let mut t = match self.txt.lock() {
Ok(inner) => inner,
Err(poisoned) => poisoned.into_inner(),
};
t.set_text(s);
}
let next = self.next + self.interval;
let now = Instant::now();
self.next = max(next, now);
trace!(next = ?self.next, "next update at");
}
}
/// Run the polling thread for a set of [UpdateSchedule]s and update their contents on
/// their requested intervals.
pub(crate) fn run_update_schedules(mut schedules: Vec<UpdateSchedule>) {
thread::spawn(move || loop {
trace!("running UpdateSchedule updates for all pending widgets");
while schedules[0].next < Instant::now() {
schedules[0].update_text();
schedules.sort_by(|a, b| a.next.cmp(&b.next));
}
// FIXME: this is a hack at the moment to ensure that an event drops into the main
// window manager event loop and triggers the `on_event` hook of the status bar.
let _ = spawn_with_args("xsetroot", &["-name", ""]);
let interval = schedules[0].next - Instant::now();
trace!(?interval, "sleeping until next update point");
thread::sleep(interval);
});
}