diff --git a/.github/workflows/binary.yml b/.github/workflows/binary.yml index df72e97..5205987 100644 --- a/.github/workflows/binary.yml +++ b/.github/workflows/binary.yml @@ -53,10 +53,10 @@ jobs: - name: Compress the built binary if: ${{ matrix.platform.zipext == '.tar.gz' }} - run: tar -zcvf ${{env.BINARY_NAME}}-${{github.ref_name}}-${{matrix.platform.target}}.tar.gz -C target/${{matrix.platform.target}}/release ${{env.BINARY_NAME}} + run: tar -zcvf ${{env.BINARY_NAME}}-${{needs.get_last_release.outputs.latest_release_tag}}-${{matrix.platform.target}}.tar.gz -C target/${{matrix.platform.target}}/release ${{env.BINARY_NAME}} - name: Upload to release - run: gh release upload ${{needs.get_last_release.outputs.latest_release_tag}} ${{env.BINARY_NAME}}-${{github.ref_name}}-${{matrix.platform.target}}${{matrix.platform.zipext}} + run: gh release upload ${{needs.get_last_release.outputs.latest_release_tag}} ${{env.BINARY_NAME}}-${{needs.get_last_release.outputs.latest_release_tag}}-${{matrix.platform.target}}${{matrix.platform.zipext}} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index ea8c4bf..0e71e34 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.direnv/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 634b117..adaf6b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v0.15.1] - 2024-05-05 + +Release to bump hyprland-rs version due to Hyprland v0.40 socket path breaking change. + +### :memo: Documentation Changes +- [`47b6c47`](https://github.com/JakeStanger/ironbar/commit/47b6c477242ad52aae77a6820740d9c5f4bfc263) - **compiling**: add lua deps *(commit by [@JakeStanger](https://github.com/JakeStanger))* +- [`1971f3b`](https://github.com/JakeStanger/ironbar/commit/1971f3bb1ef3d059b29b99527e77ffaaf92240aa) - **volume**: update deprecated volume token *(PR [#567](https://github.com/JakeStanger/ironbar/pull/567) by [@drendog](https://github.com/drendog))* + + ## [v0.15.0] - 2024-04-28 ### :sparkles: New Features - [`f4384b6`](https://github.com/JakeStanger/ironbar/commit/f4384b6252e86d4e2558e1c36810d4ef903bd58c) - enable use of markup in clock module format and format_popup, and update documentation to reflect supporting Pango markup in both *commit by [@Dridus](https://github.com/Dridus))* @@ -565,3 +574,4 @@ It also requires `lua-lgi` as a runtime dependency. [v0.14.0]: https://github.com/JakeStanger/ironbar/compare/v0.13.0...v0.14.0 [v0.14.1]: https://github.com/JakeStanger/ironbar/compare/v0.14.0...v0.14.1 [v0.15.0]: https://github.com/JakeStanger/ironbar/compare/v0.14.3...v0.15.0 +[v0.15.1]: https://github.com/JakeStanger/ironbar/compare/v0.15.0...v0.15.1 diff --git a/Cargo.lock b/Cargo.lock index f2aa0fd..f709497 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,7 @@ dependencies = [ "cfg-if", "getrandom", "once_cell", + "serde", "version_check", ] @@ -434,6 +435,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chrono" version = "0.4.38" @@ -529,12 +536,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "core-foundation" version = "0.9.3" @@ -703,10 +704,8 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case", "proc-macro2", "quote 1.0.35", - "rustc_version", "syn 1.0.109", ] @@ -756,12 +755,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "downcast-rs" version = "1.2.0" @@ -1491,32 +1484,28 @@ dependencies = [ [[package]] name = "hyprland" -version = "0.3.13" +version = "0.4.0-alpha.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87a8f1cc065d451894dd3916c0bc3fcf9b67b276126c05f27b1db912688dde8" +checksum = "d627cd06fb3389f2554b7a4bb21db8c0bfca8863e6e653702cc4c6dbf20d8276" dependencies = [ - "async-trait", + "ahash", "derive_more", - "doc-comment", - "futures", - "hex", "hyprland-macros", - "lazy_static", "num-traits", + "once_cell", "paste", "regex", "serde", "serde_json", "serde_repr", - "strum", "tokio", ] [[package]] name = "hyprland-macros" -version = "0.3.4" +version = "0.4.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c941d3d52e979612af8cb94e8de49000c7fada2014a7791d173ab41339f4e4eb" +checksum = "5dd8ce4c182ce77e485918f49262425ee51a2746fe97f14084869aeff2fbc38e" dependencies = [ "quote 1.0.35", "syn 2.0.48", @@ -1626,7 +1615,7 @@ checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "ironbar" -version = "0.15.0" +version = "0.16.0-pre" dependencies = [ "cairo-rs", "cfg-if", @@ -1637,6 +1626,7 @@ dependencies = [ "dirs", "futures-lite 2.3.0", "futures-signals", + "futures-util", "glib", "gtk", "gtk-layer-shell", @@ -1647,7 +1637,7 @@ dependencies = [ "mlua", "mpd-utils", "mpris", - "nix 0.27.1", + "nix 0.28.0", "notify", "regex", "reqwest", @@ -1714,9 +1704,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libcorn" @@ -1790,16 +1780,6 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.17" @@ -1895,9 +1875,9 @@ dependencies = [ [[package]] name = "mlua" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9bed6bce296397a9d6a86f995dd10a547a4e6949825d45225906bdcbfe7367" +checksum = "e340c022072f3208a4105458286f4985ba5355bfe243c3073afe45cbe9ecf491" dependencies = [ "bstr", "mlua-sys", @@ -1908,9 +1888,9 @@ dependencies = [ [[package]] name = "mlua-sys" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2847b42764435201d8cbee1f517edb79c4cca4181877b90047587c89e1b7bce4" +checksum = "5552e7e4e22ada0463dfdeee6caf6dc057a189fdc83136408a8f950a5e5c5540" dependencies = [ "cc", "cfg-if", @@ -2009,6 +1989,18 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -2068,9 +2060,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -2203,34 +2195,11 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.4.1", - "smallvec", - "windows-targets 0.48.0", -] - [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "percent-encoding" @@ -2508,15 +2477,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_users" version = "0.4.3" @@ -2688,12 +2648,6 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" -[[package]] -name = "rustversion" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" - [[package]] name = "ryu" version = "1.0.13" @@ -2761,18 +2715,18 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.198" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote 1.0.35", @@ -2781,9 +2735,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -2972,28 +2926,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" -[[package]] -name = "strum" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote 1.0.35", - "rustversion", - "syn 2.0.48", -] - [[package]] name = "swayipc-async" version = "2.0.1" @@ -3204,7 +3136,6 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2 0.5.5", diff --git a/Cargo.toml b/Cargo.toml index 7a147c7..f22cb7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironbar" -version = "0.15.0" +version = "0.16.0-pre" edition = "2021" license = "MIT" description = "Customisable GTK Layer Shell wlroots/sway bar" @@ -99,7 +99,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.198", features = ["derive"] } +serde = { version = "1.0.202", features = ["derive"] } indexmap = "2.2.6" dirs = "5.0.1" walkdir = "2.5.0" @@ -117,18 +117,18 @@ cfg-if = "1.0.0" clap = { version = "4.5.4", optional = true, features = ["derive"] } # ipc -serde_json = { version = "1.0.116", optional = true } +serde_json = { version = "1.0.117", optional = true } # http reqwest = { version = "0.12.4", default_features = false, features = ["default-tls", "http2"], optional = true } # cairo lua-src = { version = "546.0.2", optional = true } -mlua = { version = "0.9.7", optional = true, features = ["luajit"] } +mlua = { version = "0.9.8", optional = true, features = ["luajit"] } cairo-rs = { version = "0.18.5", optional = true, features = ["png"] } # clipboard -nix = { version = "0.27.1", optional = true, features = ["event"] } +nix = { version = "0.28.0", optional = true, features = ["event", "fs"] } # clock chrono = { version = "0.4.38", optional = true, default_features = false, features = ["clock", "unstable-locales"] } @@ -154,7 +154,8 @@ libpulse-binding = { version = "2.28.1", optional = true } # workspaces swayipc-async = { version = "2.0.1", optional = true } -hyprland = { version = "0.3.13", default_features = false, features = ["listener", "tokio", "silent"], optional = true } +hyprland = { version = "0.4.0-alpha.2", features = ["silent"], optional = true } +futures-util = { version = "0.3.30", optional = true } # shared futures-lite = { version = "2.3.0", optional = true } # networkmanager, upower, workspaces diff --git a/README.md b/README.md index 5fb5ecf..67809c3 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,10 @@ Ironbar is designed to support anything from a lightweight bar to a full desktop [![Packaging status](https://repology.org/badge/vertical-allrepos/ironbar.svg)](https://repology.org/project/ironbar/versions) +Ironbar can be installed from source or using your preferred package manager. + +It is also recommended to install a [Nerd Font](https://www.nerdfonts.com/#home) for displaying symbols. + ### Cargo [crate](https://crates.io/crates/ironbar) diff --git a/docs/Compiling.md b/docs/Compiling.md index ed5d89d..6b1c9a7 100644 --- a/docs/Compiling.md +++ b/docs/Compiling.md @@ -9,6 +9,8 @@ cargo build --release install target/release/ironbar ~/.local/bin/ironbar ``` +It is also recommended to install a [Nerd Font](https://www.nerdfonts.com/#home) for displaying symbols. + ## Build requirements To build from source, you must have GTK (>= 3.22) and GTK Layer Shell installed. @@ -45,7 +47,7 @@ dnf install gtk3-devel gtk-layer-shell-devel # for http support dnf install openssl-devel # for volume support -dnf install libpulseaudio-devel +dnf install pulseaudio-libs-devel # for lua/cairo support dnf install luajit-devel lua-lgi ``` diff --git a/docs/Configuration guide.md b/docs/Configuration guide.md index 7caeaf5..5edf0b1 100644 --- a/docs/Configuration guide.md +++ b/docs/Configuration guide.md @@ -9,6 +9,8 @@ If you want to see some ready-to-go config files check the [examples folder](https://github.com/JakeStanger/ironbar/tree/master/examples) and the example pages in the sidebar. +The examples make use of [Nerd Fonts](https://www.nerdfonts.com/#home) for displaying symbols. + ## 1. Create config file The config file lives inside the `ironbar` directory in your XDG_CONFIG_DIR, which is usually `~/.config/ironbar`. diff --git a/docs/modules/Upower.md b/docs/modules/Upower.md index e5db9aa..620c3bd 100644 --- a/docs/modules/Upower.md +++ b/docs/modules/Upower.md @@ -84,11 +84,10 @@ and will be replaced with values from the current battery state: | Selector | Description | |---------------------------------|--------------------------------| -| `.upower` | Upower widget container. | -| `.upower .button` | Upower widget button. | -| `.upower .button .contents` | Upower widget button contents. | -| `.upower .button .icon` | Upower widget battery icon. | -| `.upower .button .label` | Upower widget button label. | +| `.upower` | Upower widget button. | +| `.upower .contents` | Upower widget button contents. | +| `.upower .icon` | Upower widget battery icon. | +| `.upower .label` | Upower widget button label. | | `.popup-upower` | Upower popup box. | | `.popup-upower .upower-details` | Label inside the popup. | diff --git a/examples/config.corn b/examples/config.corn index 69b3d5f..03416d2 100644 --- a/examples/config.corn +++ b/examples/config.corn @@ -55,7 +55,7 @@ let { interval.networks = 3 format = [ - " {cpu_percent}% | {temp_c:k10temp_Tccd1}°C" + " {cpu_percent}% | {temp_c:k10temp-Tccd1}°C" " {memory_used} / {memory_total} GB ({memory_percent}%)" "| {swap_used} / {swap_total} GB ({swap_percent}%)" "󰋊 {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)" @@ -81,7 +81,7 @@ let { $volume = { type = "volume" - format = "{icon} {volume}%" + format = "{icon} {percentage}%" max_volume = 100 icons.volume_high = "󰕾" icons.volume_medium = "󰖀" diff --git a/examples/config.json b/examples/config.json index 87b788b..a54dd12 100644 --- a/examples/config.json +++ b/examples/config.json @@ -63,7 +63,7 @@ "networks": 3 }, "format": [ - " {cpu_percent}% | {temp_c:k10temp_Tccd1}°C", + " {cpu_percent}% | {temp_c:k10temp-Tccd1}°C", " {memory_used} / {memory_total} GB ({memory_percent}%)", "| {swap_used} / {swap_total} GB ({swap_percent}%)", "󰋊 {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)", @@ -74,7 +74,7 @@ }, { "type": "volume", - "format": "{icon} {volume}%", + "format": "{icon} {percentage}%", "max_volume": 100, "icons": { "volume_high": "󰕾", diff --git a/examples/config.toml b/examples/config.toml index 830e858..540c053 100644 --- a/examples/config.toml +++ b/examples/config.toml @@ -53,7 +53,7 @@ interval = 500 [[end]] type = "sys_info" format = [ - " {cpu_percent}% | {temp_c:k10temp_Tccd1}°C", + " {cpu_percent}% | {temp_c:k10temp-Tccd1}°C", " {memory_used} / {memory_total} GB ({memory_percent}%)", "| {swap_used} / {swap_total} GB ({swap_percent}%)", "󰋊 {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)", @@ -71,7 +71,7 @@ networks = 3 [[end]] type = "volume" -format = "{icon} {volume}%" +format = "{icon} {percentage}%" max_volume = 100 [end.icons] diff --git a/examples/config.yaml b/examples/config.yaml index 7693414..e05c4a1 100644 --- a/examples/config.yaml +++ b/examples/config.yaml @@ -43,7 +43,7 @@ end: disks: 300 networks: 3 format: - -  {cpu_percent}% | {temp_c:k10temp_Tccd1}°C + -  {cpu_percent}% | {temp_c:k10temp-Tccd1}°C -  {memory_used} / {memory_total} GB ({memory_percent}%) - '| {swap_used} / {swap_total} GB ({swap_percent}%)' - 󰋊 {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%) @@ -51,7 +51,7 @@ end: - 󰖡 {load_average:1} | {load_average:5} | {load_average:15} - 󰥔 {uptime} - type: volume - format: '{icon} {volume}%' + format: '{icon} {percentage}%' max_volume: 100 icons: volume_high: 󰕾 diff --git a/examples/test.corn b/examples/test.corn new file mode 100644 index 0000000..219e303 --- /dev/null +++ b/examples/test.corn @@ -0,0 +1,60 @@ +let { + $config_dir = "/home/jake/.config/ironbar" + + $workspaces = { type = "workspaces" } + $launcher = { type = "launcher" } + $volume = { + type = "volume" + format = "{icon} {percentage}%" + max_volume = 100 + icons.volume_high = "󰕾" + icons.volume_medium = "󰖀" + icons.volume_low = "󰕿" + icons.muted = "󰝟" + } + $network_manager = { type = "networkmanager" } + $clock = { + type = "clock" + // disable_popup = true + // format = "%d/%m/%Y %H:%M:%S" + } + $tray = { type = "tray" prefer_theme_icons = false } + + // $label = { type = "label" label = "hello" } + $label = { type = "label" label = "#random" } + $clipboard = { type = "clipboard" } + + $notifications = { + type = "notifications" + show_count = true + + icons.closed_none = "󰍥" + icons.closed_some = "󱥂" + icons.closed_dnd = "󱅯" + icons.open_none = "󰍡" + icons.open_some = "󱥁" + icons.open_dnd = "󱅮" + } + + $focused = { type = "focused" } + + $cairo = { type = "cairo" path = "$config_dir/clock.lua" frequency = 50 width = 300 height = 300 } + + $custom = { + type = "custom" + bar = [ { type = "button" on_click = "popup:toggle" widgets = [ $focused ] } ] + popup = [ { type = "box" orientation = "v" widgets = [ $clock $cairo ] } ] + } + + $mpris = { type = "music" } +} in { + // ironvar_defaults.color = "red" + + position = "bottom" + + icon_theme = "Paper" + + start = [ $workspaces $label ] + center = [ $custom ] + end = [ $notifications $clock ] +} \ No newline at end of file diff --git a/flake.lock b/flake.lock index 3a3b651..a1d9b2d 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1711681752, - "narHash": "sha256-LEg6/dmEFxx6Ygti5DO9MOhGNpyB7zdxdWtzv/FCTXk=", + "lastModified": 1713979152, + "narHash": "sha256-apdecPuh8SOQnkEET/kW/UcfjCRb8JbV5BKjoH+DcP4=", "owner": "ipetkov", "repo": "crane", - "rev": "ada0fb4dcce4561acb1eb17c59b7306d9d4a95f3", + "rev": "a5eca68a2cf11adb32787fc141cddd29ac8eb79c", "type": "github" }, "original": { @@ -43,11 +43,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1698420672, - "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", + "lastModified": 1713520724, + "narHash": "sha256-CO8MmVDmqZX2FovL75pu5BvwhW+Vugc7Q6ze7Hj8heI=", "owner": "nix-community", "repo": "naersk", - "rev": "aeb58d5e8faead8980a807c840232697982d47b9", + "rev": "c5037590290c6c7dae2e42e7da1e247e54ed2d49", "type": "github" }, "original": { @@ -58,11 +58,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1711715736, - "narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=", + "lastModified": 1714314149, + "narHash": "sha256-yNAevSKF4krRWacmLUsLK7D7PlfuY3zF0lYnGYNi9vQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "807c549feabce7eddbf259dbdcec9e0600a0660d", + "rev": "cf8cc1201be8bc71b7cbbbdaf349b22f4f99c7ae", "type": "github" }, "original": { @@ -72,11 +72,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1711703276, - "narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=", + "lastModified": 1714253743, + "narHash": "sha256-mdTQw2XlariysyScCv2tTE45QSU9v/ezLcHJ22f0Nxc=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d8fe5e6c92d0d190646fb9f1056741a229980089", + "rev": "58a1abdbae3217ca6b702f03d3b35125d88a2994", "type": "github" }, "original": { @@ -102,11 +102,11 @@ ] }, "locked": { - "lastModified": 1711851236, - "narHash": "sha256-EJ03x3N9ihhonAttkaCrqxb0djDq3URCuDpmVPbNZhA=", + "lastModified": 1714443211, + "narHash": "sha256-lKTA3XqRo4aVgkyTSCtpcALpGXdmkilHTtN00eRg0QU=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "f258266af947599e8069df1c2e933189270f143a", + "rev": "ce35c36f58f82cee6ec959e0d44c587d64281b6f", "type": "github" }, "original": { diff --git a/src/clients/compositor/hyprland.rs b/src/clients/compositor/hyprland.rs index 56184eb..ee526ea 100644 --- a/src/clients/compositor/hyprland.rs +++ b/src/clients/compositor/hyprland.rs @@ -149,12 +149,27 @@ impl Client { } { - event_listener.add_workspace_destroy_handler(move |workspace_type| { - let _lock = lock!(lock); - debug!("Received workspace destroy: {workspace_type:?}"); + let tx = tx.clone(); + let lock = lock.clone(); - let name = get_workspace_name(workspace_type); - send!(tx, WorkspaceUpdate::Remove(name)); + event_listener.add_workspace_rename_handler(move |data| { + let _lock = lock!(lock); + + send!( + tx, + WorkspaceUpdate::Rename { + id: data.workspace_id as i64, + name: data.workspace_name + } + ); + }); + } + + { + event_listener.add_workspace_destroy_handler(move |data| { + let _lock = lock!(lock); + debug!("Received workspace destroy: {data:?}"); + send!(tx, WorkspaceUpdate::Remove(data.workspace_id as i64)); }); } @@ -186,6 +201,7 @@ impl Client { fn get_workspace(name: &str, active: Option<&Workspace>) -> Option { Workspaces::get() .expect("Failed to get workspaces") + .into_iter() .find_map(|w| { if w.name == name { let vis = Visibility::from((&w, active.map(|w| w.name.as_ref()), &|w| { @@ -228,6 +244,7 @@ impl WorkspaceClient for Client { let workspaces = Workspaces::get() .expect("Failed to get workspaces") + .into_iter() .map(|w| { let vis = Visibility::from((&w, active_id.as_deref(), &is_visible)); @@ -262,7 +279,7 @@ fn create_is_visible() -> impl Fn(&HWorkspace) -> bool { impl From<(Visibility, HWorkspace)> for Workspace { fn from((visibility, workspace): (Visibility, HWorkspace)) -> Self { Self { - id: workspace.id.to_string(), + id: workspace.id as i64, name: workspace.name, monitor: workspace.monitor, visibility, diff --git a/src/clients/compositor/mod.rs b/src/clients/compositor/mod.rs index 0371913..051a7b8 100644 --- a/src/clients/compositor/mod.rs +++ b/src/clients/compositor/mod.rs @@ -74,7 +74,7 @@ impl Compositor { #[derive(Debug, Clone)] pub struct Workspace { /// Unique identifier - pub id: String, + pub id: i64, /// Workspace friendly name pub name: String, /// Name of the monitor (output) the workspace is located on @@ -119,13 +119,19 @@ pub enum WorkspaceUpdate { /// This is re-sent to all subscribers when a new subscription is created. Init(Vec), Add(Workspace), - Remove(String), + Remove(i64), Move(Workspace), /// Declares focus moved from the old workspace to the new. Focus { old: Option, new: Workspace, }, + + Rename { + id: i64, + name: String, + }, + /// An update was triggered by the compositor but this was not mapped by Ironbar. /// /// This is purely used for ergonomics within the compositor clients diff --git a/src/clients/compositor/sway.rs b/src/clients/compositor/sway.rs index 28e8f7f..b3f4197 100644 --- a/src/clients/compositor/sway.rs +++ b/src/clients/compositor/sway.rs @@ -90,7 +90,7 @@ impl From for Workspace { let visibility = Visibility::from(&node); Self { - id: node.id.to_string(), + id: node.id, name: node.name.unwrap_or_default(), monitor: node.output.unwrap_or_default(), visibility, @@ -103,7 +103,7 @@ impl From for Workspace { let visibility = Visibility::from(&workspace); Self { - id: workspace.id.to_string(), + id: workspace.id, name: workspace.name, monitor: workspace.output, visibility, @@ -141,13 +141,9 @@ impl From for WorkspaceUpdate { WorkspaceChange::Init => { Self::Add(event.current.expect("Missing current workspace").into()) } - WorkspaceChange::Empty => Self::Remove( - event - .current - .expect("Missing current workspace") - .name - .unwrap_or_default(), - ), + WorkspaceChange::Empty => { + Self::Remove(event.current.expect("Missing current workspace").id) + } WorkspaceChange::Focus => Self::Focus { old: event.old.map(Workspace::from), new: Workspace::from(event.current.expect("Missing current workspace")), diff --git a/src/clients/wayland/wlr_data_control/mod.rs b/src/clients/wayland/wlr_data_control/mod.rs index 1cf76a5..dbe5f8e 100644 --- a/src/clients/wayland/wlr_data_control/mod.rs +++ b/src/clients/wayland/wlr_data_control/mod.rs @@ -11,7 +11,7 @@ use crate::{lock, try_send, Ironbar}; use device::DataControlDevice; use glib::Bytes; use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ}; -use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags}; +use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags, EpollTimeout}; use smithay_client_toolkit::data_device_manager::WritePipe; use smithay_client_toolkit::reexports::calloop::{PostAction, RegistrationToken}; use std::cmp::min; @@ -274,7 +274,7 @@ impl DataControlDeviceHandler for Environment { Ok(token) => { cur_offer.token.replace(token); } - Err(err) => error!("{err:?}"), + Err(err) => error!("Failed to insert read pipe event: {err:?}"), } } } @@ -294,15 +294,15 @@ impl DataControlOfferHandler for Environment { } impl DataControlSourceHandler for Environment { - fn accept_mime( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - _source: &ZwlrDataControlSourceV1, - mime: Option, - ) { - debug!("Accepted mime type: {mime:?}"); - } + // fn accept_mime( + // &mut self, + // _conn: &Connection, + // _qh: &QueueHandle, + // _source: &ZwlrDataControlSourceV1, + // mime: Option, + // ) { + // debug!("Accepted mime type: {mime:?}"); + // } /// Writes the current clipboard item to 'paste' it /// upon request from a compositor client. @@ -349,11 +349,12 @@ impl DataControlSourceHandler for Environment { .add(fd, epoll_event) .expect("to send valid epoll operation"); + let timeout = EpollTimeout::from(100u16); while !bytes.is_empty() { let chunk = &bytes[..min(pipe_size as usize, bytes.len())]; epoll_fd - .wait(&mut events, 100) + .wait(&mut events, timeout) .expect("Failed to wait to epoll"); match file.write(chunk) { diff --git a/src/clients/wayland/wlr_data_control/offer.rs b/src/clients/wayland/wlr_data_control/offer.rs index a078ade..73f06f6 100644 --- a/src/clients/wayland/wlr_data_control/offer.rs +++ b/src/clients/wayland/wlr_data_control/offer.rs @@ -5,7 +5,7 @@ use nix::unistd::{close, pipe2}; use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError; use smithay_client_toolkit::data_device_manager::ReadPipe; use std::ops::DerefMut; -use std::os::fd::{BorrowedFd, FromRawFd}; +use std::os::fd::{AsFd, AsRawFd}; use std::sync::{Arc, Mutex}; use tracing::{trace, warn}; use wayland_client::{Connection, Dispatch, Proxy, QueueHandle}; @@ -176,11 +176,11 @@ pub unsafe fn receive( // create a pipe let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?; - offer.receive(mime_type, BorrowedFd::borrow_raw(writefd)); + offer.receive(mime_type, writefd.as_fd()); - if let Err(err) = close(writefd) { + if let Err(err) = close(writefd.as_raw_fd()) { warn!("Failed to close write pipe: {}", err); } - Ok(FromRawFd::from_raw_fd(readfd)) + Ok(ReadPipe::from(readfd)) } diff --git a/src/clients/wayland/wlr_data_control/source.rs b/src/clients/wayland/wlr_data_control/source.rs index a184d8d..b8f2a1c 100644 --- a/src/clients/wayland/wlr_data_control/source.rs +++ b/src/clients/wayland/wlr_data_control/source.rs @@ -10,13 +10,13 @@ use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1 pub struct DataControlSourceData {} pub trait DataControlSourceDataExt: Send + Sync { - fn data_source_data(&self) -> &DataControlSourceData; + // fn data_source_data(&self) -> &DataControlSourceData; } impl DataControlSourceDataExt for DataControlSourceData { - fn data_source_data(&self) -> &DataControlSourceData { - self - } + // fn data_source_data(&self) -> &DataControlSourceData { + // self + // } } /// Handler trait for `DataSource` events. @@ -24,13 +24,13 @@ impl DataControlSourceDataExt for DataControlSourceData { /// The functions defined in this trait are called as `DataSource` events are received from the compositor. pub trait DataControlSourceHandler: Sized { /// This may be called multiple times, once for each accepted mime type from the destination, if any. - fn accept_mime( - &mut self, - conn: &Connection, - qh: &QueueHandle, - source: &ZwlrDataControlSourceV1, - mime: Option, - ); + // fn accept_mime( + // &mut self, + // conn: &Connection, + // qh: &QueueHandle, + // source: &ZwlrDataControlSourceV1, + // mime: Option, + // ); /// The client has requested the data for this source to be sent. /// Send the data, then close the fd. diff --git a/src/clients/wayland/wlr_foreign_toplevel/mod.rs b/src/clients/wayland/wlr_foreign_toplevel/mod.rs index d0c3d9e..48032dc 100644 --- a/src/clients/wayland/wlr_foreign_toplevel/mod.rs +++ b/src/clients/wayland/wlr_foreign_toplevel/mod.rs @@ -77,7 +77,6 @@ impl ToplevelHandleHandler for Environment { match handle.info() { Some(info) => { trace!("Updating handle: {info:?}"); - self.handles.push(handle.clone()); if let Some(info) = handle.info() { try_send!(self.event_tx, Event::Toplevel(ToplevelEvent::Update(info))); } diff --git a/src/config/common.rs b/src/config/common.rs index 25a8944..d1446e2 100644 --- a/src/config/common.rs +++ b/src/config/common.rs @@ -7,26 +7,153 @@ use gtk::{EventBox, Orientation, Revealer, RevealerTransitionType}; use serde::Deserialize; use tracing::trace; -/// Common configuration options -/// which can be set on every module. +/// The following are module-level options which are present on **all** modules. +/// +/// Each module also provides options specific to its type. +/// For details on those, check the relevant module documentation. +/// +/// For information on the Script type, and embedding scripts in strings, +/// see [here](script). +/// For information on styling, please see the [styling guide](styling-guide). #[derive(Debug, Default, Deserialize, Clone)] pub struct CommonConfig { - pub class: Option, + /// Sets the unique widget name, + /// allowing you to target it in CSS using `#name`. + /// + /// It is best practise (although not required) to ensure that the value is + /// globally unique throughout the Ironbar instance + /// to avoid clashes. + /// + /// **Default**: `null` pub name: Option, + /// Sets one or more CSS classes, + /// allowing you to target it in CSS using `.class`. + /// + /// Unlike [name](#name), the `class` property is not expected to be unique. + /// + /// **Default**: `null` + pub class: Option, + + /// Shows this text on hover. + /// Supports embedding scripts between `{{double braces}}`. + /// + /// Note that full dynamic string support is not currently supported. + /// + /// **Default**: `null` + pub tooltip: Option, + + /// Shows the module only if the dynamic boolean evaluates to true. + /// + /// This allows for modules to be dynamically shown or hidden + /// based on custom events. + /// + /// **Default**: `null` pub show_if: Option, + + /// The transition animation to use when showing/hiding the widget. + /// + /// Note this has no effect if `show_if` is not configured. + /// + /// **Valid options**: `slide_start`, `slide_end`, `crossfade`, `none` + ///
+ /// **Default**: `slide_start` pub transition_type: Option, + + /// The length in milliseconds + /// of the transition animation to use when showing/hiding the widget. + /// + /// Note this has no effect if `show_if` is not configured. + /// + /// **Default**: `250` pub transition_duration: Option, + /// A [script](scripts) to run when the module is left-clicked. + /// + /// **Supported script types**: `oneshot`. + ///
+ /// **Default**: `null` + /// + /// # Example + /// + /// ```corn + /// { on_click_left = "echo 'event' >> log.txt" } + /// ``` pub on_click_left: Option, + + /// A [script](scripts) to run when the module is right-clicked. + /// + /// **Supported script types**: `oneshot`. + ///
+ /// **Default**: `null` + /// /// # Example + /// + /// ```corn + /// { on_click_right = "echo 'event' >> log.txt" } + /// ``` pub on_click_right: Option, + + /// A [script](scripts) to run when the module is middle-clicked. + /// + /// **Supported script types**: `oneshot`. + ///
+ /// **Default**: `null` + /// # Example + /// + /// ```corn + /// { on_click_middle = "echo 'event' >> log.txt" } + /// ``` pub on_click_middle: Option, + + /// A [script](scripts) to run when the module is scrolled up on. + /// + /// **Supported script types**: `oneshot`. + ///
+ /// **Default**: `null` + /// # Example + /// + /// ```corn + /// { on_scroll_up = "echo 'event' >> log.txt" } + /// ``` pub on_scroll_up: Option, + + /// A [script](scripts) to run when the module is scrolled down on. + /// + /// **Supported script types**: `oneshot`. + ///
+ /// **Default**: `null` + /// # Example + /// + /// ```corn + /// { on_scroll_down = "echo 'event' >> log.txt" } + /// ``` pub on_scroll_down: Option, + + /// A [script](scripts) to run when the cursor begins hovering over the module. + /// + /// **Supported script types**: `oneshot`. + ///
+ /// **Default**: `null` + /// # Example + /// + /// ```corn + /// { on_mouse_enter = "echo 'event' >> log.txt" } + /// ``` pub on_mouse_enter: Option, + + /// A [script](scripts) to run when the cursor stops hovering over the module. + /// + /// **Supported script types**: `oneshot`. + ///
+ /// **Default**: `null` + /// # Example + /// + /// ```corn + /// { on_mouse_exit = "echo 'event' >> log.txt" } + /// ``` pub on_mouse_exit: Option, - pub tooltip: Option, + /// Prevents the popup from opening on-click for this widget. #[serde(default)] pub disable_popup: bool, } diff --git a/src/config/mod.rs b/src/config/mod.rs index 03c4daf..852fc7d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -122,12 +122,6 @@ impl ModuleConfig { } } -#[derive(Debug, Deserialize, Clone)] -pub enum BarEntryConfig { - Single(BarConfig), - Monitors(HashMap), -} - #[derive(Debug, Clone)] pub enum MonitorConfig { Single(BarConfig), @@ -161,32 +155,107 @@ pub struct MarginConfig { pub top: i32, } +/// The following is a list of all top-level bar config options. +/// +/// These options can either be written at the very top object of your config, +/// or within an object in the [monitors](#monitors) config, +/// depending on your [use-case](#2-pick-your-use-case). +/// #[derive(Debug, Deserialize, Clone)] pub struct BarConfig { - #[serde(default)] - pub position: BarPosition, - #[serde(default = "default_true")] - pub anchor_to_edges: bool, - #[serde(default = "default_bar_height")] - pub height: i32, - #[serde(default)] - pub margin: MarginConfig, + /// A unique identifier for the bar, used for controlling it over IPC. + /// If not set, uses a generated integer suffix. + /// + /// **Default**: `bar-n` pub name: Option, + /// The bar's position on screen. + /// + /// **Valid options**: `top`, `bottom`, `left`, `right` + ///
+ /// **Default**: `bottom` + #[serde(default)] + pub position: BarPosition, + + /// Whether to anchor the bar to the edges of the screen. + /// Setting to false centers the bar. + /// + /// **Default**: `true` + #[serde(default = "default_true")] + pub anchor_to_edges: bool, + + /// The bar's height in pixels. + /// + /// Note that GTK treats this as a target minimum, + /// and if content inside the bar is over this, + /// it will automatically expand to fit. + /// + /// **Default**: `42` + #[serde(default = "default_bar_height")] + pub height: i32, + + /// The margin to use on each side of the bar, in pixels. + /// Object which takes `top`, `bottom`, `left` and `right` keys. + /// + /// **Default**: `0` on all sides. + /// + /// # Example + /// + /// The following would set a 10px margin around each edge. + /// + /// ```corn + /// { + /// margin.top = 10 + /// margin.bottom = 10 + /// margin.left = 10 + /// margin.right = 10 + /// } + /// ``` + #[serde(default)] + pub margin: MarginConfig, + + /// The size of the gap in pixels + /// between the bar and the popup window. + /// + /// **Default**: `5` + #[serde(default = "default_popup_gap")] + pub popup_gap: i32, + + /// Whether the bar should be hidden when Ironbar starts. + /// + /// **Default**: `false`, unless `autohide` is set. #[serde(default)] pub start_hidden: Option, + + /// The duration in milliseconds before the bar is hidden after the cursor leaves. + /// Leave unset to disable auto-hide behaviour. + /// + /// **Default**: `null` #[serde(default)] pub autohide: Option, - /// GTK icon theme to use. + /// The name of the GTK icon theme to use. + /// Leave unset to use the default Adwaita theme. + /// + /// **Default**: `null` pub icon_theme: Option, + /// An array of modules to append to the start of the bar. + /// Depending on the orientation, this is either the top of the left edge. + /// + /// **Default**: `[]` pub start: Option>, - pub center: Option>, - pub end: Option>, - #[serde(default = "default_popup_gap")] - pub popup_gap: i32, + /// An array of modules to append to the center of the bar. + /// + /// **Default**: `[]` + pub center: Option>, + + /// An array of modules to append to the end of the bar. + /// Depending on the orientation, this is either the bottom or right edge. + /// + /// **Default**: `[]` + pub end: Option>, } impl Default for BarConfig { @@ -230,10 +299,41 @@ impl Default for BarConfig { #[derive(Debug, Deserialize, Clone, Default)] pub struct Config { + /// A map of [ironvar](ironvar) keys and values + /// to initialize Ironbar with on startup. + /// + /// **Default**: `{}` + /// + /// # Example + /// + /// The following initializes an ironvar called `foo` set to `bar` on startup: + /// + /// ```corn + /// { ironvar_defaults.foo = "bar" } + /// ``` + /// + /// The variable can then be immediately fetched without needing to be manually set: + /// + /// ```sh + /// $ ironbar get foo + /// ok + /// bar + /// ``` pub ironvar_defaults: Option, String>>, + /// The configuration for the bar. + /// Setting through this will enable a single identical bar on each monitor. #[serde(flatten)] pub bar: BarConfig, + + /// A map of monitor names to configs. + /// + /// The config values can be either: + /// + /// - a single object, which denotes a single bar for that monitor, + /// - an array of multiple objects, which denotes multiple for that monitor. + /// + /// Providing this option overrides the single, global `bar` option. pub monitors: Option>, } diff --git a/src/config/truncate.rs b/src/config/truncate.rs index fed4ffc..87451e4 100644 --- a/src/config/truncate.rs +++ b/src/config/truncate.rs @@ -20,13 +20,68 @@ impl From for GtkEllipsizeMode { } } +/// Some modules provide options for truncating text. +/// This is controlled using a common `TruncateMode` type, +/// which is defined below. +/// +/// The option can be configured in one of two modes. +/// #[derive(Debug, Deserialize, Clone, Copy)] #[serde(untagged)] pub enum TruncateMode { + /// Auto mode lets GTK decide when to ellipsize. + /// + /// To use this mode, set the truncate option to a string + /// declaring the location to truncate text from and place the ellipsis. + /// + /// # Example + /// + /// ```corn + /// { truncate = "start" } + /// ``` + /// + /// **Valid options**: `start`, `middle`, `end` + ///
+ /// **Default**: `null` Auto(EllipsizeMode), + + /// Length mode defines a fixed point at which to ellipsize. + /// + /// Generally you will want to set only one of `length` or `max_length`, + /// but you can set both if required. + /// + /// # Example + /// + /// ```corn + /// { + /// truncate.mode = "start" + /// truncate.length = 50 + /// truncate.max_length = 70 + /// } + /// ``` Length { + /// The location to truncate text from and place the ellipsis. + /// **Valid options**: `start`, `middle`, `end` + ///
+ /// **Default**: `null` mode: EllipsizeMode, + + /// The fixed width (in characters) of the widget. + /// + /// The widget will be expanded to this width + /// if it would have otherwise been smaller. + /// + /// Leave unset to let GTK automatically handle. + /// + /// **Default**: `null` length: Option, + + /// The maximum number of characters to show + /// before truncating. + /// + /// Leave unset to let GTK automatically handle. + /// + /// **Default**: `null` max_length: Option, }, } diff --git a/src/image/gtk.rs b/src/image/gtk.rs index 8ed38a3..bf80bcd 100644 --- a/src/image/gtk.rs +++ b/src/image/gtk.rs @@ -44,7 +44,7 @@ pub fn new_icon_label(input: &str, icon_theme: &IconTheme, size: i32) -> gtk::Bo ImageProvider::parse(input, icon_theme, false, size) .map(|provider| provider.load_into_image(image)); } else { - let label = Label::new(Some(input)); + let label = Label::builder().use_markup(true).label(input).build(); label.add_class("icon"); label.add_class("text-icon"); diff --git a/src/ironvar.rs b/src/ironvar.rs index e99a3b0..190fea2 100644 --- a/src/ironvar.rs +++ b/src/ironvar.rs @@ -93,7 +93,7 @@ impl IronVar { /// Sets the current variable value. /// The change is broadcast to all receivers. fn set(&mut self, value: Option) { - self.value = value.clone(); + self.value.clone_from(&value); send!(self.tx, value); } diff --git a/src/main.rs b/src/main.rs index 0df08ea..441c0e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use std::rc::Rc; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; #[cfg(feature = "ipc")] use std::sync::RwLock; -use std::sync::{mpsc, Arc, OnceLock}; +use std::sync::{mpsc, Arc, Mutex, OnceLock}; use cfg_if::cfg_if; #[cfg(feature = "cli")] @@ -339,17 +339,36 @@ fn load_output_bars( app: &Application, output: &OutputInfo, ) -> Result> { + // Hack to track monitor positions due to new GTK3/wlroots bug: + // https://github.com/swaywm/sway/issues/8164 + // This relies on Wayland always tracking monitors in the same order as GDK. + // We also need this static to ensure hot-reloading continues to work as best we can. + static INDEX_MAP: OnceLock>> = OnceLock::new(); + let Some(monitor_name) = &output.name else { return Err(Report::msg("Output missing monitor name")); }; + let map = INDEX_MAP.get_or_init(|| Mutex::new(vec![])); + + let index = lock!(map).iter().position(|n| n == monitor_name); + let index = match index { + Some(index) => index, + None => { + lock!(map).push(monitor_name.clone()); + lock!(map).len() - 1 + } + }; + let config = ironbar.config.borrow(); let display = get_display(); - let pos = output.logical_position.unwrap_or_default(); - let monitor = display - .monitor_at_point(pos.0, pos.1) - .expect("monitor to exist"); + // let pos = output.logical_position.unwrap_or_default(); + // let monitor = display + // .monitor_at_point(pos.0, pos.1) + // .expect("monitor to exist"); + + let monitor = display.monitor(index as i32).expect("monitor to exist"); let show_default_bar = config.bar.start.is_some() || config.bar.center.is_some() || config.bar.end.is_some(); diff --git a/src/modules/cairo.rs b/src/modules/cairo.rs index 9e21aec..3ce42bb 100644 --- a/src/modules/cairo.rs +++ b/src/modules/cairo.rs @@ -19,16 +19,33 @@ use tracing::{debug, error}; #[derive(Debug, Clone, Deserialize)] pub struct CairoModule { + /// The path to the Lua script to load. + /// This can be absolute, or relative to the working directory. + /// + /// The script must contain the entry `draw` function. + /// + /// **Required** path: PathBuf, + /// The number of milliseconds between each draw call. + /// + /// **Default**: `200` #[serde(default = "default_frequency")] frequency: u64, + /// The canvas width in pixels. + /// + /// **Default**: `42` #[serde(default = "default_size")] width: u32, + + /// The canvas height in pixels. + /// + /// **Default**: `42` #[serde(default = "default_size")] height: u32, + /// See [common options](module-level-options#common-options). #[serde(flatten)] pub common: Option, } diff --git a/src/modules/clipboard.rs b/src/modules/clipboard.rs index ae34a03..6365088 100644 --- a/src/modules/clipboard.rs +++ b/src/modules/clipboard.rs @@ -18,18 +18,34 @@ use tracing::{debug, error}; #[derive(Debug, Deserialize, Clone)] pub struct ClipboardModule { + /// The icon to show on the bar widget button. + /// Supports [image](images) icons. + /// + /// **Default**: `󰨸` #[serde(default = "default_icon")] icon: String, + /// The size to render the icon at. + /// Note this only applies to image-type icons. + /// + /// **Default**: `32` #[serde(default = "default_icon_size")] icon_size: i32, + /// The maximum number of items to keep in the history, + /// and to show in the popup. + /// + /// **Default**: `10` #[serde(default = "default_max_items")] max_items: usize, // -- Common -- + /// See [truncate options](module-level-options#truncate-mode). + /// + /// **Default**: `null` truncate: Option, + /// See [common options](module-level-options#common-options). #[serde(flatten)] pub common: Option, } diff --git a/src/modules/clock.rs b/src/modules/clock.rs index 602c6e4..a5be924 100644 --- a/src/modules/clock.rs +++ b/src/modules/clock.rs @@ -17,23 +17,47 @@ use crate::{glib_recv, module_impl, send_async, spawn, try_send}; #[derive(Debug, Deserialize, Clone)] pub struct ClockModule { - /// Date/time format string. - /// Default: `%d/%m/%Y %H:%M` + /// The format string to use for the date/time shown on the bar. + /// Pango markup is supported. /// /// Detail on available tokens can be found here: /// + /// + /// **Default**: `%d/%m/%Y %H:%M` #[serde(default = "default_format")] format: String, + /// The format string to use for the date/time shown in the popup header. + /// Pango markup is supported. + /// + /// Detail on available tokens can be found here: + /// + /// + /// **Default**: `%H:%M:%S` #[serde(default = "default_popup_format")] format_popup: String, + /// The locale to use when formatting dates. + /// + /// Note this will not control the calendar - + /// for that you must set `LC_TIME`. + /// + /// **Valid options**: See [here](https://docs.rs/pure-rust-locales/0.8.1/pure_rust_locales/enum.Locale.html#variants) + ///
+ /// **Default**: `$LC_TIME` or `$LANG` or `'POSIX'` #[serde(default = "default_locale")] locale: String, + /// The orientation to display the widget contents. + /// Setting to vertical will rotate text 90 degrees. + /// + /// **Valid options**: `horizontal`, `vertical` + ///
+ /// **Default**: `horizontal` #[serde(default)] orientation: ModuleOrientation, + /// See [common options](module-level-options#common-options). #[serde(flatten)] pub common: Option, } diff --git a/src/modules/custom/box.rs b/src/modules/custom/box.rs index dca848a..7e448f4 100644 --- a/src/modules/custom/box.rs +++ b/src/modules/custom/box.rs @@ -7,9 +7,26 @@ use serde::Deserialize; #[derive(Debug, Deserialize, Clone)] pub struct BoxWidget { + /// Widget name. + /// + /// **Default**: `null` name: Option, + + /// Widget class name. + /// + /// **Default**: `null` class: Option, + + /// Whether child widgets should be horizontally or vertically added. + /// + /// **Valid options**: `horizontal`, `vertical`, `h`, `v` + ///
+ /// **Default**: `horizontal` orientation: Option, + + /// Modules and widgets to add to this box. + /// + /// **Default**: `null` widgets: Option>, } diff --git a/src/modules/custom/button.rs b/src/modules/custom/button.rs index 199420d..9382da2 100644 --- a/src/modules/custom/button.rs +++ b/src/modules/custom/button.rs @@ -11,13 +11,43 @@ use super::{CustomWidget, CustomWidgetContext, ExecEvent, WidgetConfig}; #[derive(Debug, Deserialize, Clone)] pub struct ButtonWidget { + /// Widget name. + /// + /// **Default**: `null` name: Option, + + /// Widget class name. + /// + /// **Default**: `null` class: Option, + + /// Widget text label. Pango markup and embedded scripts are supported. + /// + /// This is a shorthand for adding a label widget to the button. + /// Ignored if `widgets` is set. + /// + /// This is a [Dynamic String](dynamic-values#dynamic-string). + /// + /// **Default**: `null` label: Option, + + /// Command to execute. More on this [below](#commands). + /// + /// **Default**: `null` on_click: Option, - widgets: Option>, + + /// Orientation of the button. + /// + /// **Valid options**: `horizontal`, `vertical`, `h`, `v` + ///
+ /// **Default**: `horizontal` #[serde(default)] orientation: ModuleOrientation, + + /// Modules and widgets to add to this box. + /// + /// **Default**: `null` + widgets: Option>, } impl CustomWidget for ButtonWidget { diff --git a/src/modules/custom/image.rs b/src/modules/custom/image.rs index e5d099d..fffac2d 100644 --- a/src/modules/custom/image.rs +++ b/src/modules/custom/image.rs @@ -10,9 +10,27 @@ use super::{CustomWidget, CustomWidgetContext}; #[derive(Debug, Deserialize, Clone)] pub struct ImageWidget { + /// Widget name. + /// + /// **Default**: `null` name: Option, + + /// Widget class name. + /// + /// **Default**: `null` class: Option, + + /// Image source. + /// + /// This is an [image](image) via [Dynamic String](dynamic-values#dynamic-string). + /// + /// **Required** src: String, + + /// The width/height of the image. + /// Aspect ratio is preserved. + /// + /// **Default**: `32` #[serde(default = "default_size")] size: i32, } diff --git a/src/modules/custom/label.rs b/src/modules/custom/label.rs index ca551c8..65f303b 100644 --- a/src/modules/custom/label.rs +++ b/src/modules/custom/label.rs @@ -10,9 +10,29 @@ use super::{CustomWidget, CustomWidgetContext}; #[derive(Debug, Deserialize, Clone)] pub struct LabelWidget { + /// Widget name. + /// + /// **Default**: `null` name: Option, + + /// Widget class name. + /// + /// **Default**: `null` class: Option, + + /// Widget text label. Pango markup and embedded scripts are supported. + /// + /// This is a [Dynamic String](dynamic-values#dynamic-string). + /// + /// **Required** label: String, + + /// Orientation of the label. + /// Setting to vertical will rotate text 90 degrees. + /// + /// **Valid options**: `horizontal`, `vertical`, `h`, `v` + ///
+ /// **Default**: `horizontal` #[serde(default)] orientation: ModuleOrientation, } @@ -24,7 +44,6 @@ impl CustomWidget for LabelWidget { let label = build!(self, Self::Widget); label.set_angle(self.orientation.to_angle()); - label.set_use_markup(true); { diff --git a/src/modules/custom/mod.rs b/src/modules/custom/mod.rs index dc54e83..5cffb00 100644 --- a/src/modules/custom/mod.rs +++ b/src/modules/custom/mod.rs @@ -29,19 +29,28 @@ use tracing::{debug, error}; #[derive(Debug, Deserialize, Clone)] pub struct CustomModule { - /// Widgets to add to the bar container + /// Modules and widgets to add to the bar container. + /// + /// **Default**: `[]` bar: Vec, - /// Widgets to add to the popup container + + /// Modules and widgets to add to the popup container. + /// + /// **Default**: `null` popup: Option>, + /// See [common options](module-level-options#common-options). #[serde(flatten)] pub common: Option, } #[derive(Debug, Deserialize, Clone)] pub struct WidgetConfig { + /// One of a custom module native Ironbar module. #[serde(flatten)] widget: WidgetOrModule, + + /// See [common options](module-level-options#common-options). #[serde(flatten)] common: CommonConfig, } @@ -49,18 +58,27 @@ pub struct WidgetConfig { #[derive(Debug, Deserialize, Clone)] #[serde(untagged)] pub enum WidgetOrModule { + /// A custom-module specific basic widget Widget(Widget), + /// A native Ironbar module, such as `clock` or `focused`. + /// All widgets are supported, including their popups. Module(ModuleConfig), } #[derive(Debug, Deserialize, Clone)] #[serde(tag = "type", rename_all = "snake_case")] pub enum Widget { + /// A container to place nested widgets inside. Box(BoxWidget), + /// A text label. Pango markup is supported. Label(LabelWidget), + /// A clickable button, which can run a command when clicked. Button(ButtonWidget), + /// An image or icon from disk or http. Image(ImageWidget), + /// A draggable slider. Slider(SliderWidget), + /// A progress bar. Progress(ProgressWidget), } diff --git a/src/modules/custom/progress.rs b/src/modules/custom/progress.rs index 16f46f8..199ea8c 100644 --- a/src/modules/custom/progress.rs +++ b/src/modules/custom/progress.rs @@ -14,14 +14,49 @@ use super::{CustomWidget, CustomWidgetContext}; #[derive(Debug, Deserialize, Clone)] pub struct ProgressWidget { + /// Widget name. + /// + /// **Default**: `null` name: Option, + + /// Widget class name. + /// + /// **Default**: `null` class: Option, + + /// Orientation of the progress bar. + /// + /// **Valid options**: `horizontal`, `vertical`, `h`, `v` + ///
+ /// **Default**: `horizontal` #[serde(default)] orientation: ModuleOrientation, + + /// Text label to show for the progress bar. + /// + /// This is a [Dynamic String](dynamic-values#dynamic-string). + /// + /// **Default**: `null` label: Option, + + /// Script to run to get the progress bar value. + /// Output must be a valid percentage. + /// + /// Note that this expects a numeric value between `0`-`max` as output. + /// + /// **Default**: `null` value: Option, + + /// The maximum progress bar value. + /// + /// **Default**: `100` #[serde(default = "default_max")] max: f64, + + /// The progress bar length, in pixels. + /// GTK will automatically determine the size if left blank. + /// + /// **Default**: `null` length: Option, } diff --git a/src/modules/custom/slider.rs b/src/modules/custom/slider.rs index 6bb3634..c533719 100644 --- a/src/modules/custom/slider.rs +++ b/src/modules/custom/slider.rs @@ -17,18 +17,67 @@ use super::{CustomWidget, CustomWidgetContext, ExecEvent}; #[derive(Debug, Deserialize, Clone)] pub struct SliderWidget { + /// Widget name. + /// + /// **Default**: `null` name: Option, + + /// Widget class name. + /// + /// **Default**: `null` class: Option, + + /// Orientation of the slider. + /// + /// **Valid options**: `horizontal`, `vertical`, `h`, `v` + ///
+ /// **Default**: `horizontal` #[serde(default)] orientation: ModuleOrientation, + + /// Script to run to get the slider value. + /// Output must be a valid number. + /// + /// **Default**: `null` value: Option, + + /// Command to execute when the slider changes. + /// More on this [below](#slider). + /// + /// Note that this will provide the floating point value as an argument. + /// If your input program requires an integer, you will need to round it. + /// + /// **Default**: `null` on_change: Option, + + /// Minimum slider value. + /// + /// **Default**: `0` #[serde(default = "default_min")] min: f64, + + /// Maximum slider value. + /// + /// **Default**: `100` #[serde(default = "default_max")] max: f64, + + /// If the increment to change when scrolling with the mousewheel. + /// If left blank, GTK will use the default value, + /// determined by the current environment. + /// + /// **Default**: `null` step: Option, + + /// The slider length. + /// GTK will automatically determine the size if left blank. + /// + /// **Default**: `null` length: Option, + + /// Whether to show the value label above the slider. + /// + /// **Default**: `true` #[serde(default = "crate::config::default_true")] show_label: bool, } diff --git a/src/modules/focused.rs b/src/modules/focused.rs index c63d263..9fb43d2 100644 --- a/src/modules/focused.rs +++ b/src/modules/focused.rs @@ -14,18 +14,29 @@ use tracing::debug; #[derive(Debug, Deserialize, Clone)] pub struct FocusedModule { /// Whether to show icon on the bar. + /// + /// **Default**: `true` #[serde(default = "crate::config::default_true")] show_icon: bool, /// Whether to show app name on the bar. + /// + /// **Default**: `true` #[serde(default = "crate::config::default_true")] show_title: bool, /// Icon size in pixels. + /// + /// **Default**: `32` #[serde(default = "default_icon_size")] icon_size: i32, + // -- common -- + /// See [truncate options](module-level-options#truncate-mode). + /// + /// **Default**: `null` truncate: Option, + /// See [common options](module-level-options#common-options). #[serde(flatten)] pub common: Option, } @@ -81,7 +92,6 @@ impl Module for FocusedModule { while let Ok(event) = wlrx.recv().await { match event { ToplevelEvent::Update(info) => { - println!("{current:?} | {info:?}"); if info.focused { debug!("Changing focus"); diff --git a/src/modules/label.rs b/src/modules/label.rs index dde6f46..2a8a20c 100644 --- a/src/modules/label.rs +++ b/src/modules/label.rs @@ -10,8 +10,13 @@ use tokio::sync::mpsc; #[derive(Debug, Deserialize, Clone)] pub struct LabelModule { + /// The text to show on the label. + /// This is a [Dynamic String](dynamic-values#dynamic-string). + /// + /// **Required** label: String, + /// See [common options](module-level-options#common-options). #[serde(flatten)] pub common: Option, } diff --git a/src/modules/launcher/item.rs b/src/modules/launcher/item.rs index 924b74a..07d7fb3 100644 --- a/src/modules/launcher/item.rs +++ b/src/modules/launcher/item.rs @@ -40,7 +40,7 @@ impl Item { let id = info.id; if self.windows.is_empty() { - self.name = info.title.clone(); + self.name.clone_from(&info.title); } let window = Window::from(info); @@ -59,7 +59,7 @@ impl Item { pub fn set_window_name(&mut self, window_id: usize, name: String) { if let Some(window) = self.windows.get_mut(&window_id) { if let OpenState::Open { focused: true, .. } = window.open_state { - self.name = name.clone(); + self.name.clone_from(&name); } window.name = name; diff --git a/src/modules/launcher/mod.rs b/src/modules/launcher/mod.rs index 03599b1..bb48cd9 100644 --- a/src/modules/launcher/mod.rs +++ b/src/modules/launcher/mod.rs @@ -22,20 +22,38 @@ use tracing::{debug, error, trace}; pub struct LauncherModule { /// List of app IDs (or classes) to always show regardless of open state, /// in the order specified. + /// + /// **Default**: `null` favorites: Option>, + /// Whether to show application names on the bar. + /// + /// **Default**: `false` #[serde(default = "crate::config::default_false")] show_names: bool, + /// Whether to show application icons on the bar. + /// + /// **Default**: `true` #[serde(default = "crate::config::default_true")] show_icons: bool, + /// Size in pixels to render icon at (image icons only). + /// + /// **Default**: `32` #[serde(default = "default_icon_size")] icon_size: i32, + /// Whether items should be added from right-to-left + /// instead of left-to-right. + /// + /// This includes favourites. + /// + /// **Default**: `false` #[serde(default = "crate::config::default_false")] reversed: bool, + /// See [common options](module-level-options#common-options). #[serde(flatten)] pub common: Option, } @@ -182,13 +200,22 @@ impl Module for LauncherModule { }?; } ToplevelEvent::Update(info) => { - if let Some(item) = lock!(items).get_mut(&info.app_id) { + // check if open, as updates can be sent as program closes + // if it's a focused favourite closing, it otherwise incorrectly re-focuses. + let is_open = if let Some(item) = lock!(items).get_mut(&info.app_id) { item.set_window_focused(info.id, info.focused); item.set_window_name(info.id, info.title.clone()); - } - send_update(LauncherUpdate::Focus(info.app_id.clone(), info.focused)) - .await?; + item.open_state.is_open() + } else { + false + }; + + send_update(LauncherUpdate::Focus( + info.app_id.clone(), + is_open && info.focused, + )) + .await?; send_update(LauncherUpdate::Title( info.app_id.clone(), info.id, @@ -355,8 +382,7 @@ impl Module for LauncherModule { button.set_open(true); button.set_focused(win.open_state.is_focused()); - let mut menu_state = write_lock!(button.menu_state); - menu_state.num_windows += 1; + write_lock!(button.menu_state).num_windows += 1; } } LauncherUpdate::RemoveItem(app_id) => { diff --git a/src/modules/music/config.rs b/src/modules/music/config.rs index c772684..6d3de79 100644 --- a/src/modules/music/config.rs +++ b/src/modules/music/config.rs @@ -6,34 +6,50 @@ use std::path::PathBuf; #[derive(Debug, Deserialize, Clone)] pub struct Icons { /// Icon to display when playing. + /// + /// **Default**: `` #[serde(default = "default_icon_play")] pub(crate) play: String, /// Icon to display when paused. + /// + /// **Default**: `` #[serde(default = "default_icon_pause")] pub(crate) pause: String, /// Icon to display for previous button. + /// + /// **Default**: `󰒮` #[serde(default = "default_icon_prev")] pub(crate) prev: String, /// Icon to display for next button. + /// + /// **Default**: `󰒭` #[serde(default = "default_icon_next")] pub(crate) next: String, - /// Icon to display under volume slider + /// Icon to display under volume slider. + /// + /// **Default**: `󰕾` #[serde(default = "default_icon_volume")] pub(crate) volume: String, - /// Icon to display nex to track title + /// Icon to display nex to track title. + /// + /// **Default**: `󰎈` #[serde(default = "default_icon_track")] pub(crate) track: String, - /// Icon to display nex to album name + /// Icon to display nex to album name. + /// + /// **Default**: `󰀥` #[serde(default = "default_icon_album")] pub(crate) album: String, - /// Icon to display nex to artist name + /// Icon to display nex to artist name. + /// + /// **Default**: `󰠃` #[serde(default = "default_icon_artist")] pub(crate) artist: String, } @@ -73,33 +89,62 @@ pub struct MusicModule { pub(crate) player_type: PlayerType, /// Format of current song info to display on the bar. + /// + /// Info on formatting tokens [below](#formatting-tokens). + /// + /// **Default**: `{title} / {artist}` #[serde(default = "default_format")] pub(crate) format: String, - /// Player state icons + /// Player state icons. + /// + /// See [icons](#icons). #[serde(default)] pub(crate) icons: Icons, - // -- MPD -- - /// TCP or Unix socket address. - #[serde(default = "default_socket")] - pub(crate) host: String, - /// Path to root of music directory. - #[serde(default = "default_music_dir")] - pub(crate) music_dir: PathBuf, - + /// Whether to show the play/pause status icon + /// on the bar. + /// + /// **Default**: `true` #[serde(default = "crate::config::default_true")] pub(crate) show_status_icon: bool, + /// Size to render the icons at, in pixels (image icons only). + /// + /// **Default** `32` #[serde(default = "default_icon_size")] pub(crate) icon_size: i32, + /// Size to render the album art image at inside the popup, in pixels. + /// + /// **Default**: `128` #[serde(default = "default_cover_image_size")] pub(crate) cover_image_size: i32, + // -- MPD -- + /// *[MPD Only]* + /// TCP or Unix socket address of the MPD server. + /// For TCP, this should include the port number. + /// + /// **Default**: `localhost:6600` + #[serde(default = "default_socket")] + pub(crate) host: String, + + /// *[MPD Only]* + /// Path to root of the MPD server's music directory. + /// This is required for displaying album art. + /// + /// **Default**: `$HOME/Music` + #[serde(default = "default_music_dir")] + pub(crate) music_dir: PathBuf, + // -- Common -- + /// See [truncate options](module-level-options#truncate-mode). + /// + /// **Default**: `null` pub(crate) truncate: Option, + /// See [common options](module-level-options#common-options). #[serde(flatten)] pub common: Option, } diff --git a/src/modules/music/mod.rs b/src/modules/music/mod.rs index 3e9604c..a8bb7b0 100644 --- a/src/modules/music/mod.rs +++ b/src/modules/music/mod.rs @@ -191,6 +191,7 @@ impl Module