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

Merge branch 'master' into feat/networkmanager

This commit is contained in:
Reinout Meliesie 2024-03-29 20:40:46 +01:00
commit 4e2352c9e9
Signed by: zedfrigg
GPG key ID: 3AFCC06481308BC6
22 changed files with 579 additions and 390 deletions

20
.github/scripts/ubuntu_setup.sh vendored Executable file
View file

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

39
.github/workflows/binary.yml vendored Normal file
View file

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

View file

@ -32,9 +32,7 @@ jobs:
name: Cache dependencies name: Cache dependencies
- name: Install build deps - name: Install build deps
run: | run: ./.github/scripts/ubuntu_setup.sh
sudo apt-get update
sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libpulse-dev
- name: Clippy - name: Clippy
run: cargo clippy --no-default-features --features config+json run: cargo clippy --no-default-features --features config+json
@ -53,9 +51,7 @@ jobs:
name: Cache dependencies name: Cache dependencies
- name: Install build deps - name: Install build deps
run: | run: ./.github/scripts/ubuntu_setup.sh
sudo apt-get update
sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libpulse-dev
- name: Clippy - name: Clippy
run: cargo clippy --all-targets --all-features run: cargo clippy --all-targets --all-features
@ -72,9 +68,7 @@ jobs:
name: Cache dependencies name: Cache dependencies
- name: Install build deps - name: Install build deps
run: | run: ./.github/scripts/ubuntu_setup.sh
sudo apt-get update
sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libpulse-dev
- name: Build - name: Build
run: cargo build --verbose run: cargo build --verbose
@ -82,4 +76,4 @@ jobs:
- name: Run tests - name: Run tests
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test

View file

@ -18,9 +18,7 @@ jobs:
override: true override: true
- name: Install build deps - name: Install build deps
run: | run: ./.github/scripts/ubuntu_setup.sh
sudo apt-get update
sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libpulse-dev
- name: Update CHANGELOG - name: Update CHANGELOG
id: changelog id: changelog
@ -48,4 +46,4 @@ jobs:
- uses: katyo/publish-crates@v1 - uses: katyo/publish-crates@v1
with: with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}

212
Cargo.lock generated
View file

@ -101,12 +101,6 @@ dependencies = [
"windows-sys 0.48.0", "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]] [[package]]
name = "async-broadcast" name = "async-broadcast"
version = "0.5.1" version = "0.5.1"
@ -444,9 +438,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.2" version = "4.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -466,11 +460,11 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.0" version = "4.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f"
dependencies = [ dependencies = [
"heck 0.4.1", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote 1.0.35", "quote 1.0.35",
"syn 2.0.48", "syn 2.0.48",
@ -494,9 +488,9 @@ dependencies = [
[[package]] [[package]]
name = "color-eyre" name = "color-eyre"
version = "0.6.2" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"color-spantrace", "color-spantrace",
@ -1043,9 +1037,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-lite" name = "futures-lite"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
dependencies = [ dependencies = [
"fastrand 2.0.1", "fastrand 2.0.1",
"futures-core", "futures-core",
@ -1352,9 +1346,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.24" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -1362,7 +1356,7 @@ dependencies = [
"futures-sink", "futures-sink",
"futures-util", "futures-util",
"http", "http",
"indexmap 2.2.5", "indexmap 2.2.6",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
@ -1396,6 +1390,12 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.2.6" version = "0.2.6"
@ -1419,9 +1419,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.9" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -1430,12 +1430,24 @@ dependencies = [
[[package]] [[package]]
name = "http-body" name = "http-body"
version = "0.4.5" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "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", "pin-project-lite",
] ]
@ -1445,47 +1457,60 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.26" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-core",
"futures-util", "futures-util",
"h2", "h2",
"http", "http",
"http-body", "http-body",
"httparse", "httparse",
"httpdate",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"socket2 0.4.9", "smallvec",
"tokio", "tokio",
"tower-service",
"tracing",
"want", "want",
] ]
[[package]] [[package]]
name = "hyper-tls" name = "hyper-tls"
version = "0.5.0" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [ dependencies = [
"bytes", "bytes",
"http-body-util",
"hyper", "hyper",
"hyper-util",
"native-tls", "native-tls",
"tokio", "tokio",
"tokio-native-tls", "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]] [[package]]
@ -1579,9 +1604,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.5" version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.14.1", "hashbrown 0.14.1",
@ -1643,13 +1668,13 @@ dependencies = [
"color-eyre", "color-eyre",
"ctrlc", "ctrlc",
"dirs", "dirs",
"futures-lite 2.2.0", "futures-lite 2.3.0",
"futures-util", "futures-util",
"glib", "glib",
"gtk", "gtk",
"gtk-layer-shell", "gtk-layer-shell",
"hyprland", "hyprland",
"indexmap 2.2.5", "indexmap 2.2.6",
"libpulse-binding", "libpulse-binding",
"mpd-utils", "mpd-utils",
"mpris", "mpris",
@ -2078,9 +2103,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.17.1" version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]] [[package]]
name = "openssl" name = "openssl"
@ -2264,6 +2289,26 @@ dependencies = [
"sha2", "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]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.12" version = "0.2.12"
@ -2484,9 +2529,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.10.3" version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -2528,9 +2573,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.25" version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eea5a9eb898d3783f17c6407670e3592fd174cb81a10e51d4c37f49450b9946" checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338"
dependencies = [ dependencies = [
"base64 0.21.0", "base64 0.21.0",
"bytes", "bytes",
@ -2540,8 +2585,10 @@ dependencies = [
"h2", "h2",
"http", "http",
"http-body", "http-body",
"http-body-util",
"hyper", "hyper",
"hyper-tls", "hyper-tls",
"hyper-util",
"ipnet", "ipnet",
"js-sys", "js-sys",
"log", "log",
@ -2842,9 +2889,9 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.10.0" version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]] [[package]]
name = "smithay-client-toolkit" name = "smithay-client-toolkit"
@ -3030,20 +3077,20 @@ dependencies = [
[[package]] [[package]]
name = "system-configuration" name = "system-configuration"
version = "0.6.0" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bc6ee10a9b4fcf576e9b0819d95ec16f4d2c02d39fd83ac1c8789785c4a42" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [ dependencies = [
"bitflags 2.4.0", "bitflags 1.3.2",
"core-foundation", "core-foundation",
"system-configuration-sys", "system-configuration-sys",
] ]
[[package]] [[package]]
name = "system-configuration-sys" name = "system-configuration-sys"
version = "0.6.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [ dependencies = [
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -3064,18 +3111,13 @@ dependencies = [
[[package]] [[package]]
name = "system-tray" name = "system-tray"
version = "0.1.5" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a456e3e6cbd396f1a3a91f8f74d1fdcf2bde85c97afe174442c367f4749fc09b" checksum = "82a053bfb84b11f5eb8655a762ba826a2524d02a2f355b0fd6fce4125272f2e0"
dependencies = [ dependencies = [
"anyhow",
"byteorder",
"chrono",
"log",
"serde", "serde",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-stream",
"tracing", "tracing",
"zbus", "zbus",
] ]
@ -3104,18 +3146,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.56" version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.56" version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.35", "quote 1.0.35",
@ -3190,7 +3232,6 @@ dependencies = [
"signal-hook-registry", "signal-hook-registry",
"socket2 0.5.5", "socket2 0.5.5",
"tokio-macros", "tokio-macros",
"tracing",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -3215,17 +3256,6 @@ dependencies = [
"tokio", "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]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.7" version = "0.7.7"
@ -3267,7 +3297,7 @@ version = "0.19.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
dependencies = [ dependencies = [
"indexmap 2.2.5", "indexmap 2.2.6",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
@ -3280,11 +3310,33 @@ version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
dependencies = [ dependencies = [
"indexmap 2.2.5", "indexmap 2.2.6",
"toml_datetime", "toml_datetime",
"winnow", "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]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.2" version = "0.3.2"
@ -3297,6 +3349,7 @@ version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [ dependencies = [
"log",
"pin-project-lite", "pin-project-lite",
"tracing-attributes", "tracing-attributes",
"tracing-core", "tracing-core",
@ -4068,7 +4121,6 @@ dependencies = [
"serde_repr", "serde_repr",
"sha1", "sha1",
"static_assertions", "static_assertions",
"tokio",
"tracing", "tracing",
"uds_windows", "uds_windows",
"winapi", "winapi",

View file

@ -95,9 +95,9 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
tracing-error = "0.2.0" tracing-error = "0.2.0"
tracing-appender = "0.2.3" tracing-appender = "0.2.3"
strip-ansi-escapes = "0.2.0" strip-ansi-escapes = "0.2.0"
color-eyre = "0.6.2" color-eyre = "0.6.3"
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
indexmap = "2.2.5" indexmap = "2.2.6"
dirs = "5.0.1" dirs = "5.0.1"
walkdir = "2.5.0" walkdir = "2.5.0"
notify = { version = "6.1.1", default-features = false } notify = { version = "6.1.1", default-features = false }
@ -112,13 +112,13 @@ ctrlc = "3.4.2"
cfg-if = "1.0.0" cfg-if = "1.0.0"
# cli # cli
clap = { version = "4.5.2", optional = true, features = ["derive"] } clap = { version = "4.5.3", optional = true, features = ["derive"] }
# ipc # ipc
serde_json = { version = "1.0.114", optional = true } serde_json = { version = "1.0.114", optional = true }
# http # http
reqwest = { version = "0.11.25", optional = true } reqwest = { version = "0.12.2", optional = true }
# clipboard # clipboard
nix = { version = "0.27.1", optional = true, features = ["event"] } 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 } sysinfo = { version = "0.29.11", optional = true }
# tray # tray
system-tray = { version = "0.1.5", optional = true } system-tray = { version = "0.2.0", optional = true }
# upower # upower
upower_dbus = { version = "0.3.2", optional = true } upower_dbus = { version = "0.3.2", optional = true }
# volume # volume
libpulse-binding = { version = "2.28.1", optional = true } libpulse-binding = { version = "2.28.1", optional = true }
# libpulse-glib-binding = { version = "2.27.1", optional = true }
# workspaces # workspaces
swayipc-async = { version = "2.0.1", optional = true } 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 } futures-util = { version = "0.3.30", optional = true }
# shared # shared
regex = { version = "1.10.3", default-features = false, features = [ regex = { version = "1.10.4", default-features = false, features = [
"std", "std",
], optional = true } # music, sys_info ], 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 zbus = { version = "3.15.2", optional = true } # networkmanager, notifications, upower

7
Cross.toml Normal file
View file

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

View file

@ -57,6 +57,8 @@ Ironbar is designed to support anything from a lightweight bar to a full desktop
## Installation ## Installation
[![Packaging status](https://repology.org/badge/vertical-allrepos/ironbar.svg)](https://repology.org/project/ironbar/versions)
### Cargo ### Cargo
[crate](https://crates.io/crates/ironbar) [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. CI builds are automatically cached by Garnix.
You can use their binary cache by following the steps [here](https://garnix.io/docs/caching). 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 ### Source
[repo](https://github.com/jakestanger/ironbar) [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 - [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 - [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 - [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. - [Mixxc](https://github.com/Elvyria/Mixxc) - Basis for Ironbar's PulseAudio client code and a cool standalone volume widget.

View file

@ -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) ![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` > Type: `tray`
| Name | Type | Default | Description |
| 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` |
| `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. |
<details> <details>
<summary>JSON</summary> <summary>JSON</summary>
@ -54,12 +55,12 @@ end:
```corn ```corn
{ {
end = [ end = [
{ {
type = "tray" type = "tray"
direction = "top_to_bottom" direction = "top_to_bottom"
} }
] ]
} }
``` ```

View file

@ -1,3 +1,4 @@
use crate::Ironbar;
use std::sync::Arc; use std::sync::Arc;
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard")]
@ -9,7 +10,7 @@ pub mod music;
#[cfg(feature = "notifications")] #[cfg(feature = "notifications")]
pub mod swaync; pub mod swaync;
#[cfg(feature = "tray")] #[cfg(feature = "tray")]
pub mod system_tray; pub mod tray;
#[cfg(feature = "upower")] #[cfg(feature = "upower")]
pub mod upower; pub mod upower;
#[cfg(feature = "volume")] #[cfg(feature = "volume")]
@ -30,7 +31,7 @@ pub struct Clients {
#[cfg(feature = "notifications")] #[cfg(feature = "notifications")]
notifications: Option<Arc<swaync::Client>>, notifications: Option<Arc<swaync::Client>>,
#[cfg(feature = "tray")] #[cfg(feature = "tray")]
tray: Option<Arc<system_tray::TrayEventReceiver>>, tray: Option<Arc<tray::Client>>,
#[cfg(feature = "upower")] #[cfg(feature = "upower")]
upower: Option<Arc<zbus::fdo::PropertiesProxy<'static>>>, upower: Option<Arc<zbus::fdo::PropertiesProxy<'static>>>,
#[cfg(feature = "volume")] #[cfg(feature = "volume")]
@ -85,11 +86,17 @@ impl Clients {
} }
#[cfg(feature = "tray")] #[cfg(feature = "tray")]
pub fn tray(&mut self) -> Arc<system_tray::TrayEventReceiver> { pub fn tray(&mut self) -> Arc<tray::Client> {
// TODO: Error handling here isn't great - should throw a user-friendly error
self.tray self.tray
.get_or_insert_with(|| { .get_or_insert_with(|| {
Arc::new(crate::await_sync(async { 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() .clone()

View file

@ -34,14 +34,15 @@ pub struct Track {
pub cover_path: Option<String>, pub cover_path: Option<String>,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, Default)]
pub enum PlayerState { pub enum PlayerState {
#[default]
Stopped,
Playing, Playing,
Paused, Paused,
Stopped,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, Default)]
pub struct Status { pub struct Status {
pub state: PlayerState, pub state: PlayerState,
pub volume_percent: Option<u8>, pub volume_percent: Option<u8>,

View file

@ -18,6 +18,11 @@ pub struct Client {
_rx: broadcast::Receiver<PlayerUpdate>, _rx: broadcast::Receiver<PlayerUpdate>,
} }
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 { impl Client {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
let (tx, rx) = broadcast::channel(32); let (tx, rx) = broadcast::channel(32);
@ -35,44 +40,48 @@ impl Client {
// D-Bus gives no event for new players, // D-Bus gives no event for new players,
// so we have to keep polling the player list // so we have to keep polling the player list
loop { loop {
let players = player_finder // mpris-rs does not filter NoActivePlayer errors, so we have to do it ourselves
.find_all() let players = player_finder.find_all().unwrap_or_else(|e| match e {
.expect("Failed to connect to D-Bus"); 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); let mut players_list_val = lock!(players_list);
for player in players { for player in players {
let identity = player.identity(); let identity = player.identity();
if !players_list_val.contains(identity) { if current_player_lock.is_none() {
debug!("Adding MPRIS player '{identity}'"); debug!("Setting active player to '{identity}'");
players_list_val.insert(identity.to_string()); current_player_lock.replace(identity.to_string());
let status = player if let Err(err) = Self::send_update(&player, &tx) {
.get_playback_status() error!("{err:?}");
.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 !players_list_val.contains(identity) {
debug!("Adding MPRIS player '{identity}'");
players_list_val.insert(identity.to_string());
Self::listen_player_events( Self::listen_player_events(
identity.to_string(), identity.to_string(),
players_list.clone(), players_list.clone(),
current_player.clone(), current_player.clone(),
tx.clone(), tx.clone(),
); );
}
} }
} }
// wait 1 second before re-checking players // wait 1 second before re-checking players
sleep(Duration::from_secs(1)); sleep(Duration::from_secs(1));
} }
@ -111,28 +120,56 @@ impl Client {
if let Ok(player) = player_finder.find_by_name(&player_id) { if let Ok(player) = player_finder.find_by_name(&player_id) {
let identity = player.identity(); let identity = player.identity();
let handle_shutdown = |current_player_lock_option: Option<
std::sync::MutexGuard<'_, Option<String>>,
>| {
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()? { for event in player.events()? {
trace!("Received player event from '{identity}': {event:?}"); trace!("Received player event from '{identity}': {event:?}");
match event { match event {
Ok(Event::PlayerShutDown) => { Ok(Event::PlayerShutDown) => {
lock!(current_player).take(); handle_shutdown(None);
lock!(players).remove(identity);
break; break;
} }
Ok(Event::Playing) => { Err(mpris::EventError::DBusError(DBusError::TransportError(
lock!(current_player).replace(identity.to_string()); transport_error,
))) if transport_error.name() == Some(NO_ACTIVE_PLAYER)
if let Err(err) = Self::send_update(&player, &tx) { || transport_error.name() == Some(NO_REPLY)
error!("{err:?}"); || transport_error.name() == Some(NO_METHOD)
} || transport_error.name() == Some(NO_SERVICE) =>
{
handle_shutdown(None);
break;
} }
Ok(_) => { Ok(_) => {
let current_player = lock!(current_player); let mut current_player_lock = lock!(current_player);
let current_player = current_player.as_ref(); if matches!(event, Ok(Event::Playing)) {
if let Some(current_player) = current_player { current_player_lock.replace(identity.to_string());
if current_player == identity { }
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 Err(err) = Self::send_update(&player, &tx) {
if let Some(DBusError::TransportError(transport_error)) =
err.downcast_ref::<DBusError>()
{
if transport_error.name() == Some(NO_SERVICE) {
handle_shutdown(Some(current_player_lock));
break;
}
}
error!("{err:?}"); error!("{err:?}");
} }
} }

View file

@ -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<String, (Box<StatusNotifierItem>, Option<TrayMenu>)>;
#[derive(Debug)]
pub struct TrayEventReceiver {
tx: mpsc::Sender<NotifierItemCommand>,
b_tx: broadcast::Sender<NotifierItemMessage>,
_b_rx: broadcast::Receiver<NotifierItemMessage>,
tray: Arc<Mutex<Tray>>,
}
impl TrayEventReceiver {
async fn new() -> system_tray::error::Result<Self> {
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<NotifierItemMessage>>(())
});
}
Ok(Self {
tx,
b_tx,
_b_rx: b_rx,
tray,
})
}
pub fn subscribe(
&self,
) -> (
mpsc::Sender<NotifierItemCommand>,
broadcast::Receiver<NotifierItemMessage>,
) {
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);

4
src/clients/tray.rs Normal file
View file

@ -0,0 +1,4 @@
use crate::register_client;
pub use system_tray::client::Client;
register_client!(Client, tray);

View file

@ -98,6 +98,7 @@ pub enum Response {
} }
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)]
struct BroadcastChannel<T>(broadcast::Sender<T>, Arc<Mutex<broadcast::Receiver<T>>>); struct BroadcastChannel<T>(broadcast::Sender<T>, Arc<Mutex<broadcast::Receiver<T>>>);
impl<T> From<(broadcast::Sender<T>, broadcast::Receiver<T>)> for BroadcastChannel<T> { impl<T> From<(broadcast::Sender<T>, broadcast::Receiver<T>)> for BroadcastChannel<T> {

View file

@ -11,7 +11,7 @@ use gtk::{IconLookupFlags, IconTheme};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[cfg(feature = "http")] #[cfg(feature = "http")]
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::warn; use tracing::{debug, warn};
cfg_if!( cfg_if!(
if #[cfg(feature = "http")] { if #[cfg(feature = "http")] {
@ -45,6 +45,7 @@ impl<'a> ImageProvider<'a> {
/// but no other check is performed. /// but no other check is performed.
pub fn parse(input: &str, theme: &'a IconTheme, use_fallback: bool, size: i32) -> Option<Self> { pub fn parse(input: &str, theme: &'a IconTheme, use_fallback: bool, size: i32) -> Option<Self> {
let location = Self::get_location(input, theme, size, use_fallback, 0)?; let location = Self::get_location(input, theme, size, use_fallback, 0)?;
debug!("Resolved {input} --> {location:?} (size: {size})");
Some(Self { location, size }) Some(Self { location, size })
} }
@ -171,7 +172,7 @@ impl<'a> ImageProvider<'a> {
); );
// Different error types makes this a bit awkward // 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:?}"), Ok(Err(err)) => error!("{err:?}"),
Err(err) => error!("{err:?}"), Err(err) => error!("{err:?}"),
@ -202,7 +203,7 @@ impl<'a> ImageProvider<'a> {
_ => unreachable!(), // handled above _ => 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`, /// 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. /// The surface is then loaded into the provided image.
/// ///
/// This is necessary for HiDPI since `Pixbuf`s are always treated as scale factor 1. /// This is necessary for HiDPI since `Pixbuf`s are always treated as scale factor 1.
fn create_and_load_surface(pixbuf: &Pixbuf, image: &gtk::Image, scale: i32) -> Result<()> { pub fn create_and_load_surface(pixbuf: &Pixbuf, image: &gtk::Image) -> Result<()> {
let surface = unsafe { let surface = unsafe {
let ptr = let ptr = gdk_cairo_surface_create_from_pixbuf(
gdk_cairo_surface_create_from_pixbuf(pixbuf.as_ptr(), scale, std::ptr::null_mut()); pixbuf.as_ptr(),
image.scale_factor(),
std::ptr::null_mut(),
);
Surface::from_raw_full(ptr) Surface::from_raw_full(ptr)
}?; }?;

View file

@ -181,6 +181,13 @@ macro_rules! arc_rw {
}; };
} }
/// Wraps `val` in a new `Rc<RefCell<T>>`.
///
/// # Usage
///
/// ```rs
/// let val = rc_mut!(MyService::new())
/// ```
#[macro_export] #[macro_export]
macro_rules! rc_mut { macro_rules! rc_mut {
($val:expr) => { ($val:expr) => {

View file

@ -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. /// Diff change type and associated info.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Diff { pub enum Diff {
Add(MenuItemInfo), Add(MenuItem),
Update(i32, MenuItemDiff), Update(i32, MenuItemDiff),
Remove(i32), Remove(i32),
} }
@ -12,7 +12,7 @@ pub enum Diff {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MenuItemDiff { pub struct MenuItemDiff {
/// Text of the item, /// Text of the item,
pub label: Option<String>, pub label: Option<Option<String>>,
/// Whether the item can be activated or not. /// Whether the item can be activated or not.
pub enabled: Option<bool>, pub enabled: Option<bool>,
/// True if the item is visible in the menu. /// True if the item is visible in the menu.
@ -29,7 +29,7 @@ pub struct MenuItemDiff {
} }
impl MenuItemDiff { impl MenuItemDiff {
fn new(old: &MenuItemInfo, new: &MenuItemInfo) -> Self { fn new(old: &MenuItem, new: &MenuItem) -> Self {
macro_rules! diff { macro_rules! diff {
($field:ident) => { ($field:ident) => {
if old.$field == new.$field { if old.$field == new.$field {
@ -70,7 +70,7 @@ impl MenuItemDiff {
} }
/// Gets a diff set between old and new state. /// Gets a diff set between old and new state.
pub fn get_diffs(old: &[MenuItemInfo], new: &[MenuItemInfo]) -> Vec<Diff> { pub fn get_diffs(old: &[MenuItem], new: &[MenuItem]) -> Vec<Diff> {
let mut diffs = vec![]; let mut diffs = vec![];
for new_item in new { for new_item in new {

View file

@ -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::ffi::g_strfreev;
use glib::translate::ToGlibPtr; use glib::translate::ToGlibPtr;
use gtk::ffi::gtk_icon_theme_get_search_path; 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::prelude::IconThemeExt;
use gtk::{gdk_pixbuf, IconLookupFlags, IconTheme, Image}; use gtk::{IconLookupFlags, IconTheme, Image};
use std::collections::HashSet; use std::collections::HashSet;
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
use std::ptr; use std::ptr;
use system_tray::message::tray::StatusNotifierItem;
/// Gets the GTK icon theme search paths by calling the FFI function. /// Gets the GTK icon theme search paths by calling the FFI function.
/// Conveniently returns the result as a `HashSet`. /// Conveniently returns the result as a `HashSet`.
@ -36,40 +38,60 @@ fn get_icon_theme_search_paths(icon_theme: &IconTheme) -> HashSet<String> {
paths paths
} }
pub fn get_image(
item: &TrayMenu,
icon_theme: &IconTheme,
size: u32,
prefer_icons: bool,
) -> Result<Image> {
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 /// Attempts to get a GTK `Image` component
/// for the status notifier item's icon. /// for the status notifier item's icon.
pub(crate) fn get_image_from_icon_name( fn get_image_from_icon_name(item: &TrayMenu, icon_theme: &IconTheme, size: u32) -> Result<Image> {
item: &StatusNotifierItem,
icon_theme: &IconTheme,
) -> Option<Image> {
if let Some(path) = item.icon_theme_path.as_ref() { if let Some(path) = item.icon_theme_path.as_ref() {
if !path.is_empty() && !get_icon_theme_search_paths(icon_theme).contains(path) { if !path.is_empty() && !get_icon_theme_search_paths(icon_theme).contains(path) {
icon_theme.append_search_path(path); icon_theme.append_search_path(path);
} }
} }
item.icon_name.as_ref().and_then(|icon_name| { let icon_info = item.icon_name.as_ref().and_then(|icon_name| {
let icon_info = icon_theme.lookup_icon(icon_name, 16, IconLookupFlags::empty()); icon_theme.lookup_icon(icon_name, size as i32, IconLookupFlags::empty())
icon_info.map(|icon_info| Image::from_pixbuf(icon_info.load_icon().ok().as_ref())) });
})
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. /// Attempts to get an image from the item pixmap.
/// ///
/// The pixmap is supplied in ARGB32 format, /// The pixmap is supplied in ARGB32 format,
/// which has 8 bits per sample and a bit stride of `4*width`. /// which has 8 bits per sample and a bit stride of `4*width`.
pub(crate) fn get_image_from_pixmap(item: &StatusNotifierItem) -> Option<Image> { fn get_image_from_pixmap(item: &TrayMenu, size: u32) -> Result<Image> {
const BITS_PER_SAMPLE: i32 = 8; const BITS_PER_SAMPLE: i32 = 8;
let pixmap = item let pixmap = item
.icon_pixmap .icon_pixmap
.as_ref() .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 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, &bytes,
Colorspace::Rgb, Colorspace::Rgb,
true, true,
@ -80,7 +102,10 @@ pub(crate) fn get_image_from_pixmap(item: &StatusNotifierItem) -> Option<Image>
); );
let pixbuf = pixbuf let pixbuf = pixbuf
.scale_simple(16, 16, InterpType::Bilinear) .scale_simple(size as i32, size as i32, InterpType::Bilinear)
.unwrap_or(pixbuf); .unwrap_or(pixbuf);
Some(Image::from_pixbuf(Some(&pixbuf)))
let image = Image::new();
ImageProvider::create_and_load_surface(&pixbuf, &image)?;
Ok(image)
} }

View file

@ -1,10 +1,12 @@
use crate::modules::tray::diff::{Diff, MenuItemDiff}; use super::diff::{Diff, MenuItemDiff};
use crate::{spawn, try_send}; use crate::{spawn, try_send};
use glib::Propagation;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{CheckMenuItem, Image, Label, Menu, MenuItem, SeparatorMenuItem}; use gtk::{CheckMenuItem, Image, Label, Menu, MenuItem, SeparatorMenuItem};
use std::collections::HashMap; use std::collections::HashMap;
use system_tray::message::menu::{MenuItem as MenuItemInfo, MenuType, ToggleState, ToggleType}; use system_tray::client::ActivateRequest;
use system_tray::message::NotifierItemCommand; use system_tray::item::{IconPixmap, StatusNotifierItem};
use system_tray::menu::{MenuItem as MenuItemInfo, MenuType, ToggleState, ToggleType};
use tokio::sync::mpsc; use tokio::sync::mpsc;
/// Calls a method on the underlying widget, /// Calls a method on the underlying widget,
@ -49,37 +51,47 @@ macro_rules! call {
/// Main tray icon to show on the bar /// Main tray icon to show on the bar
pub(crate) struct TrayMenu { pub(crate) struct TrayMenu {
pub(crate) widget: MenuItem, pub widget: MenuItem,
menu_widget: Menu, menu_widget: Menu,
image_widget: Option<Image>, image_widget: Option<Image>,
label_widget: Option<Label>, label_widget: Option<Label>,
menu: HashMap<i32, TrayMenuItem>, menu: HashMap<i32, TrayMenuItem>,
state: Vec<MenuItemInfo>, state: Vec<MenuItemInfo>,
icon_name: Option<String>,
pub title: Option<String>,
pub icon_name: Option<String>,
pub icon_theme_path: Option<String>,
pub icon_pixmap: Option<Vec<IconPixmap>>,
tx: mpsc::Sender<i32>, tx: mpsc::Sender<i32>,
} }
impl TrayMenu { impl TrayMenu {
pub fn new(tx: mpsc::Sender<NotifierItemCommand>, address: String, path: String) -> Self { pub fn new(
tx: mpsc::Sender<ActivateRequest>,
address: String,
item: StatusNotifierItem,
) -> Self {
let widget = MenuItem::new(); let widget = MenuItem::new();
widget.style_context().add_class("item"); widget.style_context().add_class("item");
let (item_tx, mut item_rx) = mpsc::channel(8); let (item_tx, mut item_rx) = mpsc::channel(8);
spawn(async move { if let Some(menu) = item.menu {
while let Some(id) = item_rx.recv().await { spawn(async move {
try_send!( while let Some(id) = item_rx.recv().await {
tx, try_send!(
NotifierItemCommand::MenuItemClicked { tx,
submenu_id: id, ActivateRequest {
menu_path: path.clone(), submenu_id: id,
notifier_address: address.clone(), menu_path: menu.clone(),
} address: address.clone(),
); }
} );
}); }
});
}
let menu = Menu::new(); let menu = Menu::new();
widget.set_submenu(Some(&menu)); widget.set_submenu(Some(&menu));
@ -90,7 +102,10 @@ impl TrayMenu {
image_widget: None, image_widget: None,
label_widget: None, label_widget: None,
state: vec![], state: vec![],
icon_name: None, title: item.title,
icon_name: item.icon_name,
icon_theme_path: item.icon_theme_path,
icon_pixmap: item.icon_pixmap,
menu: HashMap::new(), menu: HashMap::new(),
tx: item_tx, tx: item_tx,
} }
@ -112,6 +127,18 @@ impl TrayMenu {
.set_label(text); .set_label(text);
} }
/// Shows the label, using its current text.
/// The image is hidden if present.
pub fn show_label(&self) {
if let Some(image) = &self.image_widget {
image.hide();
}
if let Some(label) = &self.label_widget {
label.show();
}
}
/// Updates the image, and shows it in favour of the label. /// Updates the image, and shows it in favour of the label.
pub fn set_image(&mut self, image: &Image) { pub fn set_image(&mut self, image: &Image) {
if let Some(label) = &self.label_widget { if let Some(label) = &self.label_widget {
@ -134,6 +161,7 @@ impl TrayMenu {
let item = TrayMenuItem::new(&info, self.tx.clone()); let item = TrayMenuItem::new(&info, self.tx.clone());
call!(self.menu_widget, add, item.widget); call!(self.menu_widget, add, item.widget);
self.menu.insert(item.id, item); self.menu.insert(item.id, item);
// self.widget.show_all();
} }
Diff::Update(id, info) => { Diff::Update(id, info) => {
if let Some(item) = self.menu.get_mut(&id) { if let Some(item) = self.menu.get_mut(&id) {
@ -188,36 +216,61 @@ enum TrayMenuWidget {
impl TrayMenuItem { impl TrayMenuItem {
fn new(info: &MenuItemInfo, tx: mpsc::Sender<i32>) -> Self { fn new(info: &MenuItemInfo, tx: mpsc::Sender<i32>) -> Self {
let mut submenu = HashMap::new();
let menu = Menu::new(); let menu = Menu::new();
macro_rules! add_submenu {
($menu:expr, $widget:expr) => {
if !info.submenu.is_empty() {
for sub_item in &info.submenu {
let sub_item = TrayMenuItem::new(sub_item, tx.clone());
call!($menu, add, sub_item.widget);
submenu.insert(sub_item.id, sub_item);
}
$widget.set_submenu(Some(&menu));
}
};
}
let widget = match (info.menu_type, info.toggle_type) { let widget = match (info.menu_type, info.toggle_type) {
(MenuType::Separator, _) => TrayMenuWidget::Separator(SeparatorMenuItem::new()), (MenuType::Separator, _) => TrayMenuWidget::Separator(SeparatorMenuItem::new()),
(MenuType::Standard, ToggleType::Checkmark) => { (MenuType::Standard, ToggleType::Checkmark) => {
let widget = CheckMenuItem::builder() let widget = CheckMenuItem::builder()
.label(info.label.as_str())
.visible(info.visible) .visible(info.visible)
.sensitive(info.enabled) .sensitive(info.enabled)
.active(info.toggle_state == ToggleState::On) .active(info.toggle_state == ToggleState::On)
.build(); .build();
if let Some(label) = &info.label {
widget.set_label(label);
}
add_submenu!(menu, widget);
{ {
let tx = tx.clone(); let tx = tx.clone();
let id = info.id; let id = info.id;
widget.connect_activate(move |_item| { widget.connect_button_press_event(move |_item, _button| {
try_send!(tx, id); try_send!(tx, id);
Propagation::Proceed
}); });
} }
TrayMenuWidget::Checkbox(widget) TrayMenuWidget::Checkbox(widget)
} }
(MenuType::Standard, _) => { (MenuType::Standard, _) => {
let builder = MenuItem::builder() let widget = MenuItem::builder()
.label(&info.label)
.visible(info.visible) .visible(info.visible)
.sensitive(info.enabled); .sensitive(info.enabled)
.build();
let widget = builder.build(); if let Some(label) = &info.label {
widget.set_label(label);
}
add_submenu!(menu, widget);
{ {
let tx = tx.clone(); let tx = tx.clone();
@ -236,7 +289,7 @@ impl TrayMenuItem {
id: info.id, id: info.id,
widget, widget,
menu_widget: menu, menu_widget: menu,
submenu: HashMap::new(), submenu,
tx, tx,
} }
} }
@ -247,6 +300,7 @@ impl TrayMenuItem {
/// applying the submenu diffs to any further submenu items. /// applying the submenu diffs to any further submenu items.
fn apply_diff(&mut self, diff: MenuItemDiff) { fn apply_diff(&mut self, diff: MenuItemDiff) {
if let Some(label) = diff.label { if let Some(label) = diff.label {
let label = label.unwrap_or_default();
match &self.widget { match &self.widget {
TrayMenuWidget::Separator(widget) => widget.set_label(&label), TrayMenuWidget::Separator(widget) => widget.set_label(&label),
TrayMenuWidget::Standard(widget) => widget.set_label(&label), TrayMenuWidget::Standard(widget) => widget.set_label(&label),

View file

@ -2,28 +2,41 @@ mod diff;
mod icon; mod icon;
mod interface; mod interface;
use crate::clients::system_tray::TrayEventReceiver; use crate::clients::tray;
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::modules::tray::diff::get_diffs; use crate::modules::tray::diff::get_diffs;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{glib_recv, spawn}; use crate::{glib_recv, lock, send_async, spawn};
use color_eyre::Result; use color_eyre::{Report, Result};
use gtk::{prelude::*, PackDirection}; use gtk::{prelude::*, PackDirection};
use gtk::{IconTheme, MenuBar}; use gtk::{IconTheme, MenuBar};
use interface::TrayMenu; use interface::TrayMenu;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use system_tray::message::{NotifierItemCommand, NotifierItemMessage}; use system_tray::client::Event;
use system_tray::client::{ActivateRequest, UpdateEvent};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::{debug, error, warn};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct TrayModule { pub struct TrayModule {
#[serde(default = "crate::config::default_true")]
prefer_theme_icons: bool,
#[serde(default = "default_icon_size")]
icon_size: u32,
#[serde(default, deserialize_with = "deserialize_orientation")] #[serde(default, deserialize_with = "deserialize_orientation")]
pub direction: Option<PackDirection>, direction: Option<PackDirection>,
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
} }
const fn default_icon_size() -> u32 {
16
}
fn deserialize_orientation<'de, D>(deserializer: D) -> Result<Option<PackDirection>, D::Error> fn deserialize_orientation<'de, D>(deserializer: D) -> Result<Option<PackDirection>, D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
@ -41,8 +54,8 @@ where
} }
impl Module<MenuBar> for TrayModule { impl Module<MenuBar> for TrayModule {
type SendMessage = NotifierItemMessage; type SendMessage = Event;
type ReceiveMessage = NotifierItemCommand; type ReceiveMessage = ActivateRequest;
fn name() -> &'static str { fn name() -> &'static str {
"tray" "tray"
@ -56,26 +69,39 @@ impl Module<MenuBar> for TrayModule {
) -> Result<()> { ) -> Result<()> {
let tx = context.tx.clone(); let tx = context.tx.clone();
let client = context.client::<TrayEventReceiver>(); let client = context.client::<tray::Client>();
let mut tray_rx = client.subscribe();
let (tray_tx, mut tray_rx) = client.subscribe(); let initial_items = lock!(client.items()).clone();
// listen to tray updates // listen to tray updates
spawn(async move { spawn(async move {
while let Ok(message) = tray_rx.recv().await { for (key, (item, menu)) in initial_items.into_iter() {
tx.send(ModuleUpdateEvent::Update(message)).await?; send_async!(
tx,
ModuleUpdateEvent::Update(Event::Add(key.clone(), item.into()))
);
if let Some(menu) = menu.clone() {
send_async!(
tx,
ModuleUpdateEvent::Update(Event::Update(key, UpdateEvent::Menu(menu)))
);
}
} }
Ok::<(), mpsc::error::SendError<ModuleUpdateEvent<Self::SendMessage>>>(()) while let Ok(message) = tray_rx.recv().await {
send_async!(tx, ModuleUpdateEvent::Update(message))
}
}); });
// send tray commands // send tray commands
spawn(async move { spawn(async move {
while let Some(cmd) = rx.recv().await { while let Some(cmd) = rx.recv().await {
tray_tx.send(cmd).await?; client.activate(cmd).await?;
} }
Ok::<(), mpsc::error::SendError<NotifierItemCommand>>(()) Ok::<_, Report>(())
}); });
Ok(()) Ok(())
@ -106,7 +132,7 @@ impl Module<MenuBar> for TrayModule {
// listen for UI updates // listen for UI updates
glib_recv!(context.subscribe(), update => glib_recv!(context.subscribe(), update =>
on_update(update, &container, &mut menus, &icon_theme, &context.controller_tx) on_update(update, &container, &mut menus, &icon_theme, self.icon_size, self.prefer_theme_icons, &context.controller_tx)
); );
}; };
@ -120,53 +146,81 @@ impl Module<MenuBar> for TrayModule {
/// Handles UI updates as callback, /// Handles UI updates as callback,
/// getting the diff since the previous update and applying it to the menu. /// getting the diff since the previous update and applying it to the menu.
fn on_update( fn on_update(
update: NotifierItemMessage, update: Event,
container: &MenuBar, container: &MenuBar,
menus: &mut HashMap<Box<str>, TrayMenu>, menus: &mut HashMap<Box<str>, TrayMenu>,
icon_theme: &IconTheme, icon_theme: &IconTheme,
tx: &mpsc::Sender<NotifierItemCommand>, icon_size: u32,
prefer_icons: bool,
tx: &mpsc::Sender<ActivateRequest>,
) { ) {
match update { match update {
NotifierItemMessage::Update { Event::Add(address, item) => {
item, debug!("Received new tray item at '{address}': {item:?}");
address,
menu,
} => {
if let (Some(menu_opts), Some(menu_path)) = (menu, &item.menu) {
let submenus = menu_opts.submenus;
let mut menu_item = menus.remove(address.as_str()).unwrap_or_else(|| { let mut menu_item = TrayMenu::new(tx.clone(), address.clone(), *item);
let item = TrayMenu::new(tx.clone(), address.clone(), menu_path.to_string()); container.add(&menu_item.widget);
container.add(&item.widget);
item match icon::get_image(&menu_item, icon_theme, icon_size, prefer_icons) {
}); Ok(image) => menu_item.set_image(&image),
Err(_) => {
let label = item.title.as_ref().unwrap_or(&address); let label = menu_item.title.clone().unwrap_or(address.clone());
if let Some(label_widget) = menu_item.label_widget() { menu_item.set_label(&label)
label_widget.set_label(label);
} }
};
if item.icon_name.as_ref() != menu_item.icon_name() { menu_item.widget.show();
match icon::get_image_from_icon_name(&item, icon_theme) menus.insert(address.into(), menu_item);
.or_else(|| icon::get_image_from_pixmap(&item)) }
{ Event::Update(address, update) => {
Some(image) => menu_item.set_image(&image), debug!("Received tray update for '{address}': {update:?}");
None => menu_item.set_label(label),
}; let Some(menu_item) = menus.get_mut(address.as_str()) else {
error!("Attempted to update menu at '{address}' but could not find it");
return;
};
match update {
UpdateEvent::AttentionIcon(_icon) => {
warn!("received unimplemented NewAttentionIcon event");
} }
UpdateEvent::Icon(icon) => {
if icon.as_ref() != menu_item.icon_name() {
match icon::get_image(menu_item, icon_theme, icon_size, prefer_icons) {
Ok(image) => menu_item.set_image(&image),
Err(_) => menu_item.show_label(),
};
}
let diffs = get_diffs(menu_item.state(), &submenus); menu_item.set_icon_name(icon);
menu_item.apply_diffs(diffs); }
menu_item.widget.show(); UpdateEvent::OverlayIcon(_icon) => {
warn!("received unimplemented NewOverlayIcon event");
}
UpdateEvent::Status(_status) => {
warn!("received unimplemented NewStatus event");
}
UpdateEvent::Title(title) => {
if let Some(label_widget) = menu_item.label_widget() {
label_widget.set_label(&title.unwrap_or_default());
}
}
// UpdateEvent::Tooltip(_tooltip) => {
// warn!("received unimplemented NewAttentionIcon event");
// }
UpdateEvent::Menu(menu) => {
debug!("received new menu for '{}'", address);
menu_item.set_state(submenus); let diffs = get_diffs(menu_item.state(), &menu.submenus);
menu_item.set_icon_name(item.icon_name);
menus.insert(address.into(), menu_item); menu_item.apply_diffs(diffs);
menu_item.set_state(menu.submenus);
}
} }
} }
NotifierItemMessage::Remove { address } => { Event::Remove(address) => {
debug!("Removing tray item at '{address}'");
if let Some(menu) = menus.get(address.as_str()) { if let Some(menu) = menus.get(address.as_str()) {
container.remove(&menu.widget); container.remove(&menu.widget);
} }

View file

@ -199,7 +199,9 @@ impl Module<gtk::Button> for UpowerModule {
let format = format.replace("{percentage}", &properties.percentage.to_string()) let format = format.replace("{percentage}", &properties.percentage.to_string())
.replace("{time_remaining}", &time_remaining) .replace("{time_remaining}", &time_remaining)
.replace("{state}", battery_state_to_string(state)); .replace("{state}", battery_state_to_string(state));
let icon_name = String::from("icon:") + &properties.icon_name;
let mut icon_name = String::from("icon:");
icon_name.push_str(&properties.icon_name);
ImageProvider::parse(&icon_name, &icon_theme, false, self.icon_size) ImageProvider::parse(&icon_name, &icon_theme, false, self.icon_size)
.map(|provider| provider.load_into_image(icon.clone())); .map(|provider| provider.load_into_image(icon.clone()));