From 28af00becd2c8b46d4ce7ab452ab8786d192cffa Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Mon, 29 Aug 2022 15:33:27 +0200 Subject: [PATCH] Refactor app.rs, cleaning up run-while handling --- crates/eww/src/app.rs | 70 +++++---- crates/eww/src/config/eww_config.rs | 32 ++--- crates/eww/src/config/inbuilt.rs | 1 - crates/eww/src/daemon_response.rs | 15 +- .../yuck/src/config/script_var_definition.rs | 3 - mf:w | 134 ------------------ 6 files changed, 68 insertions(+), 187 deletions(-) delete mode 100644 mf:w diff --git a/crates/eww/src/app.rs b/crates/eww/src/app.rs index f281cf9..fa91e38 100644 --- a/crates/eww/src/app.rs +++ b/crates/eww/src/app.rs @@ -29,6 +29,9 @@ use yuck::{ value::Coords, }; +/// A command for the eww daemon. +/// While these are mostly generated from eww CLI commands (see [`opts::ActionWithServer`]), +/// they may also be generated from other places internally. #[derive(Debug)] pub enum DaemonCommand { NoOp, @@ -115,7 +118,7 @@ impl std::fmt::Debug for App { } impl App { - /// Handle a DaemonCommand event. + /// Handle a [DaemonCommand] event. pub fn handle_command(&mut self, event: DaemonCommand) { log::debug!("Handling event: {:?}", &event); let result: Result<_> = try { @@ -126,7 +129,7 @@ impl App { } DaemonCommand::UpdateVars(mappings) => { for (var_name, new_value) in mappings { - self.update_global_state(var_name, new_value); + self.update_global_variable(var_name, new_value); } } DaemonCommand::ReloadConfigAndCss(sender) => { @@ -146,7 +149,6 @@ impl App { DaemonCommand::KillServer => { log::info!("Received kill command, stopping server!"); self.stop_application(); - let _ = crate::application_lifecycle::send_exit(); } DaemonCommand::CloseAll => { log::info!("Received close command, closing all windows"); @@ -169,15 +171,12 @@ impl App { } DaemonCommand::OpenWindow { window_name, pos, size, anchor, screen: monitor, should_toggle, sender } => { let is_open = self.open_windows.contains_key(&window_name); - let result = if is_open { - if should_toggle { - self.close_window(&window_name) - } else { - // user should use `eww reload` to reload windows (https://github.com/elkowar/eww/issues/260) - Ok(()) - } - } else { + let result = if !is_open { self.open_window(&window_name, pos, size, monitor, anchor) + } else if should_toggle { + self.close_window(&window_name) + } else { + Ok(()) }; sender.respond_with_result(result)?; } @@ -231,35 +230,48 @@ impl App { } } + /// Fully stop eww: + /// close all windows, stop the script_var_handler, quit the gtk appliaction and send the exit instruction to the lifecycle manager fn stop_application(&mut self) { self.script_var_handler.stop_all(); for (_, window) in self.open_windows.drain() { window.close(); } gtk::main_quit(); + let _ = crate::application_lifecycle::send_exit(); } - fn update_global_state(&mut self, fieldname: VarName, value: DynVal) { - let result = self.scope_graph.borrow_mut().update_global_value(&fieldname, value); + fn update_global_variable(&mut self, name: VarName, value: DynVal) { + let result = self.scope_graph.borrow_mut().update_global_value(&name, value); if let Err(err) = result { error_handling_ctx::print_error(err); } - 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 { - let scope_graph = self.scope_graph.borrow(); - let run_while = scope_graph - .evaluate_simplexpr_in_scope(scope_graph.root_index, &poll_var.run_while_expr) - .map(|v| v.as_bool()); - match run_while { - 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)), - }; - } - }); + self.apply_run_while_expressions_mentioning(&name); + } + + /// Variables may be referenced in defpoll :run-while expressions. + /// Thus, when a variable changes, the run-while conditions of all variables + /// that mention the changed variable need to be reevaluated and reapplied. + fn apply_run_while_expressions_mentioning(&mut self, name: &VarName) { + let mentioning_vars = match self.eww_config.get_run_while_mentions_of(&name) { + Some(x) => x, + None => return, + }; + let mentioning_vars = mentioning_vars.iter().filter_map(|name| self.eww_config.get_script_var(name).ok()); + for var in mentioning_vars { + if let ScriptVarDefinition::Poll(poll_var) = var { + let scope_graph = self.scope_graph.borrow(); + let run_while_result = scope_graph + .evaluate_simplexpr_in_scope(scope_graph.root_index, &poll_var.run_while_expr) + .map(|v| v.as_bool()); + match run_while_result { + 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)), + }; + } } } @@ -276,7 +288,7 @@ impl App { let unused_variables = self.scope_graph.borrow().currently_unused_globals(); for unused_var in unused_variables { - log::debug!("stopping for {}", &unused_var); + log::debug!("stopping script-var {}", &unused_var); self.script_var_handler.stop_for_variable(unused_var.clone()); } diff --git a/crates/eww/src/config/eww_config.rs b/crates/eww/src/config/eww_config.rs index e10a3d2..f17a96a 100644 --- a/crates/eww/src/config/eww_config.rs +++ b/crates/eww/src/config/eww_config.rs @@ -11,7 +11,7 @@ use yuck::{ use simplexpr::dynval::DynVal; -use crate::{config::inbuilt, error_handling_ctx, widgets::widget_definitions, paths::EwwPaths}; +use crate::{config::inbuilt, error_handling_ctx, paths::EwwPaths, widgets::widget_definitions}; use super::script_var; @@ -30,8 +30,8 @@ pub struct EwwConfig { initial_variables: HashMap, script_vars: HashMap, - // Links variable which affect state (active/inactive) of poll var to those poll variables - poll_var_links: HashMap>, + // map of variables to all pollvars which refer to them in their run-while-expression + run_while_mentions: HashMap>, } impl Default for EwwConfig { @@ -41,7 +41,7 @@ impl Default for EwwConfig { windows: HashMap::new(), initial_variables: HashMap::new(), script_vars: HashMap::new(), - poll_var_links: HashMap::new(), + run_while_mentions: HashMap::new(), } } } @@ -76,22 +76,21 @@ impl EwwConfig { script_vars.extend(inbuilt::get_inbuilt_vars()); var_definitions.extend(inbuilt::get_magic_constants(eww_paths)); - 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())) - }); + let mut run_while_mentions = HashMap::>::new(); + for var in script_vars.values() { + if let ScriptVarDefinition::Poll(var) = var { + for name in var.run_while_expr.collect_var_refs() { + run_while_mentions.entry(name.clone()).or_default().push(var.name.clone()) + } + } + } Ok(EwwConfig { windows: window_definitions, widgets: widget_definitions, initial_variables: var_definitions.into_iter().map(|(k, v)| (k, v.initial_value)).collect(), script_vars, - poll_var_links, + run_while_mentions, }) } @@ -128,7 +127,8 @@ impl EwwConfig { &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)) + /// Given a variable name, get the names of all variables that reference that variable in their run-while (active/inactive) state + pub fn get_run_while_mentions_of(&self, name: &VarName) -> Option<&Vec> { + self.run_while_mentions.get(name) } } diff --git a/crates/eww/src/config/inbuilt.rs b/crates/eww/src/config/inbuilt.rs index 66a2c17..672b6d6 100644 --- a/crates/eww/src/config/inbuilt.rs +++ b/crates/eww/src/config/inbuilt.rs @@ -18,7 +18,6 @@ macro_rules! define_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/daemon_response.rs b/crates/eww/src/daemon_response.rs index e2189dd..c60deeb 100644 --- a/crates/eww/src/daemon_response.rs +++ b/crates/eww/src/daemon_response.rs @@ -1,5 +1,12 @@ +//! Types to manage messages that notify the eww client over the result of a command +//! +//! Communcation between the daemon and eww client happens via IPC. +//! If the daemon needs to send messages back to the client as a response to a command (mostly for CLI output), +//! this happens via the DaemonResponse types + use anyhow::{Context, Result}; use itertools::Itertools; +use tokio::sync::mpsc; use crate::error_handling_ctx; @@ -12,10 +19,10 @@ pub enum DaemonResponse { } #[derive(Debug)] -pub struct DaemonResponseSender(tokio::sync::mpsc::UnboundedSender); +pub struct DaemonResponseSender(mpsc::UnboundedSender); -pub fn create_pair() -> (DaemonResponseSender, tokio::sync::mpsc::UnboundedReceiver) { - let (sender, recv) = tokio::sync::mpsc::unbounded_channel(); +pub fn create_pair() -> (DaemonResponseSender, mpsc::UnboundedReceiver) { + let (sender, recv) = mpsc::unbounded_channel(); (DaemonResponseSender(sender), recv) } @@ -56,4 +63,4 @@ impl DaemonResponseSender { } } -pub type DaemonResponseReceiver = tokio::sync::mpsc::UnboundedReceiver; +pub type DaemonResponseReceiver = mpsc::UnboundedReceiver; diff --git a/crates/yuck/src/config/script_var_definition.rs b/crates/yuck/src/config/script_var_definition.rs index cbeef42..6e4725d 100644 --- a/crates/yuck/src/config/script_var_definition.rs +++ b/crates/yuck/src/config/script_var_definition.rs @@ -56,7 +56,6 @@ pub enum VarSource { 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, @@ -76,14 +75,12 @@ impl FromAstElementContent for PollScriptVar { 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/mf:w b/mf:w deleted file mode 100644 index e10a3d2..0000000 --- a/mf:w +++ /dev/null @@ -1,134 +0,0 @@ -use anyhow::{bail, Context, Result}; -use eww_shared_util::VarName; -use std::collections::HashMap; -use yuck::{ - config::{ - file_provider::YuckFiles, script_var_definition::ScriptVarDefinition, validate::ValidationError, - widget_definition::WidgetDefinition, window_definition::WindowDefinition, Config, - }, - error::AstError, -}; - -use simplexpr::dynval::DynVal; - -use crate::{config::inbuilt, error_handling_ctx, widgets::widget_definitions, paths::EwwPaths}; - -use super::script_var; - -/// Load an [`EwwConfig`] from the config dir of the given [`crate::EwwPaths`], -/// resetting and applying the global YuckFiles object in [`crate::error_handling_ctx`]. -pub fn read_from_eww_paths(eww_paths: &EwwPaths) -> Result { - error_handling_ctx::clear_files(); - EwwConfig::read_from_dir(&mut error_handling_ctx::YUCK_FILES.write().unwrap(), eww_paths) -} - -/// Eww configuration structure. -#[derive(Debug, Clone)] -pub struct EwwConfig { - widgets: HashMap, - 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(), - poll_var_links: HashMap::new(), - } - } -} - -impl EwwConfig { - /// Load an [`EwwConfig`] from the config dir of the given [`crate::EwwPaths`], reading the main config file. - pub fn read_from_dir(files: &mut YuckFiles, eww_paths: &EwwPaths) -> Result { - let yuck_path = eww_paths.get_yuck_path(); - if !yuck_path.exists() { - bail!("The configuration file `{}` does not exist", yuck_path.display()); - } - let config = Config::generate_from_main_file(files, yuck_path)?; - - // run some validations on the configuration - let magic_globals: Vec<_> = inbuilt::INBUILT_VAR_NAMES - .into_iter() - .chain(inbuilt::MAGIC_CONSTANT_NAMES) - .into_iter() - .map(|x| VarName::from(x.clone())) - .collect(); - yuck::config::validate::validate(&config, magic_globals)?; - - for (name, def) in &config.widget_definitions { - if widget_definitions::BUILTIN_WIDGET_NAMES.contains(&name.as_str()) { - return Err( - AstError::ValidationError(ValidationError::AccidentalBuiltinOverride(def.span, name.to_string())).into() - ); - } - } - - let Config { widget_definitions, window_definitions, mut var_definitions, mut script_vars } = config; - script_vars.extend(inbuilt::get_inbuilt_vars()); - var_definitions.extend(inbuilt::get_magic_constants(eww_paths)); - - 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, - widgets: widget_definitions, - initial_variables: var_definitions.into_iter().map(|(k, v)| (k, v.initial_value)).collect(), - script_vars, - poll_var_links, - }) - } - - // TODO this is kinda ugly - pub fn generate_initial_state(&self) -> Result> { - let mut vars = self - .script_vars - .iter() - .map(|(name, var)| Ok((name.clone(), script_var::initial_value(var)?))) - .collect::>>()?; - vars.extend(self.initial_variables.clone()); - Ok(vars) - } - - pub fn get_windows(&self) -> &HashMap { - &self.windows - } - - pub fn get_window(&self, name: &str) -> Result<&WindowDefinition> { - self.windows.get(name).with_context(|| { - format!( - "No window named '{}' exists in config.\nThis may also be caused by your config failing to load properly, \ - please check for any other errors in that case.", - name - ) - }) - } - - pub fn get_script_var(&self, name: &VarName) -> Result<&ScriptVarDefinition> { - self.script_vars.get(name).with_context(|| format!("No script var named '{}' exists", name)) - } - - 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)) - } -}