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

Merge branch 'master' into feat/volume-icon

This commit is contained in:
Reinout Meliesie 2024-06-30 18:15:15 +02:00
commit 9ea49202b3
Signed by: zedfrigg
GPG key ID: 3AFCC06481308BC6
55 changed files with 1161 additions and 556 deletions

View file

@ -6,20 +6,13 @@ on:
- v[0-9]+.[0-9]+.[0-9]+
jobs:
deploy:
release:
name: 'Create Release'
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: Update CHANGELOG
id: changelog
uses: Requarks/changelog-action@v1
@ -41,9 +34,54 @@ jobs:
uses: stefanzweifel/git-auto-commit-action@v4
with:
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
- 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:
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

211
Cargo.lock generated
View file

@ -437,9 +437,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
@ -456,9 +456,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.4"
version = "4.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
dependencies = [
"clap_builder",
"clap_derive",
@ -466,9 +466,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.2"
version = "4.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
dependencies = [
"anstream",
"anstyle",
@ -478,9 +478,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.4"
version = "4.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@ -761,6 +761,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "dyn-clone"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "either"
version = "1.8.1"
@ -891,9 +897,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.1.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
@ -1446,6 +1452,23 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
dependencies = [
"futures-util",
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
@ -1542,9 +1565,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.3.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
@ -1637,10 +1660,11 @@ dependencies = [
"mlua",
"mpd-utils",
"mpris",
"nix 0.28.0",
"nix 0.29.0",
"notify",
"regex",
"reqwest",
"schemars",
"serde",
"serde_json",
"smithay-client-toolkit",
@ -1875,9 +1899,9 @@ dependencies = [
[[package]]
name = "mlua"
version = "0.9.8"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e340c022072f3208a4105458286f4985ba5355bfe243c3073afe45cbe9ecf491"
checksum = "d111deb18a9c9bd33e1541309f4742523bfab01d276bfa9a27519f6de9c11dc7"
dependencies = [
"bstr",
"mlua-sys",
@ -1888,9 +1912,9 @@ dependencies = [
[[package]]
name = "mlua-sys"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5552e7e4e22ada0463dfdeee6caf6dc057a189fdc83136408a8f950a5e5c5540"
checksum = "a088ed0723df7567f569ba018c5d48c23c501f3878b190b04144dfa5ebfa8abc"
dependencies = [
"cc",
"cfg-if",
@ -1991,9 +2015,9 @@ dependencies = [
[[package]]
name = "nix"
version = "0.28.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.4.0",
"cfg-if",
@ -2203,9 +2227,9 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "percent-encoding"
version = "2.2.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pest"
@ -2490,9 +2514,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.4"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
@ -2534,9 +2558,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "reqwest"
version = "0.12.4"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37"
dependencies = [
"base64 0.22.0",
"bytes",
@ -2547,6 +2571,7 @@ dependencies = [
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"ipnet",
@ -2572,6 +2597,20 @@ dependencies = [
"winreg",
]
[[package]]
name = "ring"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e"
dependencies = [
"cc",
"getrandom",
"libc",
"spin",
"untrusted",
"windows-sys 0.48.0",
]
[[package]]
name = "ron"
version = "0.8.1"
@ -2592,9 +2631,9 @@ checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b"
[[package]]
name = "rustc-hash"
version = "1.1.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
[[package]]
name = "rustc_version"
@ -2632,6 +2671,19 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustls"
version = "0.23.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebbbdb961df0ad3f2652da8f3fdc4b36122f568f968f45ad3316f26c025c677b"
dependencies = [
"once_cell",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "2.1.2"
@ -2648,6 +2700,17 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247"
[[package]]
name = "rustls-webpki"
version = "0.102.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "ryu"
version = "1.0.13"
@ -2672,6 +2735,30 @@ dependencies = [
"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]]
name = "scoped-tls"
version = "1.0.1"
@ -2715,18 +2802,29 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "serde"
version = "1.0.202"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.202"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote 1.0.35",
"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",
@ -2899,6 +2997,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -2926,6 +3030,12 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "subtle"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5"
[[package]]
name = "swayipc-async"
version = "2.0.1"
@ -2986,9 +3096,9 @@ dependencies = [
[[package]]
name = "sync_wrapper"
version = "0.1.2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
[[package]]
name = "synom"
@ -3127,9 +3237,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.37.0"
version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
dependencies = [
"backtrace",
"bytes",
@ -3146,9 +3256,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [
"proc-macro2",
"quote 1.0.35",
@ -3165,6 +3275,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls",
"rustls-pki-types",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.7"
@ -3445,6 +3566,12 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "upower_dbus"
version = "0.3.2"
@ -3458,9 +3585,9 @@ dependencies = [
[[package]]
name = "url"
version = "2.3.1"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
dependencies = [
"form_urlencoded",
"idna",
@ -4102,6 +4229,12 @@ dependencies = [
"zvariant",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zvariant"
version = "3.15.0"

View file

@ -79,12 +79,14 @@ workspaces = ["futures-lite"]
"workspaces+sway" = ["workspaces", "swayipc-async"]
"workspaces+hyprland" = ["workspaces", "hyprland"]
schema = ["dep:schemars"]
[dependencies]
# core
gtk = "0.18.1"
gtk-layer-shell = "0.8.0"
glib = "0.18.5"
tokio = { version = "1.37.0", features = [
tokio = { version = "1.38.0", features = [
"macros",
"rt-multi-thread",
"time",
@ -99,7 +101,7 @@ tracing-error = { version = "0.2.0" , default-features = false }
tracing-appender = "0.2.3"
strip-ansi-escapes = "0.2.0"
color-eyre = "0.6.3"
serde = { version = "1.0.202", features = ["derive"] }
serde = { version = "1.0.203", features = ["derive"] }
indexmap = "2.2.6"
dirs = "5.0.1"
walkdir = "2.5.0"
@ -109,29 +111,29 @@ wayland-protocols-wlr = { version = "0.2.0", features = ["client"] }
smithay-client-toolkit = { version = "0.18.1", default-features = false, features = [
"calloop",
] }
universal-config = { version = "0.5.0", default_features = false }
universal-config = { version = "0.5.0", default-features = false }
ctrlc = "3.4.2"
cfg-if = "1.0.0"
# cli
clap = { version = "4.5.4", optional = true, features = ["derive"] }
clap = { version = "4.5.7", optional = true, features = ["derive"] }
# ipc
serde_json = { version = "1.0.117", optional = true }
# http
reqwest = { version = "0.12.4", default_features = false, features = ["default-tls", "http2"], optional = true }
reqwest = { version = "0.12.5", default-features = false, features = ["default-tls", "http2"], optional = true }
# cairo
lua-src = { version = "546.0.2", optional = true }
mlua = { version = "0.9.8", optional = true, features = ["luajit"] }
mlua = { version = "0.9.9", optional = true, features = ["luajit"] }
cairo-rs = { version = "0.18.5", optional = true, features = ["png"] }
# clipboard
nix = { version = "0.28.0", optional = true, features = ["event", "fs"] }
nix = { version = "0.29.0", optional = true, features = ["event", "fs"] }
# clock
chrono = { version = "0.4.38", optional = true, default_features = false, features = ["clock", "unstable-locales"] }
chrono = { version = "0.4.38", optional = true, default-features = false, features = ["clock", "unstable-locales"] }
# music
mpd-utils = { version = "0.2.1", optional = true }
@ -159,7 +161,10 @@ futures-util = { version = "0.3.30", optional = true }
# shared
futures-lite = { version = "2.3.0", optional = true } # networkmanager, upower, workspaces
regex = { version = "1.10.4", default-features = false, features = [
regex = { version = "1.10.5", default-features = false, features = [
"std",
], optional = true } # music, sys_info
zbus = { version = "3.15.2", default-features = false, features = ["tokio"], optional = true } # networkmanager, notifications, upower
# schema
schemars = { version = "0.8.21", optional = true }

View file

@ -136,6 +136,15 @@ A flake is included with the repo which can be used with Home Manager.
CI builds are automatically cached by Garnix.
You can use their binary cache by following the steps [here](https://garnix.io/docs/caching).
### Fedora
[fedora package](https://copr.fedorainfracloud.org/coprs/victorvintorez/tilingtools/packages/)
``` sh
dnf copr enable victorvintorez/tilingtools
dnf install ironbar
```
### Void Linux
[void package](https://github.com/void-linux/void-packages/tree/master/srcpkgs/ironbar)

View file

@ -101,6 +101,9 @@ cargo build --release --no-default-features \
| workspaces+all | Enables the `workspaces` module with support for all compositors. |
| workspaces+sway | Enables the `workspaces` module with support for Sway. |
| 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

View file

@ -20,11 +20,18 @@ Ironbar supports a range of configuration formats, so you can pick your favourit
- `config.json`
- `config.toml`
- `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)
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
Ironbar gives you a few ways to configure the bar to suit your needs.
@ -288,23 +295,25 @@ The following table lists each of the top-level bar config options:
The following table lists each of the bar-level bar config options:
| Name | Type | Default | Description |
|-------------------|----------------------------------------|--------------------------------------|----------------------------------------------------------------------------------------------------------------------------|
| `name` | `string` | `bar-<n>` | A unique identifier for the bar, used for controlling it over IPC. If not set, uses a generated integer suffix. |
| `position` | `top` or `bottom` or `left` or `right` | `bottom` | The bar's position on screen. |
| `anchor_to_edges` | `boolean` | `false` | Whether to anchor the bar to the edges of the screen. Setting to false centres the bar. |
| `height` | `integer` | `42` | The bar's height in pixels. |
| `popup_gap` | `integer` | `5` | The gap between the bar and popup window. |
| `margin.top` | `integer` | `0` | The margin on the top of the bar |
| `margin.bottom` | `integer` | `0` | The margin on the bottom of the bar |
| `margin.left` | `integer` | `0` | The margin on the left of the bar |
| `margin.right` | `integer` | `0` | The margin on the right of the bar |
| `icon_theme` | `string` | `null` | Name of the GTK icon theme to use. Leave blank to use default. |
| `start_hidden` | `boolean` | `false`, or `true` if `autohide` set | Whether the bar should be hidden when the application starts. Enabled by default when `autohide` is set. |
| `autohide` | `integer` | `null` | The duration in milliseconds before the bar is hidden after the cursor leaves. Leave unset to disable auto-hide behaviour. |
| `start` | `Module[]` | `[]` | Array of left or top modules. |
| `center` | `Module[]` | `[]` | Array of center modules. |
| `end` | `Module[]` | `[]` | Array of right or bottom modules. |
| Name | Type | Default | Description |
|-------------------|------------------------------------------------|------------------------------------------|----------------------------------------------------------------------------------------------------------------------------|
| `name` | `string` | `bar-<n>` | A unique identifier for the bar, used for controlling it over IPC. If not set, uses a generated integer suffix. |
| `position` | `top` or `bottom` or `left` or `right` | `bottom` | The bar's position on screen. |
| `anchor_to_edges` | `boolean` | `false` | Whether to anchor the bar to the edges of the screen. Setting to false centres the bar. |
| `height` | `integer` | `42` | The bar's height in pixels. |
| `margin.top` | `integer` | `0` | The margin on the top of the bar |
| `margin.bottom` | `integer` | `0` | The margin on the bottom of the bar |
| `margin.left` | `integer` | `0` | The margin on the left of the bar |
| `margin.right` | `integer` | `0` | The margin on the right of the bar |
| `layer` | `background` or `bottom` or `top` or `overlay` | `top` | The layer-shell layer to place the bar on. |
| `exclusive_zone` | `boolean` | `true` unless `start_hidden` is enabled. | Whether the bar should reserve an exclusive zone around it. |
| `popup_gap` | `integer` | `5` | The gap between the bar and popup window. |
| `icon_theme` | `string` | `null` | Name of the GTK icon theme to use. Leave blank to use default. |
| `start_hidden` | `boolean` | `false`, or `true` if `autohide` set | Whether the bar should be hidden when the application starts. Enabled by default when `autohide` is set. |
| `autohide` | `integer` | `null` | The duration in milliseconds before the bar is hidden after the cursor leaves. Leave unset to disable auto-hide behaviour. |
| `start` | `Module[]` | `[]` | Array of left or top modules. |
| `center` | `Module[]` | `[]` | Array of center modules. |
| `end` | `Module[]` | `[]` | Array of right or bottom modules. |
### 3.2 Module-level options

View file

@ -5,28 +5,38 @@ It also includes a command line interface, which can be used for interacting wit
# CLI
This is shipped as part of the `ironbar` binary. To view commands, you can use `ironbar --help`.
You can also view help per-command, for example using `ironbar set --help`.
You can also view help per sub-command or command, for example using `ironbar var --help` or `ironbar var set --help`.
Responses are handled by writing their type to stdout, followed by any value starting on the next line.
Error responses are written to stderr in the same format.
The CLI supports plaintext and JSON output. Plaintext will:
- Print `ok` for empty success responses
- Print the returned body for success responses
- Print `error` to followed by the error on the next line for error responses. This is printed to `stderr`.
Example:
```shell
$ ironbar set subject world
$ ironbar var set subject world
ok
$ ironbar get subject
ok
$ ironbar var get subject
world
$ ironbar var get foo
error
Variable not found
```
All error responses will cause the CLI to exit code 3.
# IPC
The server listens on a Unix socket.
This can usually be found at `/run/user/$UID/ironbar-ipc.sock`.
The path is printed on startup, and can usually be found at `/run/user/$UID/ironbar-ipc.sock`.
Commands and responses are sent as JSON objects, denoted by their `type` key.
Commands and responses are sent as JSON objects.
Commands will have a `command` key, and a `subcommand` key when part of a sub-command.
The message buffer is currently limited to `1024` bytes.
Particularly large messages will be truncated or cause an error.
@ -47,7 +57,7 @@ Responds with `ok`.
```json
{
"type": "ping"
"command": "ping"
}
```
@ -59,7 +69,7 @@ Responds with `ok`.
```json
{
"type": "inspect"
"command": "inspect"
}
```
@ -73,48 +83,7 @@ Responds with `ok`.
```json
{
"type": "reload"
}
```
### `get`
Gets an [ironvar](ironvars) value.
Responds with `ok_value` if the value exists, otherwise `error`.
```json
{
"type": "get",
"key": "foo"
}
```
### `set`
Sets an [ironvar](ironvars) value.
Responds with `ok`.
```json
{
"type": "set",
"key": "foo",
"value": "bar"
}
```
### list
Gets a list of all [ironvar](ironvars) values.
Responds with `ok_value`.
Each key/value pair is on its own `\n` separated newline. The key and value are separated by a colon and space `: `.
```json
{
"type": "list"
"command": "reload"
}
```
@ -126,26 +95,113 @@ Responds with `ok` if the stylesheet exists, otherwise `error`.
```json
{
"type": "load_css",
"command": "load_css",
"path": "/path/to/style.css"
}
```
### `set_visible`
### `var`
Sets a bar's visibility.
Subcommand for controlling Ironvars.
#### `get`
Gets an [ironvar](ironvars) value.
Responds with `ok_value` if the value exists, otherwise `error`.
```json
{
"command": "var",
"subcommand": "get",
"key": "foo"
}
```
#### `set`
Sets an [ironvar](ironvars) value.
Responds with `ok`.
```json
{
"command": "var",
"subcommand": "set",
"key": "foo",
"value": "bar"
}
```
#### `list`
Gets a list of all [ironvar](ironvars) values.
Responds with `ok_value`.
Each key/value pair is on its own `\n` separated newline. The key and value are separated by a colon and space `: `.
```json
{
"command": "var",
"subcommand": "list"
}
```
### `bar`
#### `show`
Forces a bar to be shown, regardless of the current visibility state.
```json
{
"command": "bar",
"subcommand": "show",
"name": "bar-123"
}
```
#### `hide`
Forces a bar to be hidden, regardless of the current visibility state.
```json
{
"command": "bar",
"subcommand": "hide",
"name": "bar-123"
}
```
#### `set_visible`
Sets a bar's visibility to one of shown/hidden.
Responds with `ok` if the bar exists, otherwise `error`.
```json
{
"type": "set_visible",
"bar_name": "bar-123",
"command": "bar",
"subcommand": "set_visible",
"name": "bar-123",
"visible": true
}
```
### `get_visible`
#### `toggle_visible`
Toggles the current visibility state of a bar between shown and hidden.
```json
{
"command": "bar",
"subcommand": "toggle_visible",
"name": "bar-123"
}
```
#### `get_visible`
Gets a bar's visibility.
@ -153,54 +209,98 @@ Responds with `ok_value` and the visibility (`true`/`false`) if the bar exists,
```json
{
"type": "get_visible",
"bar_name": "bar-123"
"command": "bar",
"subcommand": "get_visible",
"name": "bar-123"
}
```
### `toggle_popup`
Toggles the open/closed state for a module's popup.
Since each bar only has a single popup, any open popup on the bar is closed.
Responds with `ok` if the popup exists, otherwise `error`.
```json
{
"type": "toggle_popup",
"bar_name": "bar-123",
"name": "clock"
}
```
### `open_popup`
#### `show_popup`
Sets a module's popup open, regardless of its current state.
Since each bar only has a single popup, any open popup on the bar is closed.
Responds with `ok` if the popup exists, otherwise `error`.
Responds with `ok` if the bar and widget exist, otherwise `error`.
```json
{
"type": "open_popup",
"bar_name": "bar-123",
"name": "clock"
"command": "bar",
"subcommand": "show_popup",
"name": "bar-123",
"widget_name": "clock"
}
```
### `close_popup`
#### `hide_popup`
Sets the popup on a bar closed, regardless of which module it is open for.
Responds with `ok` if the popup exists, otherwise `error`.
Responds with `ok` if the bar and widget exist, otherwise `error`.
```json
{
"type": "close_popup",
"command": "bar",
"subcommand": "hide_popup",
"bar_name": "bar-123"
}
```
#### `set_popup_visible`
Sets a popup's visibility to one of shown/hidden.
Responds with `ok` if the bar and widget exist, otherwise `error`.
```json
{
"command": "bar",
"subcommand": "set_popup_visible",
"name": "bar-123",
"widget_name": "clock",
"visible": true
}
```
#### `toggle_popup`
Toggles the open/closed state for a module's popup.
Since each bar only has a single popup, any open popup on the bar is closed.
Responds with `ok` if the bar and widget exist, otherwise `error`.
```json
{
"command": "bar",
"subcommand": "toggle_popup",
"bar_name": "bar-123",
"widget_name": "clock"
}
```
#### `get_popup_visible`
Gets the popup's current visibility state.
```json
{
"command": "bar",
"subcommand": "get_popup_visible",
"bar_name": "bar-123"
}
```
#### `set_exclusive`
Sets whether the bar reserves an exclusive zone.
```json
{
"command": "bar",
"subcommand": "set_exclusive",
"exclusive": true
}
```
## Responses
### `ok`

View file

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

View file

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

30
flake.lock generated
View file

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

View file

@ -120,27 +120,28 @@ impl Bar {
self.name, self.monitor_name
);
self.setup_layer_shell(
&self.window,
true,
config.anchor_to_edges,
config.margin,
monitor,
);
let start_hidden = config
.start_hidden
.unwrap_or_else(|| config.autohide.is_some());
self.setup_layer_shell(
&self.window,
config.exclusive_zone.unwrap_or(!start_hidden),
config.anchor_to_edges,
config.margin,
config.layer,
monitor,
);
if let Some(autohide) = config.autohide {
let hotspot_window = Window::new(WindowType::Toplevel);
Self::setup_autohide(&self.window, &hotspot_window, autohide);
self.setup_layer_shell(
&hotspot_window,
false,
config.anchor_to_edges,
config.margin,
gtk_layer_shell::Layer::Top,
monitor,
);
@ -166,43 +167,46 @@ impl Bar {
exclusive_zone: bool,
anchor_to_edges: bool,
margin: MarginConfig,
layer: gtk_layer_shell::Layer,
monitor: &Monitor,
) {
use gtk_layer_shell::Edge;
let position = self.position;
win.init_layer_shell();
win.set_monitor(monitor);
win.set_layer(gtk_layer_shell::Layer::Top);
win.set_layer(layer);
win.set_namespace(env!("CARGO_PKG_NAME"));
if exclusive_zone {
win.auto_exclusive_zone_enable();
}
win.set_layer_shell_margin(gtk_layer_shell::Edge::Top, margin.top);
win.set_layer_shell_margin(gtk_layer_shell::Edge::Bottom, margin.bottom);
win.set_layer_shell_margin(gtk_layer_shell::Edge::Left, margin.left);
win.set_layer_shell_margin(gtk_layer_shell::Edge::Right, margin.right);
win.set_layer_shell_margin(Edge::Top, margin.top);
win.set_layer_shell_margin(Edge::Bottom, margin.bottom);
win.set_layer_shell_margin(Edge::Left, margin.left);
win.set_layer_shell_margin(Edge::Right, margin.right);
let bar_orientation = position.orientation();
win.set_anchor(
gtk_layer_shell::Edge::Top,
Edge::Top,
position == BarPosition::Top
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
);
win.set_anchor(
gtk_layer_shell::Edge::Bottom,
Edge::Bottom,
position == BarPosition::Bottom
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
);
win.set_anchor(
gtk_layer_shell::Edge::Left,
Edge::Left,
position == BarPosition::Left
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
);
win.set_anchor(
gtk_layer_shell::Edge::Right,
Edge::Right,
position == BarPosition::Right
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
);
@ -320,6 +324,23 @@ impl Bar {
Inner::Loaded { popup } => popup.clone(),
}
}
pub fn visible(&self) -> bool {
self.window.is_visible()
}
/// Sets the window visibility status
pub fn set_visible(&self, visible: bool) {
self.window.set_visible(visible)
}
pub fn set_exclusive(&self, exclusive: bool) {
if exclusive {
self.window.auto_exclusive_zone_enable();
} else {
self.window.set_exclusive_zone(0);
}
}
}
/// 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::responses::Response;
use clap::Parser;
use clap::{Parser, ValueEnum};
use serde::{Deserialize, Serialize};
use std::process::exit;
#[derive(Parser, Debug, Serialize, Deserialize)]
#[command(version)]
@ -9,16 +11,50 @@ pub struct Args {
#[command(subcommand)]
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`.
/// Not used.
#[arg(short('b'), hide(true))]
sway_bar_id: Option<String>,
}
pub fn handle_response(response: Response) {
match response {
Response::Ok => println!("ok"),
Response::OkValue { value } => println!("ok\n{value}"),
Response::Err { message } => eprintln!("error\n{}", message.unwrap_or_default()),
#[derive(Debug, Serialize, Deserialize, Default, ValueEnum, Clone, Copy)]
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::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

@ -14,7 +14,7 @@ pub struct Event {
pub count: u32,
pub dnd: bool,
pub cc_open: bool,
pub inhibited: bool,
// pub inhibited: bool,
}
type GetSubscribeData = (bool, bool, u32, bool);
@ -22,12 +22,12 @@ type GetSubscribeData = (bool, bool, u32, bool);
/// Converts the data returned from
/// `get_subscribe_data` into an event for convenience.
impl From<GetSubscribeData> for Event {
fn from((dnd, cc_open, count, inhibited): (bool, bool, u32, bool)) -> Self {
fn from((dnd, cc_open, count, _inhibited): (bool, bool, u32, bool)) -> Self {
Self {
count,
dnd,
cc_open,
inhibited,
// inhibited,
}
}
}

View file

@ -2,8 +2,9 @@ mod macros;
mod wl_output;
mod wl_seat;
use crate::error::ERR_CHANNEL_RECV;
use crate::error::{ExitCode, ERR_CHANNEL_RECV};
use crate::{arc_mut, lock, register_client, send, spawn, spawn_blocking};
use std::process::exit;
use std::sync::{Arc, Mutex};
use calloop_channel::Event::Msg;
@ -305,6 +306,8 @@ impl Environment {
"{:?}",
Report::new(err).wrap_err("Failed to dispatch pending wayland events")
);
exit(ExitCode::WaylandDispatchError as i32)
}
}
}

View file

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

View file

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

View file

@ -35,6 +35,37 @@ impl<'de> Deserialize<'de> for MonitorConfig {
}
}
pub fn deserialize_layer<'de, D>(deserializer: D) -> Result<gtk_layer_shell::Layer, D::Error>
where
D: serde::Deserializer<'de>,
{
use gtk_layer_shell::Layer;
let value = Option::<String>::deserialize(deserializer)?;
value
.map(|v| match v.as_str() {
"background" => Ok(Layer::Background),
"bottom" => Ok(Layer::Bottom),
"top" => Ok(Layer::Top),
"overlay" => Ok(Layer::Overlay),
_ => Err(serde::de::Error::custom("invalid value for orientation")),
})
.unwrap_or(Ok(Layer::Top))
}
#[cfg(feature = "schema")]
pub fn schema_layer(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![
"background".into(),
"bottom".into(),
"top".into(),
"overlay".into(),
]);
schema.into()
}
impl BarPosition {
/// Gets the orientation the bar and widgets should use
/// based on this position.

View file

@ -38,11 +38,15 @@ use color_eyre::Result;
use serde::Deserialize;
use std::collections::HashMap;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
pub use self::common::{CommonConfig, ModuleOrientation, TransitionType};
pub use self::truncate::TruncateMode;
#[derive(Debug, Deserialize, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum ModuleConfig {
#[cfg(feature = "cairo")]
Cairo(Box<CairoModule>),
@ -123,6 +127,7 @@ impl ModuleConfig {
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum MonitorConfig {
Single(BarConfig),
Multiple(Vec<BarConfig>),
@ -130,6 +135,7 @@ pub enum MonitorConfig {
#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum BarPosition {
Top,
Bottom,
@ -144,6 +150,7 @@ impl Default for BarPosition {
}
#[derive(Debug, Default, Deserialize, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct MarginConfig {
#[serde(default)]
pub bottom: i32,
@ -162,6 +169,7 @@ pub struct MarginConfig {
/// depending on your [use-case](#2-pick-your-use-case).
///
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct BarConfig {
/// A unique identifier for the bar, used for controlling it over IPC.
/// If not set, uses a generated integer suffix.
@ -214,6 +222,36 @@ pub struct BarConfig {
#[serde(default)]
pub margin: MarginConfig,
/// The layer-shell layer to place the bar on.
///
/// Taken from the
/// [wlr_layer_shell](https://wayland.app/protocols/wlr-layer-shell-unstable-v1#zwlr_layer_shell_v1:enum:layer) definition:
///
/// > These values indicate which layers a surface can be rendered in.
/// > They are ordered by z depth, bottom-most first.
/// > Traditional shell surfaces will typically be rendered between the bottom and top layers.
/// > Fullscreen shell surfaces are typically rendered at the top layer.
/// > Multiple surfaces can share a single layer, and ordering within a single layer is undefined.
///
/// **Valid options**: `background`, `bottom`, `top`, `overlay`
/// <br>
/// **Default**: `top`
#[serde(
default = "default_layer",
deserialize_with = "r#impl::deserialize_layer"
)]
#[cfg_attr(feature = "schema", schemars(schema_with = "r#impl::schema_layer"))]
pub layer: gtk_layer_shell::Layer,
/// Whether the bar should reserve an exclusive zone around it.
///
/// When true, this prevents windows from rendering in the same space
/// as the bar, causing them to shift.
///
/// **Default**: `true` unless `start_hidden` is set.
#[serde(default)]
pub exclusive_zone: Option<bool>,
/// The size of the gap in pixels
/// between the bar and the popup window.
///
@ -280,9 +318,11 @@ impl Default for BarConfig {
Self {
position: BarPosition::default(),
height: default_bar_height(),
margin: MarginConfig::default(),
name: None,
layer: default_layer(),
exclusive_zone: None,
height: default_bar_height(),
start_hidden: None,
autohide: None,
icon_theme: None,
@ -298,6 +338,7 @@ impl Default for BarConfig {
}
#[derive(Debug, Deserialize, Clone, Default)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct Config {
/// A map of [ironvar](ironvar) keys and values
/// to initialize Ironbar with on startup.
@ -337,6 +378,10 @@ pub struct Config {
pub monitors: Option<HashMap<String, MonitorConfig>>,
}
const fn default_layer() -> gtk_layer_shell::Layer {
gtk_layer_shell::Layer::Top
}
const fn default_bar_height() -> i32 {
42
}

View file

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

View file

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

View file

@ -2,6 +2,8 @@
pub enum ExitCode {
GtkDisplay = 1,
CreateBars = 2,
IpcResponseError = 3,
WaylandDispatchError = 4,
}
pub const ERR_MUTEX_LOCK: &str = "Failed to get lock on Mutex";

View file

@ -8,7 +8,7 @@ use tokio::net::UnixStream;
impl Ipc {
/// Sends a command to the IPC server.
/// 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 {
Ok(stream) => Ok(stream),
Err(err) => Err(Report::new(err)
@ -17,6 +17,11 @@ impl Ipc {
}?;
let write_buffer = serde_json::to_vec(&command)?;
if debug {
eprintln!("REQUEST JSON: {}", serde_json::to_string(&command)?);
}
stream.write_all(&write_buffer).await?;
let mut read_buffer = vec![0; 1024];

View file

@ -1,20 +1,39 @@
use clap::ArgAction;
use std::path::PathBuf;
use clap::Subcommand;
use clap::{Args, Subcommand};
use serde::{Deserialize, Serialize};
#[derive(Subcommand, Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[serde(tag = "command", rename_all = "snake_case")]
pub enum Command {
/// Return "ok"
/// Pong
Ping,
/// Open the GTK inspector
/// Open the GTK inspector.
Inspect,
/// Reload the config
/// Reload the config.
Reload,
/// Load an additional CSS stylesheet.
/// The sheet is automatically hot-reloaded.
LoadCss {
/// The path to the sheet.
path: PathBuf,
},
/// Get and set reactive Ironvar values.
#[command(subcommand)]
Var(IronvarCommand),
/// Interact with a specific bar.
Bar(BarCommand),
}
#[derive(Subcommand, Debug, Serialize, Deserialize)]
#[serde(tag = "subcommand", rename_all = "snake_case")]
pub enum IronvarCommand {
/// Set an `ironvar` value.
/// This creates it if it does not already exist, and updates it if it does.
/// Any references to this variable are automatically and immediately updated.
@ -34,49 +53,80 @@ pub enum Command {
/// Gets the current value of all `ironvar`s.
List,
}
/// Load an additional CSS stylesheet.
/// The sheet is automatically hot-reloaded.
LoadCss {
/// The path to the sheet.
path: PathBuf,
},
#[derive(Args, Debug, Serialize, Deserialize)]
pub struct BarCommand {
/// The name of the bar.
pub name: String,
/// Set the visibility of the bar with the given name.
#[command(subcommand)]
#[serde(flatten)]
pub subcommand: BarCommandType,
}
#[derive(Subcommand, Debug, Serialize, Deserialize)]
#[serde(tag = "subcommand", rename_all = "snake_case")]
pub enum BarCommandType {
// == Visibility == \\
/// Force the bar to be shown, regardless of current visibility state.
Show,
/// Force the bar to be hidden, regardless of current visibility state.
Hide,
/// Set the bar's visibility state via an argument.
SetVisible {
///Bar name to target.
bar_name: String,
/// The visibility status.
#[arg(short, long)]
/// The new visibility state.
#[clap(
num_args(1),
require_equals(true),
action = ArgAction::Set,
)]
visible: bool,
},
/// Toggle the current visibility state between shown and hidden.
ToggleVisible,
/// Get the bar's visibility state.
GetVisible,
/// Get the visibility of the bar with the given name.
GetVisible {
/// Bar name to target.
bar_name: String,
// == Popup visibility == \\
/// Open a popup, regardless of current state.
/// If opening this popup, and a different popup on the same bar is already open, the other is closed.
ShowPopup {
/// The configured name of the widget.
widget_name: String,
},
/// Close a popup, regardless of current state.
HidePopup,
/// Set the popup's visibility state via an argument.
/// If opening this popup, and a different popup on the same bar is already open, the other is closed.
SetPopupVisible {
/// The configured name of the widget.
widget_name: String,
#[clap(
num_args(1),
require_equals(true),
action = ArgAction::Set,
)]
visible: bool,
},
/// Toggle a popup open/closed.
/// If opening this popup, and a different popup on the same bar is already open, the other is closed.
TogglePopup {
/// The name of the monitor the bar is located on.
bar_name: String,
/// The name of the widget.
name: String,
/// The configured name of the widget.
widget_name: String,
},
/// Get the popup's current visibility state.
GetPopupVisible,
/// 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,
// == Exclusivity == \\
/// Set whether the bar reserves an exclusive zone.
SetExclusive {
#[clap(
num_args(1),
require_equals(true),
action = ArgAction::Set,
)]
exclusive: bool,
},
}

View file

@ -6,7 +6,7 @@ mod server;
use std::path::{Path, PathBuf};
use tracing::warn;
pub use commands::Command;
pub use commands::*;
pub use responses::Response;
#[derive(Debug)]

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

89
src/ipc/server/bar.rs Normal file
View file

@ -0,0 +1,89 @@
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(),
},
SetExclusive { exclusive } => {
bar.set_exclusive(exclusive);
Response::Ok
}
}
}
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() {
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 {
Some(command) => {
if args.debug {
eprintln!("REQUEST: {command:?}")
}
let rt = create_runtime();
rt.block_on(async move {
let ipc = ipc::Ipc::new();
match ipc.send(command).await {
Ok(res) => cli::handle_response(res),
match ipc.send(command, args.debug).await {
Ok(res) => {
if args.debug {
eprintln!("RESPONSE: {res:?}")
}
cli::handle_response(res, args.format.unwrap_or_default())
}
Err(err) => error!("{err:?}"),
};
});
@ -232,6 +249,7 @@ impl Ironbar {
/// 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.
#[must_use]
pub fn unique_id() -> usize {
static COUNTER: AtomicUsize = AtomicUsize::new(1);
COUNTER.fetch_add(1, Ordering::Relaxed)

View file

@ -18,6 +18,7 @@ use tokio::time::sleep;
use tracing::{debug, error};
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CairoModule {
/// The path to the Lua script to load.
/// 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};
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ClipboardModule {
/// The icon to show on the bar widget button.
/// Supports [image](images) icons.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,6 +9,7 @@ use serde::Deserialize;
use tokio::sync::mpsc;
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct LabelModule {
/// The text to show on the label.
/// 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};
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct LauncherModule {
/// List of app IDs (or classes) to always show regardless of open state,
/// in the order specified.

View file

@ -384,7 +384,7 @@ impl ModuleFactory for BarModuleFactory {
}
ModuleUpdateEvent::TogglePopup(button_id) if !disable_popup => {
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();
} else {
popup.show(id, button_id);
@ -457,7 +457,7 @@ impl ModuleFactory for PopupModuleFactory {
}
ModuleUpdateEvent::TogglePopup(_) if !disable_popup => {
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();
} else {
popup.show(id, button_id);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,6 +19,7 @@ use tokio::sync::mpsc;
use tracing::{debug, error, warn};
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct TrayModule {
/// 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.
@ -38,7 +39,8 @@ pub struct TrayModule {
/// **Valid options**: `top_to_bottom`, `bottom_to_top`, `left_to_right`, `right_to_left`
/// <br>
/// **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>,
/// See [common options](module-level-options#common-options).
@ -50,7 +52,7 @@ const fn default_icon_size() -> u32 {
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
D: serde::Deserializer<'de>,
{
@ -61,11 +63,24 @@ where
"right_to_left" => Ok(PackDirection::Rtl),
"top_to_bottom" => Ok(PackDirection::Ttb),
"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()
}
#[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 {
type SendMessage = Event;
type ReceiveMessage = ActivateRequest;

View file

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

View file

@ -18,6 +18,7 @@ use std::collections::HashMap;
use tokio::sync::mpsc;
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct VolumeModule {
/// Maximum value to allow volume sliders to reach.
/// 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)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum SortOrder {
/// Shows workspaces in the order they're added
Added,
@ -31,6 +32,7 @@ impl Default for SortOrder {
#[derive(Debug, Deserialize, Clone)]
#[serde(untagged)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum Favorites {
ByMonitor(HashMap<String, Vec<String>>),
Global(Vec<String>),
@ -43,6 +45,7 @@ impl Default for Favorites {
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct WorkspacesModule {
/// Map of actual workspace names to custom names.
///

View file

@ -229,7 +229,7 @@ impl Popup {
}
/// Checks if the popup is currently visible
pub fn is_visible(&self) -> bool {
pub fn visible(&self) -> bool {
self.window.is_visible()
}

View file

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