diff --git a/crates/eww/src/app.rs b/crates/eww/src/app.rs index 05b4f6d..2544e01 100644 --- a/crates/eww/src/app.rs +++ b/crates/eww/src/app.rs @@ -25,6 +25,7 @@ pub enum DaemonCommand { UpdateCss(String), OpenMany { windows: Vec, + should_toggle: bool, sender: DaemonResponseSender, }, OpenWindow { @@ -36,8 +37,8 @@ pub enum DaemonCommand { should_toggle: bool, sender: DaemonResponseSender, }, - CloseWindow { - window_name: String, + CloseWindows { + windows: Vec, sender: DaemonResponseSender, }, KillServer, @@ -97,23 +98,15 @@ impl App { let mut errors = Vec::new(); let config_result = config::read_from_file(&self.paths.get_yuck_path()); - match config_result.and_then(|new_config| self.load_config(new_config)) { - Ok(()) => {} - Err(e) => errors.push(e), + if let Err(e) = config_result.and_then(|new_config| self.load_config(new_config)) { + errors.push(e) } - let css_result = crate::util::parse_scss_from_file(&self.paths.get_eww_scss_path()); - match css_result.and_then(|css| self.load_css(&css)) { - Ok(()) => {} - Err(e) => errors.push(e), + if let Err(e) = css_result.and_then(|css| self.load_css(&css)) { + errors.push(e) } - let errors = errors.into_iter().map(|e| error_handling_ctx::format_error(&e)).join("\n"); - if errors.is_empty() { - sender.send_success(String::new())?; - } else { - sender.send_failure(errors)?; - } + sender.respond_with_error_list(errors)?; } DaemonCommand::UpdateConfig(config) => { self.load_config(config)?; @@ -132,9 +125,18 @@ impl App { self.close_window(&window_name)?; } } - DaemonCommand::OpenMany { windows, sender } => { - let result = windows.iter().try_for_each(|w| self.open_window(w, None, None, None, None)); - respond_with_result(sender, result)?; + DaemonCommand::OpenMany { windows, should_toggle, sender } => { + let errors = windows + .iter() + .map(|w| { + if should_toggle && self.open_windows.contains_key(w) { + self.close_window(w) + } else { + self.open_window(w, None, None, None, None) + } + }) + .filter_map(Result::err); + sender.respond_with_error_list(errors)?; } DaemonCommand::OpenWindow { window_name, pos, size, anchor, screen: monitor, should_toggle, sender } => { let result = if should_toggle && self.open_windows.contains_key(&window_name) { @@ -142,11 +144,11 @@ impl App { } else { self.open_window(&window_name, pos, size, monitor, anchor) }; - respond_with_result(sender, result)?; + sender.respond_with_result(result)?; } - DaemonCommand::CloseWindow { window_name, sender } => { - let result = self.close_window(&window_name); - respond_with_result(sender, result)?; + DaemonCommand::CloseWindows { windows, sender } => { + let errors = windows.iter().map(|window| self.close_window(&window)).filter_map(Result::err); + sender.respond_with_error_list(errors)?; } DaemonCommand::PrintState { all, sender } => { let vars = self.eww_state.get_variables().iter(); @@ -269,7 +271,8 @@ impl App { self.eww_config = config; self.eww_state.clear_all_window_states(); - let window_names: Vec = self.open_windows.keys().cloned().chain(self.failed_windows.iter().cloned()).dedup().collect(); + let window_names: Vec = + self.open_windows.keys().cloned().chain(self.failed_windows.iter().cloned()).dedup().collect(); for window_name in &window_names { self.open_window(&window_name, None, None, None, None)?; } @@ -381,19 +384,6 @@ fn get_monitor_geometry(n: Option) -> Result { Ok(monitor.get_geometry()) } -/// In case of an Err, send the error message to a sender. -fn respond_with_result(sender: DaemonResponseSender, result: Result) -> Result<()> { - match result { - Ok(_) => sender.send_success(String::new()), - Err(e) => { - let formatted = error_handling_ctx::format_error(&e); - println!("Action failed with error: {}", formatted); - sender.send_failure(formatted) - }, - } - .context("sending response from main thread") -} - pub fn get_window_rectangle(geometry: WindowGeometry, screen_rect: gdk::Rectangle) -> gdk::Rectangle { let (offset_x, offset_y) = geometry.offset.relative_to(screen_rect.width, screen_rect.height); let (width, height) = geometry.size.relative_to(screen_rect.width, screen_rect.height); diff --git a/crates/eww/src/daemon_response.rs b/crates/eww/src/daemon_response.rs index 19e96e2..35fb7ed 100644 --- a/crates/eww/src/daemon_response.rs +++ b/crates/eww/src/daemon_response.rs @@ -1,4 +1,7 @@ use anyhow::*; +use itertools::Itertools; + +use crate::error_handling_ctx; /// Response that the app may send as a response to a event. /// This is used in `DaemonCommand`s that contain a response sender. @@ -34,6 +37,33 @@ impl DaemonResponseSender { pub fn send_failure(&self, s: String) -> Result<()> { self.0.send(DaemonResponse::Failure(s)).context("Failed to send failure response from application thread") } + + /// Given a list of errors, respond with an error value if there are any errors, and respond with success otherwise. + pub fn respond_with_error_list(&self, errors: impl IntoIterator) -> Result<()> { + let errors = errors.into_iter().map(|e| error_handling_ctx::format_error(&e)).join("\n"); + if errors.is_empty() { + self.send_success(String::new()) + } else { + self.respond_with_error_msg(errors) + } + } + + /// In case of an Err, send the error message to a sender. + pub fn respond_with_result(&self, result: Result) -> Result<()> { + match result { + Ok(_) => self.send_success(String::new()), + Err(e) => { + let formatted = error_handling_ctx::format_error(&e); + self.respond_with_error_msg(formatted) + } + } + .context("sending response from main thread") + } + + fn respond_with_error_msg(&self, msg: String) -> Result<()> { + println!("Action failed with error: {}", msg); + self.send_failure(msg) + } } pub type DaemonResponseReceiver = tokio::sync::mpsc::UnboundedReceiver; diff --git a/crates/eww/src/opts.rs b/crates/eww/src/opts.rs index 3925ed4..404d23b 100644 --- a/crates/eww/src/opts.rs +++ b/crates/eww/src/opts.rs @@ -106,11 +106,17 @@ pub enum ActionWithServer { /// Open multiple windows at once. /// NOTE: This will in the future be part of eww open, and will then be removed. #[structopt(name = "open-many")] - OpenMany { windows: Vec }, + OpenMany { + windows: Vec, - /// Close the window with the given name + /// If a window is already open, close it instead + #[structopt(long = "toggle")] + should_toggle: bool, + }, + + /// Close the given windows #[structopt(name = "close", alias = "c")] - CloseWindow { window_name: String }, + CloseWindows { windows: Vec }, /// Reload the configuration #[structopt(name = "reload", alias = "r")] @@ -184,8 +190,8 @@ impl ActionWithServer { let _ = send.send(DaemonResponse::Success("pong".to_owned())); return (app::DaemonCommand::NoOp, Some(recv)); } - ActionWithServer::OpenMany { windows } => { - return with_response_channel(|sender| app::DaemonCommand::OpenMany { windows, sender }); + ActionWithServer::OpenMany { windows, should_toggle } => { + return with_response_channel(|sender| app::DaemonCommand::OpenMany { windows, should_toggle, sender }); } ActionWithServer::OpenWindow { window_name, pos, size, screen, anchor, should_toggle } => { return with_response_channel(|sender| app::DaemonCommand::OpenWindow { @@ -198,8 +204,8 @@ impl ActionWithServer { sender, }) } - ActionWithServer::CloseWindow { window_name } => { - return with_response_channel(|sender| app::DaemonCommand::CloseWindow { window_name, sender }); + ActionWithServer::CloseWindows { windows } => { + return with_response_channel(|sender| app::DaemonCommand::CloseWindows { windows, sender }); } ActionWithServer::Reload => return with_response_channel(app::DaemonCommand::ReloadConfigAndCss), ActionWithServer::ShowWindows => return with_response_channel(app::DaemonCommand::PrintWindows),