diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e753483 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f1b7556 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,26 @@ +name: build + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Set up + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + components: rustfmt + + - uses: actions/checkout@v2 + - name: Format + run: cargo build diff --git a/.github/workflows/fmt.yml b/.github/workflows/fmt.yml new file mode 100644 index 0000000..ead5199 --- /dev/null +++ b/.github/workflows/fmt.yml @@ -0,0 +1,26 @@ +name: format + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +jobs: + fmt: + runs-on: ubuntu-latest + + steps: + - name: Set up + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + components: rustfmt + + - uses: actions/checkout@v2 + - name: Format + run: cargo fmt -- --check diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..e657d52 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,26 @@ +name: test + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Set up + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + components: rustfmt + + - uses: actions/checkout@v2 + - name: Test + run: cargo test --verbose diff --git a/Cargo.toml b/Cargo.toml index 4a25298..6d8b156 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,6 @@ version = "0.1.0" authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"] edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] gtk = { version = "0.9", features = [ "v3_16" ] } gdk = { version = "", features = ["v3_16"] } @@ -13,7 +11,6 @@ gio = { version = "", features = ["v2_44"] } glib = { version = "", features = ["v2_44"] } gdk-pixbuf = "0.9" - regex = "1" try_match = "0.2.2" anyhow = "1.0" @@ -39,8 +36,5 @@ lazy_static = "1.4.0" libc = "0.2" ref-cast = "1.0" -#thiserror = "1.0" - [dev-dependencies] pretty_assertions = "0.6.1" - diff --git a/README.md b/README.md index 33f4305..ae842d4 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,12 @@ - # Table of Contents -* [About](#org4ab08b6) -* [Configuration](#org581ca61) - * [Example Config](#orgb769597) -* [Building](#orgbf66ce2) - * [Prerequisites](#org727b3da) - * [Installation](#orgdd31739) -* [Usage](#org4a9b3c6) - - +- [About](#org4ab08b6) +- [Configuration](#org581ca61) + - [Example Config](#orgb769597) +- [Building](#orgbf66ce2) + - [Prerequisites](#org727b3da) + - [Installation](#orgdd31739) +- [Usage](#org4a9b3c6) @@ -17,17 +14,16 @@ Elkowar’s Wacky Widgets is a standalone Widget System made in rust to add AwesomeWM like widgets to any WM - # Configuration Eww’s configuration should be placed in `~/.config/eww/eww.xml` and any `scss` styles you want to add should be put into `~/.config/eww/eww.scss`. - ## Example Config + ```xml @@ -66,17 +62,16 @@ Eww’s configuration should be placed in `~/.config/eww/eww.xml` and any `s # Building - ## Prerequisites -- cargo with nightly toolchain +- rustc +- cargo (nightly toolchain) Rather than with your system package manager, I recommend installing it using [rustup](https://rustup.rs/), as this makes it easy to use the nightly toolchain, which is necessary to build eww. - ## Installation @@ -89,10 +84,8 @@ Build the Binary using -: then copy the built binary from `./target/release` to anywhere in `$PATH` (example - `~/.local/bin`) - # Usage Create a Config and then just do `eww`! - diff --git a/rustfmt.toml b/rustfmt.toml index 7e7e554..a989f2b 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,13 @@ -unstable_features=true -fn_single_line=false -max_width=130 - +unstable_features = true +fn_single_line = false +max_width = 130 +reorder_impl_items = true +merge_imports = true +normalize_comments = true +use_field_init_shorthand = true +wrap_comments = true +combine_control_expr = false +condense_wildcard_suffixes = true +format_code_in_doc_comments = true +format_macro_matchers = true +format_strings = true diff --git a/src/config/element.rs b/src/config/element.rs index fe49dcd..07f5b62 100644 --- a/src/config/element.rs +++ b/src/config/element.rs @@ -3,9 +3,10 @@ use lazy_static::lazy_static; use regex::Regex; use std::ops::Range; -use crate::value::AttrValue; -use crate::value::VarName; -use crate::with_text_pos_context; +use crate::{ + value::{AttrValue, VarName}, + with_text_pos_context, +}; use maplit::hashmap; use std::collections::HashMap; diff --git a/src/config/mod.rs b/src/config/mod.rs index 4a2555f..314b0a8 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,12 +1,12 @@ -use crate::util; -use crate::value::PrimitiveValue; -use crate::value::VarName; +use crate::{ + util, + value::{PrimitiveValue, VarName}, +}; use anyhow::*; use derive_more; use element::*; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fmt; +use std::{collections::HashMap, fmt}; use util::Coords; use xml_ext::*; @@ -134,12 +134,15 @@ impl EwwConfig { pub fn get_widgets(&self) -> &HashMap { &self.widgets } + pub fn get_windows(&self) -> &HashMap { &self.windows } + pub fn get_default_vars(&self) -> &HashMap { &self.initial_variables } + pub fn get_script_vars(&self) -> &Vec { &self.script_vars } @@ -211,6 +214,7 @@ impl Default for WindowStacking { impl std::str::FromStr for WindowStacking { type Err = anyhow::Error; + fn from_str(s: &str) -> Result { let s = s.to_lowercase(); match s.as_str() { diff --git a/src/config/xml_ext.rs b/src/config/xml_ext.rs index fc715a4..4908520 100644 --- a/src/config/xml_ext.rs +++ b/src/config/xml_ext.rs @@ -141,6 +141,7 @@ impl<'a, 'b> XmlElement<'a, 'b> { format!("<{} {}>", self.tag_name(), attrs) } + pub fn tag_name(&self) -> &str { self.0.tag_name().name() } @@ -159,6 +160,7 @@ impl<'a, 'b> XmlElement<'a, 'b> { .filter(|child| child.is_element() || (child.is_text() && !child.text().unwrap_or_default().is_blank())) .map(XmlNode::from) } + pub fn child_elements(&self) -> impl Iterator { self.0.children().filter(|child| child.is_element()).map(XmlElement) } diff --git a/src/eww_state.rs b/src/eww_state.rs index 1263746..f91fa20 100644 --- a/src/eww_state.rs +++ b/src/eww_state.rs @@ -1,14 +1,11 @@ -use crate::config::WindowName; -use crate::value::VarName; +use crate::{config::WindowName, value::VarName}; use anyhow::*; -use std::collections::HashMap; -use std::process::Command; -use std::sync::Arc; +use std::{collections::HashMap, process::Command, sync::Arc}; use crate::value::{AttrValue, PrimitiveValue}; -/// 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. +/// 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, @@ -57,7 +54,8 @@ impl EwwWindowState { } } -/// Stores the actual state of eww, including the variable state and the window-specific state-change handlers. +/// Stores the actual state of eww, including the variable state and the +/// window-specific state-change handlers. #[derive(Default)] pub struct EwwState { windows: HashMap, @@ -88,7 +86,8 @@ impl EwwState { self.windows.clear(); } - /// Update the value of a variable, running all registered [StateChangeHandler]s. + /// 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); @@ -111,7 +110,8 @@ impl EwwState { /// resolves a value if possible, using the current eww_state /// Expects there to be at max one level of nesting var_refs from local-env. - /// This means that no elements in the local_env may be var-refs into the local_env again, but only into the global state. + /// This means that no elements in the local_env may be var-refs into the + /// local_env again, but only into the global state. pub fn resolve_once<'a>( &'a self, local_env: &'a HashMap, @@ -134,9 +134,11 @@ impl EwwState { } } - /// Resolve takes a function that applies a set of fully resolved attribute values to it's gtk widget. - /// Expects there to be at max one level of nesting var_refs from local-env. - /// This means that no elements in the local_env may be var-refs into the local_env again, but only into the global state. + /// Resolve takes a function that applies a set of fully resolved attribute + /// values to it's gtk widget. Expects there to be at max one level of + /// nesting var_refs from local-env. This means that no elements in the + /// local_env may be var-refs into the local_env again, but only into the + /// global state. pub fn resolve) -> Result<()> + 'static + Clone>( &mut self, window_name: &WindowName, @@ -144,9 +146,11 @@ impl EwwState { mut needed_attributes: HashMap, set_value: F, ) { - // 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. + // 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 diff --git a/src/main.rs b/src/main.rs index 55732da..bb7178d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,11 +16,12 @@ use ipc_channel::ipc; use log; use pretty_env_logger; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::{Path, PathBuf}; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; use structopt::StructOpt; -use value::PrimitiveValue; -use value::VarName; +use value::{PrimitiveValue, VarName}; pub mod app; pub mod config; @@ -221,7 +222,8 @@ fn run_filewatch_thread>( Ok(hotwatch) } -/// detach the process from the terminal, also closing stdout and redirecting stderr into /dev/null +/// 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() }; @@ -238,7 +240,7 @@ fn do_detach() { libc::close(1); } } - //close stderr to not spam output + // close stderr to not spam output if unsafe { libc::isatty(2) } != 0 { unsafe { let fd = libc::open(std::ffi::CString::new("/dev/null").unwrap().as_ptr(), libc::O_RDWR); diff --git a/src/script_var_handler.rs b/src/script_var_handler.rs index 2569f1b..35bac1c 100644 --- a/src/script_var_handler.rs +++ b/src/script_var_handler.rs @@ -19,7 +19,8 @@ impl ScriptVarHandler { }) } - /// clears and stops the currently running poll handles, then opens the new ones as configured + /// 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"); self.poll_handles.iter().for_each(|handle| handle.stop()); diff --git a/src/util.rs b/src/util.rs index c0081c4..f2c4142 100644 --- a/src/util.rs +++ b/src/util.rs @@ -3,8 +3,7 @@ use extend::ext; use grass; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use std::fmt; -use std::path::Path; +use std::{fmt, path::Path}; pub fn parse_scss_from_file>(path: P) -> Result { let scss_content = std::fs::read_to_string(path)?; @@ -14,7 +13,8 @@ pub fn parse_scss_from_file>(path: P) -> Result { #[ext(pub, name = StringExt)] impl> T { - /// check if the string is empty after removing all linebreaks and trimming whitespace + /// check if the string is empty after removing all linebreaks and trimming + /// whitespace fn is_blank(self) -> bool { self.as_ref().replace('\n', "").trim().is_empty() } @@ -51,6 +51,7 @@ impl fmt::Display for Coords { impl std::str::FromStr for Coords { type Err = anyhow::Error; + fn from_str(s: &str) -> Result { let (x, y) = s.split_once('x').ok_or_else(|| anyhow!("must be formatted like 200x500"))?; Ok(Coords(x.parse()?, y.parse()?)) diff --git a/src/value.rs b/src/value.rs index 15ea06d..6fddff7 100644 --- a/src/value.rs +++ b/src/value.rs @@ -4,8 +4,7 @@ use lazy_static::lazy_static; use ref_cast::RefCast; use regex::Regex; use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; -use std::fmt; +use std::{convert::TryFrom, fmt}; #[derive(Clone, PartialEq, Deserialize, Serialize, derive_more::From)] pub enum PrimitiveValue { @@ -32,7 +31,8 @@ impl fmt::Debug for PrimitiveValue { impl std::str::FromStr for PrimitiveValue { type Err = anyhow::Error; - /// parses the value, trying to turn it into a number and a boolean first, before deciding that it is a string. + /// parses the value, trying to turn it into a number and a boolean first, + /// before deciding that it is a string. fn from_str(s: &str) -> Result { Ok(PrimitiveValue::parse_string(s)) } @@ -44,6 +44,7 @@ fn remove_surrounding(s: &str, surround: char) -> &str { impl TryFrom for String { type Error = anyhow::Error; + fn try_from(x: PrimitiveValue) -> Result { x.as_string() } @@ -51,6 +52,7 @@ impl TryFrom for String { impl TryFrom for f64 { type Error = anyhow::Error; + fn try_from(x: PrimitiveValue) -> Result { x.as_f64() } @@ -58,6 +60,7 @@ impl TryFrom for f64 { impl TryFrom for bool { type Error = anyhow::Error; + fn try_from(x: PrimitiveValue) -> Result { x.as_bool() } @@ -70,13 +73,15 @@ impl From<&str> for PrimitiveValue { } impl PrimitiveValue { - /// parses the value, trying to turn it into a number and a boolean first, before deciding that it is a string. + /// parses the value, trying to turn it into a number and a boolean first, + /// before deciding that it is a string. pub fn parse_string(s: &str) -> Self { s.parse() .map(PrimitiveValue::Number) .or_else(|_| s.parse().map(PrimitiveValue::Boolean)) .unwrap_or_else(|_| PrimitiveValue::String(remove_surrounding(s, '\'').to_string())) } + pub fn as_string(&self) -> Result { match self { PrimitiveValue::String(x) => Ok(x.clone()), @@ -84,6 +89,7 @@ impl PrimitiveValue { PrimitiveValue::Boolean(x) => Ok(format!("{}", x)), } } + pub fn as_f64(&self) -> Result { match self { PrimitiveValue::Number(x) => Ok(*x), @@ -93,6 +99,7 @@ impl PrimitiveValue { _ => Err(anyhow!("{:?} is not an f64", &self)), } } + pub fn as_bool(&self) -> Result { match self { PrimitiveValue::Boolean(x) => Ok(*x), @@ -145,12 +152,14 @@ impl AttrValue { _ => Err(anyhow!("{:?} is not a string", self)), } } + pub fn as_f64(&self) -> Result { match self { AttrValue::Concrete(x) => Ok(x.as_f64()?), _ => Err(anyhow!("{:?} is not an f64", self)), } } + pub fn as_bool(&self) -> Result { match self { AttrValue::Concrete(x) => Ok(x.as_bool()?), diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index acf54cb..569dddd 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,6 +1,8 @@ -use crate::config::{element, WindowName}; -use crate::eww_state::*; -use crate::value::{AttrValue, VarName}; +use crate::{ + config::{element, WindowName}, + eww_state::*, + value::{AttrValue, VarName}, +}; use anyhow::*; use gtk::prelude::*; @@ -11,8 +13,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] +/// 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() { @@ -30,12 +32,14 @@ struct BuilderArgs<'a, 'b, 'c, 'd, 'e> { } /// Generate a [gtk::Widget] from a [element::WidgetUse]. -/// The widget_use may be using a builtin widget, or a custom [element::WidgetDefinition]. +/// 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. +/// 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, @@ -48,18 +52,20 @@ pub fn widget_use_to_gtk_widget( 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()) { - //let mut local_env = local_env.clone(); + // 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}}" + // 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() @@ -93,13 +99,14 @@ pub fn widget_use_to_gtk_widget( Ok(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]. +/// 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. +/// 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_builtin_gtk_widget( widget_definitions: &HashMap, eww_state: &mut EwwState, diff --git a/src/widgets/widget_definitions.rs b/src/widgets/widget_definitions.rs index b9eacb8..1cf98b7 100644 --- a/src/widgets/widget_definitions.rs +++ b/src/widgets/widget_definitions.rs @@ -1,15 +1,12 @@ use super::{run_command, BuilderArgs}; -use crate::config; -use crate::eww_state; -use crate::resolve_block; -use crate::value::{AttrValue, PrimitiveValue}; +use crate::{ + config, eww_state, resolve_block, + value::{AttrValue, PrimitiveValue}, +}; use anyhow::*; -use gtk::prelude::*; -use gtk::ImageExt; +use gtk::{prelude::*, ImageExt}; use maplit::hashmap; -use std::cell::RefCell; -use std::path::Path; -use std::rc::Rc; +use std::{cell::RefCell, path::Path, rc::Rc}; use gdk_pixbuf; @@ -259,7 +256,8 @@ fn parse_align(o: &str) -> Result { } fn connect_first_map, F: Fn(&W) + 'static>(widget: &W, func: F) { - // TODO it would be better to actually remove the connect_map after first map, but that would be highly annoying to implement... + // TODO it would be better to actually remove the connect_map after first map, + // but that would be highly annoying to implement... let is_first_map = std::rc::Rc::new(std::cell::RefCell::new(true)); widget.connect_map(move |w| { if is_first_map.replace(false) {