mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-07-01 10:41:03 +02:00
Merge pull request #595 from JakeStanger/refactor/cli-change
Refactor/cli change
This commit is contained in:
commit
c28de8d902
14 changed files with 603 additions and 431 deletions
|
@ -5,28 +5,38 @@ It also includes a command line interface, which can be used for interacting wit
|
||||||
# CLI
|
# CLI
|
||||||
|
|
||||||
This is shipped as part of the `ironbar` binary. To view commands, you can use `ironbar --help`.
|
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.
|
The CLI supports plaintext and JSON output. Plaintext will:
|
||||||
Error responses are written to stderr in the same format.
|
|
||||||
|
- 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:
|
Example:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ ironbar set subject world
|
$ ironbar var set subject world
|
||||||
ok
|
ok
|
||||||
|
|
||||||
$ ironbar get subject
|
$ ironbar var get subject
|
||||||
ok
|
|
||||||
world
|
world
|
||||||
|
|
||||||
|
$ ironbar var get foo
|
||||||
|
error
|
||||||
|
Variable not found
|
||||||
```
|
```
|
||||||
|
|
||||||
|
All error responses will cause the CLI to exit code 3.
|
||||||
|
|
||||||
# IPC
|
# IPC
|
||||||
|
|
||||||
The server listens on a Unix socket.
|
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.
|
The message buffer is currently limited to `1024` bytes.
|
||||||
Particularly large messages will be truncated or cause an error.
|
Particularly large messages will be truncated or cause an error.
|
||||||
|
@ -47,7 +57,7 @@ Responds with `ok`.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "ping"
|
"command": "ping"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -59,7 +69,7 @@ Responds with `ok`.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "inspect"
|
"command": "inspect"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -73,48 +83,7 @@ Responds with `ok`.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "reload"
|
"command": "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"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -126,26 +95,113 @@ Responds with `ok` if the stylesheet exists, otherwise `error`.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "load_css",
|
"command": "load_css",
|
||||||
"path": "/path/to/style.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`.
|
Responds with `ok` if the bar exists, otherwise `error`.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "set_visible",
|
"command": "bar",
|
||||||
"bar_name": "bar-123",
|
"subcommand": "set_visible",
|
||||||
|
"name": "bar-123",
|
||||||
"visible": true
|
"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.
|
Gets a bar's visibility.
|
||||||
|
|
||||||
|
@ -153,50 +209,82 @@ Responds with `ok_value` and the visibility (`true`/`false`) if the bar exists,
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "get_visible",
|
"command": "bar",
|
||||||
"bar_name": "bar-123"
|
"subcommand": "get_visible",
|
||||||
|
"name": "bar-123"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `toggle_popup`
|
#### `show_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`
|
|
||||||
|
|
||||||
Sets a module's popup open, regardless of its current state.
|
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.
|
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
|
```json
|
||||||
{
|
{
|
||||||
"type": "open_popup",
|
"command": "bar",
|
||||||
"bar_name": "bar-123",
|
"subcommand": "show_popup",
|
||||||
"name": "clock"
|
"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.
|
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
|
```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"
|
"bar_name": "bar-123"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -320,6 +320,15 @@ impl Bar {
|
||||||
Inner::Loaded { popup } => popup.clone(),
|
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.
|
/// Creates a `gtk::Box` container to place widgets inside.
|
||||||
|
|
38
src/cli.rs
38
src/cli.rs
|
@ -1,7 +1,9 @@
|
||||||
|
use crate::error::ExitCode;
|
||||||
use crate::ipc::commands::Command;
|
use crate::ipc::commands::Command;
|
||||||
use crate::ipc::responses::Response;
|
use crate::ipc::responses::Response;
|
||||||
use clap::Parser;
|
use clap::{Parser, ValueEnum};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
#[derive(Parser, Debug, Serialize, Deserialize)]
|
#[derive(Parser, Debug, Serialize, Deserialize)]
|
||||||
#[command(version)]
|
#[command(version)]
|
||||||
|
@ -15,16 +17,44 @@ pub struct Args {
|
||||||
#[arg(long("print-schema"))]
|
#[arg(long("print-schema"))]
|
||||||
pub print_schema: bool,
|
pub print_schema: bool,
|
||||||
|
|
||||||
|
/// Print debug information to stderr
|
||||||
|
/// TODO: Make bar follow this too
|
||||||
|
#[arg(long)]
|
||||||
|
pub debug: bool,
|
||||||
|
|
||||||
|
/// Format to output the response as.
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub format: Option<Format>,
|
||||||
|
|
||||||
/// `bar_id` argument passed by `swaybar_command`.
|
/// `bar_id` argument passed by `swaybar_command`.
|
||||||
/// Not used.
|
/// Not used.
|
||||||
#[arg(short('b'), hide(true))]
|
#[arg(short('b'), hide(true))]
|
||||||
sway_bar_id: Option<String>,
|
sway_bar_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_response(response: Response) {
|
#[derive(Debug, Serialize, Deserialize, Default, ValueEnum, Clone, Copy)]
|
||||||
match response {
|
pub enum Format {
|
||||||
|
#[default]
|
||||||
|
Plain,
|
||||||
|
Json,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_response(response: Response, format: Format) {
|
||||||
|
let is_err = matches!(response, Response::Err { .. });
|
||||||
|
|
||||||
|
match format {
|
||||||
|
Format::Plain => match response {
|
||||||
Response::Ok => println!("ok"),
|
Response::Ok => println!("ok"),
|
||||||
Response::OkValue { value } => println!("ok\n{value}"),
|
Response::OkValue { value } => println!("{value}"),
|
||||||
Response::Err { message } => eprintln!("error\n{}", message.unwrap_or_default()),
|
Response::Err { message } => eprintln!("error\n{}", message.unwrap_or_default()),
|
||||||
|
},
|
||||||
|
Format::Json => println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string(&response).expect("to be valid json")
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_err {
|
||||||
|
exit(ExitCode::IpcResponseError as i32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
pub enum ExitCode {
|
pub enum ExitCode {
|
||||||
GtkDisplay = 1,
|
GtkDisplay = 1,
|
||||||
CreateBars = 2,
|
CreateBars = 2,
|
||||||
|
IpcResponseError = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ERR_MUTEX_LOCK: &str = "Failed to get lock on Mutex";
|
pub const ERR_MUTEX_LOCK: &str = "Failed to get lock on Mutex";
|
||||||
|
|
|
@ -8,7 +8,7 @@ use tokio::net::UnixStream;
|
||||||
impl Ipc {
|
impl Ipc {
|
||||||
/// Sends a command to the IPC server.
|
/// Sends a command to the IPC server.
|
||||||
/// The server response is returned.
|
/// The server response is returned.
|
||||||
pub async fn send(&self, command: Command) -> Result<Response> {
|
pub async fn send(&self, command: Command, debug: bool) -> Result<Response> {
|
||||||
let mut stream = match UnixStream::connect(&self.path).await {
|
let mut stream = match UnixStream::connect(&self.path).await {
|
||||||
Ok(stream) => Ok(stream),
|
Ok(stream) => Ok(stream),
|
||||||
Err(err) => Err(Report::new(err)
|
Err(err) => Err(Report::new(err)
|
||||||
|
@ -17,6 +17,11 @@ impl Ipc {
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let write_buffer = serde_json::to_vec(&command)?;
|
let write_buffer = serde_json::to_vec(&command)?;
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
eprintln!("REQUEST JSON: {}", serde_json::to_string(&command)?);
|
||||||
|
}
|
||||||
|
|
||||||
stream.write_all(&write_buffer).await?;
|
stream.write_all(&write_buffer).await?;
|
||||||
|
|
||||||
let mut read_buffer = vec![0; 1024];
|
let mut read_buffer = vec![0; 1024];
|
||||||
|
|
|
@ -1,20 +1,39 @@
|
||||||
|
use clap::ArgAction;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::Subcommand;
|
use clap::{Args, Subcommand};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Subcommand, Debug, Serialize, Deserialize)]
|
#[derive(Subcommand, Debug, Serialize, Deserialize)]
|
||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
#[serde(tag = "command", rename_all = "snake_case")]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// Return "ok"
|
/// Pong
|
||||||
Ping,
|
Ping,
|
||||||
|
|
||||||
/// Open the GTK inspector
|
/// Open the GTK inspector.
|
||||||
Inspect,
|
Inspect,
|
||||||
|
|
||||||
/// Reload the config
|
/// Reload the config.
|
||||||
Reload,
|
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.
|
/// Set an `ironvar` value.
|
||||||
/// This creates it if it does not already exist, and updates it if it does.
|
/// 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.
|
/// 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.
|
/// Gets the current value of all `ironvar`s.
|
||||||
List,
|
List,
|
||||||
|
}
|
||||||
|
|
||||||
/// Load an additional CSS stylesheet.
|
#[derive(Args, Debug, Serialize, Deserialize)]
|
||||||
/// The sheet is automatically hot-reloaded.
|
pub struct BarCommand {
|
||||||
LoadCss {
|
/// The name of the bar.
|
||||||
/// The path to the sheet.
|
pub name: String,
|
||||||
path: PathBuf,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// 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 {
|
SetVisible {
|
||||||
///Bar name to target.
|
/// The new visibility state.
|
||||||
bar_name: String,
|
#[clap(
|
||||||
/// The visibility status.
|
num_args(1),
|
||||||
#[arg(short, long)]
|
require_equals(true),
|
||||||
|
action = ArgAction::Set,
|
||||||
|
)]
|
||||||
visible: bool,
|
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.
|
// == Popup visibility == \\
|
||||||
GetVisible {
|
/// Open a popup, regardless of current state.
|
||||||
/// Bar name to target.
|
/// If opening this popup, and a different popup on the same bar is already open, the other is closed.
|
||||||
bar_name: String,
|
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.
|
/// Toggle a popup open/closed.
|
||||||
/// If opening this popup, and a different popup on the same bar is already open, the other is closed.
|
/// If opening this popup, and a different popup on the same bar is already open, the other is closed.
|
||||||
TogglePopup {
|
TogglePopup {
|
||||||
/// The name of the monitor the bar is located on.
|
/// The configured name of the widget.
|
||||||
bar_name: String,
|
widget_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,
|
|
||||||
},
|
},
|
||||||
|
/// Get the popup's current visibility state.
|
||||||
|
GetPopupVisible,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ mod server;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
pub use commands::Command;
|
pub use commands::*;
|
||||||
pub use responses::Response;
|
pub use responses::Response;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[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();
|
||||||
|
}
|
||||||
|
}
|
15
src/main.rs
15
src/main.rs
|
@ -85,11 +85,21 @@ fn run_with_args() {
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Some(command) => {
|
Some(command) => {
|
||||||
|
if args.debug {
|
||||||
|
eprintln!("REQUEST: {command:?}")
|
||||||
|
}
|
||||||
|
|
||||||
let rt = create_runtime();
|
let rt = create_runtime();
|
||||||
rt.block_on(async move {
|
rt.block_on(async move {
|
||||||
let ipc = ipc::Ipc::new();
|
let ipc = ipc::Ipc::new();
|
||||||
match ipc.send(command).await {
|
match ipc.send(command, args.debug).await {
|
||||||
Ok(res) => cli::handle_response(res),
|
Ok(res) => {
|
||||||
|
if args.debug {
|
||||||
|
eprintln!("RESPONSE: {res:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
cli::handle_response(res, args.format.unwrap_or_default())
|
||||||
|
}
|
||||||
Err(err) => error!("{err:?}"),
|
Err(err) => error!("{err:?}"),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -239,6 +249,7 @@ impl Ironbar {
|
||||||
|
|
||||||
/// Gets a `usize` ID value that is unique to the entire Ironbar instance.
|
/// Gets a `usize` ID value that is unique to the entire Ironbar instance.
|
||||||
/// This is just a static `AtomicUsize` that increments every time this function is called.
|
/// This is just a static `AtomicUsize` that increments every time this function is called.
|
||||||
|
#[must_use]
|
||||||
pub fn unique_id() -> usize {
|
pub fn unique_id() -> usize {
|
||||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||||
|
|
|
@ -382,7 +382,7 @@ impl ModuleFactory for BarModuleFactory {
|
||||||
}
|
}
|
||||||
ModuleUpdateEvent::TogglePopup(button_id) if !disable_popup => {
|
ModuleUpdateEvent::TogglePopup(button_id) if !disable_popup => {
|
||||||
debug!("Toggling popup for {} [#{}] (button id: {button_id})", name, id);
|
debug!("Toggling popup for {} [#{}] (button id: {button_id})", name, id);
|
||||||
if popup.is_visible() && popup.current_widget().unwrap_or_default() == id {
|
if popup.visible() && popup.current_widget().unwrap_or_default() == id {
|
||||||
popup.hide();
|
popup.hide();
|
||||||
} else {
|
} else {
|
||||||
popup.show(id, button_id);
|
popup.show(id, button_id);
|
||||||
|
@ -455,7 +455,7 @@ impl ModuleFactory for PopupModuleFactory {
|
||||||
}
|
}
|
||||||
ModuleUpdateEvent::TogglePopup(_) if !disable_popup => {
|
ModuleUpdateEvent::TogglePopup(_) if !disable_popup => {
|
||||||
debug!("Toggling popup for {} [#{}] (button id: {button_id})", name, id);
|
debug!("Toggling popup for {} [#{}] (button id: {button_id})", name, id);
|
||||||
if popup.is_visible() && popup.current_widget().unwrap_or_default() == id {
|
if popup.visible() && popup.current_widget().unwrap_or_default() == id {
|
||||||
popup.hide();
|
popup.hide();
|
||||||
} else {
|
} else {
|
||||||
popup.show(id, button_id);
|
popup.show(id, button_id);
|
||||||
|
|
|
@ -229,7 +229,7 @@ impl Popup {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the popup is currently visible
|
/// Checks if the popup is currently visible
|
||||||
pub fn is_visible(&self) -> bool {
|
pub fn visible(&self) -> bool {
|
||||||
self.window.is_visible()
|
self.window.is_visible()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue