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)
This commit is contained in:
Ridan Vandenbergh 2024-11-16 21:48:12 +01:00 committed by GitHub
parent e7c56ee09b
commit 42e25f5ef2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 75 additions and 45 deletions

View File

@ -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: The CLI supports plaintext and JSON output. Plaintext will:
- Print `ok` for empty success responses - 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`. - Print `error` to followed by the error on the next line for error responses. This is printed to `stderr`.
Example: Example:
@ -150,6 +151,9 @@ Each key/value pair is on its own `\n` separated newline. The key and value are
### `bar` ### `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` #### `show`
Forces a bar to be shown, regardless of the current visibility state. 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` ### `error`
The operation failed. The operation failed.
@ -335,4 +350,4 @@ Message is optional.
"type": "error", "type": "error",
"message": "lorem ipsum" "message": "lorem ipsum"
} }
``` ```

View File

@ -46,6 +46,7 @@ pub fn handle_response(response: Response, format: Format) {
Format::Plain => match response { Format::Plain => match response {
Response::Ok => println!("ok"), Response::Ok => println!("ok"),
Response::OkValue { value } => println!("{value}"), Response::OkValue { value } => println!("{value}"),
Response::Multi { values } => println!("{}", values.join("\n")),
Response::Err { message } => eprintln!("error\n{}", message.unwrap_or_default()), Response::Err { message } => eprintln!("error\n{}", message.unwrap_or_default()),
}, },
Format::Json => println!( Format::Json => println!(

View File

@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
pub enum Response { pub enum Response {
Ok, Ok,
OkValue { value: String }, OkValue { value: String },
Multi { values: Vec<String> },
Err { message: Option<String> }, Err { message: Option<String> },
} }

View File

@ -8,48 +8,60 @@ use std::rc::Rc;
pub fn handle_command(command: BarCommand, ironbar: &Rc<Ironbar>) -> Response { pub fn handle_command(command: BarCommand, ironbar: &Rc<Ironbar>) -> Response {
use BarCommandType::*; use BarCommandType::*;
let bar = ironbar.bar_by_name(&command.name); let bars = ironbar.bars_by_name(&command.name);
let Some(bar) = bar else {
return Response::error("Invalid bar name");
};
match command.subcommand { bars.into_iter()
Show => set_visible(&bar, true), .map(|bar| match &command.subcommand {
Hide => set_visible(&bar, false), Show => set_visible(&bar, true),
SetVisible { visible } => set_visible(&bar, visible), Hide => set_visible(&bar, false),
ToggleVisible => set_visible(&bar, !bar.visible()), SetVisible { visible } => set_visible(&bar, *visible),
GetVisible => Response::OkValue { ToggleVisible => set_visible(&bar, !bar.visible()),
value: bar.visible().to_string(), GetVisible => Response::OkValue {
}, value: bar.visible().to_string(),
},
ShowPopup { widget_name } => show_popup(&bar, &widget_name), ShowPopup { widget_name } => show_popup(&bar, widget_name),
HidePopup => hide_popup(&bar), HidePopup => hide_popup(&bar),
SetPopupVisible { SetPopupVisible {
widget_name, widget_name,
visible, visible,
} => { } => {
if visible { if *visible {
show_popup(&bar, &widget_name) show_popup(&bar, widget_name)
} else { } else {
hide_popup(&bar) hide_popup(&bar)
};
Response::Ok
} }
} TogglePopup { widget_name } => {
TogglePopup { widget_name } => { if bar.popup().visible() {
if bar.popup().visible() { hide_popup(&bar)
hide_popup(&bar) } else {
} else { show_popup(&bar, widget_name)
show_popup(&bar, &widget_name) };
Response::Ok
} }
} GetPopupVisible => Response::OkValue {
GetPopupVisible => Response::OkValue { value: bar.popup().visible().to_string(),
value: bar.popup().visible().to_string(), },
}, SetExclusive { exclusive } => {
SetExclusive { exclusive } => { bar.set_exclusive(*exclusive);
bar.set_exclusive(exclusive); Response::Ok
}
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 { fn set_visible(bar: &Bar, visible: bool) -> Response {

View File

@ -267,17 +267,18 @@ impl Ironbar {
.clone() .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. /// the clone is cheap enough to not worry about.
#[must_use] #[must_use]
pub fn bar_by_name(&self, name: &str) -> Option<Bar> { pub fn bars_by_name(&self, name: &str) -> Vec<Bar> {
self.bars self.bars
.borrow() .borrow()
.iter() .iter()
.find(|&bar| bar.name() == name) .filter(|&bar| bar.name() == name)
.cloned() .cloned()
.collect()
} }
/// Re-reads the config file from disk and replaces the active config. /// Re-reads the config file from disk and replaces the active config.