156 lines
5.8 KiB
Rust
156 lines
5.8 KiB
Rust
use std::{
|
|
collections::HashMap,
|
|
io::BufReader,
|
|
process::{ChildStdout, Stdio},
|
|
time::Duration,
|
|
};
|
|
|
|
use crate::{
|
|
app, config, eww_state,
|
|
value::{PrimitiveValue, VarName},
|
|
};
|
|
use anyhow::*;
|
|
use glib;
|
|
use itertools::Itertools;
|
|
use scheduled_executor;
|
|
use std::io::BufRead;
|
|
|
|
/// Handler that manages running and updating [ScriptVar]s
|
|
pub struct ScriptVarHandler {
|
|
evt_send: glib::Sender<app::EwwCommand>,
|
|
pub poll_handles: Vec<scheduled_executor::executor::TaskHandle>,
|
|
pub poll_executor: scheduled_executor::CoreExecutor,
|
|
pub tail_handler_thread: Option<stoppable_thread::StoppableHandle<()>>,
|
|
}
|
|
|
|
impl ScriptVarHandler {
|
|
pub fn new(evt_send: glib::Sender<app::EwwCommand>) -> Result<Self> {
|
|
log::info!("initializing handler for poll script vars");
|
|
Ok(ScriptVarHandler {
|
|
evt_send,
|
|
poll_handles: Vec::new(),
|
|
poll_executor: scheduled_executor::CoreExecutor::new()?,
|
|
tail_handler_thread: None,
|
|
})
|
|
}
|
|
|
|
/// stop all running handlers
|
|
pub fn stop(&mut self) {
|
|
self.poll_handles.iter().for_each(|handle| handle.stop());
|
|
self.poll_handles.clear();
|
|
self.tail_handler_thread.take().map(|handle| handle.stop());
|
|
}
|
|
|
|
/// initialize this handler, cleaning up any previously ran executors and
|
|
/// threads.
|
|
pub fn initialize_clean(&mut self, script_vars: Vec<config::ScriptVar>) -> Result<()> {
|
|
self.stop();
|
|
|
|
let mut poll_script_vars = Vec::new();
|
|
let mut tail_script_vars = Vec::new();
|
|
for var in script_vars {
|
|
match var {
|
|
config::ScriptVar::Poll(x) => poll_script_vars.push(x),
|
|
config::ScriptVar::Tail(x) => tail_script_vars.push(x),
|
|
}
|
|
}
|
|
self.setup_poll_tasks(&poll_script_vars)?;
|
|
self.setup_tail_tasks(&tail_script_vars)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// initialize the poll handler thread.
|
|
fn setup_poll_tasks(&mut self, poll_script_vars: &[config::PollScriptVar]) -> Result<()> {
|
|
log::info!("initializing handler for poll script vars");
|
|
self.poll_handles.iter().for_each(|handle| handle.stop());
|
|
self.poll_handles.clear();
|
|
|
|
let evt_send = self.evt_send.clone();
|
|
self.poll_handles = poll_script_vars
|
|
.iter()
|
|
.map(|var| {
|
|
self.poll_executor.schedule_fixed_interval(
|
|
Duration::from_secs(0),
|
|
var.interval,
|
|
glib::clone!(@strong var, @strong evt_send => move |_| {
|
|
let result = eww_state::run_command(&var.command)
|
|
.and_then(|output| Ok(evt_send.send(app::EwwCommand::UpdateVar(var.name.clone(), output))?));
|
|
if let Err(e) = result {
|
|
eprintln!("Error while running script-var command: {:?}", e);
|
|
}
|
|
}),
|
|
)
|
|
})
|
|
.collect_vec();
|
|
Ok(())
|
|
}
|
|
|
|
/// initialize the tail_var handler thread
|
|
pub fn setup_tail_tasks(&mut self, tail_script_vars: &[config::TailScriptVar]) -> Result<()> {
|
|
log::info!("initializing handler for tail script vars");
|
|
let mut sources = popol::Sources::with_capacity(tail_script_vars.len());
|
|
|
|
let mut command_out_readers: HashMap<VarName, BufReader<_>> = tail_script_vars
|
|
.iter()
|
|
.filter_map(|var| Some((var.name.clone(), try_run_command(&var.command)?)))
|
|
.collect();
|
|
|
|
for (var_name, reader) in command_out_readers.iter() {
|
|
sources.register(var_name.clone(), reader.get_ref(), popol::interest::READ);
|
|
}
|
|
|
|
let mut events = popol::Events::with_capacity(tail_script_vars.len());
|
|
let evt_send = self.evt_send.clone();
|
|
// TODO this is rather ugly
|
|
let thread_handle = stoppable_thread::spawn(move |stopped| {
|
|
while !stopped.get() {
|
|
let result: Result<_> = try {
|
|
sources.wait(&mut events)?;
|
|
for (var_name, event) in events.iter() {
|
|
if event.readable {
|
|
let handle = command_out_readers
|
|
.get_mut(var_name)
|
|
.with_context(|| format!("No command output handle found for variable '{}'", var_name))?;
|
|
let mut buffer = String::new();
|
|
handle.read_line(&mut buffer)?;
|
|
evt_send.send(app::EwwCommand::UpdateVar(
|
|
var_name.clone(),
|
|
PrimitiveValue::from_string(buffer),
|
|
))?;
|
|
} else if event.hangup {
|
|
command_out_readers.remove(var_name);
|
|
}
|
|
}
|
|
};
|
|
if let Err(err) = result {
|
|
eprintln!("Error in script-var tail handler thread: {:?}", err);
|
|
continue;
|
|
}
|
|
}
|
|
});
|
|
self.tail_handler_thread = Some(thread_handle);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Run a command in sh, returning its stdout-handle wrapped in a
|
|
/// [`BufReader`]. If running the command fails, will print a warning
|
|
/// and return `None`.
|
|
fn try_run_command(command: &str) -> Option<BufReader<ChildStdout>> {
|
|
let result = std::process::Command::new("sh")
|
|
.arg("-c")
|
|
.arg(command)
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::inherit())
|
|
.stdin(Stdio::null())
|
|
.spawn()
|
|
.map(|mut x| x.stdout.take().unwrap());
|
|
|
|
match result {
|
|
Ok(stdout) => Some(BufReader::new(stdout)),
|
|
Err(err) => {
|
|
eprintln!("WARN: Error running command from script-variable: {:?}", err);
|
|
None
|
|
}
|
|
}
|
|
}
|