From 42e25f5ef2ce9886d8fafb42aff9ced7ef183726 Mon Sep 17 00:00:00 2001 From: Ridan Vandenbergh Date: Sat, 16 Nov 2024 21:48:12 +0100 Subject: [PATCH] fix(ipc): support querying against duplicate bar names It is possible/valid to define multiple bars by the same name by setting `name` on the top-level bar object, but not specifying monitors. This updates IPC to support this scenario. Allow IPC to act on multiple bars by the same name (#777) --- docs/Controlling Ironbar.md | 19 +++++++- src/cli.rs | 1 + src/ipc/responses.rs | 1 + src/ipc/server/bar.rs | 90 +++++++++++++++++++++---------------- src/main.rs | 9 ++-- 5 files changed, 75 insertions(+), 45 deletions(-) diff --git a/docs/Controlling Ironbar.md b/docs/Controlling Ironbar.md index 3edfdcc..49d1f8c 100644 --- a/docs/Controlling Ironbar.md +++ b/docs/Controlling Ironbar.md @@ -10,7 +10,8 @@ You can also view help per sub-command or command, for example using `ironbar va The CLI supports plaintext and JSON output. Plaintext will: - Print `ok` for empty success responses -- Print the returned body for success responses +- Print the returned body for each success response + - Some commands act on multiple objects, in which case the CLI will print one line for each body. - Print `error` to followed by the error on the next line for error responses. This is printed to `stderr`. Example: @@ -150,6 +151,9 @@ Each key/value pair is on its own `\n` separated newline. The key and value are ### `bar` +> [!NOTE] +> If there are multiple bars by the same name, the `bar` subcommand will act on all of them and return a `multi` response for commands that get a value. + #### `show` Forces a bar to be shown, regardless of the current visibility state. @@ -324,6 +328,17 @@ The operation completed successfully, with response data. } ``` +### `multi` + +The operation completed successfully on multiple objects, with response data. + +```json +{ + "type": "multi", + "values": ["lorem ipsum", "dolor sit"] +} +``` + ### `error` The operation failed. @@ -335,4 +350,4 @@ Message is optional. "type": "error", "message": "lorem ipsum" } -``` \ No newline at end of file +``` diff --git a/src/cli.rs b/src/cli.rs index 769d706..a273f96 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -46,6 +46,7 @@ pub fn handle_response(response: Response, format: Format) { Format::Plain => match response { Response::Ok => println!("ok"), Response::OkValue { value } => println!("{value}"), + Response::Multi { values } => println!("{}", values.join("\n")), Response::Err { message } => eprintln!("error\n{}", message.unwrap_or_default()), }, Format::Json => println!( diff --git a/src/ipc/responses.rs b/src/ipc/responses.rs index f033669..a8c5048 100644 --- a/src/ipc/responses.rs +++ b/src/ipc/responses.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; pub enum Response { Ok, OkValue { value: String }, + Multi { values: Vec }, Err { message: Option }, } diff --git a/src/ipc/server/bar.rs b/src/ipc/server/bar.rs index ab047d3..d5c42c5 100644 --- a/src/ipc/server/bar.rs +++ b/src/ipc/server/bar.rs @@ -8,48 +8,60 @@ use std::rc::Rc; pub fn handle_command(command: BarCommand, ironbar: &Rc) -> Response { use BarCommandType::*; - let bar = ironbar.bar_by_name(&command.name); - let Some(bar) = bar else { - return Response::error("Invalid bar name"); - }; + let bars = ironbar.bars_by_name(&command.name); - match command.subcommand { - Show => set_visible(&bar, true), - Hide => set_visible(&bar, false), - SetVisible { visible } => set_visible(&bar, visible), - ToggleVisible => set_visible(&bar, !bar.visible()), - GetVisible => Response::OkValue { - value: bar.visible().to_string(), - }, - - ShowPopup { widget_name } => show_popup(&bar, &widget_name), - HidePopup => hide_popup(&bar), - SetPopupVisible { - widget_name, - visible, - } => { - if visible { - show_popup(&bar, &widget_name) - } else { - hide_popup(&bar) + bars.into_iter() + .map(|bar| match &command.subcommand { + Show => set_visible(&bar, true), + Hide => set_visible(&bar, false), + SetVisible { visible } => set_visible(&bar, *visible), + ToggleVisible => set_visible(&bar, !bar.visible()), + GetVisible => Response::OkValue { + value: bar.visible().to_string(), + }, + ShowPopup { widget_name } => show_popup(&bar, widget_name), + HidePopup => hide_popup(&bar), + SetPopupVisible { + widget_name, + visible, + } => { + if *visible { + show_popup(&bar, widget_name) + } else { + hide_popup(&bar) + }; + Response::Ok } - } - TogglePopup { widget_name } => { - if bar.popup().visible() { - hide_popup(&bar) - } else { - show_popup(&bar, &widget_name) + TogglePopup { widget_name } => { + if bar.popup().visible() { + hide_popup(&bar) + } else { + show_popup(&bar, widget_name) + }; + Response::Ok } - } - GetPopupVisible => Response::OkValue { - value: bar.popup().visible().to_string(), - }, - SetExclusive { exclusive } => { - bar.set_exclusive(exclusive); - - Response::Ok - } - } + GetPopupVisible => Response::OkValue { + value: bar.popup().visible().to_string(), + }, + SetExclusive { exclusive } => { + bar.set_exclusive(*exclusive); + Response::Ok + } + }) + .reduce(|acc, rsp| match (acc, rsp) { + // If all responses are Ok, return one Ok. We assume we'll never mix Ok and OkValue. + (Response::Ok, _) => Response::Ok, + // Two or more OkValues create a multi: + (Response::OkValue { value: v1 }, Response::OkValue { value: v2 }) => Response::Multi { + values: vec![v1, v2], + }, + (Response::Multi { mut values }, Response::OkValue { value: v }) => { + values.push(v); + Response::Multi { values } + } + _ => unreachable!(), + }) + .unwrap_or(Response::error("Invalid bar name")) } fn set_visible(bar: &Bar, visible: bool) -> Response { diff --git a/src/main.rs b/src/main.rs index a642e73..4711b7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -267,17 +267,18 @@ impl Ironbar { .clone() } - /// Gets a clone of a bar by its unique name. + /// Gets clones of bars by their name. /// - /// Since the bar contains mostly GTK objects, + /// Since the bars contain mostly GTK objects, /// the clone is cheap enough to not worry about. #[must_use] - pub fn bar_by_name(&self, name: &str) -> Option { + pub fn bars_by_name(&self, name: &str) -> Vec { self.bars .borrow() .iter() - .find(|&bar| bar.name() == name) + .filter(|&bar| bar.name() == name) .cloned() + .collect() } /// Re-reads the config file from disk and replaces the active config.