diff --git a/.github/scripts/ubuntu_setup.sh b/.github/scripts/ubuntu_setup.sh new file mode 100755 index 0000000..ea8f1cc --- /dev/null +++ b/.github/scripts/ubuntu_setup.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# sudo needed for github runner, not available by default for cross images +if command -v sudo >/dev/null 2>&1; then + SUDO="sudo" +else + SUDO="" +fi + +# Needed for cross-compilation +if [ -n "$CROSS_DEB_ARCH" ]; then + $SUDO dpkg --add-architecture "$CROSS_DEB_ARCH" +fi + +# CROSS_DEB_ARCH is empty for native builds +$SUDO apt-get update && $SUDO apt-get install --assume-yes \ + libssl-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \ + libgtk-3-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \ + libgtk-layer-shell-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \ + libpulse-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} diff --git a/.github/workflows/binary.yml b/.github/workflows/binary.yml new file mode 100644 index 0000000..ed7c53e --- /dev/null +++ b/.github/workflows/binary.yml @@ -0,0 +1,39 @@ +# .github/workflows/binary.yml + +name: Binary +on: + release: + types: [created] +jobs: + build: + name: Build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform: + - {target: x86_64-unknown-linux-gnu, zipext: ".tar.gz"} + - {target: aarch64-unknown-linux-gnu, zipext: ".tar.gz"} + steps: + - uses: actions/checkout@v4 + - uses: taiki-e/install-action@v2 + with: + tool: cross + + - name: Add OpenSSL crate (vendored) + run: cargo add openssl --features vendored + + - name: Cross Build Release + run: cross build --locked --release --target=${{ matrix.platform.target }} + + - name: Get name of Binary from metadata + run: echo "BINARY_NAME=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[].targets[] | select( .kind | map(. == "bin") | any ) | .name')" >> $GITHUB_ENV + + - 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}} + + - name: Upload to release + run: gh release upload ${GITHUB_REF#refs/*/} ${{env.BINARY_NAME}}-${{github.ref_name}}-${{matrix.platform.target}}${{matrix.platform.zipext}} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b02903c..8f8b4dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,9 +32,7 @@ jobs: name: Cache dependencies - name: Install build deps - run: | - sudo apt-get update - sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libpulse-dev + run: ./.github/scripts/ubuntu_setup.sh - name: Clippy run: cargo clippy --no-default-features --features config+json @@ -53,9 +51,7 @@ jobs: name: Cache dependencies - name: Install build deps - run: | - sudo apt-get update - sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libpulse-dev + run: ./.github/scripts/ubuntu_setup.sh - name: Clippy run: cargo clippy --all-targets --all-features @@ -72,9 +68,7 @@ jobs: name: Cache dependencies - name: Install build deps - run: | - sudo apt-get update - sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libpulse-dev + run: ./.github/scripts/ubuntu_setup.sh - name: Build run: cargo build --verbose @@ -82,4 +76,4 @@ jobs: - name: Run tests uses: actions-rs/cargo@v1 with: - command: test \ No newline at end of file + command: test diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2d6e37b..f8f68a1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -18,9 +18,7 @@ jobs: override: true - name: Install build deps - run: | - sudo apt-get update - sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libpulse-dev + run: ./.github/scripts/ubuntu_setup.sh - name: Update CHANGELOG id: changelog @@ -48,4 +46,4 @@ jobs: - uses: katyo/publish-crates@v1 with: - registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} \ No newline at end of file + registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index 61e928e..c6d55ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,12 +101,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "anyhow" -version = "1.0.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" - [[package]] name = "async-broadcast" version = "0.5.1" @@ -444,9 +438,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.2" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" dependencies = [ "clap_builder", "clap_derive", @@ -466,11 +460,11 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote 1.0.35", "syn 2.0.48", @@ -494,9 +488,9 @@ dependencies = [ [[package]] name = "color-eyre" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" dependencies = [ "backtrace", "color-spantrace", @@ -1043,9 +1037,9 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "fastrand 2.0.1", "futures-core", @@ -1352,9 +1346,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" dependencies = [ "bytes", "fnv", @@ -1362,7 +1356,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.5", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1396,6 +1390,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.2.6" @@ -1419,9 +1419,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.9" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1430,12 +1430,24 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", "pin-project-lite", ] @@ -1445,47 +1457,60 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - [[package]] name = "hyper" -version = "0.14.26" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "h2", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", + "http-body-util", "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -1579,9 +1604,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.1", @@ -1643,13 +1668,13 @@ dependencies = [ "color-eyre", "ctrlc", "dirs", - "futures-lite 2.2.0", + "futures-lite 2.3.0", "futures-util", "glib", "gtk", "gtk-layer-shell", "hyprland", - "indexmap 2.2.5", + "indexmap 2.2.6", "libpulse-binding", "mpd-utils", "mpris", @@ -2078,9 +2103,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" @@ -2264,6 +2289,26 @@ dependencies = [ "sha2", ] +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote 1.0.35", + "syn 2.0.48", +] + [[package]] name = "pin-project-lite" version = "0.2.12" @@ -2484,9 +2529,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -2528,9 +2573,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.25" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eea5a9eb898d3783f17c6407670e3592fd174cb81a10e51d4c37f49450b9946" +checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" dependencies = [ "base64 0.21.0", "bytes", @@ -2540,8 +2585,10 @@ dependencies = [ "h2", "http", "http-body", + "http-body-util", "hyper", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -2842,9 +2889,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay-client-toolkit" @@ -3030,20 +3077,20 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bc6ee10a9b4fcf576e9b0819d95ec16f4d2c02d39fd83ac1c8789785c4a42" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ - "bitflags 2.4.0", + "bitflags 1.3.2", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.6.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" dependencies = [ "core-foundation-sys", "libc", @@ -3064,18 +3111,13 @@ dependencies = [ [[package]] name = "system-tray" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a456e3e6cbd396f1a3a91f8f74d1fdcf2bde85c97afe174442c367f4749fc09b" +checksum = "82a053bfb84b11f5eb8655a762ba826a2524d02a2f355b0fd6fce4125272f2e0" dependencies = [ - "anyhow", - "byteorder", - "chrono", - "log", "serde", "thiserror", "tokio", - "tokio-stream", "tracing", "zbus", ] @@ -3104,18 +3146,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote 1.0.35", @@ -3190,7 +3232,6 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.5", "tokio-macros", - "tracing", "windows-sys 0.48.0", ] @@ -3215,17 +3256,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.7" @@ -3267,7 +3297,7 @@ version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -3280,11 +3310,33 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -3297,6 +3349,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -4068,7 +4121,6 @@ dependencies = [ "serde_repr", "sha1", "static_assertions", - "tokio", "tracing", "uds_windows", "winapi", diff --git a/Cargo.toml b/Cargo.toml index 85f5c09..0b70115 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,9 +95,9 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } tracing-error = "0.2.0" tracing-appender = "0.2.3" strip-ansi-escapes = "0.2.0" -color-eyre = "0.6.2" +color-eyre = "0.6.3" serde = { version = "1.0.197", features = ["derive"] } -indexmap = "2.2.5" +indexmap = "2.2.6" dirs = "5.0.1" walkdir = "2.5.0" notify = { version = "6.1.1", default-features = false } @@ -112,13 +112,13 @@ ctrlc = "3.4.2" cfg-if = "1.0.0" # cli -clap = { version = "4.5.2", optional = true, features = ["derive"] } +clap = { version = "4.5.3", optional = true, features = ["derive"] } # ipc serde_json = { version = "1.0.114", optional = true } # http -reqwest = { version = "0.11.25", optional = true } +reqwest = { version = "0.12.2", optional = true } # clipboard nix = { version = "0.27.1", optional = true, features = ["event"] } @@ -134,14 +134,13 @@ mpris = { version = "2.0.1", optional = true } sysinfo = { version = "0.29.11", optional = true } # tray -system-tray = { version = "0.1.5", optional = true } + system-tray = { version = "0.2.0", optional = true } # upower upower_dbus = { version = "0.3.2", optional = true } # volume libpulse-binding = { version = "2.28.1", optional = true } -# libpulse-glib-binding = { version = "2.27.1", optional = true } # workspaces swayipc-async = { version = "2.0.1", optional = true } @@ -149,8 +148,8 @@ hyprland = { version = "0.3.13", features = ["silent"], optional = true } futures-util = { version = "0.3.30", optional = true } # shared -regex = { version = "1.10.3", default-features = false, features = [ +regex = { version = "1.10.4", default-features = false, features = [ "std", ], optional = true } # music, sys_info -futures-lite = { version = "2.2.0", optional = true } # networkmanager, upower +futures-lite = { version = "2.3.0", optional = true } # networkmanager, upower zbus = { version = "3.15.2", optional = true } # networkmanager, notifications, upower diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..660445b --- /dev/null +++ b/Cross.toml @@ -0,0 +1,7 @@ +[build] +pre-build = "./.github/scripts/ubuntu_setup.sh" +[target.aarch64-unknown-linux-gnu] +image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:main" + +[target.x86_64-unknown-linux-gnu] +image = "ghcr.io/cross-rs/x86_64-unknown-linux-gnu:main" diff --git a/README.md b/README.md index 0db0282..5fb5ecf 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ Ironbar is designed to support anything from a lightweight bar to a full desktop ## Installation +[![Packaging status](https://repology.org/badge/vertical-allrepos/ironbar.svg)](https://repology.org/project/ironbar/versions) + ### Cargo [crate](https://crates.io/crates/ironbar) @@ -130,6 +132,14 @@ 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). +### Void Linux + +[void package](https://github.com/void-linux/void-packages/tree/master/srcpkgs/ironbar) + +```sh +xbps-install ironbar +``` + ### Source [repo](https://github.com/jakestanger/ironbar) @@ -183,4 +193,4 @@ All are welcome, but I ask a few basic things to help make things easier. Please - [Rustbar](https://github.com/zeroeightysix/rustbar) - Served as a good demo for writing a basic GTK bar in Rust - [Smithay Client Toolkit](https://github.com/Smithay/client-toolkit) - Essential in being able to communicate to Wayland - [gtk-layer-shell](https://github.com/wmww/gtk-layer-shell) - Ironbar and many other projects would be impossible without this -- [Mixxc](https://github.com/Elvyria/Mixxc) - Basis for Ironbar's PulseAudio client code and a cool standalone volume widget. \ No newline at end of file +- [Mixxc](https://github.com/Elvyria/Mixxc) - Basis for Ironbar's PulseAudio client code and a cool standalone volume widget. diff --git a/docs/modules/Tray.md b/docs/modules/Tray.md index a80c48e..6a6753b 100644 --- a/docs/modules/Tray.md +++ b/docs/modules/Tray.md @@ -1,4 +1,4 @@ -Displays a fully interactive icon tray using the KDE `libappindicator` protocol. +Displays a fully interactive icon tray using the KDE `libappindicator` protocol. ![Screenshot showing icon tray widget](https://user-images.githubusercontent.com/5057870/184540135-78ffd79d-f802-4c79-b09a-05a733dadc55.png) @@ -6,10 +6,11 @@ Displays a fully interactive icon tray using the KDE `libappindicator` protocol. > Type: `tray` - -| Name | Type | Default | Description | -|-------------|----------|-----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------| -| `direction` | `string` | `left_to_right` if bar is horizontal, `top_to_bottom` otherwise | Direction to display the tray items. Possible values: `top_to_bottom`, `bottom_to_top`, `left_to_right`, `right_to_left` | +| Name | Type | Default | Description | +|----------------------|-----------|-----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `direction` | `string` | `left_to_right` if bar is horizontal, `top_to_bottom` otherwise | Direction to display the tray items. Possible values: `top_to_bottom`, `bottom_to_top`, `left_to_right`, `right_to_left` | +| `icon_size` | `integer` | `16` | Size in pixels to display tray icons as. | +| `prefer_theme_icons` | `bool` | `true` | 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. |
JSON @@ -54,12 +55,12 @@ end: ```corn { - end = [ +end = [ { - type = "tray" - direction = "top_to_bottom" + type = "tray" + direction = "top_to_bottom" } - ] + ] } ``` diff --git a/src/clients/mod.rs b/src/clients/mod.rs index fa9c876..a3899b2 100644 --- a/src/clients/mod.rs +++ b/src/clients/mod.rs @@ -1,3 +1,4 @@ +use crate::Ironbar; use std::sync::Arc; #[cfg(feature = "clipboard")] @@ -9,7 +10,7 @@ pub mod music; #[cfg(feature = "notifications")] pub mod swaync; #[cfg(feature = "tray")] -pub mod system_tray; +pub mod tray; #[cfg(feature = "upower")] pub mod upower; #[cfg(feature = "volume")] @@ -30,7 +31,7 @@ pub struct Clients { #[cfg(feature = "notifications")] notifications: Option>, #[cfg(feature = "tray")] - tray: Option>, + tray: Option>, #[cfg(feature = "upower")] upower: Option>>, #[cfg(feature = "volume")] @@ -85,11 +86,17 @@ impl Clients { } #[cfg(feature = "tray")] - pub fn tray(&mut self) -> Arc { + pub fn tray(&mut self) -> Arc { + // TODO: Error handling here isn't great - should throw a user-friendly error self.tray .get_or_insert_with(|| { Arc::new(crate::await_sync(async { - system_tray::create_client().await + let service_name = + format!("{}-{}", env!("CARGO_CRATE_NAME"), Ironbar::unique_id()); + + tray::Client::new(&service_name) + .await + .expect("to be able to start client") })) }) .clone() diff --git a/src/clients/music/mod.rs b/src/clients/music/mod.rs index 4269f81..e42b60f 100644 --- a/src/clients/music/mod.rs +++ b/src/clients/music/mod.rs @@ -34,14 +34,15 @@ pub struct Track { pub cover_path: Option, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub enum PlayerState { + #[default] + Stopped, Playing, Paused, - Stopped, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct Status { pub state: PlayerState, pub volume_percent: Option, diff --git a/src/clients/music/mpris.rs b/src/clients/music/mpris.rs index f6eae4c..d505560 100644 --- a/src/clients/music/mpris.rs +++ b/src/clients/music/mpris.rs @@ -18,6 +18,11 @@ pub struct Client { _rx: broadcast::Receiver, } +const NO_ACTIVE_PLAYER: &str = "com.github.altdesktop.playerctld.NoActivePlayer"; +const NO_REPLY: &str = "org.freedesktop.DBus.Error.NoReply"; +const NO_SERVICE: &str = "org.freedesktop.DBus.Error.ServiceUnknown"; +const NO_METHOD: &str = "org.freedesktop.DBus.Error.UnknownMethod"; + impl Client { pub(crate) fn new() -> Self { let (tx, rx) = broadcast::channel(32); @@ -35,44 +40,48 @@ impl Client { // D-Bus gives no event for new players, // so we have to keep polling the player list loop { - let players = player_finder - .find_all() - .expect("Failed to connect to D-Bus"); + // mpris-rs does not filter NoActivePlayer errors, so we have to do it ourselves + let players = player_finder.find_all().unwrap_or_else(|e| match e { + mpris::FindingError::DBusError(DBusError::TransportError( + transport_error, + )) if transport_error.name() == Some(NO_ACTIVE_PLAYER) + || transport_error.name() == Some(NO_REPLY) => + { + Vec::new() + } + _ => panic!("Failed to connect to D-Bus"), + }); + // Acquire the lock of current_player before players to avoid deadlock. + // There are places where we lock on current_player and players, but we always lock on current_player first. + // This is because we almost never need to lock on players without locking on current_player. + { + let mut current_player_lock = lock!(current_player); - let mut players_list_val = lock!(players_list); - for player in players { - let identity = player.identity(); + let mut players_list_val = lock!(players_list); + for player in players { + let identity = player.identity(); - if !players_list_val.contains(identity) { - debug!("Adding MPRIS player '{identity}'"); - players_list_val.insert(identity.to_string()); + if current_player_lock.is_none() { + debug!("Setting active player to '{identity}'"); + current_player_lock.replace(identity.to_string()); - let status = player - .get_playback_status() - .expect("Failed to connect to D-Bus"); - - { - let mut current_player = lock!(current_player); - - if status == PlaybackStatus::Playing || current_player.is_none() { - debug!("Setting active player to '{identity}'"); - - current_player.replace(identity.to_string()); - if let Err(err) = Self::send_update(&player, &tx) { - error!("{err:?}"); - } + if let Err(err) = Self::send_update(&player, &tx) { + error!("{err:?}"); } } + if !players_list_val.contains(identity) { + debug!("Adding MPRIS player '{identity}'"); + players_list_val.insert(identity.to_string()); - Self::listen_player_events( - identity.to_string(), - players_list.clone(), - current_player.clone(), - tx.clone(), - ); + Self::listen_player_events( + identity.to_string(), + players_list.clone(), + current_player.clone(), + tx.clone(), + ); + } } } - // wait 1 second before re-checking players sleep(Duration::from_secs(1)); } @@ -111,28 +120,56 @@ impl Client { if let Ok(player) = player_finder.find_by_name(&player_id) { let identity = player.identity(); + let handle_shutdown = |current_player_lock_option: Option< + std::sync::MutexGuard<'_, Option>, + >| { + debug!("Player '{identity}' shutting down"); + // Lock of player before players (see new() to make sure order is consistent) + if let Some(mut guard) = current_player_lock_option { + guard.take(); + } else { + lock!(current_player).take(); + } + let mut players_locked = lock!(players); + players_locked.remove(identity); + if players_locked.is_empty() { + send!(tx, PlayerUpdate::Update(Box::new(None), Status::default())); + } + }; for event in player.events()? { trace!("Received player event from '{identity}': {event:?}"); match event { Ok(Event::PlayerShutDown) => { - lock!(current_player).take(); - lock!(players).remove(identity); + handle_shutdown(None); break; } - Ok(Event::Playing) => { - lock!(current_player).replace(identity.to_string()); - - if let Err(err) = Self::send_update(&player, &tx) { - error!("{err:?}"); - } + Err(mpris::EventError::DBusError(DBusError::TransportError( + transport_error, + ))) if transport_error.name() == Some(NO_ACTIVE_PLAYER) + || transport_error.name() == Some(NO_REPLY) + || transport_error.name() == Some(NO_METHOD) + || transport_error.name() == Some(NO_SERVICE) => + { + handle_shutdown(None); + break; } Ok(_) => { - let current_player = lock!(current_player); - let current_player = current_player.as_ref(); - if let Some(current_player) = current_player { - if current_player == identity { + let mut current_player_lock = lock!(current_player); + if matches!(event, Ok(Event::Playing)) { + current_player_lock.replace(identity.to_string()); + } + if let Some(current_identity) = current_player_lock.as_ref() { + if current_identity == identity { if let Err(err) = Self::send_update(&player, &tx) { + if let Some(DBusError::TransportError(transport_error)) = + err.downcast_ref::() + { + if transport_error.name() == Some(NO_SERVICE) { + handle_shutdown(Some(current_player_lock)); + break; + } + } error!("{err:?}"); } } diff --git a/src/clients/system_tray.rs b/src/clients/system_tray.rs deleted file mode 100644 index aec5c0a..0000000 --- a/src/clients/system_tray.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::{arc_mut, lock, register_client, send, spawn, Ironbar}; -use color_eyre::Report; -use std::collections::BTreeMap; -use std::sync::{Arc, Mutex}; -use system_tray::message::menu::TrayMenu; -use system_tray::message::tray::StatusNotifierItem; -use system_tray::message::{NotifierItemCommand, NotifierItemMessage}; -use system_tray::StatusNotifierWatcher; -use tokio::sync::{broadcast, mpsc}; -use tracing::{debug, error, trace}; - -type Tray = BTreeMap, Option)>; - -#[derive(Debug)] -pub struct TrayEventReceiver { - tx: mpsc::Sender, - b_tx: broadcast::Sender, - _b_rx: broadcast::Receiver, - - tray: Arc>, -} - -impl TrayEventReceiver { - async fn new() -> system_tray::error::Result { - let id = format!("ironbar-{}", Ironbar::unique_id()); - - let (tx, rx) = mpsc::channel(16); - let (b_tx, b_rx) = broadcast::channel(64); - - let tray = StatusNotifierWatcher::new(rx).await?; - let mut host = Box::pin(tray.create_notifier_host(&id)).await?; - - let tray = arc_mut!(BTreeMap::new()); - - { - let b_tx = b_tx.clone(); - let tray = tray.clone(); - - spawn(async move { - while let Ok(message) = host.recv().await { - trace!("Received message: {message:?}"); - - send!(b_tx, message.clone()); - let mut tray = lock!(tray); - match message { - NotifierItemMessage::Update { - address, - item, - menu, - } => { - debug!("Adding/updating item with address '{address}'"); - tray.insert(address, (item, menu)); - } - NotifierItemMessage::Remove { address } => { - debug!("Removing item with address '{address}'"); - tray.remove(&address); - } - } - } - - Ok::<(), broadcast::error::SendError>(()) - }); - } - - Ok(Self { - tx, - b_tx, - _b_rx: b_rx, - tray, - }) - } - - pub fn subscribe( - &self, - ) -> ( - mpsc::Sender, - broadcast::Receiver, - ) { - let tx = self.tx.clone(); - let b_rx = self.b_tx.subscribe(); - - let tray = lock!(self.tray).clone(); - for (address, (item, menu)) in tray { - let update = NotifierItemMessage::Update { - address, - item, - menu, - }; - send!(self.b_tx, update); - } - - (tx, b_rx) - } -} - -/// Attempts to create a new `TrayEventReceiver` instance, -/// retrying a maximum of 10 times before panicking the thread. -pub async fn create_client() -> TrayEventReceiver { - const MAX_RETRIES: i32 = 10; - - // sometimes this can fail - let mut retries = 0; - - let value = loop { - retries += 1; - - let tray = Box::pin(TrayEventReceiver::new()).await; - - match tray { - Ok(tray) => break Some(tray), - Err(err) => error!( - "{:?}", - Report::new(err).wrap_err(format!( - "Failed to create StatusNotifierWatcher (attempt {retries})" - )) - ), - } - - if retries == MAX_RETRIES { - break None; - } - }; - - value.expect("Failed to create StatusNotifierWatcher") -} - -register_client!(TrayEventReceiver, tray); diff --git a/src/clients/tray.rs b/src/clients/tray.rs new file mode 100644 index 0000000..3b84815 --- /dev/null +++ b/src/clients/tray.rs @@ -0,0 +1,4 @@ +use crate::register_client; +pub use system_tray::client::Client; + +register_client!(Client, tray); diff --git a/src/clients/wayland/mod.rs b/src/clients/wayland/mod.rs index abaa869..8c7fcb8 100644 --- a/src/clients/wayland/mod.rs +++ b/src/clients/wayland/mod.rs @@ -98,6 +98,7 @@ pub enum Response { } #[derive(Debug)] +#[allow(dead_code)] struct BroadcastChannel(broadcast::Sender, Arc>>); impl From<(broadcast::Sender, broadcast::Receiver)> for BroadcastChannel { diff --git a/src/image/provider.rs b/src/image/provider.rs index a23debf..eaf2222 100644 --- a/src/image/provider.rs +++ b/src/image/provider.rs @@ -11,7 +11,7 @@ use gtk::{IconLookupFlags, IconTheme}; use std::path::{Path, PathBuf}; #[cfg(feature = "http")] use tokio::sync::mpsc; -use tracing::warn; +use tracing::{debug, warn}; cfg_if!( if #[cfg(feature = "http")] { @@ -45,6 +45,7 @@ impl<'a> ImageProvider<'a> { /// but no other check is performed. pub fn parse(input: &str, theme: &'a IconTheme, use_fallback: bool, size: i32) -> Option { let location = Self::get_location(input, theme, size, use_fallback, 0)?; + debug!("Resolved {input} --> {location:?} (size: {size})"); Some(Self { location, size }) } @@ -171,7 +172,7 @@ impl<'a> ImageProvider<'a> { ); // Different error types makes this a bit awkward - match pixbuf.map(|pixbuf| Self::create_and_load_surface(&pixbuf, &image, scale)) + match pixbuf.map(|pixbuf| Self::create_and_load_surface(&pixbuf, &image)) { Ok(Err(err)) => error!("{err:?}"), Err(err) => error!("{err:?}"), @@ -202,7 +203,7 @@ impl<'a> ImageProvider<'a> { _ => unreachable!(), // handled above }?; - Self::create_and_load_surface(&pixbuf, image, scale) + Self::create_and_load_surface(&pixbuf, image) } /// Attempts to create a Cairo surface from the provided `Pixbuf`, @@ -210,10 +211,13 @@ impl<'a> ImageProvider<'a> { /// The surface is then loaded into the provided image. /// /// This is necessary for HiDPI since `Pixbuf`s are always treated as scale factor 1. - fn create_and_load_surface(pixbuf: &Pixbuf, image: >k::Image, scale: i32) -> Result<()> { + pub fn create_and_load_surface(pixbuf: &Pixbuf, image: >k::Image) -> Result<()> { let surface = unsafe { - let ptr = - gdk_cairo_surface_create_from_pixbuf(pixbuf.as_ptr(), scale, std::ptr::null_mut()); + let ptr = gdk_cairo_surface_create_from_pixbuf( + pixbuf.as_ptr(), + image.scale_factor(), + std::ptr::null_mut(), + ); Surface::from_raw_full(ptr) }?; diff --git a/src/macros.rs b/src/macros.rs index 4cc986c..e5fd195 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -181,6 +181,13 @@ macro_rules! arc_rw { }; } +/// Wraps `val` in a new `Rc>`. +/// +/// # Usage +/// +/// ```rs +/// let val = rc_mut!(MyService::new()) +/// ``` #[macro_export] macro_rules! rc_mut { ($val:expr) => { diff --git a/src/modules/tray/diff.rs b/src/modules/tray/diff.rs index 25e27b6..dd756af 100644 --- a/src/modules/tray/diff.rs +++ b/src/modules/tray/diff.rs @@ -1,9 +1,9 @@ -use system_tray::message::menu::{MenuItem as MenuItemInfo, ToggleState}; +use system_tray::menu::{MenuItem, ToggleState}; /// Diff change type and associated info. #[derive(Debug, Clone)] pub enum Diff { - Add(MenuItemInfo), + Add(MenuItem), Update(i32, MenuItemDiff), Remove(i32), } @@ -12,7 +12,7 @@ pub enum Diff { #[derive(Debug, Clone)] pub struct MenuItemDiff { /// Text of the item, - pub label: Option, + pub label: Option>, /// Whether the item can be activated or not. pub enabled: Option, /// True if the item is visible in the menu. @@ -29,7 +29,7 @@ pub struct MenuItemDiff { } impl MenuItemDiff { - fn new(old: &MenuItemInfo, new: &MenuItemInfo) -> Self { + fn new(old: &MenuItem, new: &MenuItem) -> Self { macro_rules! diff { ($field:ident) => { if old.$field == new.$field { @@ -70,7 +70,7 @@ impl MenuItemDiff { } /// Gets a diff set between old and new state. -pub fn get_diffs(old: &[MenuItemInfo], new: &[MenuItemInfo]) -> Vec { +pub fn get_diffs(old: &[MenuItem], new: &[MenuItem]) -> Vec { let mut diffs = vec![]; for new_item in new { diff --git a/src/modules/tray/icon.rs b/src/modules/tray/icon.rs index 850137f..3918dfb 100644 --- a/src/modules/tray/icon.rs +++ b/src/modules/tray/icon.rs @@ -1,14 +1,16 @@ +use crate::image::ImageProvider; +use crate::modules::tray::interface::TrayMenu; +use color_eyre::{Report, Result}; use glib::ffi::g_strfreev; use glib::translate::ToGlibPtr; use gtk::ffi::gtk_icon_theme_get_search_path; -use gtk::gdk_pixbuf::{Colorspace, InterpType}; +use gtk::gdk_pixbuf::{Colorspace, InterpType, Pixbuf}; use gtk::prelude::IconThemeExt; -use gtk::{gdk_pixbuf, IconLookupFlags, IconTheme, Image}; +use gtk::{IconLookupFlags, IconTheme, Image}; use std::collections::HashSet; use std::ffi::CStr; use std::os::raw::{c_char, c_int}; use std::ptr; -use system_tray::message::tray::StatusNotifierItem; /// Gets the GTK icon theme search paths by calling the FFI function. /// Conveniently returns the result as a `HashSet`. @@ -36,40 +38,60 @@ fn get_icon_theme_search_paths(icon_theme: &IconTheme) -> HashSet { paths } +pub fn get_image( + item: &TrayMenu, + icon_theme: &IconTheme, + size: u32, + prefer_icons: bool, +) -> Result { + if !prefer_icons && item.icon_pixmap.is_some() { + get_image_from_pixmap(item, size) + } else { + get_image_from_icon_name(item, icon_theme, size) + .or_else(|_| get_image_from_pixmap(item, size)) + } +} + /// Attempts to get a GTK `Image` component /// for the status notifier item's icon. -pub(crate) fn get_image_from_icon_name( - item: &StatusNotifierItem, - icon_theme: &IconTheme, -) -> Option { +fn get_image_from_icon_name(item: &TrayMenu, icon_theme: &IconTheme, size: u32) -> Result { if let Some(path) = item.icon_theme_path.as_ref() { if !path.is_empty() && !get_icon_theme_search_paths(icon_theme).contains(path) { icon_theme.append_search_path(path); } } - item.icon_name.as_ref().and_then(|icon_name| { - let icon_info = icon_theme.lookup_icon(icon_name, 16, IconLookupFlags::empty()); - icon_info.map(|icon_info| Image::from_pixbuf(icon_info.load_icon().ok().as_ref())) - }) + let icon_info = item.icon_name.as_ref().and_then(|icon_name| { + icon_theme.lookup_icon(icon_name, size as i32, IconLookupFlags::empty()) + }); + + if let Some(icon_info) = icon_info { + let pixbuf = icon_info.load_icon()?; + let image = Image::new(); + ImageProvider::create_and_load_surface(&pixbuf, &image)?; + Ok(image) + } else { + Err(Report::msg("could not find icon")) + } } /// Attempts to get an image from the item pixmap. /// /// The pixmap is supplied in ARGB32 format, /// which has 8 bits per sample and a bit stride of `4*width`. -pub(crate) fn get_image_from_pixmap(item: &StatusNotifierItem) -> Option { +fn get_image_from_pixmap(item: &TrayMenu, size: u32) -> Result { const BITS_PER_SAMPLE: i32 = 8; let pixmap = item .icon_pixmap .as_ref() - .and_then(|pixmap| pixmap.first())?; + .and_then(|pixmap| pixmap.first()) + .ok_or_else(|| Report::msg("Failed to get pixmap from tray icon"))?; let bytes = glib::Bytes::from(&pixmap.pixels); - let row_stride = pixmap.width * 4; // + let row_stride = pixmap.width * 4; - let pixbuf = gdk_pixbuf::Pixbuf::from_bytes( + let pixbuf = Pixbuf::from_bytes( &bytes, Colorspace::Rgb, true, @@ -80,7 +102,10 @@ pub(crate) fn get_image_from_pixmap(item: &StatusNotifierItem) -> Option ); let pixbuf = pixbuf - .scale_simple(16, 16, InterpType::Bilinear) + .scale_simple(size as i32, size as i32, InterpType::Bilinear) .unwrap_or(pixbuf); - Some(Image::from_pixbuf(Some(&pixbuf))) + + let image = Image::new(); + ImageProvider::create_and_load_surface(&pixbuf, &image)?; + Ok(image) } diff --git a/src/modules/tray/interface.rs b/src/modules/tray/interface.rs index 3b9d988..0e44449 100644 --- a/src/modules/tray/interface.rs +++ b/src/modules/tray/interface.rs @@ -1,10 +1,12 @@ -use crate::modules::tray::diff::{Diff, MenuItemDiff}; +use super::diff::{Diff, MenuItemDiff}; use crate::{spawn, try_send}; +use glib::Propagation; use gtk::prelude::*; use gtk::{CheckMenuItem, Image, Label, Menu, MenuItem, SeparatorMenuItem}; use std::collections::HashMap; -use system_tray::message::menu::{MenuItem as MenuItemInfo, MenuType, ToggleState, ToggleType}; -use system_tray::message::NotifierItemCommand; +use system_tray::client::ActivateRequest; +use system_tray::item::{IconPixmap, StatusNotifierItem}; +use system_tray::menu::{MenuItem as MenuItemInfo, MenuType, ToggleState, ToggleType}; use tokio::sync::mpsc; /// Calls a method on the underlying widget, @@ -49,37 +51,47 @@ macro_rules! call { /// Main tray icon to show on the bar pub(crate) struct TrayMenu { - pub(crate) widget: MenuItem, + pub widget: MenuItem, menu_widget: Menu, image_widget: Option, label_widget: Option