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:
commit
4e2352c9e9
22 changed files with 579 additions and 390 deletions
20
.github/scripts/ubuntu_setup.sh
vendored
Executable file
20
.github/scripts/ubuntu_setup.sh
vendored
Executable 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
39
.github/workflows/binary.yml
vendored
Normal 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 }}
|
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
|
@ -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
|
||||||
|
|
6
.github/workflows/deploy.yml
vendored
6
.github/workflows/deploy.yml
vendored
|
@ -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
212
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
15
Cargo.toml
15
Cargo.toml
|
@ -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
7
Cross.toml
Normal 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"
|
12
README.md
12
README.md
|
@ -57,6 +57,8 @@ Ironbar is designed to support anything from a lightweight bar to a full desktop
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
[](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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
4
src/clients/tray.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
use crate::register_client;
|
||||||
|
pub use system_tray::client::Client;
|
||||||
|
|
||||||
|
register_client!(Client, tray);
|
|
@ -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> {
|
||||||
|
|
|
@ -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: >k::Image, scale: i32) -> Result<()> {
|
pub fn create_and_load_surface(pixbuf: &Pixbuf, image: >k::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)
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
Loading…
Add table
Reference in a new issue