mirror of
https://github.com/JakeStanger/ironbar.git
synced 2024-11-25 10:53:56 +03:00
feat!: improve CLI structure, add new commands
This splits most CLI/IPC commands into two categories: - `var` for ironvars - `bar` for controlling individual bars. It also introduces more commands for visibility, as well as breaking existing ones. New commands: - `show` - `hide` - `toggle_visible` - `set_popup_visible` - `get_popup_visible` Lastly, the implementation of some existing commands has been improved. BREAKING CHANGE: All IPC commands have changed. Namely, `type` has been changed to `command`, and bar/var related commands are now under a `subcommand`. The full spec can be found on the wiki. BREAKING CHANGE: Several CLI commands are now located under the `var` and `bar` categories. Usage of any commands to get/set Ironvars or control bar visibility will need to be updated. BREAKING CHANGE: The `open_popup` and `close_popup` IPC commands are now called `show_popup` and `hide_popup` respectively. BREAKING CHANGE: The popup `name` argument has been renamed to `widget_name` on all IPC commands. BREAKING CHANGE: The `set-visibility` CLI command now takes a `true`/`false` positional argument in place of the `-v` flag. BREAKING CHANGE: `ok_value` responses will no longer print `ok` as the first line when using the CLI
This commit is contained in:
parent
8dda49477b
commit
9dd711235f
@ -5,28 +5,38 @@ It also includes a command line interface, which can be used for interacting wit
|
||||
# CLI
|
||||
|
||||
This is shipped as part of the `ironbar` binary. To view commands, you can use `ironbar --help`.
|
||||
You can also view help per-command, for example using `ironbar set --help`.
|
||||
You can also view help per sub-command or command, for example using `ironbar var --help` or `ironbar var set --help`.
|
||||
|
||||
Responses are handled by writing their type to stdout, followed by any value starting on the next line.
|
||||
Error responses are written to stderr in the same format.
|
||||
The CLI supports plaintext and JSON output. Plaintext will:
|
||||
|
||||
- Print `ok` for empty success responses
|
||||
- Print the returned body for success responses
|
||||
- Print `error` to followed by the error on the next line for error responses. This is printed to `stderr`.
|
||||
|
||||
Example:
|
||||
|
||||
```shell
|
||||
$ ironbar set subject world
|
||||
$ ironbar var set subject world
|
||||
ok
|
||||
|
||||
$ ironbar get subject
|
||||
ok
|
||||
$ ironbar var get subject
|
||||
world
|
||||
|
||||
$ ironbar var get foo
|
||||
error
|
||||
Variable not found
|
||||
```
|
||||
|
||||
All error responses will cause the CLI to exit code 3.
|
||||
|
||||
# IPC
|
||||
|
||||
The server listens on a Unix socket.
|
||||
This can usually be found at `/run/user/$UID/ironbar-ipc.sock`.
|
||||
The path is printed on startup, and can usually be found at `/run/user/$UID/ironbar-ipc.sock`.
|
||||
|
||||
Commands and responses are sent as JSON objects, denoted by their `type` key.
|
||||
Commands and responses are sent as JSON objects.
|
||||
|
||||
Commands will have a `command` key, and a `subcommand` key when part of a sub-command.
|
||||
|
||||
The message buffer is currently limited to `1024` bytes.
|
||||
Particularly large messages will be truncated or cause an error.
|
||||
@ -47,7 +57,7 @@ Responds with `ok`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ping"
|
||||
"command": "ping"
|
||||
}
|
||||
```
|
||||
|
||||
@ -59,7 +69,7 @@ Responds with `ok`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "inspect"
|
||||
"command": "inspect"
|
||||
}
|
||||
```
|
||||
|
||||
@ -73,48 +83,7 @@ Responds with `ok`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "reload"
|
||||
}
|
||||
```
|
||||
|
||||
### `get`
|
||||
|
||||
Gets an [ironvar](ironvars) value.
|
||||
|
||||
Responds with `ok_value` if the value exists, otherwise `error`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "get",
|
||||
"key": "foo"
|
||||
}
|
||||
```
|
||||
|
||||
### `set`
|
||||
|
||||
Sets an [ironvar](ironvars) value.
|
||||
|
||||
Responds with `ok`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "set",
|
||||
"key": "foo",
|
||||
"value": "bar"
|
||||
}
|
||||
```
|
||||
|
||||
### list
|
||||
|
||||
Gets a list of all [ironvar](ironvars) values.
|
||||
|
||||
Responds with `ok_value`.
|
||||
|
||||
Each key/value pair is on its own `\n` separated newline. The key and value are separated by a colon and space `: `.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "list"
|
||||
"command": "reload"
|
||||
}
|
||||
```
|
||||
|
||||
@ -126,26 +95,113 @@ Responds with `ok` if the stylesheet exists, otherwise `error`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "load_css",
|
||||
"command": "load_css",
|
||||
"path": "/path/to/style.css"
|
||||
}
|
||||
```
|
||||
|
||||
### `set_visible`
|
||||
### `var`
|
||||
|
||||
Sets a bar's visibility.
|
||||
Subcommand for controlling Ironvars.
|
||||
|
||||
#### `get`
|
||||
|
||||
Gets an [ironvar](ironvars) value.
|
||||
|
||||
Responds with `ok_value` if the value exists, otherwise `error`.
|
||||
|
||||
```json
|
||||
{
|
||||
"command": "var",
|
||||
"subcommand": "get",
|
||||
"key": "foo"
|
||||
}
|
||||
```
|
||||
|
||||
#### `set`
|
||||
|
||||
Sets an [ironvar](ironvars) value.
|
||||
|
||||
Responds with `ok`.
|
||||
|
||||
```json
|
||||
{
|
||||
"command": "var",
|
||||
"subcommand": "set",
|
||||
"key": "foo",
|
||||
"value": "bar"
|
||||
}
|
||||
```
|
||||
|
||||
#### `list`
|
||||
|
||||
Gets a list of all [ironvar](ironvars) values.
|
||||
|
||||
Responds with `ok_value`.
|
||||
|
||||
Each key/value pair is on its own `\n` separated newline. The key and value are separated by a colon and space `: `.
|
||||
|
||||
```json
|
||||
{
|
||||
"command": "var",
|
||||
"subcommand": "list"
|
||||
}
|
||||
```
|
||||
|
||||
### `bar`
|
||||
|
||||
#### `show`
|
||||
|
||||
Forces a bar to be shown, regardless of the current visibility state.
|
||||
|
||||
```json
|
||||
{
|
||||
"command": "bar",
|
||||
"subcommand": "show",
|
||||
"name": "bar-123"
|
||||
}
|
||||
```
|
||||
|
||||
#### `hide`
|
||||
|
||||
Forces a bar to be hidden, regardless of the current visibility state.
|
||||
|
||||
```json
|
||||
{
|
||||
"command": "bar",
|
||||
"subcommand": "hide",
|
||||
"name": "bar-123"
|
||||
}
|
||||
```
|
||||
|
||||
#### `set_visible`
|
||||
|
||||
Sets a bar's visibility to one of shown/hidden.
|
||||
|
||||
Responds with `ok` if the bar exists, otherwise `error`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "set_visible",
|
||||
"bar_name": "bar-123",
|
||||
"command": "bar",
|
||||
"subcommand": "set_visible",
|
||||
"name": "bar-123",
|
||||
"visible": true
|
||||
}
|
||||
```
|
||||
|
||||
### `get_visible`
|
||||
#### `toggle_visible`
|
||||
|
||||
Toggles the current visibility state of a bar between shown and hidden.
|
||||
|
||||
```json
|
||||
{
|
||||
"command": "bar",
|
||||
"subcommand": "toggle_visible",
|
||||
"name": "bar-123"
|
||||
}
|
||||
```
|
||||
|
||||
#### `get_visible`
|
||||
|
||||
Gets a bar's visibility.
|
||||
|
||||
@ -153,50 +209,82 @@ Responds with `ok_value` and the visibility (`true`/`false`) if the bar exists,
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "get_visible",
|
||||
"bar_name": "bar-123"
|
||||
"command": "bar",
|
||||
"subcommand": "get_visible",
|
||||
"name": "bar-123"
|
||||
}
|
||||
```
|
||||
|
||||
### `toggle_popup`
|
||||
|
||||
Toggles the open/closed state for a module's popup.
|
||||
Since each bar only has a single popup, any open popup on the bar is closed.
|
||||
|
||||
Responds with `ok` if the popup exists, otherwise `error`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "toggle_popup",
|
||||
"bar_name": "bar-123",
|
||||
"name": "clock"
|
||||
}
|
||||
```
|
||||
|
||||
### `open_popup`
|
||||
#### `show_popup`
|
||||
|
||||
Sets a module's popup open, regardless of its current state.
|
||||
Since each bar only has a single popup, any open popup on the bar is closed.
|
||||
|
||||
Responds with `ok` if the popup exists, otherwise `error`.
|
||||
Responds with `ok` if the bar and widget exist, otherwise `error`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "open_popup",
|
||||
"bar_name": "bar-123",
|
||||
"name": "clock"
|
||||
"command": "bar",
|
||||
"subcommand": "show_popup",
|
||||
"name": "bar-123",
|
||||
"widget_name": "clock"
|
||||
}
|
||||
```
|
||||
|
||||
### `close_popup`
|
||||
#### `hide_popup`
|
||||
|
||||
Sets the popup on a bar closed, regardless of which module it is open for.
|
||||
|
||||
Responds with `ok` if the popup exists, otherwise `error`.
|
||||
Responds with `ok` if the bar and widget exist, otherwise `error`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "close_popup",
|
||||
"command": "bar",
|
||||
"subcommand": "hide_popup",
|
||||
"bar_name": "bar-123"
|
||||
}
|
||||
```
|
||||
|
||||
#### `set_popup_visible`
|
||||
|
||||
Sets a popup's visibility to one of shown/hidden.
|
||||
|
||||
Responds with `ok` if the bar and widget exist, otherwise `error`.
|
||||
|
||||
```json
|
||||
{
|
||||
"command": "bar",
|
||||
"subcommand": "set_popup_visible",
|
||||
"name": "bar-123",
|
||||
"widget_name": "clock",
|
||||
"visible": true
|
||||
}
|
||||
```
|
||||
|
||||
#### `toggle_popup`
|
||||
|
||||
Toggles the open/closed state for a module's popup.
|
||||
Since each bar only has a single popup, any open popup on the bar is closed.
|
||||
|
||||
Responds with `ok` if the bar and widget exist, otherwise `error`.
|
||||
|
||||
```json
|
||||
{
|
||||
"command": "bar",
|
||||
"subcommand": "toggle_popup",
|
||||
"bar_name": "bar-123",
|
||||
"widget_name": "clock"
|
||||
}
|
||||
```
|
||||
|
||||
#### `get_popup_visible`
|
||||
|
||||
Gets the popup's current visibility state.
|
||||
|
||||
```json
|
||||
{
|
||||
"command": "bar",
|
||||
"subcommand": "get_popup_visible",
|
||||
"bar_name": "bar-123"
|
||||
}
|
||||
```
|
||||
|
@ -320,6 +320,15 @@ impl Bar {
|
||||
Inner::Loaded { popup } => popup.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn visible(&self) -> bool {
|
||||
self.window.is_visible()
|
||||
}
|
||||
|
||||
/// Sets the window visibility status
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
self.window.set_visible(visible)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `gtk::Box` container to place widgets inside.
|
||||
|
@ -1,20 +1,39 @@
|
||||
use clap::ArgAction;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Subcommand;
|
||||
use clap::{Args, Subcommand};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Subcommand, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[serde(tag = "command", rename_all = "snake_case")]
|
||||
pub enum Command {
|
||||
/// Return "ok"
|
||||
/// Pong
|
||||
Ping,
|
||||
|
||||
/// Open the GTK inspector
|
||||
/// Open the GTK inspector.
|
||||
Inspect,
|
||||
|
||||
/// Reload the config
|
||||
/// Reload the config.
|
||||
Reload,
|
||||
|
||||
/// Load an additional CSS stylesheet.
|
||||
/// The sheet is automatically hot-reloaded.
|
||||
LoadCss {
|
||||
/// The path to the sheet.
|
||||
path: PathBuf,
|
||||
},
|
||||
|
||||
/// Get and set reactive Ironvar values.
|
||||
#[command(subcommand)]
|
||||
Var(IronvarCommand),
|
||||
|
||||
/// Interact with a specific bar.
|
||||
Bar(BarCommand),
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "subcommand", rename_all = "snake_case")]
|
||||
pub enum IronvarCommand {
|
||||
/// Set an `ironvar` value.
|
||||
/// This creates it if it does not already exist, and updates it if it does.
|
||||
/// Any references to this variable are automatically and immediately updated.
|
||||
@ -34,49 +53,69 @@ pub enum Command {
|
||||
|
||||
/// Gets the current value of all `ironvar`s.
|
||||
List,
|
||||
}
|
||||
|
||||
/// Load an additional CSS stylesheet.
|
||||
/// The sheet is automatically hot-reloaded.
|
||||
LoadCss {
|
||||
/// The path to the sheet.
|
||||
path: PathBuf,
|
||||
},
|
||||
#[derive(Args, Debug, Serialize, Deserialize)]
|
||||
pub struct BarCommand {
|
||||
/// The name of the bar.
|
||||
pub name: String,
|
||||
|
||||
/// Set the visibility of the bar with the given name.
|
||||
#[command(subcommand)]
|
||||
#[serde(flatten)]
|
||||
pub subcommand: BarCommandType,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "subcommand", rename_all = "snake_case")]
|
||||
pub enum BarCommandType {
|
||||
// == Visibility == \\
|
||||
/// Force the bar to be shown, regardless of current visibility state.
|
||||
Show,
|
||||
/// Force the bar to be hidden, regardless of current visibility state.
|
||||
Hide,
|
||||
/// Set the bar's visibility state via an argument.
|
||||
SetVisible {
|
||||
///Bar name to target.
|
||||
bar_name: String,
|
||||
/// The visibility status.
|
||||
#[arg(short, long)]
|
||||
/// The new visibility state.
|
||||
#[clap(
|
||||
num_args(1),
|
||||
require_equals(true),
|
||||
action = ArgAction::Set,
|
||||
)]
|
||||
visible: bool,
|
||||
},
|
||||
/// Toggle the current visibility state between shown and hidden.
|
||||
ToggleVisible,
|
||||
/// Get the bar's visibility state.
|
||||
GetVisible,
|
||||
|
||||
/// Get the visibility of the bar with the given name.
|
||||
GetVisible {
|
||||
/// Bar name to target.
|
||||
bar_name: String,
|
||||
// == Popup visibility == \\
|
||||
/// Open a popup, regardless of current state.
|
||||
/// If opening this popup, and a different popup on the same bar is already open, the other is closed.
|
||||
ShowPopup {
|
||||
/// The configured name of the widget.
|
||||
widget_name: String,
|
||||
},
|
||||
/// Close a popup, regardless of current state.
|
||||
HidePopup,
|
||||
/// Set the popup's visibility state via an argument.
|
||||
/// If opening this popup, and a different popup on the same bar is already open, the other is closed.
|
||||
SetPopupVisible {
|
||||
/// The configured name of the widget.
|
||||
widget_name: String,
|
||||
|
||||
#[clap(
|
||||
num_args(1),
|
||||
require_equals(true),
|
||||
action = ArgAction::Set,
|
||||
)]
|
||||
visible: bool,
|
||||
},
|
||||
/// Toggle a popup open/closed.
|
||||
/// If opening this popup, and a different popup on the same bar is already open, the other is closed.
|
||||
TogglePopup {
|
||||
/// The name of the monitor the bar is located on.
|
||||
bar_name: String,
|
||||
/// The name of the widget.
|
||||
name: String,
|
||||
},
|
||||
|
||||
/// Open a popup, regardless of current state.
|
||||
OpenPopup {
|
||||
/// The name of the monitor the bar is located on.
|
||||
bar_name: String,
|
||||
/// The name of the widget.
|
||||
name: String,
|
||||
},
|
||||
|
||||
/// Close a popup, regardless of current state.
|
||||
ClosePopup {
|
||||
/// The name of the monitor the bar is located on.
|
||||
bar_name: String,
|
||||
/// The configured name of the widget.
|
||||
widget_name: String,
|
||||
},
|
||||
/// Get the popup's current visibility state.
|
||||
GetPopupVisible,
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ mod server;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::warn;
|
||||
|
||||
pub use commands::Command;
|
||||
pub use commands::*;
|
||||
pub use responses::Response;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -1,297 +0,0 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
use color_eyre::{Report, Result};
|
||||
use gtk::prelude::*;
|
||||
use gtk::Application;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::ipc::{Command, Response};
|
||||
use crate::modules::PopupButton;
|
||||
use crate::style::load_css;
|
||||
use crate::{glib_recv_mpsc, read_lock, send_async, spawn, try_send, write_lock, Ironbar};
|
||||
|
||||
use super::Ipc;
|
||||
|
||||
impl Ipc {
|
||||
/// Starts the IPC server on its socket.
|
||||
///
|
||||
/// Once started, the server will begin accepting connections.
|
||||
pub fn start(&self, application: &Application, ironbar: Rc<Ironbar>) {
|
||||
let (cmd_tx, cmd_rx) = mpsc::channel(32);
|
||||
let (res_tx, mut res_rx) = mpsc::channel(32);
|
||||
|
||||
let path = self.path.clone();
|
||||
|
||||
if path.exists() {
|
||||
warn!("Socket already exists. Did Ironbar exit abruptly?");
|
||||
warn!("Attempting IPC shutdown to allow binding to address");
|
||||
Self::shutdown(&path);
|
||||
}
|
||||
|
||||
spawn(async move {
|
||||
info!("Starting IPC on {}", path.display());
|
||||
|
||||
let listener = match UnixListener::bind(&path) {
|
||||
Ok(listener) => listener,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"{:?}",
|
||||
Report::new(err).wrap_err("Unable to start IPC server")
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((stream, _addr)) => {
|
||||
if let Err(err) =
|
||||
Self::handle_connection(stream, &cmd_tx, &mut res_rx).await
|
||||
{
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let application = application.clone();
|
||||
glib_recv_mpsc!(cmd_rx, command => {
|
||||
let res = Self::handle_command(command, &application, &ironbar);
|
||||
try_send!(res_tx, res);
|
||||
});
|
||||
}
|
||||
|
||||
/// Takes an incoming connections,
|
||||
/// reads the command message, and sends the response.
|
||||
///
|
||||
/// The connection is closed once the response has been written.
|
||||
async fn handle_connection(
|
||||
mut stream: UnixStream,
|
||||
cmd_tx: &Sender<Command>,
|
||||
res_rx: &mut Receiver<Response>,
|
||||
) -> Result<()> {
|
||||
let (mut stream_read, mut stream_write) = stream.split();
|
||||
|
||||
let mut read_buffer = vec![0; 1024];
|
||||
let bytes = stream_read.read(&mut read_buffer).await?;
|
||||
|
||||
let command = serde_json::from_slice::<Command>(&read_buffer[..bytes])?;
|
||||
|
||||
debug!("Received command: {command:?}");
|
||||
|
||||
send_async!(cmd_tx, command);
|
||||
let res = res_rx
|
||||
.recv()
|
||||
.await
|
||||
.unwrap_or(Response::Err { message: None });
|
||||
let res = serde_json::to_vec(&res)?;
|
||||
|
||||
stream_write.write_all(&res).await?;
|
||||
stream_write.shutdown().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Takes an input command, runs it and returns with the appropriate response.
|
||||
///
|
||||
/// This runs on the main thread, allowing commands to interact with GTK.
|
||||
fn handle_command(
|
||||
command: Command,
|
||||
application: &Application,
|
||||
ironbar: &Rc<Ironbar>,
|
||||
) -> Response {
|
||||
match command {
|
||||
Command::Inspect => {
|
||||
gtk::Window::set_interactive_debugging(true);
|
||||
Response::Ok
|
||||
}
|
||||
Command::Reload => {
|
||||
info!("Closing existing bars");
|
||||
ironbar.bars.borrow_mut().clear();
|
||||
|
||||
let windows = application.windows();
|
||||
for window in windows {
|
||||
window.close();
|
||||
}
|
||||
|
||||
let wl = ironbar.clients.borrow_mut().wayland();
|
||||
let outputs = wl.output_info_all();
|
||||
|
||||
ironbar.reload_config();
|
||||
|
||||
for output in outputs {
|
||||
match crate::load_output_bars(ironbar, application, &output) {
|
||||
Ok(mut bars) => ironbar.bars.borrow_mut().append(&mut bars),
|
||||
Err(err) => error!("{err:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
Response::Ok
|
||||
}
|
||||
Command::Set { key, value } => {
|
||||
let variable_manager = Ironbar::variable_manager();
|
||||
let mut variable_manager = write_lock!(variable_manager);
|
||||
match variable_manager.set(key, value) {
|
||||
Ok(()) => Response::Ok,
|
||||
Err(err) => Response::error(&format!("{err}")),
|
||||
}
|
||||
}
|
||||
Command::Get { key } => {
|
||||
let variable_manager = Ironbar::variable_manager();
|
||||
let value = read_lock!(variable_manager).get(&key);
|
||||
match value {
|
||||
Some(value) => Response::OkValue { value },
|
||||
None => Response::error("Variable not found"),
|
||||
}
|
||||
}
|
||||
Command::List => {
|
||||
let variable_manager = Ironbar::variable_manager();
|
||||
|
||||
let mut values = read_lock!(variable_manager)
|
||||
.get_all()
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{k}: {}", v.get().unwrap_or_default()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
values.sort();
|
||||
let value = values.join("\n");
|
||||
|
||||
Response::OkValue { value }
|
||||
}
|
||||
Command::LoadCss { path } => {
|
||||
if path.exists() {
|
||||
load_css(path);
|
||||
Response::Ok
|
||||
} else {
|
||||
Response::error("File not found")
|
||||
}
|
||||
}
|
||||
Command::TogglePopup { bar_name, name } => {
|
||||
let bar = ironbar.bar_by_name(&bar_name);
|
||||
|
||||
match bar {
|
||||
Some(bar) => {
|
||||
let popup = bar.popup();
|
||||
let current_widget = popup.current_widget();
|
||||
|
||||
popup.hide();
|
||||
|
||||
let data = popup
|
||||
.container_cache
|
||||
.borrow()
|
||||
.iter()
|
||||
.find(|(_, value)| value.name == name)
|
||||
.map(|(id, value)| (*id, value.content.buttons.first().cloned()));
|
||||
|
||||
match data {
|
||||
Some((id, Some(button))) if current_widget != Some(id) => {
|
||||
let button_id = button.popup_id();
|
||||
|
||||
if popup.is_visible() {
|
||||
popup.hide();
|
||||
} else {
|
||||
popup.show(id, button_id);
|
||||
}
|
||||
|
||||
Response::Ok
|
||||
}
|
||||
Some((_, None)) => Response::error("Module has no popup functionality"),
|
||||
Some(_) => Response::Ok,
|
||||
None => Response::error("Invalid module name"),
|
||||
}
|
||||
}
|
||||
None => Response::error("Invalid bar name"),
|
||||
}
|
||||
}
|
||||
Command::OpenPopup { bar_name, name } => {
|
||||
let bar = ironbar.bar_by_name(&bar_name);
|
||||
|
||||
match bar {
|
||||
Some(bar) => {
|
||||
let popup = bar.popup();
|
||||
|
||||
// only one popup per bar, so hide if open for another widget
|
||||
popup.hide();
|
||||
|
||||
let data = popup
|
||||
.container_cache
|
||||
.borrow()
|
||||
.iter()
|
||||
.find(|(_, value)| value.name == name)
|
||||
.map(|(id, value)| (*id, value.content.buttons.first().cloned()));
|
||||
|
||||
match data {
|
||||
Some((id, Some(button))) => {
|
||||
let button_id = button.popup_id();
|
||||
popup.show(id, button_id);
|
||||
|
||||
Response::Ok
|
||||
}
|
||||
Some((_, None)) => Response::error("Module has no popup functionality"),
|
||||
None => Response::error("Invalid module name"),
|
||||
}
|
||||
}
|
||||
None => Response::error("Invalid bar name"),
|
||||
}
|
||||
}
|
||||
Command::ClosePopup { bar_name } => {
|
||||
let bar = ironbar.bar_by_name(&bar_name);
|
||||
|
||||
match bar {
|
||||
Some(bar) => {
|
||||
let popup = bar.popup();
|
||||
popup.hide();
|
||||
|
||||
Response::Ok
|
||||
}
|
||||
None => Response::error("Invalid bar name"),
|
||||
}
|
||||
}
|
||||
Command::Ping => Response::Ok,
|
||||
Command::SetVisible { bar_name, visible } => {
|
||||
let windows = application.windows();
|
||||
let found = windows
|
||||
.iter()
|
||||
.find(|window| window.widget_name() == bar_name);
|
||||
|
||||
if let Some(window) = found {
|
||||
window.set_visible(visible);
|
||||
Response::Ok
|
||||
} else {
|
||||
Response::error("Bar not found")
|
||||
}
|
||||
}
|
||||
Command::GetVisible { bar_name } => {
|
||||
let windows = application.windows();
|
||||
let found = windows
|
||||
.iter()
|
||||
.find(|window| window.widget_name() == bar_name);
|
||||
|
||||
if let Some(window) = found {
|
||||
Response::OkValue {
|
||||
value: window.is_visible().to_string(),
|
||||
}
|
||||
} else {
|
||||
Response::error("Bar not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shuts down the IPC server,
|
||||
/// removing the socket file in the process.
|
||||
///
|
||||
/// Note this is static as the `Ipc` struct is not `Send`.
|
||||
pub fn shutdown<P: AsRef<Path>>(path: P) {
|
||||
fs::remove_file(&path).ok();
|
||||
}
|
||||
}
|
84
src/ipc/server/bar.rs
Normal file
84
src/ipc/server/bar.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use super::Response;
|
||||
use crate::bar::Bar;
|
||||
use crate::ipc::{BarCommand, BarCommandType};
|
||||
use crate::modules::PopupButton;
|
||||
use crate::Ironbar;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn handle_command(command: BarCommand, ironbar: &Rc<Ironbar>) -> Response {
|
||||
let bar = ironbar.bar_by_name(&command.name);
|
||||
let Some(bar) = bar else {
|
||||
return Response::error("Invalid bar name");
|
||||
};
|
||||
|
||||
use BarCommandType::*;
|
||||
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)
|
||||
}
|
||||
}
|
||||
TogglePopup { widget_name } => {
|
||||
if bar.popup().visible() {
|
||||
hide_popup(&bar)
|
||||
} else {
|
||||
show_popup(&bar, widget_name)
|
||||
}
|
||||
}
|
||||
GetPopupVisible => Response::OkValue {
|
||||
value: bar.popup().visible().to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn set_visible(bar: &Bar, visible: bool) -> Response {
|
||||
bar.set_visible(visible);
|
||||
Response::Ok
|
||||
}
|
||||
|
||||
fn show_popup(bar: &Bar, widget_name: String) -> Response {
|
||||
let popup = bar.popup();
|
||||
|
||||
// only one popup per bar, so hide if open for another widget
|
||||
popup.hide();
|
||||
|
||||
let data = popup
|
||||
.container_cache
|
||||
.borrow()
|
||||
.iter()
|
||||
.find(|(_, value)| value.name == widget_name)
|
||||
.map(|(id, value)| (*id, value.content.buttons.first().cloned()));
|
||||
|
||||
match data {
|
||||
Some((id, Some(button))) => {
|
||||
let button_id = button.popup_id();
|
||||
popup.show(id, button_id);
|
||||
|
||||
Response::Ok
|
||||
}
|
||||
Some((_, None)) => Response::error("Module has no popup functionality"),
|
||||
None => Response::error("Invalid module name"),
|
||||
}
|
||||
}
|
||||
|
||||
fn hide_popup(bar: &Bar) -> Response {
|
||||
let popup = bar.popup();
|
||||
popup.hide();
|
||||
|
||||
Response::Ok
|
||||
}
|
38
src/ipc/server/ironvar.rs
Normal file
38
src/ipc/server/ironvar.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use crate::ipc::commands::IronvarCommand;
|
||||
use crate::ipc::Response;
|
||||
use crate::{read_lock, write_lock, Ironbar};
|
||||
|
||||
pub fn handle_command(command: IronvarCommand) -> Response {
|
||||
match command {
|
||||
IronvarCommand::Set { key, value } => {
|
||||
let variable_manager = Ironbar::variable_manager();
|
||||
let mut variable_manager = write_lock!(variable_manager);
|
||||
match variable_manager.set(key, value) {
|
||||
Ok(()) => Response::Ok,
|
||||
Err(err) => Response::error(&format!("{err}")),
|
||||
}
|
||||
}
|
||||
IronvarCommand::Get { key } => {
|
||||
let variable_manager = Ironbar::variable_manager();
|
||||
let value = read_lock!(variable_manager).get(&key);
|
||||
match value {
|
||||
Some(value) => Response::OkValue { value },
|
||||
None => Response::error("Variable not found"),
|
||||
}
|
||||
}
|
||||
IronvarCommand::List => {
|
||||
let variable_manager = Ironbar::variable_manager();
|
||||
|
||||
let mut values = read_lock!(variable_manager)
|
||||
.get_all()
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{k}: {}", v.get().unwrap_or_default()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
values.sort();
|
||||
let value = values.join("\n");
|
||||
|
||||
Response::OkValue { value }
|
||||
}
|
||||
}
|
||||
}
|
164
src/ipc/server/mod.rs
Normal file
164
src/ipc/server/mod.rs
Normal file
@ -0,0 +1,164 @@
|
||||
mod bar;
|
||||
mod ironvar;
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
use color_eyre::{Report, Result};
|
||||
use gtk::prelude::*;
|
||||
use gtk::Application;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::ipc::{Command, Response};
|
||||
use crate::style::load_css;
|
||||
use crate::{glib_recv_mpsc, send_async, spawn, try_send, Ironbar};
|
||||
|
||||
use super::Ipc;
|
||||
|
||||
impl Ipc {
|
||||
/// Starts the IPC server on its socket.
|
||||
///
|
||||
/// Once started, the server will begin accepting connections.
|
||||
pub fn start(&self, application: &Application, ironbar: Rc<Ironbar>) {
|
||||
let (cmd_tx, cmd_rx) = mpsc::channel(32);
|
||||
let (res_tx, mut res_rx) = mpsc::channel(32);
|
||||
|
||||
let path = self.path.clone();
|
||||
|
||||
if path.exists() {
|
||||
warn!("Socket already exists. Did Ironbar exit abruptly?");
|
||||
warn!("Attempting IPC shutdown to allow binding to address");
|
||||
Self::shutdown(&path);
|
||||
}
|
||||
|
||||
spawn(async move {
|
||||
info!("Starting IPC on {}", path.display());
|
||||
|
||||
let listener = match UnixListener::bind(&path) {
|
||||
Ok(listener) => listener,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"{:?}",
|
||||
Report::new(err).wrap_err("Unable to start IPC server")
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((stream, _addr)) => {
|
||||
if let Err(err) =
|
||||
Self::handle_connection(stream, &cmd_tx, &mut res_rx).await
|
||||
{
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let application = application.clone();
|
||||
glib_recv_mpsc!(cmd_rx, command => {
|
||||
let res = Self::handle_command(command, &application, &ironbar);
|
||||
try_send!(res_tx, res);
|
||||
});
|
||||
}
|
||||
|
||||
/// Takes an incoming connections,
|
||||
/// reads the command message, and sends the response.
|
||||
///
|
||||
/// The connection is closed once the response has been written.
|
||||
async fn handle_connection(
|
||||
mut stream: UnixStream,
|
||||
cmd_tx: &Sender<Command>,
|
||||
res_rx: &mut Receiver<Response>,
|
||||
) -> Result<()> {
|
||||
let (mut stream_read, mut stream_write) = stream.split();
|
||||
|
||||
let mut read_buffer = vec![0; 1024];
|
||||
let bytes = stream_read.read(&mut read_buffer).await?;
|
||||
|
||||
// FIXME: Error on invalid command
|
||||
let command = serde_json::from_slice::<Command>(&read_buffer[..bytes])?;
|
||||
|
||||
debug!("Received command: {command:?}");
|
||||
|
||||
send_async!(cmd_tx, command);
|
||||
let res = res_rx
|
||||
.recv()
|
||||
.await
|
||||
.unwrap_or(Response::Err { message: None });
|
||||
let res = serde_json::to_vec(&res)?;
|
||||
|
||||
stream_write.write_all(&res).await?;
|
||||
stream_write.shutdown().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Takes an input command, runs it and returns with the appropriate response.
|
||||
///
|
||||
/// This runs on the main thread, allowing commands to interact with GTK.
|
||||
fn handle_command(
|
||||
command: Command,
|
||||
application: &Application,
|
||||
ironbar: &Rc<Ironbar>,
|
||||
) -> Response {
|
||||
match command {
|
||||
Command::Ping => Response::Ok,
|
||||
Command::Inspect => {
|
||||
gtk::Window::set_interactive_debugging(true);
|
||||
Response::Ok
|
||||
}
|
||||
Command::Reload => {
|
||||
info!("Closing existing bars");
|
||||
ironbar.bars.borrow_mut().clear();
|
||||
|
||||
let windows = application.windows();
|
||||
for window in windows {
|
||||
window.close();
|
||||
}
|
||||
|
||||
let wl = ironbar.clients.borrow_mut().wayland();
|
||||
let outputs = wl.output_info_all();
|
||||
|
||||
ironbar.reload_config();
|
||||
|
||||
for output in outputs {
|
||||
match crate::load_output_bars(ironbar, application, &output) {
|
||||
Ok(mut bars) => ironbar.bars.borrow_mut().append(&mut bars),
|
||||
Err(err) => error!("{err:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
Response::Ok
|
||||
}
|
||||
Command::LoadCss { path } => {
|
||||
if path.exists() {
|
||||
load_css(path);
|
||||
Response::Ok
|
||||
} else {
|
||||
Response::error("File not found")
|
||||
}
|
||||
}
|
||||
Command::Var(cmd) => ironvar::handle_command(cmd),
|
||||
Command::Bar(cmd) => bar::handle_command(cmd, ironbar),
|
||||
}
|
||||
}
|
||||
|
||||
/// Shuts down the IPC server,
|
||||
/// removing the socket file in the process.
|
||||
///
|
||||
/// Note this is static as the `Ipc` struct is not `Send`.
|
||||
pub fn shutdown<P: AsRef<Path>>(path: P) {
|
||||
fs::remove_file(&path).ok();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user