Compare commits
26 commits
aa34aa1319
...
47dba6e34a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47dba6e34a | ||
|
|
d2ee6e325f | ||
|
|
2b6ad92082 | ||
|
|
901b3a3d9f | ||
|
|
f9fa83e8a2 | ||
|
|
ae39fb3ce6 | ||
|
|
60fef9751e | ||
|
|
fb52d67f21 | ||
|
|
22cc84ce90 | ||
|
|
b528706ef1 | ||
|
|
482348e3cf | ||
|
|
f2de616522 | ||
|
|
be74c6f0a5 | ||
|
|
e270cde5da | ||
|
|
4b262fce9e | ||
|
|
b8d2b09fa5 | ||
|
|
250d6dd5d3 | ||
|
|
8097330bf4 | ||
|
|
2e23a4f5ce | ||
|
|
1d583b58af | ||
|
|
691d458a70 | ||
|
|
d152af02f6 | ||
|
|
65274d0dd2 | ||
|
|
9e829d3663 | ||
|
|
9b9705520e | ||
|
|
9ed275b6c5 |
21
.forgejo/workflows/mirror.yml
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
name: Mirror to GitHub
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['**']
|
||||||
|
tags: ['**']
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mirror:
|
||||||
|
runs-on: [self-hosted, hestia]
|
||||||
|
steps:
|
||||||
|
- name: Mirror to GitHub
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git clone --mirror "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" repo.git
|
||||||
|
cd repo.git
|
||||||
|
# Mirror only branches and tags (not refs/pull/*, which GitHub rejects);
|
||||||
|
# --prune deletes GitHub refs that no longer exist on Forgejo.
|
||||||
|
git push --prune \
|
||||||
|
"https://x-access-token:${{ secrets.MIRROR_TOKEN }}@github.com/Breadway/breadbar.git" \
|
||||||
|
'+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*'
|
||||||
40
.forgejo/workflows/package.yml
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
name: Build and publish package
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
package:
|
||||||
|
runs-on: [self-hosted, hestia]
|
||||||
|
container:
|
||||||
|
image: archlinux:latest
|
||||||
|
steps:
|
||||||
|
# Note: no actions/checkout — the archlinux image has no Node, which JS
|
||||||
|
# actions require. Everything runs as shell steps and clones manually.
|
||||||
|
- name: Build and publish
|
||||||
|
env:
|
||||||
|
PUBLISH_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
VERSION="${GITHUB_REF_NAME#v}"
|
||||||
|
pacman -Syu --noconfirm base-devel git rust cargo gtk4 gtk4-layer-shell libpulse iw
|
||||||
|
useradd -m builder
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
git clone --branch "${GITHUB_REF_NAME}" --depth 1 \
|
||||||
|
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" /home/builder/src
|
||||||
|
cd /home/builder/src
|
||||||
|
git archive --format=tar.gz --prefix="breadbar-${VERSION}/" HEAD \
|
||||||
|
> packaging/arch/breadbar-${VERSION}.tar.gz
|
||||||
|
SHA=$(sha256sum packaging/arch/breadbar-${VERSION}.tar.gz | awk '{print $1}')
|
||||||
|
sed -i "s/^pkgver=.*/pkgver=${VERSION}/" packaging/arch/PKGBUILD
|
||||||
|
sed -i "s/^sha256sums=.*/sha256sums=('${SHA}')/" packaging/arch/PKGBUILD
|
||||||
|
chown -R builder:builder /home/builder/src
|
||||||
|
# --nocheck: packaging builds the artifact; tests belong in a CI job.
|
||||||
|
su builder -c "cd /home/builder/src/packaging/arch && makepkg -f --noconfirm --nocheck"
|
||||||
|
PKG=$(find /home/builder/src/packaging/arch -name '*.pkg.tar.zst' | head -1)
|
||||||
|
curl -fsS -X PUT \
|
||||||
|
-H "Authorization: token ${PUBLISH_TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
--data-binary "@${PKG}" \
|
||||||
|
"https://git.breadway.dev/api/packages/Breadway/arch/os"
|
||||||
61
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["v*"]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
DL_DIR: /srv/breadway-dl
|
||||||
|
ECOSYSTEM_DIR: /home/breadway/Projects/bread-ecosystem
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: [self-hosted, hestia]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: install build deps
|
||||||
|
run: sudo apt-get install -y libgtk-4-dev libdbus-1-dev pkg-config iw 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: build
|
||||||
|
run: cargo build --release --locked
|
||||||
|
|
||||||
|
- name: prepare artifacts
|
||||||
|
run: |
|
||||||
|
VERSION="${GITHUB_REF_NAME#v}"
|
||||||
|
PKG_DIR="${DL_DIR}/breadbar/${VERSION}"
|
||||||
|
mkdir -p "${PKG_DIR}"
|
||||||
|
cp target/release/breadbar "${PKG_DIR}/breadbar-x86_64"
|
||||||
|
strip "${PKG_DIR}/breadbar-x86_64"
|
||||||
|
sha256sum "${PKG_DIR}/breadbar-x86_64" | awk '{print $1}' \
|
||||||
|
> "${PKG_DIR}/breadbar-x86_64.sha256"
|
||||||
|
cp bakery.toml "${PKG_DIR}/bakery.toml"
|
||||||
|
ln -sfn "${VERSION}" "${DL_DIR}/breadbar/latest"
|
||||||
|
|
||||||
|
- name: ensure bread-ecosystem
|
||||||
|
run: |
|
||||||
|
if [[ -d "${ECOSYSTEM_DIR}/.git" ]]; then
|
||||||
|
git -C "${ECOSYSTEM_DIR}" pull --ff-only
|
||||||
|
else
|
||||||
|
mkdir -p "$(dirname "${ECOSYSTEM_DIR}")"
|
||||||
|
git clone https://github.com/Breadway/bread-ecosystem.git "${ECOSYSTEM_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: regenerate index.json
|
||||||
|
run: bash "${ECOSYSTEM_DIR}/scripts/gen-index.sh"
|
||||||
|
|
||||||
|
- name: upload to GitHub Release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
VERSION="${GITHUB_REF_NAME#v}"
|
||||||
|
PKG_DIR="${DL_DIR}/breadbar/${VERSION}"
|
||||||
|
gh release create "${GITHUB_REF_NAME}" \
|
||||||
|
--title "breadbar v${VERSION}" --generate-notes 2>/dev/null || true
|
||||||
|
gh release upload "${GITHUB_REF_NAME}" \
|
||||||
|
"${PKG_DIR}/breadbar-x86_64" \
|
||||||
|
"${PKG_DIR}/breadbar-x86_64.sha256" \
|
||||||
|
--clobber
|
||||||
37
.gitignore
vendored
|
|
@ -1,2 +1,37 @@
|
||||||
/target
|
# Build artifacts
|
||||||
|
/target/
|
||||||
|
|
||||||
|
# Editor files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS artifacts
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
desktop.ini
|
||||||
|
|
||||||
|
# Environment / secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.env
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.p12
|
||||||
|
secrets/
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Runtime files
|
||||||
|
*.sock
|
||||||
|
*.pid
|
||||||
|
|
||||||
|
# Claude Code session data
|
||||||
|
.claude/
|
||||||
|
|
||||||
|
# Internal design documents (not for distribution)
|
||||||
aster-brief.md
|
aster-brief.md
|
||||||
|
|
|
||||||
579
Cargo.lock
generated
|
|
@ -2,12 +2,30 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.102"
|
version = "1.0.102"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
|
@ -66,25 +84,50 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.5.0"
|
version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.11.1"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bread-theme"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.8#77417d552130281ff787e07d52541eb25e9d533b"
|
||||||
|
dependencies = [
|
||||||
|
"dirs",
|
||||||
|
"gtk4",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "breadbar"
|
name = "breadbar"
|
||||||
version = "0.1.0"
|
version = "0.1.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bread-theme",
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
"gtk4-layer-shell",
|
"gtk4-layer-shell",
|
||||||
"hyprland",
|
"hyprland",
|
||||||
"relm4",
|
"relm4",
|
||||||
|
"resvg",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -93,9 +136,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.20.2"
|
version = "3.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
|
|
@ -109,7 +158,7 @@ version = "0.22.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95"
|
checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.13.0",
|
||||||
"cairo-sys-rs",
|
"cairo-sys-rs",
|
||||||
"glib",
|
"glib",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -128,9 +177,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-expr"
|
name = "cfg-expr"
|
||||||
version = "0.20.7"
|
version = "0.20.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368"
|
checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
|
|
@ -160,12 +209,27 @@ dependencies = [
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.21"
|
version = "0.8.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "data-url"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
|
|
@ -190,10 +254,31 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "dirs"
|
||||||
version = "1.15.0"
|
version = "5.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "endi"
|
name = "endi"
|
||||||
|
|
@ -235,7 +320,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "euclid"
|
||||||
|
version = "0.22.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -268,6 +362,15 @@ dependencies = [
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "field-offset"
|
name = "field-offset"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
|
@ -278,6 +381,22 @@ dependencies = [
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flume"
|
name = "flume"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
|
|
@ -464,6 +583,17 @@ dependencies = [
|
||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
|
@ -518,7 +648,7 @@ dependencies = [
|
||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -547,7 +677,7 @@ version = "0.22.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a"
|
checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.13.0",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-executor",
|
"futures-executor",
|
||||||
|
|
@ -676,7 +806,7 @@ version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4069987ff4793699511a251028cc336b438e46565b463f111250148d574752a"
|
checksum = "a4069987ff4793699511a251028cc336b438e46565b463f111250148d574752a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.13.0",
|
||||||
"gdk4",
|
"gdk4",
|
||||||
"glib",
|
"glib",
|
||||||
"glib-sys",
|
"glib-sys",
|
||||||
|
|
@ -791,6 +921,12 @@ 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 = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "imagesize"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.14.0"
|
version = "2.14.0"
|
||||||
|
|
@ -811,9 +947,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.98"
|
version = "0.3.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
|
@ -827,6 +963,17 @@ version = "3.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kurbo"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"euclid",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leb128fmt"
|
name = "leb128fmt"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -839,6 +986,15 @@ version = "0.2.186"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libredox"
|
||||||
|
version = "0.1.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
|
@ -856,15 +1012,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.29"
|
version = "0.4.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.0"
|
version = "2.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
|
|
@ -876,14 +1032,33 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "miniz_oxide"
|
||||||
version = "1.2.0"
|
version = "0.8.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
|
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -892,6 +1067,12 @@ version = "1.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "option-ext"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-stream"
|
name = "ordered-stream"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -932,35 +1113,18 @@ version = "2.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot"
|
|
||||||
version = "0.12.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
|
||||||
dependencies = [
|
|
||||||
"lock_api",
|
|
||||||
"parking_lot_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot_core"
|
|
||||||
version = "0.9.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"redox_syscall",
|
|
||||||
"smallvec",
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pastey"
|
name = "pastey"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
|
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pico-args"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
|
@ -973,6 +1137,19 @@ version = "0.3.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.17.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.37"
|
version = "0.2.37"
|
||||||
|
|
@ -1023,12 +1200,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_users"
|
||||||
version = "0.5.18"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"getrandom 0.2.17",
|
||||||
|
"libredox",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1065,6 +1244,35 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "resvg"
|
||||||
|
version = "0.44.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a325d5e8d1cebddd070b13f44cec8071594ab67d1012797c121f27a669b7958"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"pico-args",
|
||||||
|
"rgb",
|
||||||
|
"svgtypes",
|
||||||
|
"tiny-skia",
|
||||||
|
"usvg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rgb"
|
||||||
|
version = "0.8.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roxmltree"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|
@ -1080,11 +1288,11 @@ version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.13.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1137,9 +1345,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.149"
|
version = "1.0.150"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|
@ -1178,6 +1386,27 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simplecss"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "siphasher"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
|
|
@ -1192,12 +1421,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.6.3"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1209,6 +1438,25 @@ dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strict-num"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
|
dependencies = [
|
||||||
|
"float-cmp",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "svgtypes"
|
||||||
|
version = "0.15.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
|
||||||
|
dependencies = [
|
||||||
|
"kurbo",
|
||||||
|
"siphasher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.117"
|
version = "2.0.117"
|
||||||
|
|
@ -1235,9 +1483,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "target-lexicon"
|
name = "target-lexicon"
|
||||||
version = "0.13.3"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c"
|
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
|
|
@ -1249,7 +1497,53 @@ dependencies = [
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-skia"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"bytemuck",
|
||||||
|
"cfg-if",
|
||||||
|
"log",
|
||||||
|
"png",
|
||||||
|
"tiny-skia-path",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-skia-path"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"bytemuck",
|
||||||
|
"strict-num",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1261,13 +1555,12 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"parking_lot",
|
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1307,9 +1600,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.25.11+spec-1.1.0"
|
version = "0.25.12+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
|
checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
|
|
@ -1371,7 +1664,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1382,9 +1675,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.13.2"
|
version = "1.13.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
|
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
|
|
@ -1393,10 +1686,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "usvg"
|
||||||
version = "1.23.1"
|
version = "0.44.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
|
checksum = "7447e703d7223b067607655e625e0dbca80822880248937da65966194c4864e6"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"data-url",
|
||||||
|
"flate2",
|
||||||
|
"imagesize",
|
||||||
|
"kurbo",
|
||||||
|
"log",
|
||||||
|
"pico-args",
|
||||||
|
"roxmltree",
|
||||||
|
"simplecss",
|
||||||
|
"siphasher",
|
||||||
|
"strict-num",
|
||||||
|
"svgtypes",
|
||||||
|
"tiny-skia-path",
|
||||||
|
"xmlwriter",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.23.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
|
|
@ -1435,9 +1750,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.121"
|
version = "0.2.122"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -1448,9 +1763,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.121"
|
version = "0.2.122"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
|
|
@ -1458,9 +1773,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.121"
|
version = "0.2.122"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -1471,9 +1786,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.121"
|
version = "0.2.122"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
@ -1506,7 +1821,7 @@ version = "0.244.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.13.0",
|
||||||
"hashbrown 0.15.5",
|
"hashbrown 0.15.5",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"semver",
|
"semver",
|
||||||
|
|
@ -1518,6 +1833,15 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
|
|
@ -1527,6 +1851,63 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
|
@ -1600,7 +1981,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags",
|
"bitflags 2.13.0",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -1637,10 +2018,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
|
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus"
|
name = "xmlwriter"
|
||||||
version = "5.15.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1"
|
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus"
|
||||||
|
version = "5.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-broadcast",
|
"async-broadcast",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
|
|
@ -1659,7 +2046,7 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"uds_windows",
|
"uds_windows",
|
||||||
"uuid",
|
"uuid",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
"winnow",
|
"winnow",
|
||||||
"zbus_macros",
|
"zbus_macros",
|
||||||
"zbus_names",
|
"zbus_names",
|
||||||
|
|
@ -1668,9 +2055,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus_macros"
|
name = "zbus_macros"
|
||||||
version = "5.15.0"
|
version = "5.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff"
|
checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -1700,9 +2087,9 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant"
|
name = "zvariant"
|
||||||
version = "5.11.0"
|
version = "5.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c1567a6ec68df868cbbfde844cfc6d81649fe5109a62b116b19fabd53e618ee"
|
checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"endi",
|
"endi",
|
||||||
"enumflags2",
|
"enumflags2",
|
||||||
|
|
@ -1714,9 +2101,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant_derive"
|
name = "zvariant_derive"
|
||||||
version = "5.11.0"
|
version = "5.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7d5b780599bbde114e39d9a0799577fad1ced5105d38515745f7b3099d8ceda"
|
checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -1727,9 +2114,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant_utils"
|
name = "zvariant_utils"
|
||||||
version = "3.3.1"
|
version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691"
|
checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
||||||
19
Cargo.toml
|
|
@ -1,15 +1,30 @@
|
||||||
[package]
|
[package]
|
||||||
name = "breadbar"
|
name = "breadbar"
|
||||||
version = "0.1.0"
|
version = "0.1.7"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
description = "Minimal status bar and notification daemon for Hyprland on Wayland"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["Breadway <rileyhorsham@gmail.com>"]
|
||||||
|
repository = "https://github.com/breadway/breadbar"
|
||||||
|
keywords = ["wayland", "hyprland", "bar", "status-bar", "gtk4"]
|
||||||
|
categories = ["gui"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.8", features = ["gtk"] }
|
||||||
gtk4 = { version = "0.11", features = ["v4_12"] }
|
gtk4 = { version = "0.11", features = ["v4_12"] }
|
||||||
gtk4-layer-shell = "0.8"
|
gtk4-layer-shell = "0.8"
|
||||||
relm4 = { version = "0.11", features = ["macros"] }
|
relm4 = { version = "0.11", features = ["macros"] }
|
||||||
hyprland = { version = "0.4.0-beta.3", features = ["tokio"] }
|
hyprland = { version = "0.4.0-beta.3", features = ["tokio"] }
|
||||||
futures-lite = "2"
|
futures-lite = "2"
|
||||||
zbus = { version = "5", default-features = false, features = ["tokio"] }
|
zbus = { version = "5", default-features = false, features = ["tokio"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "process", "signal", "sync"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
# Pure-Rust SVG rasteriser (default features off → no text/font deps; the icons
|
||||||
|
# are vector-only). Needed because librsvg dropped its gdk-pixbuf SVG loader.
|
||||||
|
resvg = { version = "0.44", default-features = false }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = "thin"
|
||||||
|
codegen-units = 1
|
||||||
|
strip = "symbols"
|
||||||
|
|
|
||||||
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Riley Horsham
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
BIN
Pasted image.png
|
Before Width: | Height: | Size: 5.4 KiB |
117
README.md
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
# breadbar
|
||||||
|
|
||||||
|
Minimal status bar and notification daemon for [Hyprland](https://hyprland.org/) on Wayland.
|
||||||
|
|
||||||
|
A single Rust binary that provides a full-width top bar, a system tray, and a standards-compliant D-Bus notification daemon. No launcher, no wallpaper logic.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
**Status bar** (anchored to the top of every monitor via `gtk4-layer-shell`):
|
||||||
|
|
||||||
|
- Left: live workspace buttons sourced from Hyprland IPC, active workspace highlighted
|
||||||
|
- Centre: clock (`HH:MM`, updates at the top of each minute)
|
||||||
|
- Right: CPU%, RAM, power draw (W), battery level + AC indicator, Bluetooth state, WiFi SSID with signal strength, system tray (SNI)
|
||||||
|
|
||||||
|
**Notification daemon**:
|
||||||
|
|
||||||
|
- Implements `org.freedesktop.Notifications` (D-Bus) — works with any standard notification sender (`notify-send`, etc.)
|
||||||
|
- Popups appear top-right, stack vertically, auto-dismiss after the sender-specified timeout (default 5 s)
|
||||||
|
- Supports `CloseNotification`
|
||||||
|
|
||||||
|
**Theming**:
|
||||||
|
|
||||||
|
- Reads `~/.cache/wal/colors.json` (pywal) on startup for a palette that matches your wallpaper
|
||||||
|
- Falls back to a Catppuccin Mocha palette if pywal is not present
|
||||||
|
- User CSS override: `~/.config/breadbar/style.css`
|
||||||
|
- Send `SIGHUP` to reload the theme at runtime (integrates with wallpaper-change hooks)
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Runtime:
|
||||||
|
|
||||||
|
- GTK4 (≥ 4.12)
|
||||||
|
- `gtk4-layer-shell`
|
||||||
|
- `iw` — for WiFi SSID/signal (`iw dev <iface> link`)
|
||||||
|
- A running Hyprland compositor
|
||||||
|
- D-Bus session bus
|
||||||
|
|
||||||
|
Bluetooth status is read from `/sys/class/rfkill` and BlueZ D-Bus; it degrades gracefully if unavailable.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
The binary is at `target/release/breadbar`.
|
||||||
|
|
||||||
|
Requirements: Rust 1.77+ (uses `LazyLock`), a GTK4 development environment (`libgtk-4-dev` / `gtk4` package).
|
||||||
|
|
||||||
|
On Arch Linux:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo pacman -S gtk4 gtk4-layer-shell iw
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./target/release/breadbar
|
||||||
|
```
|
||||||
|
|
||||||
|
Typically launched from your Hyprland config:
|
||||||
|
|
||||||
|
```
|
||||||
|
exec-once = /path/to/breadbar
|
||||||
|
```
|
||||||
|
|
||||||
|
breadbar claims `org.freedesktop.Notifications` on the session D-Bus on startup. If another notification daemon is already running, startup will fail — stop the other daemon first.
|
||||||
|
|
||||||
|
## Theming
|
||||||
|
|
||||||
|
### pywal integration
|
||||||
|
|
||||||
|
breadbar reads `~/.cache/wal/colors.json` automatically. To reload after a wallpaper change:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pkill -HUP breadbar
|
||||||
|
```
|
||||||
|
|
||||||
|
Or hook it into your wallpaper script:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
wal -i /path/to/wallpaper.jpg
|
||||||
|
pkill -HUP breadbar
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom CSS
|
||||||
|
|
||||||
|
Drop a `~/.config/breadbar/style.css` file and send `SIGHUP` to reload. This CSS is applied at a higher priority than the pywal palette so you can override anything.
|
||||||
|
|
||||||
|
Example — change the font size:
|
||||||
|
|
||||||
|
```css
|
||||||
|
* {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
| Module | Responsibility |
|
||||||
|
|---|---|
|
||||||
|
| `src/main.rs` | GTK4 app entry point, widget tree, `relm4` component |
|
||||||
|
| `src/bar/workspaces.rs` | Hyprland IPC event stream, workspace buttons |
|
||||||
|
| `src/bar/clock.rs` | Minute-tick clock |
|
||||||
|
| `src/bar/stats.rs` | Polling loop: CPU, RAM, power, battery, Bluetooth, WiFi |
|
||||||
|
| `src/bar/tray.rs` | `org.kde.StatusNotifierWatcher` D-Bus service, SNI item rendering |
|
||||||
|
| `src/notifications/mod.rs` | `org.freedesktop.Notifications` zbus service |
|
||||||
|
| `src/notifications/popup.rs` | Layer-shell popup window and card stack |
|
||||||
|
| `src/theme.rs` | pywal reader, GTK CSS provider injection |
|
||||||
|
|
||||||
|
Stats are polled every 2 seconds. Bluetooth and WiFi are sampled every 16 seconds and cached in between to avoid hammering D-Bus and `iw`.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
1
assets/AC Power.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plug-zap-icon lucide-plug-zap"><path d="M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z"/><path d="m2 22 3-3"/><path d="M7.5 13.5 10 11"/><path d="M10.5 16.5 13 14"/><path d="m18 3-4 4h6l-4 4"/></svg>
|
||||||
|
After Width: | Height: | Size: 424 B |
1
assets/Battery 1 Bar.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-battery-low-icon lucide-battery-low"><path d="M22 14v-4"/><path d="M6 14v-4"/><rect x="2" y="6" width="16" height="12" rx="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 335 B |
1
assets/Battery 2 Bars.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-battery-medium-icon lucide-battery-medium"><path d="M10 14v-4"/><path d="M22 14v-4"/><path d="M6 14v-4"/><rect x="2" y="6" width="16" height="12" rx="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 362 B |
1
assets/Battery 3 Bars.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-battery-full-icon lucide-battery-full"><path d="M10 10v4"/><path d="M14 10v4"/><path d="M22 14v-4"/><path d="M6 10v4"/><rect x="2" y="6" width="16" height="12" rx="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 376 B |
1
assets/Bluetooth Connected.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bluetooth-connected-icon lucide-bluetooth-connected"><path d="m7 7 10 10-5 5V2l5 5L7 17"/><line x1="18" x2="21" y1="12" y2="12"/><line x1="3" x2="6" y1="12" y2="12"/></svg>
|
||||||
|
After Width: | Height: | Size: 374 B |
1
assets/Bluetooth Off.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bluetooth-off-icon lucide-bluetooth-off"><path d="m17 17-5 5V12l-5 5"/><path d="m2 2 20 20"/><path d="M14.5 9.5 17 7l-5-5v4.5"/></svg>
|
||||||
|
After Width: | Height: | Size: 336 B |
|
|
@ -1 +1 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-battery-icon lucide-battery"><path d="M 22 14 L 22 10"/><rect x="2" y="6" width="16" height="12" rx="2"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bluetooth-icon lucide-bluetooth"><path d="m7 7 10 10-5 5V2l5 5L7 17"/></svg>
|
||||||
|
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 278 B |
1
assets/WiFi Disconnect.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-wifi-off-icon lucide-wifi-off"><path d="M12 20h.01"/><path d="M8.5 16.429a5 5 0 0 1 7 0"/><path d="M5 12.859a10 10 0 0 1 5.17-2.69"/><path d="M19 12.859a10 10 0 0 0-2.007-1.523"/><path d="M2 8.82a15 15 0 0 1 4.177-2.643"/><path d="M22 8.82a15 15 0 0 0-11.288-3.764"/><path d="m2 2 20 20"/></svg>
|
||||||
|
After Width: | Height: | Size: 497 B |
12
bakery.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
name = "breadbar"
|
||||||
|
description = "Minimal status bar and notification daemon for Hyprland"
|
||||||
|
binaries = ["breadbar"]
|
||||||
|
system_deps = ["gtk4", "gtk4-layer-shell", "iw", "libpulse"]
|
||||||
|
optional_system_deps = ["hyprland"]
|
||||||
|
bread_deps = []
|
||||||
|
|
||||||
|
[config]
|
||||||
|
dir = "~/.config/breadbar"
|
||||||
|
|
||||||
|
[install]
|
||||||
|
post_install = []
|
||||||
36
packaging/arch/PKGBUILD
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Maintainer: Breadway <rileyhorsham@gmail.com>
|
||||||
|
|
||||||
|
pkgname=breadbar
|
||||||
|
pkgver=0.1.0
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc="Minimal status bar and notification daemon for Hyprland"
|
||||||
|
arch=('x86_64')
|
||||||
|
url="https://github.com/Breadway/breadbar"
|
||||||
|
license=('MIT')
|
||||||
|
# Some Rust deps (ring/mlua) build vendored C/asm into static archives; makepkg's
|
||||||
|
# default -flto=auto emits GCC LTO bitcode the Rust (lld) link cannot read,
|
||||||
|
# causing undefined-symbol errors. Disable LTO.
|
||||||
|
options=(!lto !debug)
|
||||||
|
depends=('gtk4' 'gtk4-layer-shell' 'libpulse' 'iw')
|
||||||
|
optdepends=(
|
||||||
|
'hyprland: workspace and window data integration'
|
||||||
|
)
|
||||||
|
makedepends=('rust' 'cargo')
|
||||||
|
source=("${pkgname}-${pkgver}.tar.gz")
|
||||||
|
sha256sums=('SKIP')
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||||
|
cargo build --release --locked
|
||||||
|
}
|
||||||
|
|
||||||
|
check() {
|
||||||
|
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||||
|
cargo test --release --locked
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||||
|
install -Dm755 target/release/breadbar "${pkgdir}/usr/bin/breadbar"
|
||||||
|
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,9 @@ use relm4::ComponentSender;
|
||||||
|
|
||||||
pub fn current() -> String {
|
pub fn current() -> String {
|
||||||
let dt = gtk4::glib::DateTime::now_local().expect("local time");
|
let dt = gtk4::glib::DateTime::now_local().expect("local time");
|
||||||
format!("{:02}:{:02}", dt.hour(), dt.minute())
|
let date = dt.format("%a %d/%m").expect("date format");
|
||||||
|
let time = format!("{:02}:{:02}", dt.hour(), dt.minute());
|
||||||
|
format!("{} {}", date, time)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_ticker(sender: ComponentSender<App>) {
|
pub fn spawn_ticker(sender: ComponentSender<App>) {
|
||||||
|
|
@ -11,8 +13,7 @@ pub fn spawn_ticker(sender: ComponentSender<App>) {
|
||||||
loop {
|
loop {
|
||||||
sender.input(AppInput::ClockTick);
|
sender.input(AppInput::ClockTick);
|
||||||
// Sleep until the top of the next minute — display is HH:MM only.
|
// Sleep until the top of the next minute — display is HH:MM only.
|
||||||
let secs = gtk4::glib::DateTime::now_local()
|
let secs = gtk4::glib::DateTime::now_local().map_or(0, |dt| dt.second());
|
||||||
.map_or(0, |dt| dt.second());
|
|
||||||
let wait = (60 - secs.rem_euclid(60)) as u64;
|
let wait = (60 - secs.rem_euclid(60)) as u64;
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(wait.max(1))).await;
|
tokio::time::sleep(std::time::Duration::from_secs(wait.max(1))).await;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod clock;
|
pub mod clock;
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
|
pub mod tray;
|
||||||
pub mod workspaces;
|
pub mod workspaces;
|
||||||
|
|
|
||||||
202
src/bar/stats.rs
|
|
@ -8,11 +8,32 @@ use std::{
|
||||||
LazyLock, Mutex, OnceLock,
|
LazyLock, Mutex, OnceLock,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use tokio::sync::OnceCell as AsyncOnce;
|
||||||
|
|
||||||
pub const WIFI_STRONG: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Strong.svg");
|
static WIFI_IFACE: OnceLock<Option<String>> = OnceLock::new();
|
||||||
pub const WIFI_MEDIUM: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Medium.svg");
|
static BT_CONN: AsyncOnce<zbus::Connection> = AsyncOnce::const_new();
|
||||||
pub const WIFI_WEAK: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Weak.svg");
|
static BT_CACHE: LazyLock<Mutex<&'static str>> = LazyLock::new(|| Mutex::new(BT_OFF));
|
||||||
pub const WIFI_OFF: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Connecting.svg");
|
static BT_TICK: AtomicU8 = AtomicU8::new(0);
|
||||||
|
|
||||||
|
// Embedded SVG contents (not paths). These &str constants double as stable
|
||||||
|
// HashMap keys via their .as_ptr(); include_str! keeps each one a single
|
||||||
|
// 'static literal, so pointer identity still holds.
|
||||||
|
pub const WIFI_STRONG: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Strong.svg"));
|
||||||
|
pub const WIFI_MEDIUM: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Medium.svg"));
|
||||||
|
pub const WIFI_WEAK: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Weak.svg"));
|
||||||
|
pub const WIFI_OFF: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Disconnect.svg"));
|
||||||
|
|
||||||
|
pub const BAT_HIGH: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Battery 3 Bars.svg"));
|
||||||
|
pub const BAT_MID: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Battery 2 Bars.svg"));
|
||||||
|
pub const BAT_LOW: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Battery 1 Bar.svg"));
|
||||||
|
pub const AC_POWER: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/AC Power.svg"));
|
||||||
|
|
||||||
|
pub const BT_OFF: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Bluetooth Off.svg"));
|
||||||
|
pub const BT_ON: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Bluetooth.svg"));
|
||||||
|
pub const BT_CONNECTED: &str = include_str!(concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/assets/Bluetooth Connected.svg"
|
||||||
|
));
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Stats {
|
pub struct Stats {
|
||||||
|
|
@ -20,6 +41,9 @@ pub struct Stats {
|
||||||
pub mem: String,
|
pub mem: String,
|
||||||
pub power: String,
|
pub power: String,
|
||||||
pub bat: String,
|
pub bat: String,
|
||||||
|
pub bat_icon: &'static str,
|
||||||
|
pub ac_connected: bool,
|
||||||
|
pub bt_icon: &'static str,
|
||||||
pub wifi_ssid: String,
|
pub wifi_ssid: String,
|
||||||
pub wifi_icon: &'static str,
|
pub wifi_icon: &'static str,
|
||||||
}
|
}
|
||||||
|
|
@ -31,6 +55,7 @@ struct CpuSnapshot {
|
||||||
|
|
||||||
static PREV_CPU: OnceLock<Mutex<CpuSnapshot>> = OnceLock::new();
|
static PREV_CPU: OnceLock<Mutex<CpuSnapshot>> = OnceLock::new();
|
||||||
static BAT_PATH: OnceLock<Option<PathBuf>> = OnceLock::new();
|
static BAT_PATH: OnceLock<Option<PathBuf>> = OnceLock::new();
|
||||||
|
static AC_PATH: OnceLock<Option<PathBuf>> = OnceLock::new();
|
||||||
static WIFI_CACHE: LazyLock<Mutex<(String, &'static str)>> =
|
static WIFI_CACHE: LazyLock<Mutex<(String, &'static str)>> =
|
||||||
LazyLock::new(|| Mutex::new(("—".to_string(), WIFI_OFF)));
|
LazyLock::new(|| Mutex::new(("—".to_string(), WIFI_OFF)));
|
||||||
static WIFI_TICK: AtomicU8 = AtomicU8::new(0);
|
static WIFI_TICK: AtomicU8 = AtomicU8::new(0);
|
||||||
|
|
@ -38,16 +63,21 @@ static WIFI_TICK: AtomicU8 = AtomicU8::new(0);
|
||||||
fn read_cpu() -> f32 {
|
fn read_cpu() -> f32 {
|
||||||
let text = fs::read_to_string("/proc/stat").unwrap_or_default();
|
let text = fs::read_to_string("/proc/stat").unwrap_or_default();
|
||||||
let line = text.lines().next().unwrap_or_default();
|
let line = text.lines().next().unwrap_or_default();
|
||||||
let vals: Vec<u64> = line
|
let mut total = 0u64;
|
||||||
.split_whitespace()
|
let mut idle = 0u64;
|
||||||
.skip(1)
|
let mut count = 0usize;
|
||||||
.filter_map(|s| s.parse().ok())
|
for (i, s) in line.split_whitespace().skip(1).enumerate() {
|
||||||
.collect();
|
if let Ok(v) = s.parse::<u64>() {
|
||||||
if vals.len() < 5 {
|
total += v;
|
||||||
|
if i == 3 || i == 4 {
|
||||||
|
idle += v;
|
||||||
|
}
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count < 5 {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
let idle = vals[3] + vals.get(4).copied().unwrap_or(0);
|
|
||||||
let total: u64 = vals.iter().sum();
|
|
||||||
|
|
||||||
let state = PREV_CPU.get_or_init(|| Mutex::new(CpuSnapshot { total, idle }));
|
let state = PREV_CPU.get_or_init(|| Mutex::new(CpuSnapshot { total, idle }));
|
||||||
let mut prev = state.lock().unwrap();
|
let mut prev = state.lock().unwrap();
|
||||||
|
|
@ -65,13 +95,23 @@ fn read_ram() -> u64 {
|
||||||
let text = fs::read_to_string("/proc/meminfo").unwrap_or_default();
|
let text = fs::read_to_string("/proc/meminfo").unwrap_or_default();
|
||||||
let mut total = 0u64;
|
let mut total = 0u64;
|
||||||
let mut avail = 0u64;
|
let mut avail = 0u64;
|
||||||
|
let mut found = 0u8;
|
||||||
for line in text.lines() {
|
for line in text.lines() {
|
||||||
let mut parts = line.split_whitespace();
|
let mut parts = line.split_whitespace();
|
||||||
match parts.next() {
|
match parts.next() {
|
||||||
Some("MemTotal:") => total = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0),
|
Some("MemTotal:") => {
|
||||||
Some("MemAvailable:") => avail = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0),
|
total = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
|
||||||
|
found += 1;
|
||||||
|
}
|
||||||
|
Some("MemAvailable:") => {
|
||||||
|
avail = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
|
||||||
|
found += 1;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
if found == 2 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
total.saturating_sub(avail)
|
total.saturating_sub(avail)
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +123,10 @@ fn bat_path() -> Option<&'static PathBuf> {
|
||||||
.ok()?
|
.ok()?
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
.map(|e| e.path())
|
.map(|e| e.path())
|
||||||
.find(|p| p.file_name().map_or(false, |n| n.to_string_lossy().starts_with("BAT")))
|
.find(|p| {
|
||||||
|
p.file_name()
|
||||||
|
.is_some_and(|n| n.to_string_lossy().starts_with("BAT"))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.as_ref()
|
.as_ref()
|
||||||
}
|
}
|
||||||
|
|
@ -116,26 +159,103 @@ fn read_battery() -> Option<u8> {
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_wifi() -> (String, &'static str) {
|
fn bat_level_icon(pct: u8) -> &'static str {
|
||||||
let dev_out = tokio::process::Command::new("iw")
|
if pct >= 67 {
|
||||||
.arg("dev")
|
BAT_HIGH
|
||||||
.output()
|
} else if pct >= 34 {
|
||||||
.await
|
BAT_MID
|
||||||
.ok();
|
} else {
|
||||||
let dev_stdout = match dev_out {
|
BAT_LOW
|
||||||
Some(o) if o.status.success() => String::from_utf8_lossy(&o.stdout).into_owned(),
|
}
|
||||||
_ => return ("—".into(), WIFI_OFF),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let iface = dev_stdout
|
fn read_ac() -> bool {
|
||||||
.lines()
|
AC_PATH
|
||||||
.find_map(|l| l.trim().strip_prefix("Interface ").map(str::to_string));
|
.get_or_init(|| {
|
||||||
let Some(iface) = iface else {
|
fs::read_dir("/sys/class/power_supply")
|
||||||
|
.ok()?
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.map(|e| e.path())
|
||||||
|
.find(|p| {
|
||||||
|
fs::read_to_string(p.join("type"))
|
||||||
|
.map(|t| t.trim() == "Mains")
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|p| fs::read_to_string(p.join("online")).ok())
|
||||||
|
.map(|s| s.trim() == "1")
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bt_rfkill_on() -> bool {
|
||||||
|
fs::read_dir("/sys/class/rfkill")
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.any(|e| {
|
||||||
|
let p = e.path();
|
||||||
|
fs::read_to_string(p.join("type"))
|
||||||
|
.map(|t| t.trim() == "bluetooth")
|
||||||
|
.unwrap_or(false)
|
||||||
|
&& fs::read_to_string(p.join("state"))
|
||||||
|
.map(|s| s.trim() == "1")
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_bt() -> &'static str {
|
||||||
|
if !bt_rfkill_on() {
|
||||||
|
return BT_OFF;
|
||||||
|
}
|
||||||
|
bt_connected_icon().await.unwrap_or(BT_ON)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn bt_connected_icon() -> Option<&'static str> {
|
||||||
|
let conn = BT_CONN
|
||||||
|
.get_or_try_init(zbus::Connection::system)
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
let mgr = zbus::fdo::ObjectManagerProxy::builder(conn)
|
||||||
|
.destination("org.bluez")
|
||||||
|
.ok()?
|
||||||
|
.path("/")
|
||||||
|
.ok()?
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
let objects = mgr.get_managed_objects().await.ok()?;
|
||||||
|
let connected = objects
|
||||||
|
.values()
|
||||||
|
.filter_map(|ifaces| ifaces.get("org.bluez.Device1"))
|
||||||
|
.any(|props| {
|
||||||
|
props
|
||||||
|
.get("Connected")
|
||||||
|
.and_then(|v| bool::try_from(v.clone()).ok())
|
||||||
|
.unwrap_or(false)
|
||||||
|
});
|
||||||
|
Some(if connected { BT_CONNECTED } else { BT_ON })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wifi_iface() -> Option<&'static str> {
|
||||||
|
WIFI_IFACE
|
||||||
|
.get_or_init(|| {
|
||||||
|
fs::read_dir("/sys/class/net")
|
||||||
|
.ok()?
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.find(|e| e.path().join("wireless").is_dir())
|
||||||
|
.map(|e| e.file_name().to_string_lossy().into_owned())
|
||||||
|
})
|
||||||
|
.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_wifi() -> (String, &'static str) {
|
||||||
|
let Some(iface) = wifi_iface() else {
|
||||||
return ("—".into(), WIFI_OFF);
|
return ("—".into(), WIFI_OFF);
|
||||||
};
|
};
|
||||||
|
|
||||||
let link_out = tokio::process::Command::new("iw")
|
let link_out = tokio::process::Command::new("iw")
|
||||||
.args(["dev", &iface, "link"])
|
.args(["dev", iface, "link"])
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
@ -172,11 +292,24 @@ pub async fn poll() -> Stats {
|
||||||
let cpu = read_cpu();
|
let cpu = read_cpu();
|
||||||
let mem = read_ram();
|
let mem = read_ram();
|
||||||
let power = read_power().map_or_else(|| " —W".into(), |w| format!("{w:4.1}W"));
|
let power = read_power().map_or_else(|| " —W".into(), |w| format!("{w:4.1}W"));
|
||||||
let bat = read_battery().map_or_else(|| " —".into(), |p| format!("{p:3}%"));
|
let pct = read_battery();
|
||||||
// Refresh WiFi every 8 cycles (~16 s); cache the result in between.
|
let bat = pct.map_or_else(|| " —".into(), |p| format!("{p:3}%"));
|
||||||
|
let bat_icon = pct.map_or(BAT_MID, bat_level_icon);
|
||||||
|
let ac_connected = read_ac();
|
||||||
|
// BT and WiFi both refresh every 8 cycles (~16 s); cache in between.
|
||||||
|
let bt_icon = {
|
||||||
|
let tick = BT_TICK.fetch_add(1, Ordering::Relaxed);
|
||||||
|
if tick.is_multiple_of(8) {
|
||||||
|
let fresh = read_bt().await;
|
||||||
|
*BT_CACHE.lock().unwrap() = fresh;
|
||||||
|
fresh
|
||||||
|
} else {
|
||||||
|
*BT_CACHE.lock().unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
let (wifi_ssid, wifi_icon) = {
|
let (wifi_ssid, wifi_icon) = {
|
||||||
let tick = WIFI_TICK.fetch_add(1, Ordering::Relaxed);
|
let tick = WIFI_TICK.fetch_add(1, Ordering::Relaxed);
|
||||||
if tick % 8 == 0 {
|
if tick.is_multiple_of(8) {
|
||||||
let fresh = read_wifi().await;
|
let fresh = read_wifi().await;
|
||||||
*WIFI_CACHE.lock().unwrap() = fresh.clone();
|
*WIFI_CACHE.lock().unwrap() = fresh.clone();
|
||||||
fresh
|
fresh
|
||||||
|
|
@ -193,6 +326,9 @@ pub async fn poll() -> Stats {
|
||||||
},
|
},
|
||||||
power,
|
power,
|
||||||
bat,
|
bat,
|
||||||
|
bat_icon,
|
||||||
|
ac_connected,
|
||||||
|
bt_icon,
|
||||||
wifi_ssid,
|
wifi_ssid,
|
||||||
wifi_icon,
|
wifi_icon,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
240
src/bar/tray.rs
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
use crate::{App, AppInput};
|
||||||
|
use futures_lite::StreamExt;
|
||||||
|
use gtk4::prelude::Cast;
|
||||||
|
use relm4::ComponentSender;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use zbus::{interface, object_server::SignalEmitter};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TrayIconData {
|
||||||
|
Pixels { width: i32, height: i32, data: Vec<u8> },
|
||||||
|
Name(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TrayUpdate {
|
||||||
|
Add { id: String, icon: Option<TrayIconData>, title: String },
|
||||||
|
Remove { id: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WatcherState {
|
||||||
|
items: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Watcher {
|
||||||
|
state: Arc<Mutex<WatcherState>>,
|
||||||
|
tx: tokio::sync::mpsc::UnboundedSender<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[interface(name = "org.kde.StatusNotifierWatcher")]
|
||||||
|
impl Watcher {
|
||||||
|
async fn register_status_notifier_item(
|
||||||
|
&self,
|
||||||
|
service: String,
|
||||||
|
#[zbus(header)] header: zbus::message::Header<'_>,
|
||||||
|
#[zbus(signal_emitter)] ctx: SignalEmitter<'_>,
|
||||||
|
) {
|
||||||
|
let sender_name = header.sender().map(|s| s.to_string()).unwrap_or_default();
|
||||||
|
let (bus, path) = parse_service(&service, &sender_name);
|
||||||
|
let full = format!("{}{}", bus, path);
|
||||||
|
{
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
if !state.items.contains(&full) {
|
||||||
|
state.items.push(full.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = Self::status_notifier_item_registered(&ctx, &full).await;
|
||||||
|
let _ = self.tx.send((bus, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn register_status_notifier_host(
|
||||||
|
&self,
|
||||||
|
_service: String,
|
||||||
|
#[zbus(signal_emitter)] ctx: SignalEmitter<'_>,
|
||||||
|
) {
|
||||||
|
let _ = Self::status_notifier_host_registered(&ctx).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
fn registered_status_notifier_items(&self) -> Vec<String> {
|
||||||
|
self.state.lock().unwrap().items.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
fn is_status_notifier_host_registered(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
fn protocol_version(&self) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(signal)]
|
||||||
|
async fn status_notifier_item_registered(
|
||||||
|
ctx: &SignalEmitter<'_>,
|
||||||
|
service: &str,
|
||||||
|
) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
#[zbus(signal)]
|
||||||
|
async fn status_notifier_item_unregistered(
|
||||||
|
ctx: &SignalEmitter<'_>,
|
||||||
|
service: &str,
|
||||||
|
) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
#[zbus(signal)]
|
||||||
|
async fn status_notifier_host_registered(ctx: &SignalEmitter<'_>) -> zbus::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_service(service: &str, sender: &str) -> (String, String) {
|
||||||
|
if service.starts_with('/') {
|
||||||
|
return (sender.to_string(), service.to_string());
|
||||||
|
}
|
||||||
|
match service.find('/') {
|
||||||
|
Some(slash) => (service[..slash].to_string(), service[slash..].to_string()),
|
||||||
|
None => (service.to_string(), "/StatusNotifierItem".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_item(
|
||||||
|
conn: &zbus::Connection,
|
||||||
|
bus: &str,
|
||||||
|
path: &str,
|
||||||
|
) -> (Option<TrayIconData>, String) {
|
||||||
|
let Ok(proxy) = zbus::Proxy::new(conn, bus, path, "org.kde.StatusNotifierItem").await else {
|
||||||
|
return (None, String::new());
|
||||||
|
};
|
||||||
|
let icon = read_icon(&proxy).await;
|
||||||
|
let title = proxy.get_property::<String>("Title").await.unwrap_or_default();
|
||||||
|
(icon, title)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_icon(proxy: &zbus::Proxy<'_>) -> Option<TrayIconData> {
|
||||||
|
let pixmaps: Vec<(i32, i32, Vec<u8>)> =
|
||||||
|
proxy.get_property("IconPixmap").await.unwrap_or_default();
|
||||||
|
|
||||||
|
if !pixmaps.is_empty() {
|
||||||
|
return pixmaps
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(w, h, _)| *w > 0 && *h > 0)
|
||||||
|
.min_by_key(|(w, h, _)| (w.max(h) - 22).abs())
|
||||||
|
.map(|(width, height, data)| TrayIconData::Pixels { width, height, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
let name: String = proxy.get_property("IconName").await.ok()?;
|
||||||
|
if name.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(TrayIconData::Name(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call `Activate(0, 0)` on the SNI item identified by `id` (`{bus}{path}`).
|
||||||
|
pub fn spawn_activate(id: String) {
|
||||||
|
relm4::spawn(async move {
|
||||||
|
let (bus, path) = match id.find('/') {
|
||||||
|
Some(slash) => (id[..slash].to_string(), id[slash..].to_string()),
|
||||||
|
None => (id, "/StatusNotifierItem".to_string()),
|
||||||
|
};
|
||||||
|
let Ok(conn) = zbus::Connection::session().await else { return };
|
||||||
|
let Ok(proxy) = zbus::Proxy::new(&conn, bus.as_str(), path.as_str(), "org.kde.StatusNotifierItem").await else { return };
|
||||||
|
let _ = proxy.call_method("Activate", &(0i32, 0i32)).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_watcher(sender: ComponentSender<App>) {
|
||||||
|
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<(String, String)>();
|
||||||
|
// Maps bus name → item ids, shared between registration and cleanup tasks.
|
||||||
|
let bus_map: Arc<Mutex<HashMap<String, Vec<String>>>> = Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
let bus_map_cleanup = bus_map.clone();
|
||||||
|
let sender_cleanup = sender.clone();
|
||||||
|
|
||||||
|
// Registration task — owns the watcher service and processes new items.
|
||||||
|
relm4::spawn(async move {
|
||||||
|
let watcher = Watcher {
|
||||||
|
state: Arc::new(Mutex::new(WatcherState { items: Vec::new() })),
|
||||||
|
tx,
|
||||||
|
};
|
||||||
|
// Builder steps fail only on invalid static strings — safe to unwrap.
|
||||||
|
let conn = zbus::connection::Builder::session()
|
||||||
|
.unwrap()
|
||||||
|
.name("org.kde.StatusNotifierWatcher")
|
||||||
|
.unwrap()
|
||||||
|
.serve_at("/StatusNotifierWatcher", watcher)
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.expect("failed to register org.kde.StatusNotifierWatcher");
|
||||||
|
|
||||||
|
while let Some((bus, path)) = rx.recv().await {
|
||||||
|
let (icon, title) = read_item(&conn, &bus, &path).await;
|
||||||
|
let id = format!("{}{}", bus, path);
|
||||||
|
bus_map.lock().unwrap().entry(bus).or_default().push(id.clone());
|
||||||
|
sender.input(AppInput::TrayUpdate(TrayUpdate::Add { id, icon, title }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cleanup task — watches NameOwnerChanged and removes items when their owner exits.
|
||||||
|
relm4::spawn(async move {
|
||||||
|
let Ok(conn) = zbus::Connection::session().await else { return };
|
||||||
|
let Ok(proxy) = zbus::fdo::DBusProxy::new(&conn).await else { return };
|
||||||
|
let Ok(mut stream) = proxy.receive_name_owner_changed().await else { return };
|
||||||
|
while let Some(signal) = stream.next().await {
|
||||||
|
let Ok(args) = signal.args() else { continue };
|
||||||
|
if args.new_owner().is_none() {
|
||||||
|
let gone = args.name().to_string();
|
||||||
|
if let Some(ids) = bus_map_cleanup.lock().unwrap().remove(&gone) {
|
||||||
|
for id in ids {
|
||||||
|
sender_cleanup.input(AppInput::TrayUpdate(TrayUpdate::Remove { id }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert SNI ARGB pixel data (network byte order) to a GTK4 `Image`.
|
||||||
|
/// Falls back to an icon-name lookup or a placeholder on failure.
|
||||||
|
pub fn make_tray_image(icon: Option<&TrayIconData>) -> gtk4::Image {
|
||||||
|
let img = match icon {
|
||||||
|
Some(TrayIconData::Pixels { width, height, data }) => pixels_to_image(*width, *height, data),
|
||||||
|
Some(TrayIconData::Name(name)) => {
|
||||||
|
let img = gtk4::Image::from_icon_name(name);
|
||||||
|
img.set_pixel_size(16);
|
||||||
|
Some(img)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
img.unwrap_or_else(|| {
|
||||||
|
let img = gtk4::Image::from_icon_name("image-missing");
|
||||||
|
img.set_pixel_size(16);
|
||||||
|
img
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pixels_to_image(width: i32, height: i32, data: &[u8]) -> Option<gtk4::Image> {
|
||||||
|
if data.len() != (width * height * 4) as usize {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// SNI delivers ARGB big-endian: bytes are [A, R, G, B] per pixel.
|
||||||
|
// GTK4 R8g8b8a8 expects [R, G, B, A] per pixel.
|
||||||
|
let mut rgba = Vec::with_capacity(data.len());
|
||||||
|
for chunk in data.chunks_exact(4) {
|
||||||
|
rgba.push(chunk[1]);
|
||||||
|
rgba.push(chunk[2]);
|
||||||
|
rgba.push(chunk[3]);
|
||||||
|
rgba.push(chunk[0]);
|
||||||
|
}
|
||||||
|
let bytes = gtk4::glib::Bytes::from_owned(rgba);
|
||||||
|
let tex = gtk4::gdk::MemoryTexture::new(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
gtk4::gdk::MemoryFormat::R8g8b8a8,
|
||||||
|
&bytes,
|
||||||
|
(width * 4) as usize,
|
||||||
|
);
|
||||||
|
let tex: gtk4::gdk::Texture = tex.upcast();
|
||||||
|
let img = gtk4::Image::from_paintable(Some(&tex));
|
||||||
|
img.set_pixel_size(16);
|
||||||
|
Some(img)
|
||||||
|
}
|
||||||
|
|
@ -44,7 +44,9 @@ pub fn make_button(id: WorkspaceId, name: &str, active: WorkspaceId) -> gtk4::Bu
|
||||||
}
|
}
|
||||||
btn.connect_clicked(move |_| {
|
btn.connect_clicked(move |_| {
|
||||||
use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial};
|
use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial};
|
||||||
let _ = Dispatch::call(DispatchType::Workspace(WorkspaceIdentifierWithSpecial::Id(id)));
|
let _ = Dispatch::call(DispatchType::Workspace(WorkspaceIdentifierWithSpecial::Id(
|
||||||
|
id,
|
||||||
|
)));
|
||||||
});
|
});
|
||||||
btn
|
btn
|
||||||
}
|
}
|
||||||
|
|
|
||||||
181
src/main.rs
|
|
@ -1,11 +1,16 @@
|
||||||
|
// Embed asset SVGs into the binary at compile time. Previously these were
|
||||||
|
// referenced by their build-time filesystem path (CARGO_MANIFEST_DIR), which
|
||||||
|
// does not exist on an installed system — so the packaged binary loaded empty
|
||||||
|
// bytes and panicked. include_str! bakes the contents in instead.
|
||||||
macro_rules! asset {
|
macro_rules! asset {
|
||||||
($n:literal) => {
|
($n:literal) => {
|
||||||
concat!(env!("CARGO_MANIFEST_DIR"), "/assets/", $n)
|
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/", $n))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
mod bar;
|
mod bar;
|
||||||
mod notifications;
|
mod notifications;
|
||||||
|
mod osd;
|
||||||
mod theme;
|
mod theme;
|
||||||
|
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
|
|
@ -19,14 +24,22 @@ pub struct App {
|
||||||
active_ws: WorkspaceId,
|
active_ws: WorkspaceId,
|
||||||
time_str: String,
|
time_str: String,
|
||||||
workspace_box: gtk4::Box,
|
workspace_box: gtk4::Box,
|
||||||
|
button_map: std::collections::HashMap<WorkspaceId, gtk4::Button>,
|
||||||
cpu_lbl: gtk4::Label,
|
cpu_lbl: gtk4::Label,
|
||||||
mem_lbl: gtk4::Label,
|
mem_lbl: gtk4::Label,
|
||||||
pwr_lbl: gtk4::Label,
|
pwr_lbl: gtk4::Label,
|
||||||
bat_lbl: gtk4::Label,
|
bat_lbl: gtk4::Label,
|
||||||
|
bat_img: gtk4::Image,
|
||||||
|
bat_textures: std::collections::HashMap<usize, gtk4::gdk::Texture>,
|
||||||
|
ac_img: gtk4::Image,
|
||||||
|
bt_img: gtk4::Image,
|
||||||
|
bt_textures: std::collections::HashMap<usize, gtk4::gdk::Texture>,
|
||||||
wifi_lbl: gtk4::Label,
|
wifi_lbl: gtk4::Label,
|
||||||
wifi_img: gtk4::Image,
|
wifi_img: gtk4::Image,
|
||||||
// Pre-loaded textures indexed by the WIFI_* constant pointer values.
|
// Pre-loaded textures indexed by constant pointer values.
|
||||||
wifi_textures: std::collections::HashMap<usize, gtk4::gdk::Texture>,
|
wifi_textures: std::collections::HashMap<usize, gtk4::gdk::Texture>,
|
||||||
|
tray_box: gtk4::Box,
|
||||||
|
tray_items: std::collections::HashMap<String, gtk4::Button>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -35,6 +48,7 @@ pub enum AppInput {
|
||||||
ActiveWorkspace(WorkspaceId),
|
ActiveWorkspace(WorkspaceId),
|
||||||
ClockTick,
|
ClockTick,
|
||||||
StatsUpdate(bar::stats::Stats),
|
StatsUpdate(bar::stats::Stats),
|
||||||
|
TrayUpdate(bar::tray::TrayUpdate),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[relm4::component(pub)]
|
#[relm4::component(pub)]
|
||||||
|
|
@ -84,16 +98,45 @@ impl SimpleComponent for App {
|
||||||
root.set_anchor(Edge::Right, true);
|
root.set_anchor(Edge::Right, true);
|
||||||
root.set_exclusive_zone(32);
|
root.set_exclusive_zone(32);
|
||||||
|
|
||||||
let cpu_lbl = stat_label(4);
|
let cpu_lbl = stat_label();
|
||||||
let mem_lbl = stat_label(4);
|
let mem_lbl = stat_label();
|
||||||
let pwr_lbl = stat_label(5);
|
let pwr_lbl = stat_label();
|
||||||
let bat_lbl = stat_label(4);
|
let bat_lbl = stat_label();
|
||||||
let wifi_lbl = gtk4::Label::new(None);
|
let wifi_lbl = gtk4::Label::new(None);
|
||||||
|
wifi_lbl.add_css_class("stat-label");
|
||||||
|
wifi_lbl.add_css_class("wifi-label");
|
||||||
wifi_lbl.set_ellipsize(gtk4::pango::EllipsizeMode::End);
|
wifi_lbl.set_ellipsize(gtk4::pango::EllipsizeMode::End);
|
||||||
wifi_lbl.set_max_width_chars(12);
|
wifi_lbl.set_max_width_chars(22);
|
||||||
let wifi_img = gtk4::Image::from_paintable(Some(&svg_texture(asset!("WiFi Connecting.svg"))));
|
wifi_lbl.set_xalign(0.0);
|
||||||
|
let wifi_img =
|
||||||
|
gtk4::Image::from_paintable(Some(&svg_texture(asset!("WiFi Connecting.svg"))));
|
||||||
|
|
||||||
|
use bar::stats::{
|
||||||
|
AC_POWER, BAT_HIGH, BAT_LOW, BAT_MID, BT_CONNECTED, BT_OFF, BT_ON, WIFI_MEDIUM,
|
||||||
|
WIFI_OFF, WIFI_STRONG, WIFI_WEAK,
|
||||||
|
};
|
||||||
|
let bat_textures: std::collections::HashMap<usize, gtk4::gdk::Texture> =
|
||||||
|
[BAT_HIGH, BAT_MID, BAT_LOW]
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| (p.as_ptr() as usize, svg_texture(p)))
|
||||||
|
.collect();
|
||||||
|
// BAT_MID was just inserted into bat_textures above — key is always present.
|
||||||
|
let bat_img = gtk4::Image::from_paintable(Some(
|
||||||
|
bat_textures.get(&(BAT_MID.as_ptr() as usize)).unwrap(),
|
||||||
|
));
|
||||||
|
let ac_img = gtk4::Image::from_paintable(Some(&svg_texture(AC_POWER)));
|
||||||
|
ac_img.set_visible(false);
|
||||||
|
|
||||||
|
let bt_textures: std::collections::HashMap<usize, gtk4::gdk::Texture> =
|
||||||
|
[BT_OFF, BT_ON, BT_CONNECTED]
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| (p.as_ptr() as usize, svg_texture(p)))
|
||||||
|
.collect();
|
||||||
|
// BT_OFF was just inserted into bt_textures above — key is always present.
|
||||||
|
let bt_img = gtk4::Image::from_paintable(Some(
|
||||||
|
bt_textures.get(&(BT_OFF.as_ptr() as usize)).unwrap(),
|
||||||
|
));
|
||||||
|
|
||||||
use bar::stats::{WIFI_OFF, WIFI_STRONG, WIFI_MEDIUM, WIFI_WEAK};
|
|
||||||
let wifi_textures = [WIFI_STRONG, WIFI_MEDIUM, WIFI_WEAK, WIFI_OFF]
|
let wifi_textures = [WIFI_STRONG, WIFI_MEDIUM, WIFI_WEAK, WIFI_OFF]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| (p.as_ptr() as usize, svg_texture(p)))
|
.map(|p| (p.as_ptr() as usize, svg_texture(p)))
|
||||||
|
|
@ -104,34 +147,58 @@ impl SimpleComponent for App {
|
||||||
active_ws: 1,
|
active_ws: 1,
|
||||||
time_str: bar::clock::current(),
|
time_str: bar::clock::current(),
|
||||||
workspace_box: gtk4::Box::new(gtk4::Orientation::Horizontal, 4),
|
workspace_box: gtk4::Box::new(gtk4::Orientation::Horizontal, 4),
|
||||||
|
button_map: std::collections::HashMap::new(),
|
||||||
cpu_lbl: cpu_lbl.clone(),
|
cpu_lbl: cpu_lbl.clone(),
|
||||||
mem_lbl: mem_lbl.clone(),
|
mem_lbl: mem_lbl.clone(),
|
||||||
pwr_lbl: pwr_lbl.clone(),
|
pwr_lbl: pwr_lbl.clone(),
|
||||||
bat_lbl: bat_lbl.clone(),
|
bat_lbl: bat_lbl.clone(),
|
||||||
|
bat_img: bat_img.clone(),
|
||||||
|
bat_textures,
|
||||||
|
ac_img: ac_img.clone(),
|
||||||
|
bt_img: bt_img.clone(),
|
||||||
|
bt_textures,
|
||||||
wifi_lbl: wifi_lbl.clone(),
|
wifi_lbl: wifi_lbl.clone(),
|
||||||
wifi_img: wifi_img.clone(),
|
wifi_img: wifi_img.clone(),
|
||||||
wifi_textures,
|
wifi_textures,
|
||||||
|
tray_box: gtk4::Box::new(gtk4::Orientation::Horizontal, 4),
|
||||||
|
tray_items: std::collections::HashMap::new(),
|
||||||
};
|
};
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
model.workspace_box = widgets.workspace_box.clone();
|
model.workspace_box = widgets.workspace_box.clone();
|
||||||
|
|
||||||
let stats_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 8);
|
let stats_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||||
stats_box.set_margin_end(8);
|
stats_box.add_css_class("stats-box");
|
||||||
stats_box.append(&stat_pair(asset!("CPU.svg"), &cpu_lbl));
|
stats_box.append(&stat_pair(asset!("CPU.svg"), &cpu_lbl));
|
||||||
stats_box.append(&stat_pair(asset!("RAM Usage.svg"), &mem_lbl));
|
stats_box.append(&stat_pair(asset!("RAM Usage.svg"), &mem_lbl));
|
||||||
stats_box.append(&stat_pair(asset!("Power Draw.svg"), &pwr_lbl));
|
stats_box.append(&stat_pair(asset!("Power Draw.svg"), &pwr_lbl));
|
||||||
stats_box.append(&stat_pair(asset!("Battery.svg"), &bat_lbl));
|
let bat_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||||
let wifi_pair = gtk4::Box::new(gtk4::Orientation::Horizontal, 4);
|
bat_box.add_css_class("stat-pair");
|
||||||
|
bat_img.add_css_class("stat-icon");
|
||||||
|
bat_lbl.add_css_class("stat-label");
|
||||||
|
ac_img.add_css_class("stat-icon");
|
||||||
|
bat_box.append(&bat_img);
|
||||||
|
bat_box.append(&bat_lbl);
|
||||||
|
bat_box.append(&ac_img);
|
||||||
|
stats_box.append(&bat_box);
|
||||||
|
bt_img.add_css_class("bt-icon");
|
||||||
|
stats_box.append(&bt_img);
|
||||||
|
let wifi_pair = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||||
|
wifi_pair.add_css_class("stat-pair");
|
||||||
|
wifi_img.add_css_class("stat-icon");
|
||||||
wifi_pair.append(&wifi_img);
|
wifi_pair.append(&wifi_img);
|
||||||
wifi_pair.append(&wifi_lbl);
|
wifi_pair.append(&wifi_lbl);
|
||||||
stats_box.append(&wifi_pair);
|
stats_box.append(&wifi_pair);
|
||||||
|
model.tray_box.add_css_class("tray-box");
|
||||||
|
stats_box.append(&model.tray_box);
|
||||||
widgets.center_box.set_end_widget(Some(&stats_box));
|
widgets.center_box.set_end_widget(Some(&stats_box));
|
||||||
|
|
||||||
theme::apply();
|
theme::apply();
|
||||||
bar::workspaces::spawn_watcher(sender.clone());
|
bar::workspaces::spawn_watcher(sender.clone());
|
||||||
bar::clock::spawn_ticker(sender.clone());
|
bar::clock::spawn_ticker(sender.clone());
|
||||||
bar::stats::spawn_poller(sender);
|
bar::stats::spawn_poller(sender.clone());
|
||||||
|
bar::tray::spawn_watcher(sender.clone());
|
||||||
notifications::spawn();
|
notifications::spawn();
|
||||||
|
osd::spawn();
|
||||||
|
|
||||||
ComponentParts { model, widgets }
|
ComponentParts { model, widgets }
|
||||||
}
|
}
|
||||||
|
|
@ -145,8 +212,13 @@ impl SimpleComponent for App {
|
||||||
self.rebuild_buttons();
|
self.rebuild_buttons();
|
||||||
}
|
}
|
||||||
AppInput::ActiveWorkspace(id) => {
|
AppInput::ActiveWorkspace(id) => {
|
||||||
|
if let Some(old) = self.button_map.get(&self.active_ws) {
|
||||||
|
old.remove_css_class("active");
|
||||||
|
}
|
||||||
self.active_ws = id;
|
self.active_ws = id;
|
||||||
self.rebuild_buttons();
|
if let Some(btn) = self.button_map.get(&self.active_ws) {
|
||||||
|
btn.add_css_class("active");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AppInput::ClockTick => {
|
AppInput::ClockTick => {
|
||||||
self.time_str = bar::clock::current();
|
self.time_str = bar::clock::current();
|
||||||
|
|
@ -156,46 +228,95 @@ impl SimpleComponent for App {
|
||||||
self.mem_lbl.set_label(&stats.mem);
|
self.mem_lbl.set_label(&stats.mem);
|
||||||
self.pwr_lbl.set_label(&stats.power);
|
self.pwr_lbl.set_label(&stats.power);
|
||||||
self.bat_lbl.set_label(&stats.bat);
|
self.bat_lbl.set_label(&stats.bat);
|
||||||
|
if let Some(tex) = self.bat_textures.get(&(stats.bat_icon.as_ptr() as usize)) {
|
||||||
|
self.bat_img.set_paintable(Some(tex));
|
||||||
|
}
|
||||||
|
self.ac_img.set_visible(stats.ac_connected);
|
||||||
|
if let Some(tex) = self.bt_textures.get(&(stats.bt_icon.as_ptr() as usize)) {
|
||||||
|
self.bt_img.set_paintable(Some(tex));
|
||||||
|
}
|
||||||
self.wifi_lbl.set_label(&stats.wifi_ssid);
|
self.wifi_lbl.set_label(&stats.wifi_ssid);
|
||||||
if let Some(tex) = self.wifi_textures.get(&(stats.wifi_icon.as_ptr() as usize)) {
|
if let Some(tex) = self.wifi_textures.get(&(stats.wifi_icon.as_ptr() as usize)) {
|
||||||
self.wifi_img.set_paintable(Some(tex));
|
self.wifi_img.set_paintable(Some(tex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AppInput::TrayUpdate(bar::tray::TrayUpdate::Add { id, icon, title }) => {
|
||||||
|
if self.tray_items.contains_key(&id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let btn = gtk4::Button::new();
|
||||||
|
btn.add_css_class("tray-btn");
|
||||||
|
btn.set_child(Some(&bar::tray::make_tray_image(icon.as_ref())));
|
||||||
|
if !title.is_empty() {
|
||||||
|
btn.set_tooltip_text(Some(&title));
|
||||||
|
}
|
||||||
|
let id_click = id.clone();
|
||||||
|
btn.connect_clicked(move |_| bar::tray::spawn_activate(id_click.clone()));
|
||||||
|
self.tray_box.append(&btn);
|
||||||
|
self.tray_items.insert(id, btn);
|
||||||
|
}
|
||||||
|
AppInput::TrayUpdate(bar::tray::TrayUpdate::Remove { id }) => {
|
||||||
|
if let Some(btn) = self.tray_items.remove(&id) {
|
||||||
|
self.tray_box.remove(&btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn rebuild_buttons(&self) {
|
fn rebuild_buttons(&mut self) {
|
||||||
while let Some(child) = self.workspace_box.first_child() {
|
while let Some(child) = self.workspace_box.first_child() {
|
||||||
self.workspace_box.remove(&child);
|
self.workspace_box.remove(&child);
|
||||||
}
|
}
|
||||||
|
self.button_map.clear();
|
||||||
for ws in &self.workspaces {
|
for ws in &self.workspaces {
|
||||||
self.workspace_box
|
let btn = bar::workspaces::make_button(ws.id, &ws.name, self.active_ws);
|
||||||
.append(&bar::workspaces::make_button(ws.id, &ws.name, self.active_ws));
|
self.workspace_box.append(&btn);
|
||||||
|
self.button_map.insert(ws.id, btn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stat_pair(icon_path: &str, label: >k4::Label) -> gtk4::Box {
|
fn stat_pair(icon_svg: &str, label: >k4::Label) -> gtk4::Box {
|
||||||
let pair = gtk4::Box::new(gtk4::Orientation::Horizontal, 4);
|
let pair = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||||
pair.append(>k4::Image::from_paintable(Some(&svg_texture(icon_path))));
|
pair.add_css_class("stat-pair");
|
||||||
|
let img = gtk4::Image::from_paintable(Some(&svg_texture(icon_svg)));
|
||||||
|
img.add_css_class("stat-icon");
|
||||||
|
pair.append(&img);
|
||||||
pair.append(label);
|
pair.append(label);
|
||||||
pair
|
pair
|
||||||
}
|
}
|
||||||
|
|
||||||
fn svg_texture(path: &str) -> gtk4::gdk::Texture {
|
// Rasterise an (embedded) SVG to a texture. Done in pure Rust with resvg
|
||||||
let svg = std::fs::read_to_string(path)
|
// because librsvg dropped its gdk-pixbuf SVG loader, so gdk::Texture::from_bytes
|
||||||
.unwrap_or_default()
|
// can no longer decode SVG on a stock system.
|
||||||
.replace("currentColor", "white");
|
fn svg_texture(svg_src: &str) -> gtk4::gdk::Texture {
|
||||||
let bytes = gtk4::glib::Bytes::from_owned(svg.into_bytes());
|
use resvg::{tiny_skia, usvg};
|
||||||
gtk4::gdk::Texture::from_bytes(&bytes).expect("svg load")
|
let fg = theme::fg_color();
|
||||||
|
let svg = svg_src
|
||||||
|
.replace("currentColor", &fg)
|
||||||
|
.replace(r#"width="24" height="24""#, r#"width="16" height="16""#);
|
||||||
|
let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).expect("parse svg");
|
||||||
|
let size = tree.size().to_int_size();
|
||||||
|
let (w, h) = (size.width(), size.height());
|
||||||
|
let mut pixmap = tiny_skia::Pixmap::new(w, h).expect("alloc pixmap");
|
||||||
|
resvg::render(&tree, tiny_skia::Transform::identity(), &mut pixmap.as_mut());
|
||||||
|
let bytes = gtk4::glib::Bytes::from_owned(pixmap.take());
|
||||||
|
gtk4::gdk::MemoryTexture::new(
|
||||||
|
w as i32,
|
||||||
|
h as i32,
|
||||||
|
gtk4::gdk::MemoryFormat::R8g8b8a8Premultiplied,
|
||||||
|
&bytes,
|
||||||
|
(w * 4) as usize,
|
||||||
|
)
|
||||||
|
.upcast()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stat_label(width_chars: i32) -> gtk4::Label {
|
fn stat_label() -> gtk4::Label {
|
||||||
let lbl = gtk4::Label::new(None);
|
let lbl = gtk4::Label::new(None);
|
||||||
lbl.set_width_chars(width_chars);
|
lbl.add_css_class("stat-label");
|
||||||
lbl.set_xalign(1.0);
|
lbl.set_xalign(0.0);
|
||||||
lbl
|
lbl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,13 @@ use tokio::sync::mpsc;
|
||||||
use zbus::zvariant::OwnedValue;
|
use zbus::zvariant::OwnedValue;
|
||||||
|
|
||||||
pub enum NotifEvent {
|
pub enum NotifEvent {
|
||||||
Show { id: u32, app_name: String, summary: String, body: String, timeout_ms: u32 },
|
Show {
|
||||||
|
id: u32,
|
||||||
|
app_name: String,
|
||||||
|
summary: String,
|
||||||
|
body: String,
|
||||||
|
timeout_ms: u32,
|
||||||
|
},
|
||||||
Close(u32),
|
Close(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -16,6 +22,8 @@ struct NotifServer {
|
||||||
|
|
||||||
#[zbus::interface(name = "org.freedesktop.Notifications")]
|
#[zbus::interface(name = "org.freedesktop.Notifications")]
|
||||||
impl NotifServer {
|
impl NotifServer {
|
||||||
|
// The org.freedesktop.Notifications spec mandates exactly these 8 parameters.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn notify(
|
async fn notify(
|
||||||
&self,
|
&self,
|
||||||
app_name: &str,
|
app_name: &str,
|
||||||
|
|
@ -32,7 +40,11 @@ impl NotifServer {
|
||||||
} else {
|
} else {
|
||||||
self.next_id.fetch_add(1, Ordering::Relaxed)
|
self.next_id.fetch_add(1, Ordering::Relaxed)
|
||||||
};
|
};
|
||||||
let timeout_ms = if expire_timeout <= 0 { 5000 } else { expire_timeout as u32 };
|
let timeout_ms = if expire_timeout <= 0 {
|
||||||
|
5000
|
||||||
|
} else {
|
||||||
|
expire_timeout as u32
|
||||||
|
};
|
||||||
let _ = self
|
let _ = self
|
||||||
.tx
|
.tx
|
||||||
.send(NotifEvent::Show {
|
.send(NotifEvent::Show {
|
||||||
|
|
@ -55,7 +67,12 @@ impl NotifServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_server_information(&self) -> (String, String, String, String) {
|
fn get_server_information(&self) -> (String, String, String, String) {
|
||||||
("breadbar".into(), "breadway".into(), "0.1.0".into(), "1.2".into())
|
(
|
||||||
|
"breadbar".into(),
|
||||||
|
"breadway".into(),
|
||||||
|
env!("CARGO_PKG_VERSION").into(),
|
||||||
|
"1.2".into(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +80,11 @@ pub fn spawn() {
|
||||||
let (tx, rx) = mpsc::channel(32);
|
let (tx, rx) = mpsc::channel(32);
|
||||||
|
|
||||||
relm4::spawn(async move {
|
relm4::spawn(async move {
|
||||||
let server = NotifServer { tx, next_id: AtomicU32::new(1) };
|
let server = NotifServer {
|
||||||
|
tx,
|
||||||
|
next_id: AtomicU32::new(1),
|
||||||
|
};
|
||||||
|
// Builder failures here would only occur with invalid static strings — safe to unwrap.
|
||||||
let _conn = zbus::connection::Builder::session()
|
let _conn = zbus::connection::Builder::session()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.name("org.freedesktop.Notifications")
|
.name("org.freedesktop.Notifications")
|
||||||
|
|
@ -72,7 +93,7 @@ pub fn spawn() {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
.expect("failed to claim org.freedesktop.Notifications");
|
.expect("failed to claim org.freedesktop.Notifications on D-Bus session bus");
|
||||||
std::future::pending::<()>().await
|
std::future::pending::<()>().await
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,13 @@ pub async fn run(mut rx: Receiver<NotifEvent>) {
|
||||||
|
|
||||||
while let Some(event) = rx.recv().await {
|
while let Some(event) = rx.recv().await {
|
||||||
match event {
|
match event {
|
||||||
NotifEvent::Show { id, app_name, summary, body, timeout_ms } => {
|
NotifEvent::Show {
|
||||||
|
id,
|
||||||
|
app_name,
|
||||||
|
summary,
|
||||||
|
body,
|
||||||
|
timeout_ms,
|
||||||
|
} => {
|
||||||
// Replace existing card with same id (replaces_id case)
|
// Replace existing card with same id (replaces_id case)
|
||||||
if let Some(old) = cards.borrow_mut().remove(&id) {
|
if let Some(old) = cards.borrow_mut().remove(&id) {
|
||||||
cards_box.remove(&old);
|
cards_box.remove(&old);
|
||||||
|
|
|
||||||
184
src/osd.rs
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
use std::{cell::Cell, rc::Rc, time::Duration};
|
||||||
|
|
||||||
|
use gtk4::prelude::*;
|
||||||
|
use gtk4_layer_shell::{Edge, Layer, LayerShell};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
enum OsdEvent {
|
||||||
|
Volume { pct: u8, muted: bool },
|
||||||
|
Brightness { pct: u8 },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn() {
|
||||||
|
let (tx, rx) = mpsc::channel::<OsdEvent>(8);
|
||||||
|
|
||||||
|
let tx1 = tx.clone();
|
||||||
|
std::thread::spawn(move || volume_watcher(tx1));
|
||||||
|
std::thread::spawn(move || brightness_watcher(tx));
|
||||||
|
|
||||||
|
relm4::spawn_local(run_osd(rx));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn volume_watcher(tx: mpsc::Sender<OsdEvent>) {
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
let Ok(mut child) = Command::new("pactl")
|
||||||
|
.args(["subscribe"])
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.spawn()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdout = child.stdout.take().unwrap();
|
||||||
|
let reader = BufReader::new(stdout);
|
||||||
|
|
||||||
|
for line in reader.lines().map_while(Result::ok) {
|
||||||
|
if line.contains("'change' on sink") {
|
||||||
|
if let Some(evt) = query_volume() {
|
||||||
|
let _ = tx.blocking_send(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_volume() -> Option<OsdEvent> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let vol = Command::new("pactl")
|
||||||
|
.args(["get-sink-volume", "@DEFAULT_SINK@"])
|
||||||
|
.output()
|
||||||
|
.ok()?;
|
||||||
|
let mute = Command::new("pactl")
|
||||||
|
.args(["get-sink-mute", "@DEFAULT_SINK@"])
|
||||||
|
.output()
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
let vol_str = String::from_utf8_lossy(&vol.stdout);
|
||||||
|
let mute_str = String::from_utf8_lossy(&mute.stdout);
|
||||||
|
|
||||||
|
// "Volume: front-left: 45875 / 70% / -8.58 dB, ..."
|
||||||
|
let pct: u8 = vol_str
|
||||||
|
.split('/')
|
||||||
|
.nth(1)?
|
||||||
|
.trim()
|
||||||
|
.trim_end_matches('%')
|
||||||
|
.trim()
|
||||||
|
.parse()
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
let muted = mute_str.contains(": yes");
|
||||||
|
|
||||||
|
Some(OsdEvent::Volume { pct, muted })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn brightness_watcher(tx: mpsc::Sender<OsdEvent>) {
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
let base = match fs::read_dir("/sys/class/backlight")
|
||||||
|
.ok()
|
||||||
|
.and_then(|mut d| d.next())
|
||||||
|
.and_then(|e| e.ok())
|
||||||
|
.map(|e| e.path())
|
||||||
|
{
|
||||||
|
Some(p) => p,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let bright_path = base.join("brightness");
|
||||||
|
let max_path = base.join("max_brightness");
|
||||||
|
|
||||||
|
let max: u64 = match fs::read_to_string(&max_path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.trim().parse().ok())
|
||||||
|
{
|
||||||
|
Some(v) if v > 0 => v,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize to current value so startup doesn't trigger OSD.
|
||||||
|
let mut last: u64 = fs::read_to_string(&bright_path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.trim().parse().ok())
|
||||||
|
.unwrap_or(u64::MAX);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Some(val) = fs::read_to_string(&bright_path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.trim().parse::<u64>().ok())
|
||||||
|
{
|
||||||
|
if val != last {
|
||||||
|
last = val;
|
||||||
|
let pct = ((val * 100) / max).min(100) as u8;
|
||||||
|
let _ = tx.blocking_send(OsdEvent::Brightness { pct });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(200));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_osd(mut rx: mpsc::Receiver<OsdEvent>) {
|
||||||
|
let window = create_window();
|
||||||
|
|
||||||
|
let container = gtk4::Box::new(gtk4::Orientation::Vertical, 6);
|
||||||
|
container.set_margin_top(12);
|
||||||
|
container.set_margin_bottom(12);
|
||||||
|
container.set_margin_start(16);
|
||||||
|
container.set_margin_end(16);
|
||||||
|
window.set_child(Some(&container));
|
||||||
|
|
||||||
|
let header = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||||
|
let kind_lbl = gtk4::Label::new(Some("Volume"));
|
||||||
|
kind_lbl.add_css_class("osd-kind");
|
||||||
|
kind_lbl.set_hexpand(true);
|
||||||
|
kind_lbl.set_xalign(0.0);
|
||||||
|
let pct_lbl = gtk4::Label::new(Some("0%"));
|
||||||
|
pct_lbl.add_css_class("osd-pct");
|
||||||
|
header.append(&kind_lbl);
|
||||||
|
header.append(&pct_lbl);
|
||||||
|
container.append(&header);
|
||||||
|
|
||||||
|
let pbar = gtk4::ProgressBar::new();
|
||||||
|
pbar.add_css_class("osd-bar");
|
||||||
|
container.append(&pbar);
|
||||||
|
|
||||||
|
let dismiss_token = Rc::new(Cell::new(0u32));
|
||||||
|
|
||||||
|
while let Some(event) = rx.recv().await {
|
||||||
|
let (kind, pct) = match event {
|
||||||
|
OsdEvent::Volume { pct, muted } => {
|
||||||
|
(if muted { "Volume (Muted)" } else { "Volume" }, pct)
|
||||||
|
}
|
||||||
|
OsdEvent::Brightness { pct } => ("Brightness", pct),
|
||||||
|
};
|
||||||
|
|
||||||
|
kind_lbl.set_label(kind);
|
||||||
|
pct_lbl.set_label(&format!("{pct}%"));
|
||||||
|
pbar.set_fraction(pct as f64 / 100.0);
|
||||||
|
window.set_visible(true);
|
||||||
|
|
||||||
|
let token = dismiss_token.get().wrapping_add(1);
|
||||||
|
dismiss_token.set(token);
|
||||||
|
let dtok = dismiss_token.clone();
|
||||||
|
let win = window.clone();
|
||||||
|
relm4::spawn_local(async move {
|
||||||
|
gtk4::glib::timeout_future(Duration::from_millis(2000)).await;
|
||||||
|
if dtok.get() == token {
|
||||||
|
win.set_visible(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_window() -> gtk4::Window {
|
||||||
|
let window = gtk4::Window::new();
|
||||||
|
window.add_css_class("breadbar-osd");
|
||||||
|
window.init_layer_shell();
|
||||||
|
window.set_layer(Layer::Overlay);
|
||||||
|
window.set_anchor(Edge::Bottom, true);
|
||||||
|
window.set_margin(Edge::Bottom, 80);
|
||||||
|
window.set_default_width(280);
|
||||||
|
window
|
||||||
|
}
|
||||||
125
src/theme.rs
|
|
@ -1,77 +1,70 @@
|
||||||
|
use bread_theme::{gtk as bgtk, hex_to_rgba, ink_on, load_palette};
|
||||||
use gtk4::CssProvider;
|
use gtk4::CssProvider;
|
||||||
use serde::Deserialize;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
thread_local! {
|
||||||
struct WalColors {
|
static USER_PROVIDER: RefCell<Option<CssProvider>> = const { RefCell::new(None) };
|
||||||
special: Special,
|
|
||||||
colors: Colors,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Special {
|
|
||||||
background: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Colors {
|
|
||||||
color0: String,
|
|
||||||
color1: String,
|
|
||||||
color15: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hex_to_rgba(hex: &str, alpha: f32) -> String {
|
|
||||||
let h = hex.trim_start_matches('#');
|
|
||||||
let r = u8::from_str_radix(&h[0..2], 16).unwrap_or(0);
|
|
||||||
let g = u8::from_str_radix(&h[2..4], 16).unwrap_or(0);
|
|
||||||
let b = u8::from_str_radix(&h[4..6], 16).unwrap_or(0);
|
|
||||||
format!("rgba({r},{g},{b},{alpha})")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_css() -> String {
|
fn load_css() -> String {
|
||||||
let home = std::env::var("HOME").unwrap_or_default();
|
let p = load_palette();
|
||||||
let text = std::fs::read_to_string(format!("{home}/.cache/wal/colors.json"))
|
// breadbar-specific rules only — fonts, base colours, and generic widgets
|
||||||
.unwrap_or_default();
|
// come from the shared ecosystem stylesheet (applied first in `apply()`).
|
||||||
|
// Colour is set on each surface (bar, active workspace pill, notification
|
||||||
let (bg, surface, fg, accent) = if let Ok(wal) = serde_json::from_str::<WalColors>(&text) {
|
// card) and child labels inherit it, so text stays legible whatever lightness
|
||||||
(wal.special.background, wal.colors.color0, wal.colors.color15, wal.colors.color1)
|
// pywal hands a given slot. `on_*` are luminance-picked ink (black/white) for
|
||||||
} else {
|
// that background — the pywal hues themselves are untouched.
|
||||||
(
|
|
||||||
"#1e1e2e".to_string(),
|
|
||||||
"#181825".to_string(),
|
|
||||||
"#cdd6f4".to_string(),
|
|
||||||
"#89b4fa".to_string(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"* {{ font-family: 'JetBrainsMono Nerd Font Mono', monospace; font-size: 12px; }}\
|
"window.breadbar {{ background-color: {bg_rgba}; color: {on_bg}; border-radius: 0; }}\
|
||||||
window.breadbar {{ background-color: {bg_rgba}; border-radius: 0; }}\
|
.workspace-btn {{ background: transparent; opacity: 0.45;\
|
||||||
.workspace-btn {{ background: transparent; color: {fg}; opacity: 0.45;\
|
border-radius: 0; border: none; outline: none; box-shadow: none;\
|
||||||
border-radius: 0 0 8px 8px; border: none; outline: none; box-shadow: none;\
|
min-width: 24px; padding: 4px 8px; }}\
|
||||||
min-width: 24px; padding: 2px 8px; }}\
|
|
||||||
.workspace-btn:hover {{ opacity: 0.8; }}\
|
.workspace-btn:hover {{ opacity: 0.8; }}\
|
||||||
.workspace-btn.active {{ background: {accent}; opacity: 1; }}\
|
.workspace-btn.active {{ background: {accent}; color: {on_accent}; opacity: 1; }}\
|
||||||
label {{ color: {fg}; }}\
|
.stats-box {{ margin-right: 8px; }}\
|
||||||
window.breadbar-notification {{ background-color: alpha({bg_plain}, 0.95); }}\
|
.stat-pair {{ margin-right: 12px; }}\
|
||||||
.notification-card {{ background: {surface}; border-radius: 6px;\
|
.stat-icon {{ margin-right: 5px; }}\
|
||||||
padding: 10px; margin-bottom: 4px; }}\
|
.bt-icon {{ margin-right: 12px; }}\
|
||||||
.notification-summary {{ font-weight: bold; color: {fg}; }}\
|
window.breadbar-notification {{ background-color: alpha({bg_plain}, 0.95); color: {on_bg}; }}\
|
||||||
.notification-app {{ color: {fg}; opacity: 0.6; }}\
|
.notification-card {{ background: {surface}; color: {on_surface}; border-radius: 8px;\
|
||||||
.notification-body {{ color: {fg}; }}",
|
padding: 12px; margin-bottom: 8px; }}\
|
||||||
bg_plain = bg,
|
.notification-summary {{ font-weight: bold; }}\
|
||||||
bg_rgba = hex_to_rgba(&bg, 0.92),
|
.notification-app {{ opacity: 0.6; }}\
|
||||||
surface = surface,
|
window.breadbar-osd {{ background-color: alpha({bg_plain}, 0.95); color: {on_bg}; border-radius: 8px; }}\
|
||||||
fg = fg,
|
.osd-kind {{ opacity: 0.75; font-size: 12px; }}\
|
||||||
accent = accent,
|
.osd-pct {{ font-weight: bold; font-size: 12px; }}\
|
||||||
|
progressbar.osd-bar {{ min-height: 8px; }}\
|
||||||
|
progressbar.osd-bar trough {{ background-image: none; background-color: {trough}; border-radius: 4px; min-height: 8px; }}\
|
||||||
|
progressbar.osd-bar trough progress {{ background-image: none; background-color: {accent}; border-radius: 4px; min-height: 8px; }}",
|
||||||
|
bg_plain = p.background,
|
||||||
|
bg_rgba = hex_to_rgba(&p.background, 0.92),
|
||||||
|
surface = p.color0,
|
||||||
|
accent = p.color4,
|
||||||
|
on_bg = ink_on(&p.background),
|
||||||
|
on_surface = ink_on(&p.color0),
|
||||||
|
on_accent = ink_on(&p.color4),
|
||||||
|
trough = hex_to_rgba(&p.color4, 0.25),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply() {
|
/// Returns the ink colour for icon tinting in the stats bar — the same
|
||||||
let provider = CssProvider::new();
|
/// luminance-picked colour the bar's text uses, so icons stay legible on the bar
|
||||||
provider.load_from_string(&load_css());
|
/// whatever lightness pywal gives the background.
|
||||||
gtk4::style_context_add_provider_for_display(
|
pub fn fg_color() -> String {
|
||||||
>k4::gdk::Display::default().expect("no display"),
|
ink_on(&load_palette().background).to_string()
|
||||||
&provider,
|
}
|
||||||
gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
||||||
);
|
/// Apply (or reload) the theme CSS. Safe to call from `glib::MainContext::invoke`.
|
||||||
|
pub fn apply() {
|
||||||
|
// Shared ecosystem base (fonts, palette, generic widgets) — applied first
|
||||||
|
// (and self-reloading) so breadbar's own rules below layer on top.
|
||||||
|
bgtk::apply_shared();
|
||||||
|
|
||||||
|
// breadbar's own rules, hot-reloaded on `bread-theme reload`: the closure
|
||||||
|
// re-reads the pywal palette each time so the bar recolours without restart.
|
||||||
|
bgtk::apply_app_css(load_css);
|
||||||
|
|
||||||
|
let home = std::env::var("HOME").unwrap_or_default();
|
||||||
|
let user_path = std::path::PathBuf::from(format!("{home}/.config/breadbar/style.css"));
|
||||||
|
USER_PROVIDER.with(|cell| bgtk::apply_user_css(&user_path, cell));
|
||||||
}
|
}
|
||||||
|
|
|
||||||