Add support for tail-based script-vars
This commit is contained in:
parent
8391a9a03e
commit
6a5839a93e
10 changed files with 176 additions and 43 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -317,6 +317,7 @@ dependencies = [
|
|||
"maplit",
|
||||
"notify",
|
||||
"num",
|
||||
"popol",
|
||||
"pretty_assertions",
|
||||
"pretty_env_logger",
|
||||
"ref-cast",
|
||||
|
@ -1269,6 +1270,15 @@ version = "0.3.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
|
||||
|
||||
[[package]]
|
||||
name = "popol"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53bb69490e466db39a4442cc36fa3a593cbe94f688c55ecd2568dc1535c4fabf"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.9"
|
||||
|
|
|
@ -35,6 +35,7 @@ pretty_env_logger = "0.4"
|
|||
lazy_static = "1.4.0"
|
||||
libc = "0.2"
|
||||
ref-cast = "1.0"
|
||||
popol = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.6.1"
|
||||
|
|
|
@ -136,7 +136,7 @@ impl App {
|
|||
|
||||
pub fn reload_all_windows(&mut self, config: config::EwwConfig) -> Result<()> {
|
||||
// refresh script-var poll stuff
|
||||
if let Err(e) = self.script_var_handler.setup_command_poll_tasks(&config) {
|
||||
if let Err(e) = self.script_var_handler.initialize_clean(config.get_script_vars().clone()) {
|
||||
eprintln!("Error while setting up script-var commands: {:?}", e);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,9 +28,7 @@ impl WidgetDefinition {
|
|||
);
|
||||
}
|
||||
|
||||
let size: Option<_> = try {
|
||||
(xml.attr("width").ok()?, xml.attr("height").ok()?)
|
||||
};
|
||||
let size: Option<_> = Option::zip(xml.attr("width").ok(), xml.attr("height").ok());
|
||||
let size: Option<Result<_>> = size.map(|(x, y)| Ok((x.parse()?, y.parse()?)));
|
||||
|
||||
WidgetDefinition {
|
||||
|
@ -107,11 +105,7 @@ impl WidgetUse {
|
|||
|
||||
pub fn from_text_with_var_refs(text: &str) -> Self {
|
||||
WidgetUse {
|
||||
name: "layout".to_owned(),
|
||||
attrs: hashmap! {
|
||||
"halign".to_owned() => AttrValue::Concrete(PrimitiveValue::String("center".to_owned())),
|
||||
"space-evenly".to_owned() => AttrValue::Concrete(PrimitiveValue::String("false".to_owned())),
|
||||
},
|
||||
name: "text".to_owned(),
|
||||
children: parse_string_with_var_refs(text)
|
||||
.into_iter()
|
||||
.map(StringOrVarRef::to_attr_value)
|
||||
|
|
|
@ -29,20 +29,50 @@ macro_rules! ensure_xml_tag_is {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ScriptVar {
|
||||
pub struct PollScriptVar {
|
||||
pub name: VarName,
|
||||
pub command: String,
|
||||
pub interval: std::time::Duration,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TailScriptVar {
|
||||
pub name: VarName,
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ScriptVar {
|
||||
Poll(PollScriptVar),
|
||||
Tail(TailScriptVar),
|
||||
}
|
||||
|
||||
impl ScriptVar {
|
||||
pub fn name(&self) -> &VarName {
|
||||
match self {
|
||||
ScriptVar::Poll(x) => &x.name,
|
||||
ScriptVar::Tail(x) => &x.name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initial_value(&self) -> Result<PrimitiveValue> {
|
||||
match self {
|
||||
ScriptVar::Poll(x) => Ok(crate::run_command(&x.command)?),
|
||||
ScriptVar::Tail(_) => Ok(PrimitiveValue::String(String::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_xml_element(xml: XmlElement) -> Result<Self> {
|
||||
ensure_xml_tag_is!(xml, "script-var");
|
||||
|
||||
let name = VarName(xml.attr("name")?.to_owned());
|
||||
let interval = util::parse_duration(xml.attr("interval")?)?;
|
||||
let command = xml.only_child()?.as_text()?.text();
|
||||
Ok(ScriptVar { name, interval, command })
|
||||
if let Ok(interval) = xml.attr("interval") {
|
||||
let interval = util::parse_duration(interval)?;
|
||||
Ok(ScriptVar::Poll(PollScriptVar { name, command, interval }))
|
||||
} else {
|
||||
Ok(ScriptVar::Tail(TailScriptVar { name, command }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,7 +155,7 @@ impl EwwConfig {
|
|||
let mut vars = self
|
||||
.script_vars
|
||||
.iter()
|
||||
.map(|var| Ok((var.name.clone(), crate::eww_state::run_command(&var.command)?)))
|
||||
.map(|var| Ok((var.name().clone(), var.initial_value()?)))
|
||||
.collect::<Result<HashMap<_, _>>>()?;
|
||||
vars.extend(self.get_default_vars().clone());
|
||||
Ok(vars)
|
||||
|
|
|
@ -206,7 +206,7 @@ impl EwwState {
|
|||
|
||||
/// Run a command and get the output
|
||||
pub fn run_command(cmd: &str) -> Result<PrimitiveValue> {
|
||||
let output = String::from_utf8(Command::new("/bin/bash").arg("-c").arg(cmd).output()?.stdout)?;
|
||||
let output = String::from_utf8(Command::new("/bin/sh").arg("-c").arg(cmd).output()?.stdout)?;
|
||||
let output = output.trim_matches('\n');
|
||||
Ok(PrimitiveValue::from(output))
|
||||
}
|
||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -56,7 +56,7 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug, Serialize, Deserialize)]
|
||||
#[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Opt {
|
||||
#[structopt(short = "-c", parse(from_os_str))]
|
||||
config_file: Option<PathBuf>,
|
||||
|
@ -67,7 +67,7 @@ pub struct Opt {
|
|||
#[structopt(short = "-d", long = "--detach")]
|
||||
should_detach: bool,
|
||||
}
|
||||
#[derive(StructOpt, Debug, Serialize, Deserialize)]
|
||||
#[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum OptAction {
|
||||
#[structopt(name = "update")]
|
||||
Update { fieldname: VarName, value: PrimitiveValue },
|
||||
|
@ -122,6 +122,10 @@ fn get_config_file_path() -> PathBuf {
|
|||
}
|
||||
|
||||
fn initialize_server(opts: Opt) -> Result<()> {
|
||||
if opts.action == OptAction::KillServer {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let config_file_path = opts.config_file.clone().unwrap_or_else(get_config_file_path);
|
||||
let config_dir = config_file_path
|
||||
.clone()
|
||||
|
@ -138,7 +142,7 @@ fn initialize_server(opts: Opt) -> Result<()> {
|
|||
let (evt_send, evt_recv) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||
|
||||
let mut script_var_handler = script_var_handler::ScriptVarHandler::new(evt_send.clone())?;
|
||||
script_var_handler.setup_command_poll_tasks(&eww_config)?;
|
||||
script_var_handler.initialize_clean(eww_config.get_script_vars().clone())?;
|
||||
|
||||
let mut app = app::App {
|
||||
eww_state: EwwState::from_default_vars(eww_config.generate_initial_state()?.clone()),
|
||||
|
|
|
@ -1,38 +1,75 @@
|
|||
use crate::{app, config, eww_state};
|
||||
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::EwwEvent>,
|
||||
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::EwwEvent>) -> 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,
|
||||
})
|
||||
}
|
||||
|
||||
/// clears and stops the currently running poll handles, then opens the new
|
||||
/// ones as configured
|
||||
pub fn setup_command_poll_tasks(&mut self, config: &config::EwwConfig) -> Result<()> {
|
||||
log::info!("reloading handler for poll script vars");
|
||||
/// 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 = config
|
||||
.get_script_vars()
|
||||
self.poll_handles = poll_script_vars
|
||||
.iter()
|
||||
.map(|var| {
|
||||
self.poll_executor.schedule_fixed_interval(
|
||||
std::time::Duration::from_secs(0),
|
||||
Duration::from_secs(0),
|
||||
var.interval,
|
||||
glib::clone!(@strong var, @strong evt_send => move |_| {
|
||||
let result = eww_state::run_command(&var.command)
|
||||
|
@ -46,4 +83,73 @@ impl ScriptVarHandler {
|
|||
.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::EwwEvent::UpdateVar(
|
||||
var_name.clone(),
|
||||
PrimitiveValue::parse_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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ const CMD_STRING_PLACEHODLER: &str = "{}";
|
|||
/// placeholder ('{}') which will be replaced by the value provided as [arg]
|
||||
pub fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
|
||||
let cmd = cmd.replace(CMD_STRING_PLACEHODLER, &format!("{}", arg));
|
||||
if let Err(e) = Command::new("bash").arg("-c").arg(cmd).output() {
|
||||
if let Err(e) = Command::new("/bin/sh").arg("-c").arg(cmd).output() {
|
||||
eprintln!("{}", e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ use crate::{
|
|||
};
|
||||
use anyhow::*;
|
||||
use gtk::{prelude::*, ImageExt};
|
||||
use maplit::hashmap;
|
||||
use std::{cell::RefCell, path::Path, rc::Rc};
|
||||
|
||||
use gdk_pixbuf;
|
||||
|
@ -187,21 +186,10 @@ fn build_gtk_label(bargs: &mut BuilderArgs) -> Result<gtk::Label> {
|
|||
Ok(gtk_widget)
|
||||
}
|
||||
|
||||
// TODO this is rather ugly,.....
|
||||
fn build_gtk_text(bargs: &mut BuilderArgs) -> Result<gtk::Label> {
|
||||
let text = bargs
|
||||
.widget
|
||||
.children
|
||||
.first()
|
||||
.context("text node must contain exactly one child")?
|
||||
.get_attr("text")?;
|
||||
let gtk_widget = gtk::Label::new(None);
|
||||
bargs.eww_state.resolve(
|
||||
bargs.window_name,
|
||||
bargs.local_env,
|
||||
hashmap! {"text".to_owned() => text.clone() },
|
||||
glib::clone!(@strong gtk_widget => move |v| { gtk_widget.set_text(&v.get("text").unwrap().as_string().unwrap()); Ok(())}),
|
||||
);
|
||||
fn build_gtk_text(_bargs: &mut BuilderArgs) -> Result<gtk::Box> {
|
||||
let gtk_widget = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
||||
gtk_widget.set_halign(gtk::Align::Center);
|
||||
gtk_widget.set_homogeneous(false);
|
||||
Ok(gtk_widget)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue