diff --git a/src/app.rs b/src/app.rs index 4b379f9..dc5ab74 100644 --- a/src/app.rs +++ b/src/app.rs @@ -16,7 +16,7 @@ pub enum EwwEvent { pub struct App { pub eww_state: EwwState, pub eww_config: config::EwwConfig, - pub windows: HashMap, + pub windows: HashMap, pub css_provider: gtk::CssProvider, pub app_evt_send: glib::Sender, #[debug_stub = "ScriptVarHandler(...)"] @@ -54,19 +54,21 @@ impl App { } fn update_state(&mut self, fieldname: VarName, value: PrimitiveValue) -> Result<()> { - self.eww_state.update_value(fieldname, value) + self.eww_state.update_variable(fieldname, value) } - fn close_window(&mut self, window_name: &str) -> Result<()> { + fn close_window(&mut self, window_name: &config::WindowName) -> Result<()> { let window = self .windows .get(window_name) .context(format!("No window with name '{}' is running.", window_name))?; window.close(); + self.eww_state.clear_window_state(window_name); + Ok(()) } - fn open_window(&mut self, window_name: &str) -> Result<()> { + fn open_window(&mut self, window_name: &config::WindowName) -> Result<()> { let window_def = self .eww_config .get_windows() @@ -89,13 +91,14 @@ impl App { window.connect_screen_changed(on_screen_changed); let empty_local_state = HashMap::new(); - let root_widget = &widgets::element_to_gtk_thing( + let root_widget = &widgets::widget_use_to_gtk_widget( &self.eww_config.get_widgets(), &mut self.eww_state, + window_name, &empty_local_state, &window_def.widget, )?; - root_widget.get_style_context().add_class(window_name); + root_widget.get_style_context().add_class(&window_name.to_string()); window.add(root_widget); window.show_all(); @@ -107,7 +110,7 @@ impl App { gdk_window.raise(); window.set_keep_above(true); - self.windows.insert(window_name.to_string(), window); + self.windows.insert(window_name.clone(), window); Ok(()) } @@ -119,7 +122,7 @@ impl App { } self.eww_config = config; - self.eww_state.clear_callbacks(); + self.eww_state.clear_all_window_states(); let windows = self.windows.clone(); for (window_name, window) in windows { diff --git a/src/config/element.rs b/src/config/element.rs index 7020a62..33aefdb 100644 --- a/src/config/element.rs +++ b/src/config/element.rs @@ -21,7 +21,8 @@ impl WidgetDefinition { with_text_pos_context! { xml => if xml.tag_name() != "def" { bail!( - "Illegal element: only may be used in definition block, but found '{}'", + "{} | Illegal element: only may be used in definition block, but found '{}'", + xml.text_pos(), xml.as_tag_string() ); } @@ -89,7 +90,7 @@ impl WidgetUse { .collect::>(), ..WidgetUse::default() }, - XmlNode::Ignored(_) => Err(anyhow!("Failed to parse node {:?} as widget use", xml))?, + XmlNode::Ignored(_) => bail!("{} | Failed to parse node {:?} as widget use", xml.text_pos(), xml), }; Ok(widget_use.at_pos(text_pos)) } diff --git a/src/config/mod.rs b/src/config/mod.rs index ed14e38..90b6600 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -3,24 +3,14 @@ use crate::value::PrimitiveValue; use crate::value::VarName; use anyhow::*; use element::*; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::fmt; use xml_ext::*; pub mod element; pub mod xml_ext; -#[allow(unused)] -macro_rules! try_type { - ($typ:ty; $code:expr) => {{ - let x: $typ = try { $code }; - x - }}; - ($typ:ty; $code:block) => {{ - let x: $typ = try { $code }; - x - }}; -} - #[macro_export] macro_rules! ensure_xml_tag_is { ($element:ident, $name:literal) => { @@ -57,8 +47,8 @@ impl ScriptVar { #[derive(Debug, Clone)] pub struct EwwConfig { widgets: HashMap, - windows: HashMap, - initial_variables: HashMap, + windows: HashMap, + initial_variables: HashMap, script_vars: Vec, } @@ -88,7 +78,12 @@ impl EwwConfig { let windows = xml .child("windows")? .child_elements() - .map(|child| Ok((child.attr("name")?.to_owned(), EwwWindowDefinition::from_xml_element(child)?))) + .map(|child| { + Ok(( + WindowName(child.attr("name")?.to_owned()), + EwwWindowDefinition::from_xml_element(child)?, + )) + }) .collect::>>() .context("error parsing window definitions")?; @@ -101,7 +96,7 @@ impl EwwConfig { match node.tag_name() { "var" => { initial_variables.insert( - node.attr("name")?.to_owned(), + VarName(node.attr("name")?.to_owned()), PrimitiveValue::parse_string(&node.only_child()?.as_text()?.text()), ); } @@ -128,21 +123,17 @@ impl EwwConfig { .iter() .map(|var| Ok((var.name.clone(), crate::eww_state::run_command(&var.command)?))) .collect::>>()?; - vars.extend( - self.get_default_vars() - .into_iter() - .map(|(k, v)| (VarName(k.clone()), v.clone())), - ); + vars.extend(self.get_default_vars().clone()); Ok(vars) } pub fn get_widgets(&self) -> &HashMap { &self.widgets } - pub fn get_windows(&self) -> &HashMap { + pub fn get_windows(&self) -> &HashMap { &self.windows } - pub fn get_default_vars(&self) -> &HashMap { + pub fn get_default_vars(&self) -> &HashMap { &self.initial_variables } pub fn get_script_vars(&self) -> &Vec { @@ -150,6 +141,24 @@ impl EwwConfig { } } +#[repr(transparent)] +#[derive( + Debug, Clone, Hash, PartialEq, Eq, derive_more::AsRef, derive_more::From, derive_more::FromStr, Serialize, Deserialize, +)] +pub struct WindowName(String); + +impl std::borrow::Borrow for WindowName { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl fmt::Display for WindowName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + #[derive(Debug, Clone, PartialEq)] pub struct EwwWindowDefinition { pub position: (i32, i32), diff --git a/src/eww_state.rs b/src/eww_state.rs index f4330c9..9ce8c2b 100644 --- a/src/eww_state.rs +++ b/src/eww_state.rs @@ -1,3 +1,4 @@ +use crate::config::WindowName; use crate::value::VarName; use anyhow::*; use std::collections::HashMap; @@ -6,7 +7,8 @@ use std::sync::Arc; use crate::value::{AttrValue, PrimitiveValue}; -//pub struct StateChangeHandler(Box) + 'static>); +/// Handler that get's executed to apply the necessary parts of the eww state to a gtk widget. +/// These are created and initialized in EwwState::resolve. pub struct StateChangeHandler { func: Box) -> Result<()> + 'static>, constant_values: HashMap, @@ -14,6 +16,8 @@ pub struct StateChangeHandler { } impl StateChangeHandler { + /// Run the StateChangeHandler. + /// [`state`] should be the global [EwwState::state]. fn run_with_state(&self, state: &HashMap) -> Result<()> { let mut all_resolved_attrs = self.constant_values.clone(); for (attr_name, var_ref) in self.unresolved_attrs.iter() { @@ -33,130 +37,145 @@ impl StateChangeHandler { } } -pub struct StateChangeHandlers { - handlers: HashMap>>, +/// Collection of [StateChangeHandler]s +/// State specific to one window. +/// stores the state_change handlers that are used for that window. +#[derive(Default)] +pub struct EwwWindowState { + state_change_handlers: HashMap>>, } -impl StateChangeHandlers { +impl EwwWindowState { + /// register a new [StateChangeHandler] fn put_handler(&mut self, handler: StateChangeHandler) { let handler = Arc::new(handler); for var_name in handler.unresolved_attrs.values() { - let entry: &mut Vec> = self.handlers.entry(var_name.clone()).or_insert_with(Vec::new); + let entry: &mut Vec> = + self.state_change_handlers.entry(var_name.clone()).or_insert_with(Vec::new); entry.push(handler.clone()); } } - - fn get(&self, key: &VarName) -> Option<&Vec>> { - self.handlers.get(key) - } - - fn clear(&mut self) { - self.handlers.clear(); - } } +/// Stores the actual state of eww, including the variable state and the window-specific state-change handlers. +#[derive(Default)] pub struct EwwState { - state_change_handlers: StateChangeHandlers, - state: HashMap, -} - -impl Default for EwwState { - fn default() -> Self { - EwwState { - state_change_handlers: StateChangeHandlers { - handlers: HashMap::new(), - }, - state: HashMap::new(), - } - } + windows: HashMap, + variables_state: HashMap, } impl std::fmt::Debug for EwwState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "EwwState {{ state: {:?} }}", self.state) + write!(f, "EwwState {{ state: {:?} }}", self.variables_state) } } impl EwwState { pub fn from_default_vars(defaults: HashMap) -> Self { EwwState { - state: defaults, + variables_state: defaults, ..EwwState::default() } } - pub fn clear_callbacks(&mut self) { - self.state_change_handlers.clear(); + /// remove all state stored specific to one window + pub fn clear_window_state(&mut self, window_name: &WindowName) { + self.windows.remove(window_name); } - pub fn update_value(&mut self, key: VarName, value: PrimitiveValue) -> Result<()> { - if let Some(handlers) = self.state_change_handlers.get(&key) { - self.state.insert(key.clone(), value); - for handler in handlers { - handler - .run_with_state(&self.state) - .with_context(|| format!("When updating value of {}", &key))?; - } + /// remove all state that is specific to any window + pub fn clear_all_window_states(&mut self) { + self.windows.clear(); + } + + /// Update the value of a variable, running all registered [StateChangeHandler]s. + pub fn update_variable(&mut self, key: VarName, value: PrimitiveValue) -> Result<()> { + if !self.variables_state.contains_key(&key) { + bail!("Tried to set unknown variable '{}'", key); + } + self.variables_state.insert(key.clone(), value); + + let handlers = self + .windows + .values() + .filter_map(|window_state| window_state.state_change_handlers.get(&key)) + .flatten(); + + for handler in handlers { + handler + .run_with_state(&self.variables_state) + .with_context(|| format!("When updating value of {}", &key))?; } Ok(()) } + /// Resolve takes a function that applies a set of fully resolved attribute values to it's gtk widget. pub fn resolve) -> Result<()> + 'static + Clone>( &mut self, + window_name: &WindowName, local_env: &HashMap, mut needed_attributes: HashMap, set_value: F, ) { - let mut resolved_attrs = HashMap::new(); - let mut unresolved_attrs: HashMap = HashMap::new(); - needed_attributes - .drain() - .for_each(|(attr_name, attr_value)| match attr_value { - AttrValue::Concrete(primitive) => { - resolved_attrs.insert(attr_name, primitive); - } - AttrValue::VarRef(var_name) => match local_env.get(&var_name) { - Some(AttrValue::VarRef(var_ref_from_local)) => { - unresolved_attrs.insert(attr_name, var_ref_from_local.clone()); - } - Some(AttrValue::Concrete(concrete_from_local)) => { - resolved_attrs.insert(attr_name, concrete_from_local.clone()); - } - None => { - unresolved_attrs.insert(attr_name, var_name); - } - }, - }); + // Resolve first collects all variable references and creates a set of unresolved attribute -> VarName pairs. + // additionally, all constant values are looked up and collected, including the values from the local environment + // These are then used to generate a StateChangeHandler, which is then executed and registered in the windows state. let result: Result<_> = try { + let window_state = self + .windows + .entry(window_name.clone()) + .or_insert_with(EwwWindowState::default); + + let mut resolved_attrs = HashMap::new(); + let mut unresolved_attrs: HashMap = HashMap::new(); + needed_attributes + .drain() + .for_each(|(attr_name, attr_value)| match attr_value { + // directly resolve primitive values + AttrValue::Concrete(primitive) => { + resolved_attrs.insert(attr_name, primitive); + } + + AttrValue::VarRef(var_name) => match local_env.get(&var_name) { + // look up if variables are found in the local env, and resolve as far as possible + Some(AttrValue::Concrete(concrete_from_local)) => { + resolved_attrs.insert(attr_name, concrete_from_local.clone()); + } + Some(AttrValue::VarRef(var_ref_from_local)) => { + unresolved_attrs.insert(attr_name, var_ref_from_local.clone()); + } + None => { + // if it's not in the local env, it must reference the global state, + // and should thus directly be inserted into the unresolved attrs. + unresolved_attrs.insert(attr_name, var_name); + } + }, + }); + if unresolved_attrs.is_empty() { + // if there are no unresolved variables, we can set the value directly set_value(resolved_attrs)?; } else { + // otherwise register and execute the handler let handler = StateChangeHandler { func: Box::new(set_value.clone()), constant_values: resolved_attrs, unresolved_attrs, }; - handler.run_with_state(&self.state)?; - self.state_change_handlers.put_handler(handler); + handler.run_with_state(&self.variables_state)?; + window_state.put_handler(handler); } }; if let Err(e) = result { - eprintln!("{}", e); + eprintln!("{:?}", e); } } } +/// Run a command and get the output pub fn run_command(cmd: &str) -> Result { let output = String::from_utf8(Command::new("/bin/bash").arg("-c").arg(cmd).output()?.stdout)?; let output = output.trim_matches('\n'); Ok(PrimitiveValue::from(output)) } - -pub fn recursive_lookup<'a>(data: &'a HashMap, key: &VarName) -> Result<&'a PrimitiveValue> { - match data.get(key) { - Some(AttrValue::Concrete(x)) => Ok(x), - Some(AttrValue::VarRef(x)) => recursive_lookup(data, x), - None => Err(anyhow!("No value found for key '{}'", key)), - } -} diff --git a/src/main.rs b/src/main.rs index e68ffba..5eab6aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,10 +70,10 @@ pub enum OptAction { Update { fieldname: VarName, value: PrimitiveValue }, #[structopt(name = "open")] - OpenWindow { window_name: String }, + OpenWindow { window_name: config::WindowName }, #[structopt(name = "close")] - CloseWindow { window_name: String }, + CloseWindow { window_name: config::WindowName }, #[structopt(name = "kill")] KillServer, @@ -211,6 +211,7 @@ fn run_filewatch_thread>( Ok(hotwatch) } +/// detach the process from the terminal, also closing stdout and redirecting stderr into /dev/null fn do_detach() { // detach from terminal let pid = unsafe { libc::fork() }; diff --git a/src/util.rs b/src/util.rs index f81f079..bf94020 100644 --- a/src/util.rs +++ b/src/util.rs @@ -37,17 +37,3 @@ pub fn parse_duration(s: &str) -> Result { Err(anyhow!("unrecognized time format: {}", s)) } } - -//#[macro_export] -//macro_rules! check_that { -//($( -//$cond:expr => $err:expr -//),*$(,)?) => { -//$( -//if !($cond) { -//return Err($err); -//} -//)* -//} - -//} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 6708cb9..2b98fff 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,10 +1,10 @@ use crate::config::element; +use crate::config::WindowName; use crate::eww_state::*; use crate::value::{AttrValue, VarName}; use anyhow::*; use gtk::prelude::*; -use itertools::Itertools; -use ref_cast::RefCast; + use std::{collections::HashMap, process::Command}; use widget_definitions::*; @@ -12,6 +12,8 @@ pub mod widget_definitions; const CMD_STRING_PLACEHODLER: &str = "{}"; +/// Run a command that was provided as an attribute. This command may use a placeholder ('{}') +/// which will be replaced by the value provided as [arg] pub fn run_command(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() { @@ -19,28 +21,69 @@ pub fn run_command(cmd: &str, arg: T) { } } -struct BuilderArgs<'a, 'b, 'c> { +struct BuilderArgs<'a, 'b, 'c, 'd> { eww_state: &'a mut EwwState, local_env: &'b HashMap, widget: &'c element::WidgetUse, unhandled_attrs: Vec<&'c str>, + window_name: &'d WindowName, } -pub fn element_to_gtk_thing( +/// Generate a [gtk::Widget] from a [element::WidgetUse]. +/// The widget_use may be using a builtin widget, or a custom [element::WidgetDefinition]. +/// +/// Also registers all the necessary state-change handlers in the eww_state. +/// +/// This may return `Err` in case there was an actual error while parsing or resolving the widget, +/// Or `Ok(None)` if the widget_use just didn't match any widget name. +pub fn widget_use_to_gtk_widget( widget_definitions: &HashMap, eww_state: &mut EwwState, + window_name: &WindowName, local_env: &HashMap, widget: &element::WidgetUse, ) -> Result { - let gtk_container = build_gtk_widget(widget_definitions, eww_state, local_env, widget)?; + let builtin_gtk_widget = build_gtk_widget(widget_definitions, eww_state, window_name, local_env, widget)?; - let gtk_widget = if let Some(gtk_container) = gtk_container { - gtk_container + let gtk_widget = if let Some(builtin_gtk_widget) = builtin_gtk_widget { + builtin_gtk_widget } else if let Some(def) = widget_definitions.get(widget.name.as_str()) { - // TODO widget cleanup phase, where widget arguments are resolved as far as possible beforehand? - let mut local_env = local_env.clone(); - local_env.extend(widget.attrs.clone().into_iter().map(|(k, v)| (VarName(k), v))); - let custom_widget = element_to_gtk_thing(widget_definitions, eww_state, &local_env, &def.structure)?; + //let mut local_env = local_env.clone(); + + // the attributes that are set on the widget need to be resolved as far as possible. + // If an attribute is a variable reference, it must either reference a variable in the current local_env, or in the global state. + // As we are building widgets from the outer most to the most nested, we can resolve attributes at every step. + // This way, any definition that is affected by changes in the eww_state will be directly linked to the eww_state's value. + // Example: + // foo="{{in_eww_state}}" => attr_in_child="{{foo}}" => attr_in_nested_child="{{attr_in_child}}" + // will be resolved step by step. This code will first resolve attr_in_child to directly be attr_in_child={{in_eww_state}}. + // then, in the widget_use_to_gtk_widget call of that child element, + // attr_in_nested_child will again be resolved to point to the value of attr_in_child, + // and thus: attr_in_nested_child="{{in_eww_state}}" + let resolved_widget_attr_env = widget + .attrs + .clone() + .into_iter() + .map(|(attr_name, attr_value)| { + ( + VarName(attr_name), + match attr_value { + AttrValue::VarRef(var_ref) => { + local_env.get(&var_ref).cloned().unwrap_or_else(|| AttrValue::VarRef(var_ref)) + } + AttrValue::Concrete(value) => AttrValue::Concrete(value), + }, + ) + }) + .collect(); + + let custom_widget = widget_use_to_gtk_widget( + widget_definitions, + eww_state, + window_name, + &resolved_widget_attr_env, + &def.structure, + )?; custom_widget.get_style_context().add_class(widget.name.as_str()); custom_widget } else { @@ -50,9 +93,17 @@ pub fn element_to_gtk_thing( Ok(gtk_widget) } -pub fn build_gtk_widget( +/// build a [gtk::Widget] out of a [element::WidgetUse] that uses a **builtin widget**. +/// User defined widgets are handled by [widget_use_to_gtk_widget]. +/// +/// Also registers all the necessary handlers in the `eww_state`. +/// +/// This may return `Err` in case there was an actual error while parsing or resolving the widget, +/// Or `Ok(None)` if the widget_use just didn't match any widget name. +fn build_gtk_widget( widget_definitions: &HashMap, eww_state: &mut EwwState, + window_name: &WindowName, local_env: &HashMap, widget: &element::WidgetUse, ) -> Result> { @@ -60,6 +111,7 @@ pub fn build_gtk_widget( eww_state, local_env, widget, + window_name, unhandled_attrs: widget.attrs.keys().map(|x| x.as_ref()).collect(), }; let gtk_widget = match widget_to_gtk_widget(&mut bargs) { @@ -75,10 +127,12 @@ pub fn build_gtk_widget( } }; + // run resolve functions for superclasses such as range, orientable, and widget + if let Some(gtk_widget) = gtk_widget.dynamic_cast_ref::() { resolve_container_attrs(&mut bargs, >k_widget); for child in &widget.children { - let child_widget = element_to_gtk_thing(widget_definitions, bargs.eww_state, local_env, child); + let child_widget = widget_use_to_gtk_widget(widget_definitions, bargs.eww_state, window_name, local_env, child); let child_widget = child_widget.with_context(|| { format!( "{}error while building child '{:#?}' of '{}'", @@ -90,6 +144,7 @@ pub fn build_gtk_widget( gtk_widget.add(&child_widget); } } + gtk_widget.dynamic_cast_ref().map(|w| resolve_range_attrs(&mut bargs, &w)); gtk_widget .dynamic_cast_ref() @@ -129,6 +184,7 @@ macro_rules! resolve_block { }; if let Ok(attr_map) = attr_map { $args.eww_state.resolve( + $args.window_name, $args.local_env, attr_map, ::glib::clone!(@strong $gtk_widget => move |attrs| { diff --git a/src/widgets/widget_definitions.rs b/src/widgets/widget_definitions.rs index 2daba8b..0849c0a 100644 --- a/src/widgets/widget_definitions.rs +++ b/src/widgets/widget_definitions.rs @@ -1,6 +1,6 @@ use super::{run_command, BuilderArgs}; use crate::resolve_block; -use crate::value::{AttrValue, PrimitiveValue, VarName}; +use crate::value::{AttrValue, PrimitiveValue}; use anyhow::*; use gtk::prelude::*; use gtk::ImageExt; @@ -128,6 +128,7 @@ fn build_gtk_text(bargs: &mut BuilderArgs) -> Result { .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(())}), @@ -135,7 +136,7 @@ fn build_gtk_text(bargs: &mut BuilderArgs) -> Result { Ok(gtk_widget) } -fn build_gtk_aspect_frame(bargs: &mut BuilderArgs) -> Result { +fn build_gtk_aspect_frame(_bargs: &mut BuilderArgs) -> Result { let gtk_widget = gtk::AspectFrame::new(None, 0.5, 0.5, 1.0, true); Ok(gtk_widget) }