1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-04-19 19:34:24 +02:00

Merge branch 'refs/heads/master' into develop

# Conflicts:
#	Cargo.toml
#	src/modules/volume.rs
This commit is contained in:
Reinout Meliesie 2024-06-05 12:49:39 +02:00
commit 3bf4e9b877
Signed by: zedfrigg
GPG key ID: 3AFCC06481308BC6
51 changed files with 867 additions and 475 deletions

View file

@ -6,20 +6,13 @@ on:
- v[0-9]+.[0-9]+.[0-9]+ - v[0-9]+.[0-9]+.[0-9]+
jobs: jobs:
deploy: release:
name: 'Create Release'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install build deps
run: ./.github/scripts/ubuntu_setup.sh
- name: Update CHANGELOG - name: Update CHANGELOG
id: changelog id: changelog
uses: Requarks/changelog-action@v1 uses: Requarks/changelog-action@v1
@ -41,9 +34,54 @@ jobs:
uses: stefanzweifel/git-auto-commit-action@v4 uses: stefanzweifel/git-auto-commit-action@v4
with: with:
branch: master branch: master
commit_message: 'docs: update CHANGELOG.md for ${{ github.ref_name }} [skip ci]' commit_message: 'chore: update CHANGELOG.md for ${{ github.ref_name }} [skip ci]'
file_pattern: CHANGELOG.md file_pattern: CHANGELOG.md
- uses: katyo/publish-crates@v1
publish-crate:
name: 'Publish Crate'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install build deps
run: ./.github/scripts/ubuntu_setup.sh
- name: Publish crate
uses: katyo/publish-crates@v1
with: with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
publish-schema:
name: 'Publish Schema'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v2
name: Cache dependencies
- name: Install build deps
run: ./.github/scripts/ubuntu_setup.sh
- name: Build schema
run: cargo build --features schema -- --print-schema > target/schema-${{ github.ref_name }}.json
- name: Copy file via SSH
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SSH_HOST }}
port: ${{ secrets.SSH_PORT }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "target/schema-${{ github.ref_name }}.json"
target: /storage/Public/github/ironbar
strip_components: 1

41
.github/workflows/schema.yml vendored Normal file
View file

@ -0,0 +1,41 @@
name: Publish Schema
on:
workflow_dispatch:
push:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: '-Dwarnings'
jobs:
publish-schema:
name: 'Publish Schema'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v2
name: Cache dependencies
- name: Install build deps
run: ./.github/scripts/ubuntu_setup.sh
- name: Build
run: cargo build --features schema
- name: Print schema
run: cargo run --features schema -- --print-schema > target/schema.json
- name: Copy file via SSH
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SSH_HOST }}
port: ${{ secrets.SSH_PORT }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "target/schema.json"
target: /storage/Public/github/ironbar
strip_components: 1

50
Cargo.lock generated
View file

@ -761,6 +761,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "dyn-clone"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]] [[package]]
name = "either" name = "either"
version = "1.8.1" version = "1.8.1"
@ -1641,6 +1647,7 @@ dependencies = [
"notify", "notify",
"regex", "regex",
"reqwest", "reqwest",
"schemars",
"serde", "serde",
"serde_json", "serde_json",
"smithay-client-toolkit", "smithay-client-toolkit",
@ -2672,6 +2679,30 @@ dependencies = [
"windows-sys 0.42.0", "windows-sys 0.42.0",
] ]
[[package]]
name = "schemars"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
dependencies = [
"dyn-clone",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e"
dependencies = [
"proc-macro2",
"quote 1.0.35",
"serde_derive_internals",
"syn 2.0.48",
]
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.1" version = "1.0.1"
@ -2733,6 +2764,17 @@ dependencies = [
"syn 2.0.48", "syn 2.0.48",
] ]
[[package]]
name = "serde_derive_internals"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [
"proc-macro2",
"quote 1.0.35",
"syn 2.0.48",
]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.117" version = "1.0.117"
@ -3127,9 +3169,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.37.0" version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -3146,9 +3188,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.35", "quote 1.0.35",

View file

@ -79,12 +79,14 @@ workspaces = ["futures-lite"]
"workspaces+sway" = ["workspaces", "swayipc-async"] "workspaces+sway" = ["workspaces", "swayipc-async"]
"workspaces+hyprland" = ["workspaces", "hyprland"] "workspaces+hyprland" = ["workspaces", "hyprland"]
schema = ["dep:schemars"]
[dependencies] [dependencies]
# core # core
gtk = "0.18.1" gtk = "0.18.1"
gtk-layer-shell = "0.8.0" gtk-layer-shell = "0.8.0"
glib = "0.18.5" glib = "0.18.5"
tokio = { version = "1.37.0", features = [ tokio = { version = "1.38.0", features = [
"macros", "macros",
"rt-multi-thread", "rt-multi-thread",
"time", "time",
@ -140,6 +142,9 @@ mpris = { version = "2.0.1", optional = true }
# networkmanager # networkmanager
futures-signals = { version = "0.3.33", optional = true } futures-signals = { version = "0.3.33", optional = true }
# schema
schemars = { version = "0.8.21", optional = true }
# sys_info # sys_info
sysinfo = { version = "0.29.11", optional = true } sysinfo = { version = "0.29.11", optional = true }

View file

@ -101,6 +101,9 @@ cargo build --release --no-default-features \
| workspaces+all | Enables the `workspaces` module with support for all compositors. | | workspaces+all | Enables the `workspaces` module with support for all compositors. |
| workspaces+sway | Enables the `workspaces` module with support for Sway. | | workspaces+sway | Enables the `workspaces` module with support for Sway. |
| workspaces+hyprland | Enables the `workspaces` module with support for Hyprland. | | workspaces+hyprland | Enables the `workspaces` module with support for Hyprland. |
| **Other** | |
| schema | Enables JSON schema support and the CLI `--print-schema` flag. |
## Speeding up compiling ## Speeding up compiling

View file

@ -20,11 +20,18 @@ Ironbar supports a range of configuration formats, so you can pick your favourit
- `config.json` - `config.json`
- `config.toml` - `config.toml`
- `config.yaml` - `config.yaml`
- `config.corn` (Experimental, includes variable support for re-using blocks. - `config.corn` (Includes variable support for re-using blocks.
See [here](https://github.com/jakestanger/corn) for info) See [here](https://github.com/jakestanger/corn) for info)
You can also override the default config path using the `IRONBAR_CONFIG` environment variable. You can also override the default config path using the `IRONBAR_CONFIG` environment variable.
A hosted schema is available for the latest Git version ~~and each versioned release~~.
JSON and YAML both support schema checking by adding the `$schema` key
to the top level of your config.
- master: `https://f.jstanger.dev/github/ironbar/schema.json`
- ~~release: `https://f.jstanger.dev/github/ironbar/schema-v0.16.0.json`~~ *(Not released yet)*
## 2. Pick your use-case ## 2. Pick your use-case
Ironbar gives you a few ways to configure the bar to suit your needs. Ironbar gives you a few ways to configure the bar to suit your needs.

View file

@ -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"
} }
``` ```

View file

@ -1,4 +1,5 @@
{ {
"$schema": "https://f.jstanger.dev/github/ironbar/schema.json",
"anchor_to_edges": true, "anchor_to_edges": true,
"position": "bottom", "position": "bottom",
"icon_theme": "Paper", "icon_theme": "Paper",

View file

@ -1,3 +1,4 @@
$schema: https://f.jstanger.dev/github/ironbar/schema.json
anchor_to_edges: true anchor_to_edges: true
position: bottom position: bottom
icon_theme: Paper icon_theme: Paper

30
flake.lock generated
View file

@ -7,11 +7,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1713979152, "lastModified": 1717025063,
"narHash": "sha256-apdecPuh8SOQnkEET/kW/UcfjCRb8JbV5BKjoH+DcP4=", "narHash": "sha256-dIubLa56W9sNNz0e8jGxrX3CAkPXsq7snuFA/Ie6dn8=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "a5eca68a2cf11adb32787fc141cddd29ac8eb79c", "rev": "480dff0be03dac0e51a8dfc26e882b0d123a450e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -43,11 +43,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1713520724, "lastModified": 1717067539,
"narHash": "sha256-CO8MmVDmqZX2FovL75pu5BvwhW+Vugc7Q6ze7Hj8heI=", "narHash": "sha256-oIs5EF+6VpHJRvvpVWuqCYJMMVW/6h59aYUv9lABLtY=",
"owner": "nix-community", "owner": "nix-community",
"repo": "naersk", "repo": "naersk",
"rev": "c5037590290c6c7dae2e42e7da1e247e54ed2d49", "rev": "fa19d8c135e776dc97f4dcca08656a0eeb28d5c0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -58,11 +58,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1714314149, "lastModified": 1717112898,
"narHash": "sha256-yNAevSKF4krRWacmLUsLK7D7PlfuY3zF0lYnGYNi9vQ=", "narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "cf8cc1201be8bc71b7cbbbdaf349b22f4f99c7ae", "rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -72,11 +72,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1714253743, "lastModified": 1716948383,
"narHash": "sha256-mdTQw2XlariysyScCv2tTE45QSU9v/ezLcHJ22f0Nxc=", "narHash": "sha256-SzDKxseEcHR5KzPXLwsemyTR/kaM9whxeiJohbL04rs=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "58a1abdbae3217ca6b702f03d3b35125d88a2994", "rev": "ad57eef4ef0659193044870c731987a6df5cf56b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -102,11 +102,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1714443211, "lastModified": 1717121863,
"narHash": "sha256-lKTA3XqRo4aVgkyTSCtpcALpGXdmkilHTtN00eRg0QU=", "narHash": "sha256-/3sxIe7MZqF/jw1RTQCSmgTjwVod43mmrk84m50MJQ4=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "ce35c36f58f82cee6ec959e0d44c587d64281b6f", "rev": "2a7b53172ed08f856b8382d7dcfd36a4e0cbd866",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

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

View file

@ -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)]
@ -9,16 +11,50 @@ pub struct Args {
#[command(subcommand)] #[command(subcommand)]
pub command: Option<Command>, pub command: Option<Command>,
/// Prints the config JSON schema to `stdout`
/// and exits.
#[cfg(feature = "schema")]
#[arg(long("print-schema"))]
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 {
Response::Ok => println!("ok"), #[default]
Response::OkValue { value } => println!("ok\n{value}"), Plain,
Response::Err { message } => eprintln!("error\n{}", message.unwrap_or_default()), 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::OkValue { value } => println!("{value}"),
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)
} }
} }

View file

@ -49,6 +49,7 @@ impl ToplevelHandleData {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct ToplevelHandleDataInner { pub struct ToplevelHandleDataInner {
initial_done: bool, initial_done: bool,
closed: bool,
output: Option<WlOutput>, output: Option<WlOutput>,
current_info: Option<ToplevelInfo>, current_info: Option<ToplevelInfo>,
@ -137,14 +138,17 @@ where
} }
Event::OutputEnter { output } => lock!(data.inner).output = Some(output), Event::OutputEnter { output } => lock!(data.inner).output = Some(output),
Event::OutputLeave { output: _ } => lock!(data.inner).output = None, Event::OutputLeave { output: _ } => lock!(data.inner).output = None,
Event::Closed => state.remove_handle( Event::Closed => {
conn, lock!(data.inner).closed = true;
qh, state.remove_handle(
ToplevelHandle { conn,
handle: handle.clone(), qh,
}, ToplevelHandle {
), handle: handle.clone(),
Event::Done => { },
)
}
Event::Done if !lock!(data.inner).closed => {
{ {
let pending_info = lock!(data.inner).pending_info.clone(); let pending_info = lock!(data.inner).pending_info.clone();
lock!(data.inner).current_info = Some(pending_info); lock!(data.inner).current_info = Some(pending_info);

View file

@ -16,6 +16,7 @@ use tracing::trace;
/// see [here](script). /// see [here](script).
/// For information on styling, please see the [styling guide](styling-guide). /// For information on styling, please see the [styling guide](styling-guide).
#[derive(Debug, Default, Deserialize, Clone)] #[derive(Debug, Default, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CommonConfig { pub struct CommonConfig {
/// Sets the unique widget name, /// Sets the unique widget name,
/// allowing you to target it in CSS using `#name`. /// allowing you to target it in CSS using `#name`.
@ -160,6 +161,7 @@ pub struct CommonConfig {
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum TransitionType { pub enum TransitionType {
None, None,
Crossfade, Crossfade,
@ -169,6 +171,7 @@ pub enum TransitionType {
#[derive(Debug, Default, Deserialize, Clone, Copy)] #[derive(Debug, Default, Deserialize, Clone, Copy)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum ModuleOrientation { pub enum ModuleOrientation {
#[default] #[default]
#[serde(alias = "h")] #[serde(alias = "h")]

View file

@ -38,11 +38,15 @@ use color_eyre::Result;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
pub use self::common::{CommonConfig, ModuleOrientation, TransitionType}; pub use self::common::{CommonConfig, ModuleOrientation, TransitionType};
pub use self::truncate::TruncateMode; pub use self::truncate::TruncateMode;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[serde(tag = "type", rename_all = "snake_case")] #[serde(tag = "type", rename_all = "snake_case")]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum ModuleConfig { pub enum ModuleConfig {
#[cfg(feature = "cairo")] #[cfg(feature = "cairo")]
Cairo(Box<CairoModule>), Cairo(Box<CairoModule>),
@ -123,6 +127,7 @@ impl ModuleConfig {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum MonitorConfig { pub enum MonitorConfig {
Single(BarConfig), Single(BarConfig),
Multiple(Vec<BarConfig>), Multiple(Vec<BarConfig>),
@ -130,6 +135,7 @@ pub enum MonitorConfig {
#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum BarPosition { pub enum BarPosition {
Top, Top,
Bottom, Bottom,
@ -144,6 +150,7 @@ impl Default for BarPosition {
} }
#[derive(Debug, Default, Deserialize, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Default, Deserialize, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct MarginConfig { pub struct MarginConfig {
#[serde(default)] #[serde(default)]
pub bottom: i32, pub bottom: i32,
@ -162,6 +169,7 @@ pub struct MarginConfig {
/// depending on your [use-case](#2-pick-your-use-case). /// depending on your [use-case](#2-pick-your-use-case).
/// ///
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct BarConfig { pub struct BarConfig {
/// A unique identifier for the bar, used for controlling it over IPC. /// A unique identifier for the bar, used for controlling it over IPC.
/// If not set, uses a generated integer suffix. /// If not set, uses a generated integer suffix.
@ -298,6 +306,7 @@ impl Default for BarConfig {
} }
#[derive(Debug, Deserialize, Clone, Default)] #[derive(Debug, Deserialize, Clone, Default)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct Config { pub struct Config {
/// A map of [ironvar](ironvar) keys and values /// A map of [ironvar](ironvar) keys and values
/// to initialize Ironbar with on startup. /// to initialize Ironbar with on startup.

View file

@ -4,6 +4,7 @@ use serde::Deserialize;
#[derive(Debug, Deserialize, Clone, Copy)] #[derive(Debug, Deserialize, Clone, Copy)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum EllipsizeMode { pub enum EllipsizeMode {
Start, Start,
Middle, Middle,
@ -28,6 +29,7 @@ impl From<EllipsizeMode> for GtkEllipsizeMode {
/// ///
#[derive(Debug, Deserialize, Clone, Copy)] #[derive(Debug, Deserialize, Clone, Copy)]
#[serde(untagged)] #[serde(untagged)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum TruncateMode { pub enum TruncateMode {
/// Auto mode lets GTK decide when to ellipsize. /// Auto mode lets GTK decide when to ellipsize.
/// ///

View file

@ -8,6 +8,7 @@ use tokio::sync::mpsc;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[serde(untagged)] #[serde(untagged)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum DynamicBool { pub enum DynamicBool {
/// Either a script or variable, to be determined. /// Either a script or variable, to be determined.
Unknown(String), Unknown(String),

View file

@ -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";

View file

@ -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];

View file

@ -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,
} }

View file

@ -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)]

View file

@ -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
View 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
View 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
View 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();
}
}

View file

@ -76,13 +76,30 @@ fn main() {
fn run_with_args() { fn run_with_args() {
let args = cli::Args::parse(); let args = cli::Args::parse();
#[cfg(feature = "schema")]
if args.print_schema {
let schema = schemars::schema_for!(Config);
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
return;
}
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:?}"),
}; };
}); });
@ -232,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)

View file

@ -18,6 +18,7 @@ use tokio::time::sleep;
use tracing::{debug, error}; use tracing::{debug, error};
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CairoModule { pub struct CairoModule {
/// The path to the Lua script to load. /// The path to the Lua script to load.
/// This can be absolute, or relative to the working directory. /// This can be absolute, or relative to the working directory.

View file

@ -17,6 +17,7 @@ use tokio::sync::{broadcast, mpsc};
use tracing::{debug, error}; use tracing::{debug, error};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ClipboardModule { pub struct ClipboardModule {
/// The icon to show on the bar widget button. /// The icon to show on the bar widget button.
/// Supports [image](images) icons. /// Supports [image](images) icons.

View file

@ -16,6 +16,7 @@ use crate::modules::{
use crate::{glib_recv, module_impl, send_async, spawn, try_send}; use crate::{glib_recv, module_impl, send_async, spawn, try_send};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ClockModule { pub struct ClockModule {
/// The format string to use for the date/time shown on the bar. /// The format string to use for the date/time shown on the bar.
/// Pango markup is supported. /// Pango markup is supported.

View file

@ -6,6 +6,7 @@ use gtk::prelude::*;
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct BoxWidget { pub struct BoxWidget {
/// Widget name. /// Widget name.
/// ///

View file

@ -10,6 +10,7 @@ use crate::{build, try_send};
use super::{CustomWidget, CustomWidgetContext, ExecEvent, WidgetConfig}; use super::{CustomWidget, CustomWidgetContext, ExecEvent, WidgetConfig};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ButtonWidget { pub struct ButtonWidget {
/// Widget name. /// Widget name.
/// ///

View file

@ -9,6 +9,7 @@ use crate::image::ImageProvider;
use super::{CustomWidget, CustomWidgetContext}; use super::{CustomWidget, CustomWidgetContext};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ImageWidget { pub struct ImageWidget {
/// Widget name. /// Widget name.
/// ///

View file

@ -9,6 +9,7 @@ use crate::dynamic_value::dynamic_string;
use super::{CustomWidget, CustomWidgetContext}; use super::{CustomWidget, CustomWidgetContext};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct LabelWidget { pub struct LabelWidget {
/// Widget name. /// Widget name.
/// ///

View file

@ -28,6 +28,7 @@ use tokio::sync::{broadcast, mpsc};
use tracing::{debug, error}; use tracing::{debug, error};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CustomModule { pub struct CustomModule {
/// Modules and widgets to add to the bar container. /// Modules and widgets to add to the bar container.
/// ///
@ -45,6 +46,7 @@ pub struct CustomModule {
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct WidgetConfig { pub struct WidgetConfig {
/// One of a custom module native Ironbar module. /// One of a custom module native Ironbar module.
#[serde(flatten)] #[serde(flatten)]
@ -57,6 +59,7 @@ pub struct WidgetConfig {
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[serde(untagged)] #[serde(untagged)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum WidgetOrModule { pub enum WidgetOrModule {
/// A custom-module specific basic widget /// A custom-module specific basic widget
Widget(Widget), Widget(Widget),
@ -67,6 +70,7 @@ pub enum WidgetOrModule {
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[serde(tag = "type", rename_all = "snake_case")] #[serde(tag = "type", rename_all = "snake_case")]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum Widget { pub enum Widget {
/// A container to place nested widgets inside. /// A container to place nested widgets inside.
Box(BoxWidget), Box(BoxWidget),

View file

@ -13,6 +13,7 @@ use crate::{build, glib_recv_mpsc, spawn, try_send};
use super::{CustomWidget, CustomWidgetContext}; use super::{CustomWidget, CustomWidgetContext};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ProgressWidget { pub struct ProgressWidget {
/// Widget name. /// Widget name.
/// ///

View file

@ -16,6 +16,7 @@ use crate::{build, glib_recv_mpsc, spawn, try_send};
use super::{CustomWidget, CustomWidgetContext, ExecEvent}; use super::{CustomWidget, CustomWidgetContext, ExecEvent};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct SliderWidget { pub struct SliderWidget {
/// Widget name. /// Widget name.
/// ///

View file

@ -12,6 +12,7 @@ use tokio::sync::mpsc;
use tracing::debug; use tracing::debug;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct FocusedModule { pub struct FocusedModule {
/// Whether to show icon on the bar. /// Whether to show icon on the bar.
/// ///

View file

@ -9,6 +9,7 @@ use serde::Deserialize;
use tokio::sync::mpsc; use tokio::sync::mpsc;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct LabelModule { pub struct LabelModule {
/// The text to show on the label. /// The text to show on the label.
/// This is a [Dynamic String](dynamic-values#dynamic-string). /// This is a [Dynamic String](dynamic-values#dynamic-string).

View file

@ -19,6 +19,7 @@ use tokio::sync::{broadcast, mpsc};
use tracing::{debug, error, trace}; use tracing::{debug, error, trace};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct LauncherModule { pub struct LauncherModule {
/// List of app IDs (or classes) to always show regardless of open state, /// List of app IDs (or classes) to always show regardless of open state,
/// in the order specified. /// in the order specified.

View file

@ -384,7 +384,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);
@ -457,7 +457,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);

View file

@ -4,6 +4,7 @@ use serde::Deserialize;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Icons { pub struct Icons {
/// Icon to display when playing. /// Icon to display when playing.
/// ///
@ -71,6 +72,7 @@ impl Default for Icons {
#[derive(Debug, Deserialize, Clone, Copy)] #[derive(Debug, Deserialize, Clone, Copy)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum PlayerType { pub enum PlayerType {
Mpd, Mpd,
Mpris, Mpris,
@ -83,6 +85,7 @@ impl Default for PlayerType {
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct MusicModule { pub struct MusicModule {
/// Type of player to connect to /// Type of player to connect to
#[serde(default)] #[serde(default)]

View file

@ -5,7 +5,7 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use color_eyre::Result; use color_eyre::Result;
use glib::{Propagation, PropertySet}; use glib::{markup_escape_text, Propagation, PropertySet};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, IconTheme, Label, Orientation, Scale}; use gtk::{Button, IconTheme, Label, Orientation, Scale};
use regex::Regex; use regex::Regex;
@ -531,6 +531,7 @@ fn get_token_value(song: &Track, token: &str) -> String {
"track" => song.track.map(|x| x.to_string()), "track" => song.track.map(|x| x.to_string()),
_ => Some(token.to_string()), _ => Some(token.to_string()),
} }
.map(|str| markup_escape_text(str.as_str()).to_string())
.unwrap_or_default() .unwrap_or_default()
} }

View file

@ -10,6 +10,7 @@ use tokio::sync::mpsc::Receiver;
use tracing::error; use tracing::error;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct NotificationsModule { pub struct NotificationsModule {
/// Whether to show the current notification count. /// Whether to show the current notification count.
/// ///
@ -29,6 +30,7 @@ pub struct NotificationsModule {
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
struct Icons { struct Icons {
/// Icon to show when the panel is closed, with no notifications. /// Icon to show when the panel is closed, with no notifications.
/// ///
@ -193,6 +195,7 @@ impl Module<Overlay> for NotificationsModule {
if self.show_count { if self.show_count {
label.add_class("count"); label.add_class("count");
overlay.add_overlay(&label); overlay.add_overlay(&label);
overlay.set_overlay_pass_through(&label, true);
} }
let ctx = context.controller_tx.clone(); let ctx = context.controller_tx.clone();

View file

@ -10,6 +10,7 @@ use tokio::sync::mpsc;
use tracing::error; use tracing::error;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ScriptModule { pub struct ScriptModule {
/// Path to script to execute. /// Path to script to execute.
/// ///

View file

@ -14,6 +14,7 @@ use tokio::sync::mpsc;
use tokio::time::sleep; use tokio::time::sleep;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct SysInfoModule { pub struct SysInfoModule {
/// List of strings including formatting tokens. /// List of strings including formatting tokens.
/// For available tokens, see [below](#formatting-tokens). /// For available tokens, see [below](#formatting-tokens).
@ -51,6 +52,7 @@ pub struct SysInfoModule {
} }
#[derive(Debug, Deserialize, Copy, Clone)] #[derive(Debug, Deserialize, Copy, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Intervals { pub struct Intervals {
/// The number of seconds between refreshing memory data. /// The number of seconds between refreshing memory data.
/// ///
@ -91,6 +93,7 @@ pub struct Intervals {
#[derive(Debug, Deserialize, Copy, Clone)] #[derive(Debug, Deserialize, Copy, Clone)]
#[serde(untagged)] #[serde(untagged)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum Interval { pub enum Interval {
All(u64), All(u64),
Individual(Intervals), Individual(Intervals),

View file

@ -19,6 +19,7 @@ use tokio::sync::mpsc;
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct TrayModule { pub struct TrayModule {
/// Requests that icons from the theme be used over the item-provided item. /// Requests that icons from the theme be used over the item-provided item.
/// Most items only provide one or the other so this will have no effect in most circumstances. /// Most items only provide one or the other so this will have no effect in most circumstances.
@ -38,7 +39,8 @@ pub struct TrayModule {
/// **Valid options**: `top_to_bottom`, `bottom_to_top`, `left_to_right`, `right_to_left` /// **Valid options**: `top_to_bottom`, `bottom_to_top`, `left_to_right`, `right_to_left`
/// <br> /// <br>
/// **Default**: `left_to_right` if bar is horizontal, `top_to_bottom` if bar is vertical /// **Default**: `left_to_right` if bar is horizontal, `top_to_bottom` if bar is vertical
#[serde(default, deserialize_with = "deserialize_orientation")] #[serde(default, deserialize_with = "deserialize_pack_direction")]
#[cfg_attr(feature = "schema", schemars(schema_with = "schema_pack_direction"))]
direction: Option<PackDirection>, direction: Option<PackDirection>,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
@ -50,7 +52,7 @@ const fn default_icon_size() -> u32 {
16 16
} }
fn deserialize_orientation<'de, D>(deserializer: D) -> Result<Option<PackDirection>, D::Error> fn deserialize_pack_direction<'de, D>(deserializer: D) -> Result<Option<PackDirection>, D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
@ -61,11 +63,24 @@ where
"right_to_left" => Ok(PackDirection::Rtl), "right_to_left" => Ok(PackDirection::Rtl),
"top_to_bottom" => Ok(PackDirection::Ttb), "top_to_bottom" => Ok(PackDirection::Ttb),
"bottom_to_top" => Ok(PackDirection::Btt), "bottom_to_top" => Ok(PackDirection::Btt),
_ => Err(serde::de::Error::custom("invalid value for orientation")), _ => Err(serde::de::Error::custom("invalid value for direction")),
}) })
.transpose() .transpose()
} }
#[cfg(feature = "schema")]
fn schema_pack_direction(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
use schemars::JsonSchema;
let mut schema: schemars::schema::SchemaObject = <String>::json_schema(gen).into();
schema.enum_values = Some(vec![
"top_to_bottom".into(),
"bottom_to_top".into(),
"left_to_right".into(),
"right_to_left".into(),
]);
schema.into()
}
impl Module<MenuBar> for TrayModule { impl Module<MenuBar> for TrayModule {
type SendMessage = Event; type SendMessage = Event;
type ReceiveMessage = ActivateRequest; type ReceiveMessage = ActivateRequest;

View file

@ -22,6 +22,7 @@ const HOUR: i64 = 60 * 60;
const MINUTE: i64 = 60; const MINUTE: i64 = 60;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct UpowerModule { pub struct UpowerModule {
/// The format string to use for the widget button label. /// The format string to use for the widget button label.
/// For available tokens, see [below](#formatting-tokens). /// For available tokens, see [below](#formatting-tokens).

View file

@ -18,6 +18,7 @@ use std::collections::HashMap;
use tokio::sync::mpsc; use tokio::sync::mpsc;
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct VolumeModule { pub struct VolumeModule {
/// Maximum value to allow volume sliders to reach. /// Maximum value to allow volume sliders to reach.
/// Pulse supports values > 100 but this may result in distortion. /// Pulse supports values > 100 but this may result in distortion.

View file

@ -15,6 +15,7 @@ use tracing::{debug, trace, warn};
#[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum SortOrder { pub enum SortOrder {
/// Shows workspaces in the order they're added /// Shows workspaces in the order they're added
Added, Added,
@ -31,6 +32,7 @@ impl Default for SortOrder {
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[serde(untagged)] #[serde(untagged)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum Favorites { pub enum Favorites {
ByMonitor(HashMap<String, Vec<String>>), ByMonitor(HashMap<String, Vec<String>>),
Global(Vec<String>), Global(Vec<String>),
@ -43,6 +45,7 @@ impl Default for Favorites {
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct WorkspacesModule { pub struct WorkspacesModule {
/// Map of actual workspace names to custom names. /// Map of actual workspace names to custom names.
/// ///

View file

@ -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()
} }

View file

@ -14,6 +14,7 @@ use tracing::{debug, error, trace, warn};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[serde(untagged)] #[serde(untagged)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum ScriptInput { pub enum ScriptInput {
String(String), String(String),
Struct(Script), Struct(Script),
@ -21,6 +22,7 @@ pub enum ScriptInput {
#[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum ScriptMode { pub enum ScriptMode {
Poll, Poll,
Watch, Watch,
@ -75,6 +77,7 @@ impl ScriptMode {
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Script { pub struct Script {
#[serde(default = "ScriptMode::default")] #[serde(default = "ScriptMode::default")]
pub(crate) mode: ScriptMode, pub(crate) mode: ScriptMode,