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:
- 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.

View File

@ -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!(

View File

@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
pub enum Response {
Ok,
OkValue { value: String },
Multi { values: Vec<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 {
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 {

View File

@ -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<Bar> {
pub fn bars_by_name(&self, name: &str) -> Vec<Bar> {
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.