From 1f6fe840fcfd36714e6958f1f34e11917a2fce28 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Fri, 2 Oct 2020 21:19:17 +0200 Subject: [PATCH] cleanup, but file watching breaks --- Cargo.lock | 42 +++++++ Cargo.toml | 2 + src/app.rs | 112 +++++++++++++++++++ src/config/mod.rs | 21 ++-- src/eww_state.rs | 9 ++ src/main.rs | 271 +++++++++++++++------------------------------- src/util.rs | 9 ++ src/value.rs | 4 +- 8 files changed, 275 insertions(+), 195 deletions(-) create mode 100644 src/app.rs create mode 100644 src/util.rs diff --git a/Cargo.lock b/Cargo.lock index 2a42ee3..cce744f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -335,9 +335,11 @@ dependencies = [ "hotwatch", "ipc-channel", "maplit", + "num", "pretty_assertions", "regex", "serde", + "stoppable_thread", "structopt", "try_match", ] @@ -1064,6 +1066,20 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3e176191bc4faad357e3122c4747aa098ac880e88b168f106386128736cf4a" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.3.0" @@ -1075,6 +1091,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05ad05bd8977050b171b3f6b48175fea6e0565b7981059b486075e1026a9fb5" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.43" @@ -1085,6 +1110,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.3.0" @@ -1457,6 +1493,12 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" +[[package]] +name = "stoppable_thread" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6700c1ed393aa9c1fff950032a64a4856436dadee820641ce1b914cab65019f" + [[package]] name = "strsim" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 6a54578..bd38bdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,8 @@ grass = "0.10" hotwatch = "0.4" calloop = "0.6" crossbeam-channel = "0.4" +num = "0.3" +stoppable_thread = "0.2" #thiserror = "1.0" diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..6608373 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,112 @@ +use crate::*; +use std::collections::HashMap; + +#[derive(Debug)] +pub enum EwwEvent { + UserCommand(Opt), + ReloadConfig(config::EwwConfig), + ReloadCss(String), +} + +#[derive(Debug)] +pub struct App { + pub eww_state: EwwState, + pub eww_config: config::EwwConfig, + pub windows: HashMap, + pub css_provider: gtk::CssProvider, +} + +impl App { + pub fn handle_user_command(&mut self, opts: Opt) -> Result<()> { + match opts.action { + OptAction::Update { fieldname, value } => self.update_state(fieldname, value), + OptAction::OpenWindow { window_name } => self.open_window(&window_name)?, + OptAction::CloseWindow { window_name } => self.close_window(&window_name)?, + } + Ok(()) + } + + pub fn handle_event(&mut self, event: EwwEvent) { + let result: Result<_> = try { + match event { + EwwEvent::UserCommand(command) => self.handle_user_command(command)?, + EwwEvent::ReloadConfig(config) => self.reload_all_windows(config)?, + EwwEvent::ReloadCss(css) => self.load_css(&css)?, + } + }; + if let Err(err) = result { + eprintln!("Error while handling event: {:?}", err); + } + } + + fn update_state(&mut self, fieldname: String, value: PrimitiveValue) { + self.eww_state.update_value(fieldname, value); + } + + fn close_window(&mut self, window_name: &str) -> Result<()> { + let window = self + .windows + .get(window_name) + .context(format!("No window with name '{}' is running.", window_name))?; + window.close(); + Ok(()) + } + + fn open_window(&mut self, window_name: &str) -> Result<()> { + let window_def = self + .eww_config + .get_windows() + .get(window_name) + .context(format!("No window named '{}' defined", window_name))? + .clone(); + + let window = gtk::Window::new(gtk::WindowType::Popup); + window.set_title("Eww"); + window.set_wmclass("noswallow", "noswallow"); + 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( + &self.eww_config.get_widgets(), + &mut self.eww_state, + &empty_local_state, + &window_def.widget, + )?; + root_widget.get_style_context().add_class(window_name); + window.add(root_widget); + + window.show_all(); + + let gdk_window = window.get_window().context("couldn't get gdk window from gtk window")?; + gdk_window.set_override_redirect(true); + gdk_window.move_(window_def.position.0, window_def.position.1); + gdk_window.show(); + gdk_window.raise(); + window.set_keep_above(true); + + self.windows.insert(window_name.to_string(), window); + + Ok(()) + } + + pub fn reload_all_windows(&mut self, config: config::EwwConfig) -> Result<()> { + self.eww_config = config; + self.eww_state.clear_callbacks(); + let windows = self.windows.clone(); + for (window_name, window) in windows { + window.close(); + self.open_window(&window_name)?; + } + Ok(()) + } + + pub fn load_css(&mut self, css: &str) -> Result<()> { + self.css_provider.load_from_data(css.as_bytes())?; + Ok(()) + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs index ac21320..6f6d819 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -9,13 +9,6 @@ use std::convert::TryFrom; pub mod element; pub mod hocon_ext; -#[derive(Debug, Clone)] -pub struct EwwConfig { - widgets: HashMap, - windows: HashMap, - default_vars: HashMap, -} - #[allow(unused)] macro_rules! try_type { ($typ:ty; $code:expr) => {{ @@ -28,8 +21,20 @@ macro_rules! try_type { }}; } +#[derive(Debug, Clone)] +pub struct EwwConfig { + widgets: HashMap, + windows: HashMap, + default_vars: HashMap, +} + impl EwwConfig { - pub fn from_hocon(hocon: &Hocon) -> Result { + pub fn read_from_file>(path: P) -> Result { + let content = std::fs::read_to_string(path)?; + EwwConfig::from_hocon(&parse_hocon(&content)?) + } + + pub fn from_hocon(hocon: &Hocon) -> Result { let data = hocon.as_hash()?; let widgets = data diff --git a/src/eww_state.rs b/src/eww_state.rs index d6c820a..3ba6f4e 100644 --- a/src/eww_state.rs +++ b/src/eww_state.rs @@ -26,6 +26,15 @@ impl EwwState { ..EwwState::default() } } + + pub fn clear_callbacks(&mut self) { + self.on_change_handlers.clear(); + } + + pub fn get_command_polling_uses(&self) -> Vec<&CommandPollingUse> { + self.polling_commands.iter().map(|(x, _)| x).collect() + } + pub fn update_value(&mut self, key: String, value: PrimitiveValue) { if let Some(handlers) = self.on_change_handlers.get(&key) { for on_change in handlers { diff --git a/src/main.rs b/src/main.rs index 119354e..6d12a29 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,24 +4,24 @@ extern crate gio; extern crate gtk; use anyhow::*; +use eww_state::*; use gdk::*; -use grass; use gtk::prelude::*; +use hotwatch; use ipc_channel::ipc; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::path; +use std::path::{Path, PathBuf}; use structopt::StructOpt; +use value::PrimitiveValue; +pub mod app; pub mod config; pub mod eww_state; +pub mod util; pub mod value; pub mod widgets; -use eww_state::*; -use hotwatch; -use value::PrimitiveValue; - #[macro_export] macro_rules! build { ($var_name:ident = $value:expr ; $code:block) => {{ @@ -31,6 +31,15 @@ macro_rules! build { }}; } +macro_rules! try_logging_errors { + ($context:literal => $code:block) => {{ + let result: Result<_> = try { $code }; + if let Err(err) = result { + eprintln!("Error while {}: {:?}", $context, err); + } + }}; +} + fn main() { if let Err(e) = try_main() { eprintln!("{:?}", e); @@ -38,15 +47,15 @@ fn main() { } #[derive(StructOpt, Debug, Serialize, Deserialize)] -struct Opt { +pub struct Opt { #[structopt(short = "-c", parse(from_os_str))] - config_file: Option, + config_file: Option, #[structopt(subcommand)] action: OptAction, } #[derive(StructOpt, Debug, Serialize, Deserialize)] -enum OptAction { +pub enum OptAction { #[structopt(name = "update")] Update { fieldname: String, value: PrimitiveValue }, @@ -57,13 +66,6 @@ enum OptAction { CloseWindow { window_name: String }, } -#[derive(Debug)] -enum EwwEvent { - UserCommand(Opt), - ReloadConfig(config::EwwConfig), - ReloadCss(String), -} - fn try_main() -> Result<()> { let opts: Opt = StructOpt::from_args(); if let Ok(sender) = find_server_process() { @@ -79,10 +81,10 @@ fn find_server_process() -> Result> { Ok(ipc::IpcSender::connect(instance_path)?) } -fn get_config_file_path() -> path::PathBuf { +fn get_config_file_path() -> PathBuf { std::env::var("XDG_CONFIG_HOME") - .map(|v| path::PathBuf::from(v)) - .unwrap_or_else(|_| path::PathBuf::from(std::env::var("HOME").unwrap()).join(".config")) + .map(|v| PathBuf::from(v)) + .unwrap_or_else(|_| PathBuf::from(std::env::var("HOME").unwrap()).join(".config")) .join("eww") .join("eww.conf") } @@ -97,90 +99,33 @@ fn initialize_server(opts: Opt) -> Result<()> { .to_path_buf(); let scss_file_path = config_dir.join("eww.scss"); - let (watcher_tx, watcher_rx) = crossbeam_channel::unbounded(); - - 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) - } - - let config_content = std::fs::read_to_string(config_file_path.clone())?; - let scss_content = std::fs::read_to_string(scss_file_path.clone()).unwrap_or_default(); - - let eww_config = config::EwwConfig::from_hocon(&config::parse_hocon(&config_content)?)?; - let eww_css = - grass::from_string(scss_content, &grass::Options::default()).map_err(|err| anyhow!("SCSS parsing error: {:?}", err))?; + let eww_config = config::EwwConfig::read_from_file(&config_file_path)?; gtk::init()?; - let mut app = App { + let mut app = 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(), }; - gdk::Screen::get_default().map(|screen| { + if let Some(screen) = gdk::Screen::get_default() { gtk::StyleContext::add_provider_for_screen(&screen, &app.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); - }); + } - app.load_css(&eww_css)?; + if let Ok(eww_css) = util::parse_scss_from_file(&scss_file_path) { + app.load_css(&eww_css)?; + } + + // run the command that eww was started with app.handle_user_command(opts)?; - let (send, recv) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); - std::thread::spawn({ - let send = send.clone(); - { - move || { - let result: Result<_> = try { - loop { - let (ipc_server, instance_path): (ipc::IpcOneShotServer, _) = ipc::IpcOneShotServer::new()?; - std::fs::write("/tmp/eww-instance-path", instance_path)?; - let (_, initial) = ipc_server.accept()?; - send.send(EwwEvent::UserCommand(initial))?; - } - }; - if let Err(err) = result { - eprintln!("error in server thread: {}", err); - std::process::exit(1); - } - } - } - }); - std::thread::spawn(move || { - while let Ok(event) = watcher_rx.recv() { - let result: Result<_> = try { - match event { - 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))?; - } - 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))?; - send.send(EwwEvent::ReloadCss(eww_css))?; - } - _ => {} - } - }; - if let Err(err) = result { - eprintln!("error in file watcher thread: {}", err); - } - } - }); + let (evt_send, evt_recv) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); + run_server_thread(evt_send.clone()); + run_filewatch_thread(&config_file_path, &scss_file_path, evt_send.clone())?; - recv.attach(None, move |msg| { + evt_recv.attach(None, move |msg| { app.handle_event(msg); glib::Continue(true) }); @@ -190,106 +135,62 @@ fn initialize_server(opts: Opt) -> Result<()> { Ok(()) } -#[derive(Debug)] -struct App { - eww_state: EwwState, - eww_config: config::EwwConfig, - eww_css: String, - windows: HashMap, - css_provider: gtk::CssProvider, -} - -impl App { - fn handle_user_command(&mut self, opts: Opt) -> Result<()> { - match opts.action { - OptAction::Update { fieldname, value } => self.update_state(fieldname, value), - OptAction::OpenWindow { window_name } => self.open_window(&window_name)?, - OptAction::CloseWindow { window_name } => self.close_window(&window_name)?, - } - Ok(()) - } - - fn update_state(&mut self, fieldname: String, value: PrimitiveValue) { - self.eww_state.update_value(fieldname, value); - } - - fn close_window(&mut self, window_name: &str) -> Result<()> { - let window = self - .windows - .get(window_name) - .context(format!("No window with name '{}' is running.", window_name))?; - window.close(); - Ok(()) - } - - fn open_window(&mut self, window_name: &str) -> Result<()> { - let window_def = self - .eww_config - .get_windows() - .get(window_name) - .context(format!("No window named '{}' defined", window_name))? - .clone(); - - let window = gtk::Window::new(gtk::WindowType::Popup); - window.set_title("Eww"); - window.set_wmclass("noswallow", "noswallow"); - 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( - &self.eww_config.get_widgets(), - &mut self.eww_state, - &empty_local_state, - &window_def.widget, - )?; - root_widget.get_style_context().add_class(window_name); - window.add(root_widget); - - window.show_all(); - - let gdk_window = window.get_window().context("couldn't get gdk window from gtk window")?; - gdk_window.set_override_redirect(true); - gdk_window.move_(window_def.position.0, window_def.position.1); - gdk_window.show(); - gdk_window.raise(); - window.set_keep_above(true); - - self.windows.insert(window_name.to_string(), window); - - Ok(()) - } - - fn reload_all_windows(&mut self, config: config::EwwConfig) -> Result<()> { - self.eww_config = config; - // TODO this needs to handle removing the callbacks to the old gtk windows, as otherwise this might by horribly fucked. - let windows = self.windows.clone(); - for (window_name, window) in windows { - window.close(); - self.open_window(&window_name)?; - } - Ok(()) - } - - fn load_css(&mut self, css: &str) -> Result<()> { - self.css_provider.load_from_data(css.as_bytes())?; - Ok(()) - } - - fn handle_event(&mut self, event: EwwEvent) { +fn run_server_thread(evt_send: glib::Sender) -> std::thread::JoinHandle<()> { + std::thread::spawn(move || { let result: Result<_> = try { - match event { - EwwEvent::UserCommand(command) => self.handle_user_command(command)?, - EwwEvent::ReloadConfig(config) => self.reload_all_windows(config)?, - EwwEvent::ReloadCss(css) => self.load_css(&css)?, + loop { + let (ipc_server, instance_path): (ipc::IpcOneShotServer, _) = ipc::IpcOneShotServer::new()?; + std::fs::write("/tmp/eww-instance-path", instance_path)?; + let (_, initial) = ipc_server.accept()?; + evt_send.send(app::EwwEvent::UserCommand(initial))?; } }; if let Err(err) = result { - eprintln!("Error while handling event: {:?}", err); + eprintln!("error in server thread: {}", err); + std::process::exit(1); } + }) +} + +fn run_filewatch_thread>( + config_file_path: P, + scss_file_path: P, + evt_send: glib::Sender, +) -> Result<()> { + let mut hotwatch = hotwatch::Hotwatch::new()?; + hotwatch.watch_file_changes( + config_file_path, + glib::clone!(@strong evt_send => move |path| { + try_logging_errors!("handling change of config file" => { + let new_eww_config = config::EwwConfig::read_from_file(path)?; + evt_send.send(app::EwwEvent::ReloadConfig(new_eww_config))?; + }); + }), + )?; + + let result = hotwatch.watch_file_changes(scss_file_path, move |path| { + try_logging_errors!("handling change of scss file" => { + let eww_css = util::parse_scss_from_file(path)?; + evt_send.send(app::EwwEvent::ReloadCss(eww_css))?; + }) + }); + if let Err(e) = result { + eprintln!("WARN: error while loading CSS file for hot-reloading: \n{}", e) + }; + Ok(()) +} + +#[extend::ext(pub)] +impl hotwatch::Hotwatch { + fn watch_file_changes(&mut self, path: P, callback: F) -> Result<()> + where + P: AsRef, + F: 'static + Fn(PathBuf) + Send, + { + let result = self.watch(path, move |evt| match evt { + hotwatch::Event::Write(path) | hotwatch::Event::NoticeWrite(path) => callback(path), + _ => {} + }); + Ok(result?) } } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..b38f4a7 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,9 @@ +use anyhow::*; +use grass; +use std::path::Path; + +pub fn parse_scss_from_file>(path: P) -> Result { + let scss_content = std::fs::read_to_string(path)?; + grass::from_string(scss_content, &grass::Options::default()) + .map_err(|err| anyhow!("encountered SCSS parsing error: {:?}", err)) +} diff --git a/src/value.rs b/src/value.rs index 6953adb..ad52638 100644 --- a/src/value.rs +++ b/src/value.rs @@ -113,8 +113,8 @@ pub enum AttrValue { #[derive(Clone, Debug, PartialEq)] pub struct CommandPollingUse { - command: String, - interval: std::time::Duration, + pub command: String, + pub interval: std::time::Duration, } impl AttrValue {