From aefa9016f1e6018bd6784fe310bcccbdfce66d3e Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Sun, 27 Sep 2020 12:29:39 +0200 Subject: [PATCH] implement attribute type conversions --- Cargo.lock | 36 ++++++++++++++++++- Cargo.toml | 4 ++- src/config/mod.rs | 3 +- src/eww_state.rs | 17 ++++++++- src/main.rs | 54 ++++++++++++++++------------ src/value.rs | 58 ++++++++++++++++++++----------- src/widgets/mod.rs | 2 +- src/widgets/widget_definitions.rs | 24 ++++++++++--- 8 files changed, 146 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9ac40f..2a42ee3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,6 +156,16 @@ dependencies = [ "system-deps", ] +[[package]] +name = "calloop" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59561a8b3968ba4bda0c46f42e0568507c5d26e94c3b6f2a0c730cbecd83ff3a" +dependencies = [ + "log", + "nix", +] + [[package]] name = "cc" version = "1.0.60" @@ -312,6 +322,8 @@ name = "eww" version = "0.1.0" dependencies = [ "anyhow", + "calloop", + "crossbeam-channel", "derive_more", "extend", "gdk", @@ -320,9 +332,9 @@ dependencies = [ "grass", "gtk", "hocon", + "hotwatch", "ipc-channel", "maplit", - "notify", "pretty_assertions", "regex", "serde", @@ -798,6 +810,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "hotwatch" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0add391e9cd7d19c29024617a44df79c867ab003bce7f3224c1636595ec740" +dependencies = [ + "log", + "notify", +] + [[package]] name = "indexmap" version = "1.6.0" @@ -1002,6 +1024,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nix" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", +] + [[package]] name = "nom" version = "4.2.3" diff --git a/Cargo.toml b/Cargo.toml index 3dd375b..6a54578 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,9 @@ ipc-channel="0.14.1" serde = {version = "1.0", features = ["derive"]} extend = "0.3.0" grass = "0.10" -notify = "4.0" +hotwatch = "0.4" +calloop = "0.6" +crossbeam-channel = "0.4" #thiserror = "1.0" diff --git a/src/config/mod.rs b/src/config/mod.rs index a71c299..64d9392 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -108,5 +108,6 @@ impl EwwWindowDefinition { } pub fn parse_hocon(s: &str) -> Result { - Ok(HoconLoader::new().load_str(s)?.hocon()?) + let s = s.trim(); + Ok(HoconLoader::new().strict().load_str(s)?.hocon()?) } diff --git a/src/eww_state.rs b/src/eww_state.rs index 5e00d14..d6c820a 100644 --- a/src/eww_state.rs +++ b/src/eww_state.rs @@ -1,12 +1,15 @@ +use anyhow::*; use std::collections::HashMap; use std::convert::TryFrom; use std::convert::TryInto; +use std::process::Command; -use crate::value::{AttrValue, PrimitiveValue}; +use crate::value::{AttrValue, CommandPollingUse, PrimitiveValue}; #[derive(Default)] pub struct EwwState { on_change_handlers: HashMap>>, + polling_commands: Vec<(CommandPollingUse, Box)>, state: HashMap, } @@ -54,6 +57,13 @@ impl EwwState { false } } + AttrValue::CommandPolling(command_polling_use) => { + self.polling_commands + .push((command_polling_use.clone(), Box::new(set_value.clone()))); + // TODO how do i handle commands needing to be run on the first resolve? this is an issue,.... + //self.resolve(local_env, &value.into(), set_value); + true + } AttrValue::Concrete(value) => { set_value(value.clone()); true @@ -112,3 +122,8 @@ impl EwwState { }) } } + +pub fn run_command(cmd: &str) -> Result { + let output = String::from_utf8(Command::new("/bin/bash").arg("-c").arg(cmd).output()?.stdout)?; + Ok(PrimitiveValue::from(output)) +} diff --git a/src/main.rs b/src/main.rs index 4c6315b..5c58fcb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,11 +5,9 @@ extern crate gtk; use anyhow::*; use gdk::*; -use gio::prelude::*; use grass; use gtk::prelude::*; use ipc_channel::ipc; -use notify::{self, Watcher}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path; @@ -21,6 +19,7 @@ pub mod value; pub mod widgets; use eww_state::*; +use hotwatch; use value::PrimitiveValue; #[macro_export] @@ -98,11 +97,18 @@ fn initialize_server(opts: Opt) -> Result<()> { .to_path_buf(); let scss_file_path = config_dir.join("eww.scss"); - let (watcher_tx, watcher_rx) = std::sync::mpsc::channel(); + let (watcher_tx, watcher_rx) = crossbeam_channel::unbounded(); - let mut file_watcher = notify::watcher(watcher_tx, std::time::Duration::from_millis(100))?; - file_watcher.watch(config_file_path.clone(), notify::RecursiveMode::NonRecursive)?; - if let Err(e) = file_watcher.watch(scss_file_path.clone(), notify::RecursiveMode::NonRecursive) { + let mut hotwatch = hotwatch::Hotwatch::new()?; + hotwatch.watch( + config_file_path.clone(), + glib::clone!(@strong watcher_tx => move |evt| watcher_tx.send(evt).unwrap()), + )?; + + if let Err(e) = hotwatch.watch( + scss_file_path.clone(), + glib::clone!(@strong watcher_tx => move |evt| watcher_tx.send(evt).unwrap()), + ) { eprintln!("WARN: error while loading CSS file for hot-reloading: \n{}", e) } @@ -113,20 +119,21 @@ fn initialize_server(opts: Opt) -> Result<()> { let eww_css = grass::from_string(scss_content, &grass::Options::default()).map_err(|err| anyhow!("SCSS parsing error: {:?}", err))?; + gtk::init()?; + let mut app = App { eww_state: EwwState::from_default_vars(eww_config.get_default_vars().clone()), eww_config, eww_css: eww_css.clone(), windows: HashMap::new(), + css_provider: gtk::CssProvider::new(), }; - gtk::init()?; - let css_provider = gtk::CssProvider::new(); - css_provider.load_from_data(eww_css.as_bytes())?; gdk::Screen::get_default().map(|screen| { - gtk::StyleContext::add_provider_for_screen(&screen, &css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); + gtk::StyleContext::add_provider_for_screen(&screen, &app.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); }); + app.load_css(&eww_css)?; app.handle_user_command(opts)?; let (send, recv) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); @@ -152,15 +159,13 @@ fn initialize_server(opts: Opt) -> Result<()> { std::thread::spawn(move || { while let Ok(event) = watcher_rx.recv() { let result: Result<_> = try { - dbg!(&event); match event { - notify::DebouncedEvent::Write(updated_path) | notify::DebouncedEvent::NoticeWrite(updated_path) - if updated_path == config_file_path => - { + hotwatch::Event::Write(path) | hotwatch::Event::NoticeWrite(path) if path == config_file_path => { + let config_content = std::fs::read_to_string(path).unwrap_or_default(); let new_eww_config = config::EwwConfig::from_hocon(&config::parse_hocon(&config_content)?)?; send.send(EwwEvent::ReloadConfig(new_eww_config))?; } - notify::DebouncedEvent::Write(updated_path) if updated_path == scss_file_path => { + hotwatch::Event::Write(path) if path == scss_file_path => { let scss_content = std::fs::read_to_string(scss_file_path.clone()).unwrap_or_default(); let eww_css = grass::from_string(scss_content, &grass::Options::default()) .map_err(|err| anyhow!("SCSS parsing error: {:?}", err))?; @@ -170,8 +175,7 @@ fn initialize_server(opts: Opt) -> Result<()> { } }; if let Err(err) = result { - eprintln!("error in server thread: {}", err); - std::process::exit(1); + eprintln!("error in file watcher thread: {}", err); } } }); @@ -192,6 +196,7 @@ struct App { eww_config: config::EwwConfig, eww_css: String, windows: HashMap, + css_provider: gtk::CssProvider, } impl App { @@ -231,7 +236,9 @@ impl App { window.set_type_hint(gdk::WindowTypeHint::Dock); window.set_position(gtk::WindowPosition::Center); window.set_default_size(window_def.size.0, window_def.size.1); + window.set_size_request(window_def.size.0, window_def.size.1); window.set_decorated(false); + window.set_resizable(false); let empty_local_state = HashMap::new(); let root_widget = &widgets::element_to_gtk_thing( @@ -259,18 +266,21 @@ impl App { fn reload_all_windows(&mut self, config: config::EwwConfig) -> Result<()> { self.eww_config = config; + let windows = self.windows.clone(); for (window_name, window) in windows { - dbg!(&window_name); window.close(); - window.hide(); self.open_window(&window_name)?; } Ok(()) } - fn reload_css(&mut self, css: String) -> Result<()> { - for window in self.windows.values() {} + fn load_css(&mut self, css: &str) -> Result<()> { + self.css_provider.load_from_data(css.as_bytes())?; + //self.css_provider.load_from_data(eww_css.as_bytes())?; + //gdk::Screen::get_default().map(|screen| { + //gtk::StyleContext::add_provider_for_screen(&screen, &self.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); + //}); Ok(()) } @@ -279,7 +289,7 @@ impl App { match event { EwwEvent::UserCommand(command) => self.handle_user_command(command)?, EwwEvent::ReloadConfig(config) => self.reload_all_windows(config)?, - EwwEvent::ReloadCss(css) => self.reload_css(css)?, + EwwEvent::ReloadCss(css) => self.load_css(&css)?, } }; if let Err(err) = result { diff --git a/src/value.rs b/src/value.rs index 2595891..6953adb 100644 --- a/src/value.rs +++ b/src/value.rs @@ -12,6 +12,12 @@ pub enum PrimitiveValue { Boolean(bool), } +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct PollingCommandValue { + command: String, + interval: std::time::Duration, +} + impl std::str::FromStr for PrimitiveValue { type Err = anyhow::Error; @@ -30,28 +36,21 @@ fn remove_surrounding(s: &str, surround: char) -> &str { impl TryFrom for String { type Error = anyhow::Error; fn try_from(x: PrimitiveValue) -> Result { - match x { - PrimitiveValue::String(x) => Ok(x), - _ => return Err(anyhow!("'{:?}' is not a string", x.clone())), - } + x.as_string() } } impl TryFrom for f64 { type Error = anyhow::Error; fn try_from(x: PrimitiveValue) -> Result { - try_match!(PrimitiveValue::Number(x) = &x) - .map_err(|_| anyhow!("'{:?}' is not a number", &x)) - .map(|&x| x) + x.as_f64() } } impl TryFrom for bool { type Error = anyhow::Error; fn try_from(x: PrimitiveValue) -> Result { - try_match!(PrimitiveValue::Boolean(x) = &x) - .map_err(|_| anyhow!("'{:?}' is not a bool", &x)) - .map(|&x| x) + x.as_bool() } } @@ -62,18 +61,30 @@ impl From<&str> for PrimitiveValue { } impl PrimitiveValue { - pub fn as_string(&self) -> Result<&String> { - try_match!(PrimitiveValue::String(x) = self).map_err(|x| anyhow!("{:?} is not a string", x)) + pub fn as_string(&self) -> Result { + match self { + PrimitiveValue::String(x) => Ok(x.clone()), + PrimitiveValue::Number(x) => Ok(format!("{}", x)), + PrimitiveValue::Boolean(x) => Ok(format!("{}", x)), + } } pub fn as_f64(&self) -> Result { - try_match!(PrimitiveValue::Number(x) = self) - .map_err(|x| anyhow!("{:?} is not an f64", x)) - .map(|&x| x) + match self { + PrimitiveValue::Number(x) => Ok(*x), + PrimitiveValue::String(x) => x + .parse() + .map_err(|e| anyhow!("couldn't convert string {:?} to f64: {}", &self, e)), + _ => Err(anyhow!("{:?} is not an f64", &self)), + } } pub fn as_bool(&self) -> Result { - try_match!(PrimitiveValue::Boolean(x) = self) - .map_err(|x| anyhow!("{:?} is not a bool", x)) - .map(|&x| x) + match self { + PrimitiveValue::Boolean(x) => Ok(*x), + PrimitiveValue::String(x) => x + .parse() + .map_err(|e| anyhow!("couldn't convert string {:?} to bool: {}", &self, e)), + _ => Err(anyhow!("{:?} is not a string", &self)), + } } } @@ -88,7 +99,7 @@ impl std::convert::TryFrom<&Hocon> for PrimitiveValue { Hocon::Integer(n) => PrimitiveValue::Number(*n as f64), Hocon::Real(n) => PrimitiveValue::Number(*n as f64), Hocon::Boolean(b) => PrimitiveValue::Boolean(*b), - _ => return Err(anyhow!("cannot convert {} to config::PrimitiveValue")), + _ => return Err(anyhow!("cannot convert {} to config::ConcreteValue")), }) } } @@ -97,10 +108,17 @@ impl std::convert::TryFrom<&Hocon> for PrimitiveValue { pub enum AttrValue { Concrete(PrimitiveValue), VarRef(String), + CommandPolling(CommandPollingUse), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CommandPollingUse { + command: String, + interval: std::time::Duration, } impl AttrValue { - pub fn as_string(&self) -> Result<&String> { + pub fn as_string(&self) -> Result { try_match!(AttrValue::Concrete(x) = self) .map_err(|e| anyhow!("{:?} is not a string", e))? .as_string() diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 1cb7d3f..6f2d4ca 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -30,7 +30,7 @@ pub fn element_to_gtk_thing( element: &element::ElementUse, ) -> Result { match element { - element::ElementUse::Text(text) => Ok(gtk::Label::new(Some(text.as_string()?)).upcast()), // TODO this should use resolve + element::ElementUse::Text(text) => Ok(gtk::Label::new(Some(&text.as_string()?)).upcast()), // TODO this should use resolve element::ElementUse::Widget(widget) => { let gtk_container = build_gtk_widget(widget_definitions, eww_state, local_env, widget)?; diff --git a/src/widgets/widget_definitions.rs b/src/widgets/widget_definitions.rs index f1ec313..4383a70 100644 --- a/src/widgets/widget_definitions.rs +++ b/src/widgets/widget_definitions.rs @@ -16,6 +16,8 @@ pub(super) fn resolve_widget_attrs(bargs: &mut BuilderArgs, gtk_widget: >k::Wi resolve!(bargs, gtk_widget, { resolve_str => "class" => |v| gtk_widget.get_style_context().add_class(&v), resolve_bool => "active" = true => |v| gtk_widget.set_sensitive(v), + resolve_str => "valign" => |v| gtk_widget.set_valign(parse_align(&v)), + resolve_str => "halign" => |v| gtk_widget.set_halign(parse_align(&v)), }); } @@ -51,9 +53,9 @@ pub(super) fn resolve_orientable_attrs(bargs: &mut BuilderArgs, gtk_widget: >k pub(super) fn widget_to_gtk_widget(bargs: &mut BuilderArgs) -> Result> { let gtk_widget = match bargs.widget.name.as_str() { + "layout" => build_gtk_layout(bargs)?.upcast(), "slider" => build_gtk_scale(bargs)?.upcast(), "image" => build_gtk_image(bargs)?.upcast(), - "layout" => build_gtk_layout(bargs)?.upcast(), "button" => build_gtk_button(bargs)?.upcast(), "label" => build_gtk_label(bargs)?.upcast(), _ => return Ok(None), @@ -69,7 +71,8 @@ fn build_gtk_scale(bargs: &mut BuilderArgs) -> Result { Some(>k::Adjustment::new(0.0, 0.0, 100.0, 1.0, 1.0, 1.0)), ); resolve!(bargs, gtk_widget, { - resolve_bool => "flipped" => |v| gtk_widget.set_inverted(v) + resolve_bool => "flipped" => |v| gtk_widget.set_inverted(v), + resolve_bool => "draw-value" = false => |v| gtk_widget.set_draw_value(v), }); Ok(gtk_widget) } @@ -94,9 +97,9 @@ fn build_gtk_image(bargs: &mut BuilderArgs) -> Result { fn build_gtk_layout(bargs: &mut BuilderArgs) -> Result { let gtk_widget = gtk::Box::new(gtk::Orientation::Horizontal, 0); resolve!(bargs, gtk_widget, { - resolve_f64 => "spacing" = 10.0 => |v| gtk_widget.set_spacing(v as i32), - resolve_str => "orientation" => |v| gtk_widget.set_orientation(parse_orientation(&v)), - + resolve_f64 => "spacing" = 0.0 => |v| gtk_widget.set_spacing(v as i32), + resolve_str => "orientation" => |v| gtk_widget.set_orientation(parse_orientation(&v)), + resolve_bool => "homogenous" = true => |v| gtk_widget.set_homogeneous(v), }); Ok(gtk_widget) } @@ -115,3 +118,14 @@ fn parse_orientation(o: &str) -> gtk::Orientation { _ => gtk::Orientation::Horizontal, } } + +fn parse_align(o: &str) -> gtk::Align { + match o { + "fill" => gtk::Align::Fill, + "baseline" => gtk::Align::Baseline, + "center" => gtk::Align::Center, + "start" => gtk::Align::Start, + "end" => gtk::Align::End, + _ => gtk::Align::Start, + } +}