Add documentation

This commit is contained in:
elkowar 2020-10-08 01:03:19 +02:00
parent 11139ad4dc
commit 600f104552
8 changed files with 209 additions and 133 deletions

View file

@ -16,7 +16,7 @@ pub enum EwwEvent {
pub struct App {
pub eww_state: EwwState,
pub eww_config: config::EwwConfig,
pub windows: HashMap<String, gtk::Window>,
pub windows: HashMap<config::WindowName, gtk::Window>,
pub css_provider: gtk::CssProvider,
pub app_evt_send: glib::Sender<EwwEvent>,
#[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 {

View file

@ -21,7 +21,8 @@ impl WidgetDefinition {
with_text_pos_context! { xml =>
if xml.tag_name() != "def" {
bail!(
"Illegal element: only <def> may be used in definition block, but found '{}'",
"{} | Illegal element: only <def> may be used in definition block, but found '{}'",
xml.text_pos(),
xml.as_tag_string()
);
}
@ -89,7 +90,7 @@ impl WidgetUse {
.collect::<HashMap<_, _>>(),
..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))
}

View file

@ -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<String, WidgetDefinition>,
windows: HashMap<String, EwwWindowDefinition>,
initial_variables: HashMap<String, PrimitiveValue>,
windows: HashMap<WindowName, EwwWindowDefinition>,
initial_variables: HashMap<VarName, PrimitiveValue>,
script_vars: Vec<ScriptVar>,
}
@ -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::<Result<HashMap<_, _>>>()
.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::<Result<HashMap<_, _>>>()?;
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<String, WidgetDefinition> {
&self.widgets
}
pub fn get_windows(&self) -> &HashMap<String, EwwWindowDefinition> {
pub fn get_windows(&self) -> &HashMap<WindowName, EwwWindowDefinition> {
&self.windows
}
pub fn get_default_vars(&self) -> &HashMap<String, PrimitiveValue> {
pub fn get_default_vars(&self) -> &HashMap<VarName, PrimitiveValue> {
&self.initial_variables
}
pub fn get_script_vars(&self) -> &Vec<ScriptVar> {
@ -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<str> 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),

View file

@ -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<dyn Fn(HashMap<String, PrimitiveValue>) + '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<dyn Fn(HashMap<String, PrimitiveValue>) -> Result<()> + 'static>,
constant_values: HashMap<String, PrimitiveValue>,
@ -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<VarName, PrimitiveValue>) -> 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<VarName, Vec<Arc<StateChangeHandler>>>,
/// 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<VarName, Vec<Arc<StateChangeHandler>>>,
}
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<Arc<StateChangeHandler>> = self.handlers.entry(var_name.clone()).or_insert_with(Vec::new);
let entry: &mut Vec<Arc<StateChangeHandler>> =
self.state_change_handlers.entry(var_name.clone()).or_insert_with(Vec::new);
entry.push(handler.clone());
}
}
fn get(&self, key: &VarName) -> Option<&Vec<Arc<StateChangeHandler>>> {
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<VarName, PrimitiveValue>,
}
impl Default for EwwState {
fn default() -> Self {
EwwState {
state_change_handlers: StateChangeHandlers {
handlers: HashMap::new(),
},
state: HashMap::new(),
}
}
windows: HashMap<WindowName, EwwWindowState>,
variables_state: HashMap<VarName, PrimitiveValue>,
}
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<VarName, PrimitiveValue>) -> 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<F: Fn(HashMap<String, PrimitiveValue>) -> Result<()> + 'static + Clone>(
&mut self,
window_name: &WindowName,
local_env: &HashMap<VarName, AttrValue>,
mut needed_attributes: HashMap<String, AttrValue>,
set_value: F,
) {
let mut resolved_attrs = HashMap::new();
let mut unresolved_attrs: HashMap<String, VarName> = 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<String, VarName> = 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<PrimitiveValue> {
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<VarName, AttrValue>, 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)),
}
}

View file

@ -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<P: AsRef<Path>>(
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() };

View file

@ -37,17 +37,3 @@ pub fn parse_duration(s: &str) -> Result<std::time::Duration> {
Err(anyhow!("unrecognized time format: {}", s))
}
}
//#[macro_export]
//macro_rules! check_that {
//($(
//$cond:expr => $err:expr
//),*$(,)?) => {
//$(
//if !($cond) {
//return Err($err);
//}
//)*
//}
//}

View file

@ -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<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() {
@ -19,28 +21,69 @@ pub fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
}
}
struct BuilderArgs<'a, 'b, 'c> {
struct BuilderArgs<'a, 'b, 'c, 'd> {
eww_state: &'a mut EwwState,
local_env: &'b HashMap<VarName, AttrValue>,
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<String, element::WidgetDefinition>,
eww_state: &mut EwwState,
window_name: &WindowName,
local_env: &HashMap<VarName, AttrValue>,
widget: &element::WidgetUse,
) -> Result<gtk::Widget> {
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<String, element::WidgetDefinition>,
eww_state: &mut EwwState,
window_name: &WindowName,
local_env: &HashMap<VarName, AttrValue>,
widget: &element::WidgetUse,
) -> Result<Option<gtk::Widget>> {
@ -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::<gtk::Container>() {
resolve_container_attrs(&mut bargs, &gtk_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| {

View file

@ -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<gtk::Label> {
.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<gtk::Label> {
Ok(gtk_widget)
}
fn build_gtk_aspect_frame(bargs: &mut BuilderArgs) -> Result<gtk::AspectFrame> {
fn build_gtk_aspect_frame(_bargs: &mut BuilderArgs) -> Result<gtk::AspectFrame> {
let gtk_widget = gtk::AspectFrame::new(None, 0.5, 0.5, 1.0, true);
Ok(gtk_widget)
}