Refactor theme onto bread-theme; add bakery.toml and release workflow
- Cargo.toml: depend on bread-theme (path dep for local dev, git dep for production) with gtk feature; remove local theme dependencies - src/theme.rs: replace local pywal/Catppuccin impl with bread_theme::gtk helpers; local bar-specific CSS is preserved - bakery.toml: describes breadbar for bakery install - release.yml: builds on hestia self-hosted runner, publishes binary to dl.breadway.dev and GitHub Releases on v* tags Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9b9705520e
commit
9e829d3663
8 changed files with 456 additions and 114 deletions
56
.github/workflows/release.yml
vendored
Normal file
56
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["v*"]
|
||||||
|
|
||||||
|
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 system deps
|
||||||
|
run: sudo pacman -S --noconfirm gtk4 gtk4-layer-shell 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 "${PKG_DIR}" "${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 upload "${GITHUB_REF_NAME}" \
|
||||||
|
"${PKG_DIR}/breadbar-x86_64" \
|
||||||
|
"${PKG_DIR}/breadbar-x86_64.sha256" \
|
||||||
|
--clobber
|
||||||
173
Cargo.lock
generated
173
Cargo.lock
generated
|
|
@ -76,10 +76,21 @@ version = "2.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bread-theme"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"dirs",
|
||||||
|
"gtk4",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "breadbar"
|
name = "breadbar"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bread-theme",
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
"gtk4-layer-shell",
|
"gtk4-layer-shell",
|
||||||
|
|
@ -189,6 +200,27 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "5.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
|
@ -235,7 +267,7 @@ 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]]
|
[[package]]
|
||||||
|
|
@ -464,6 +496,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 +561,7 @@ dependencies = [
|
||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -839,6 +882,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"
|
||||||
|
|
@ -883,7 +935,7 @@ checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -892,6 +944,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"
|
||||||
|
|
@ -999,6 +1057,17 @@ version = "6.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"libredox",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "relm4"
|
name = "relm4"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
@ -1052,7 +1121,7 @@ dependencies = [
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1165,7 +1234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1217,7 +1286,27 @@ 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]]
|
[[package]]
|
||||||
|
|
@ -1234,7 +1323,7 @@ dependencies = [
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1338,7 +1427,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1485,6 +1574,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"
|
||||||
|
|
@ -1494,6 +1592,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"
|
||||||
|
|
@ -1626,7 +1781,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",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ keywords = ["wayland", "hyprland", "bar", "status-bar", "gtk4"]
|
||||||
categories = ["gui"]
|
categories = ["gui"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# Path dep for local dev; replace with git dep on first tag:
|
||||||
|
# bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "theme-v0.1.0", features = ["gtk"] }
|
||||||
|
bread-theme = { path = "../bread-ecosystem/bread-theme", 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"] }
|
||||||
|
|
|
||||||
11
bakery.toml
Normal file
11
bakery.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
name = "breadbar"
|
||||||
|
description = "Minimal status bar and notification daemon for Hyprland"
|
||||||
|
binaries = ["breadbar"]
|
||||||
|
system_deps = ["gtk4", "gtk4-layer-shell", "iw"]
|
||||||
|
bread_deps = []
|
||||||
|
|
||||||
|
[config]
|
||||||
|
dir = "~/.config/breadbar"
|
||||||
|
|
||||||
|
[install]
|
||||||
|
post_install = []
|
||||||
|
|
@ -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>) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ macro_rules! asset {
|
||||||
|
|
||||||
mod bar;
|
mod bar;
|
||||||
mod notifications;
|
mod notifications;
|
||||||
|
mod osd;
|
||||||
mod theme;
|
mod theme;
|
||||||
|
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
|
|
@ -185,6 +186,7 @@ impl SimpleComponent for App {
|
||||||
bar::clock::spawn_ticker(sender.clone());
|
bar::clock::spawn_ticker(sender.clone());
|
||||||
bar::stats::spawn_poller(sender);
|
bar::stats::spawn_poller(sender);
|
||||||
notifications::spawn();
|
notifications::spawn();
|
||||||
|
osd::spawn();
|
||||||
|
|
||||||
ComponentParts { model, widgets }
|
ComponentParts { model, widgets }
|
||||||
}
|
}
|
||||||
|
|
@ -255,9 +257,10 @@ fn stat_pair(icon_path: &str, label: >k4::Label) -> gtk4::Box {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn svg_texture(path: &str) -> gtk4::gdk::Texture {
|
fn svg_texture(path: &str) -> gtk4::gdk::Texture {
|
||||||
|
let fg = theme::fg_color();
|
||||||
let svg = std::fs::read_to_string(path)
|
let svg = std::fs::read_to_string(path)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.replace("currentColor", "white")
|
.replace("currentColor", &fg)
|
||||||
.replace(r#"width="24" height="24""#, r#"width="16" height="16""#);
|
.replace(r#"width="24" height="24""#, r#"width="16" height="16""#);
|
||||||
let bytes = gtk4::glib::Bytes::from_owned(svg.into_bytes());
|
let bytes = gtk4::glib::Bytes::from_owned(svg.into_bytes());
|
||||||
gtk4::gdk::Texture::from_bytes(&bytes).expect("svg load")
|
gtk4::gdk::Texture::from_bytes(&bytes).expect("svg load")
|
||||||
|
|
|
||||||
184
src/osd.rs
Normal file
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().flatten() {
|
||||||
|
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
|
||||||
|
}
|
||||||
134
src/theme.rs
134
src/theme.rs
|
|
@ -1,5 +1,5 @@
|
||||||
|
use bread_theme::{gtk as bgtk, hex_to_rgba, load_palette};
|
||||||
use gtk4::CssProvider;
|
use gtk4::CssProvider;
|
||||||
use serde::Deserialize;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
|
|
@ -7,125 +7,53 @@ thread_local! {
|
||||||
static USER_PROVIDER: RefCell<Option<CssProvider>> = const { RefCell::new(None) };
|
static USER_PROVIDER: RefCell<Option<CssProvider>> = const { RefCell::new(None) };
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct WalColors {
|
|
||||||
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")).unwrap_or_default();
|
|
||||||
|
|
||||||
let (bg, surface, fg, accent) = if let Ok(wal) = serde_json::from_str::<WalColors>(&text) {
|
|
||||||
(
|
|
||||||
wal.special.background,
|
|
||||||
wal.colors.color0,
|
|
||||||
wal.colors.color15,
|
|
||||||
wal.colors.color1,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
"#1e1e2e".to_string(),
|
|
||||||
"#181825".to_string(),
|
|
||||||
"#cdd6f4".to_string(),
|
|
||||||
"#89b4fa".to_string(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"* {{ font-family: 'JetBrainsMono Nerd Font Mono', monospace; font-size: 14px; }}\
|
"* {{ font-family: 'Varela Round', sans-serif; font-size: 14px; }}\
|
||||||
window.breadbar {{ background-color: {bg_rgba}; border-radius: 0; }}\
|
window.breadbar {{ background-color: {bg_rgba}; border-radius: 0; }}\
|
||||||
label {{ color: {fg}; }}\
|
label {{ color: {fg}; }}\
|
||||||
.workspace-btn {{ background: transparent; color: {fg}; opacity: 0.45;\
|
.workspace-btn {{ background: transparent; color: {fg}; opacity: 0.45;\
|
||||||
border-radius: 0 0 8px 8px; border: none; outline: none; box-shadow: none;\
|
border-radius: 0 0 8px 8px; border: none; outline: none; box-shadow: none;\
|
||||||
min-width: 24px; padding: 2px 8px; }}\
|
min-width: 24px; padding: 4px 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}; opacity: 1; }}\
|
||||||
.stats-box {{ margin-right: 8px; }}\
|
.stats-box {{ margin-right: 8px; }}\
|
||||||
.stat-pair {{ margin-right: 8px; }}\
|
.stat-pair {{ margin-right: 12px; }}\
|
||||||
.stat-icon {{ margin-right: 3px; }}\
|
.stat-icon {{ margin-right: 5px; }}\
|
||||||
.bt-icon {{ margin-right: 8px; }}\
|
.bt-icon {{ margin-right: 12px; }}\
|
||||||
window.breadbar-notification {{ background-color: alpha({bg_plain}, 0.95); }}\
|
window.breadbar-notification {{ background-color: alpha({bg_plain}, 0.95); }}\
|
||||||
.notification-card {{ background: {surface}; border-radius: 6px;\
|
.notification-card {{ background: {surface}; border-radius: 8px;\
|
||||||
padding: 10px; margin-bottom: 4px; }}\
|
padding: 12px; margin-bottom: 8px; }}\
|
||||||
.notification-summary {{ font-weight: bold; color: {fg}; }}\
|
.notification-summary {{ font-weight: bold; color: {fg}; }}\
|
||||||
.notification-app {{ color: {fg}; opacity: 0.6; }}\
|
.notification-app {{ color: {fg}; opacity: 0.6; }}\
|
||||||
.notification-body {{ color: {fg}; }}",
|
.notification-body {{ color: {fg}; }}\
|
||||||
bg_plain = bg,
|
window.breadbar-osd {{ background-color: alpha({bg_plain}, 0.95); border-radius: 8px; }}\
|
||||||
bg_rgba = hex_to_rgba(&bg, 0.92),
|
.osd-kind {{ color: {fg}; opacity: 0.75; font-size: 12px; }}\
|
||||||
surface = surface,
|
.osd-pct {{ color: {fg}; font-weight: bold; font-size: 12px; }}\
|
||||||
fg = fg,
|
progressbar.osd-bar {{ min-height: 8px; }}\
|
||||||
accent = accent,
|
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,
|
||||||
|
fg = p.foreground,
|
||||||
|
accent = p.color4,
|
||||||
|
trough = hex_to_rgba(&p.color4, 0.25),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current foreground colour (used for icon tinting in the stats bar).
|
||||||
|
pub fn fg_color() -> String {
|
||||||
|
load_palette().foreground
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply (or reload) the theme CSS. Safe to call from `glib::MainContext::invoke`.
|
||||||
pub fn apply() {
|
pub fn apply() {
|
||||||
let css = load_css();
|
let css = load_css();
|
||||||
let display = gtk4::gdk::Display::default().expect("no display");
|
PROVIDER.with(|cell| bgtk::apply_css(&css, cell));
|
||||||
|
|
||||||
PROVIDER.with(|cell| {
|
|
||||||
let mut guard = cell.borrow_mut();
|
|
||||||
if let Some(p) = guard.as_ref() {
|
|
||||||
p.load_from_string(&css);
|
|
||||||
} else {
|
|
||||||
let p = CssProvider::new();
|
|
||||||
p.load_from_string(&css);
|
|
||||||
gtk4::style_context_add_provider_for_display(
|
|
||||||
&display,
|
|
||||||
&p,
|
|
||||||
gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
||||||
);
|
|
||||||
*guard = Some(p);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// User override: ~/.config/breadbar/style.css — send SIGHUP to reload.
|
|
||||||
let home = std::env::var("HOME").unwrap_or_default();
|
let home = std::env::var("HOME").unwrap_or_default();
|
||||||
let user_path = format!("{home}/.config/breadbar/style.css");
|
let user_path = std::path::PathBuf::from(format!("{home}/.config/breadbar/style.css"));
|
||||||
USER_PROVIDER.with(|cell| {
|
USER_PROVIDER.with(|cell| bgtk::apply_user_css(&user_path, cell));
|
||||||
let mut guard = cell.borrow_mut();
|
|
||||||
match std::fs::read_to_string(&user_path) {
|
|
||||||
Ok(user_css) => {
|
|
||||||
if let Some(p) = guard.as_ref() {
|
|
||||||
p.load_from_string(&user_css);
|
|
||||||
} else {
|
|
||||||
let p = CssProvider::new();
|
|
||||||
p.load_from_string(&user_css);
|
|
||||||
gtk4::style_context_add_provider_for_display(
|
|
||||||
&display,
|
|
||||||
&p,
|
|
||||||
gtk4::STYLE_PROVIDER_PRIORITY_USER,
|
|
||||||
);
|
|
||||||
*guard = Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
if let Some(p) = guard.as_ref() {
|
|
||||||
p.load_from_string("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue