diff --git a/crates/eww/src/app.rs b/crates/eww/src/app.rs index f55f56b..4c7d7ba 100644 --- a/crates/eww/src/app.rs +++ b/crates/eww/src/app.rs @@ -12,7 +12,10 @@ use simplexpr::dynval::DynVal; use std::collections::{HashMap, HashSet}; use tokio::sync::mpsc::UnboundedSender; use yuck::{ - config::window_geometry::{AnchorPoint, WindowGeometry}, + config::{ + script_var_definition::ScriptVarDefinition, + window_geometry::{AnchorPoint, WindowGeometry}, + }, value::Coords, }; @@ -200,7 +203,20 @@ impl App { } fn update_state(&mut self, fieldname: VarName, value: DynVal) { - self.eww_state.update_variable(fieldname, value) + self.eww_state.update_variable(fieldname.clone(), value); + + if let Ok(linked_poll_vars) = self.eww_config.get_poll_var_link(&fieldname) { + linked_poll_vars.iter().filter_map(|name| self.eww_config.get_script_var(name).ok()).for_each(|var| { + if let ScriptVarDefinition::Poll(poll_var) = var { + match poll_var.run_while_expr.eval(self.eww_state.get_variables()).map(|v| v.as_bool()) { + Ok(Ok(true)) => self.script_var_handler.add(var.clone()), + Ok(Ok(false)) => self.script_var_handler.stop_for_variable(poll_var.name.clone()), + Ok(Err(err)) => error_handling_ctx::print_error(anyhow!(err)), + Err(err) => error_handling_ctx::print_error(anyhow!(err)), + }; + } + }); + } } fn close_window(&mut self, window_name: &String) -> Result<()> { diff --git a/crates/eww/src/config/eww_config.rs b/crates/eww/src/config/eww_config.rs index 1dc6182..9d23120 100644 --- a/crates/eww/src/config/eww_config.rs +++ b/crates/eww/src/config/eww_config.rs @@ -24,11 +24,20 @@ pub struct EwwConfig { windows: HashMap, initial_variables: HashMap, script_vars: HashMap, + + // Links variable which affect state (active/inactive) of poll var to those poll variables + poll_var_links: HashMap>, } impl Default for EwwConfig { fn default() -> Self { - Self { widgets: HashMap::new(), windows: HashMap::new(), initial_variables: HashMap::new(), script_vars: HashMap::new() } + Self { + widgets: HashMap::new(), + windows: HashMap::new(), + initial_variables: HashMap::new(), + script_vars: HashMap::new(), + poll_var_links: HashMap::new(), + } } } @@ -44,6 +53,17 @@ impl EwwConfig { let Config { widget_definitions, window_definitions, var_definitions, mut script_vars } = config; script_vars.extend(crate::config::inbuilt::get_inbuilt_vars()); + + let mut poll_var_links = HashMap::>::new(); + script_vars + .iter() + .filter_map(|(_, var)| if let ScriptVarDefinition::Poll(poll_var) = var { Some(poll_var) } else { None }) + .for_each(|var| { + var.run_while_var_refs + .iter() + .for_each(|name| poll_var_links.entry(name.clone()).or_default().push(var.name.clone())) + }); + Ok(EwwConfig { windows: window_definitions .into_iter() @@ -52,6 +72,7 @@ impl EwwConfig { widgets: widget_definitions, initial_variables: var_definitions.into_iter().map(|(k, v)| (k, v.initial_value)).collect(), script_vars, + poll_var_links, }) } @@ -87,4 +108,8 @@ impl EwwConfig { pub fn get_widget_definitions(&self) -> &HashMap { &self.widgets } + + pub fn get_poll_var_link(&self, name: &VarName) -> Result<&Vec> { + self.poll_var_links.get(name).with_context(|| format!("{} does not links to any poll variable", name.0)) + } } diff --git a/crates/eww/src/config/inbuilt.rs b/crates/eww/src/config/inbuilt.rs index f09af9d..74b4bcb 100644 --- a/crates/eww/src/config/inbuilt.rs +++ b/crates/eww/src/config/inbuilt.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, time::Duration}; -use simplexpr::dynval::DynVal; +use simplexpr::{dynval::DynVal, SimplExpr}; use yuck::config::script_var_definition::{PollScriptVar, ScriptVarDefinition, VarSource}; use crate::config::system_stats::*; @@ -12,6 +12,8 @@ macro_rules! builtin_vars { $( VarName::from($name) => ScriptVarDefinition::Poll(PollScriptVar { name: VarName::from($name), + run_while_expr: SimplExpr::Literal(DynVal::from(true)), + run_while_var_refs: Vec::new(), command: VarSource::Function($fun), initial_value: None, interval: $interval, diff --git a/crates/eww/src/script_var_handler.rs b/crates/eww/src/script_var_handler.rs index a684a3b..ade0425 100644 --- a/crates/eww/src/script_var_handler.rs +++ b/crates/eww/src/script_var_handler.rs @@ -134,6 +134,10 @@ impl PollVarHandler { } async fn start(&mut self, var: PollScriptVar) { + if self.poll_handles.contains_key(&var.name) { + return; + } + log::debug!("starting poll var {}", &var.name); let cancellation_token = CancellationToken::new(); self.poll_handles.insert(var.name.clone(), cancellation_token.clone()); diff --git a/crates/eww/src/util.rs b/crates/eww/src/util.rs index 3660b94..afbfcc6 100644 --- a/crates/eww/src/util.rs +++ b/crates/eww/src/util.rs @@ -116,7 +116,6 @@ pub fn parse_scss_from_file(path: &Path) -> Result { grass::from_string(file_content, &grass_config).map_err(|err| anyhow!("Encountered SCSS parsing error: {:?}", err)) } - #[ext(pub, name = StringExt)] impl> T { /// check if the string is empty after removing all linebreaks and trimming diff --git a/crates/simplexpr/src/ast.rs b/crates/simplexpr/src/ast.rs index 88bd320..6da9546 100644 --- a/crates/simplexpr/src/ast.rs +++ b/crates/simplexpr/src/ast.rs @@ -86,6 +86,39 @@ impl SimplExpr { pub fn synth_literal>(s: T) -> Self { Self::Literal(s.into()) } + + pub fn collect_var_refs_into(&self, dest: &mut Vec) { + match self { + SimplExpr::VarRef(_, x) => dest.push(x.clone()), + SimplExpr::UnaryOp(_, _, x) => x.as_ref().collect_var_refs_into(dest), + SimplExpr::BinOp(_, l, _, r) => { + l.as_ref().collect_var_refs_into(dest); + r.as_ref().collect_var_refs_into(dest); + } + SimplExpr::IfElse(_, a, b, c) => { + a.as_ref().collect_var_refs_into(dest); + b.as_ref().collect_var_refs_into(dest); + c.as_ref().collect_var_refs_into(dest); + } + SimplExpr::JsonAccess(_, value, index) => { + value.as_ref().collect_var_refs_into(dest); + index.as_ref().collect_var_refs_into(dest); + } + SimplExpr::FunctionCall(_, _, args) => args.iter().for_each(|x: &SimplExpr| x.collect_var_refs_into(dest)), + SimplExpr::JsonArray(_, values) => values.iter().for_each(|v| v.collect_var_refs_into(dest)), + SimplExpr::JsonObject(_, entries) => entries.iter().for_each(|(k, v)| { + k.collect_var_refs_into(dest); + v.collect_var_refs_into(dest); + }), + _ => (), + }; + } + + pub fn collect_var_refs(&self) -> Vec { + let mut dest = Vec::new(); + self.collect_var_refs_into(&mut dest); + dest + } } impl Spanned for SimplExpr { diff --git a/crates/yuck/src/config/script_var_definition.rs b/crates/yuck/src/config/script_var_definition.rs index 8687d3e..cbeef42 100644 --- a/crates/yuck/src/config/script_var_definition.rs +++ b/crates/yuck/src/config/script_var_definition.rs @@ -55,6 +55,8 @@ pub enum VarSource { #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] pub struct PollScriptVar { pub name: VarName, + pub run_while_expr: SimplExpr, + pub run_while_var_refs: Vec, pub command: VarSource, pub initial_value: Option, pub interval: std::time::Duration, @@ -71,10 +73,17 @@ impl FromAstElementContent for PollScriptVar { let initial_value = Some(attrs.primitive_optional("initial")?.unwrap_or_else(|| DynVal::from_string(String::new()))); let interval = attrs.primitive_required::("interval")?.as_duration()?; let (script_span, script) = iter.expect_literal()?; + + let run_while_expr = + attrs.ast_optional::("run-while")?.unwrap_or_else(|| SimplExpr::Literal(DynVal::from(true))); + let run_while_var_refs = run_while_expr.collect_var_refs(); + iter.expect_done()?; Self { name_span, name: VarName(name), + run_while_expr, + run_while_var_refs, command: VarSource::Shell(script_span, script.to_string()), initial_value, interval, diff --git a/docs/src/configuration.md b/docs/src/configuration.md index 70dc1dd..c921756 100644 --- a/docs/src/configuration.md +++ b/docs/src/configuration.md @@ -154,8 +154,13 @@ They may also be useful to have buttons within eww change what is shown within y **Polling variables (`defpoll`)** ```lisp +(defvar time-visible false) ; for :run-while property of below variable + ; when this turns true, the polling starts and + ; var gets updated with given interval + (defpoll time :interval "1s" - :initial "initial-value" ; setting initial is optional + :initial "initial-value" ; optional, defaults to poll at startup + :run-while time-visible ; optional, defaults to 'true' `date +%H:%M:%S`) ``` @@ -163,7 +168,7 @@ A polling variable is a variable which runs a provided shell-script repeatedly, This may be the most commonly used type of variable. They are useful to access any quickly retrieved value repeatedly, -and thus are the perfect choice for showing your time, date, as well as other bits of information such as your volume. +and thus are the perfect choice for showing your time, date, as well as other bits of information such as pending package updates, weather and battery-level. You can also specify an initial-value, this should prevent eww from waiting for the result of a give command during startup, thus, making the startup time faster. @@ -180,9 +185,12 @@ A listening variable runs a script once, and reads its output continously. Whenever the script outputs a new line, the value will be updated to that new line. In the example given above, the value of `foo` will start out as `"whatever"`, and will change whenever a new line is appended to `/tmp/some_file`. -These are particularly useful if you have a script that can monitor some value on its own. +These are particularly useful when you want to apply changes instantaneously when an operation happens if you have a script +that can monitor some value on its own. Volume, brighness, workspaces that get added/removed at runtime, +monitoring currently focused desktop/tag, etc. are the most common usecases of this type of variable. +These are particularly efficient and should be preffered if possible. + For example, the command `xprop -spy -root _NET_CURRENT_DESKTOP` writes the currently focused desktop whenever it changes. -This can be used to implement a workspace widget for a bar, for example. Another example usecase is monitoring the currently playing song with playerctl: `playerctl --follow metadata --format {{title}}`. **Built-in "magic" variables**