From 2b47f796d2191c926a0c1eeedc26d698449421ef Mon Sep 17 00:00:00 2001 From: Breadway Date: Sun, 7 Jun 2026 09:02:38 +0800 Subject: [PATCH 01/18] fix: use relative symlink for latest to work inside Docker containers --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e3e4ff1..c5e7668 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: 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" + ln -sfn "${VERSION}" "${DL_DIR}/breadbar/latest" - name: ensure bread-ecosystem run: | From 9d2a4c9b547badac8588ecb3b8876639495b04c0 Mon Sep 17 00:00:00 2001 From: Breadway Date: Thu, 11 Jun 2026 13:37:40 +0800 Subject: [PATCH 02/18] fix: add missing libpulse dep, add optional_system_deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libpulse (pactl) was missing — breadbar shells pactl 3x for volume. Optional: hyprland (workspace display, not a linked dep). --- bakery.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bakery.toml b/bakery.toml index a334ea4..03f03fe 100644 --- a/bakery.toml +++ b/bakery.toml @@ -1,7 +1,8 @@ name = "breadbar" description = "Minimal status bar and notification daemon for Hyprland" binaries = ["breadbar"] -system_deps = ["gtk4", "gtk4-layer-shell", "iw"] +system_deps = ["gtk4", "gtk4-layer-shell", "iw", "libpulse"] +optional_system_deps = ["hyprland"] bread_deps = [] [config] From 432c2da18c9ebd985aee79b9591922181eb41d73 Mon Sep 17 00:00:00 2001 From: Breadway Date: Thu, 11 Jun 2026 14:21:28 +0800 Subject: [PATCH 03/18] chore: bump version to 0.1.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e127970..8191caa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "breadbar" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = "Minimal status bar and notification daemon for Hyprland on Wayland" license = "MIT" From a036737823f2482496004b4de62645b6d81dcab5 Mon Sep 17 00:00:00 2001 From: Breadway Date: Thu, 11 Jun 2026 14:27:58 +0800 Subject: [PATCH 04/18] chore: update Cargo.lock for v0.1.1 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index e31b4e6..5835492 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,7 +89,7 @@ dependencies = [ [[package]] name = "breadbar" -version = "0.1.0" +version = "0.1.1" dependencies = [ "bread-theme", "futures-lite", From 44a8d95887ee5e853565aa08539adbc43bf3ec72 Mon Sep 17 00:00:00 2001 From: Breadway Date: Thu, 11 Jun 2026 22:31:25 +0800 Subject: [PATCH 05/18] feat: add system tray (StatusNotifierWatcher / SNI) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements org.kde.StatusNotifierWatcher as a D-Bus service so apps like Nextcloud can register their tray icons. Icons are rendered from SNI ARGB pixmaps (falling back to icon-name theme lookup), click calls Activate(0,0), and NameOwnerChanged cleans up ghost icons when an app exits. Styling follows the Bread Design System (4px tertiary radius, xs/sm spacing, opacity transitions). Also fixes a latent infinite-loop risk in osd.rs (.flatten → .map_while) and syncs the notifications server version string to CARGO_PKG_VERSION. --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 5 +- src/bar/mod.rs | 1 + src/bar/tray.rs | 240 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 30 ++++- src/notifications/mod.rs | 2 +- src/osd.rs | 2 +- 8 files changed, 277 insertions(+), 7 deletions(-) create mode 100644 src/bar/tray.rs diff --git a/Cargo.lock b/Cargo.lock index 5835492..dd6d212 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,7 +89,7 @@ dependencies = [ [[package]] name = "breadbar" -version = "0.1.1" +version = "0.1.2" dependencies = [ "bread-theme", "futures-lite", diff --git a/Cargo.toml b/Cargo.toml index 8191caa..4594c93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "breadbar" -version = "0.1.1" +version = "0.1.2" edition = "2021" description = "Minimal status bar and notification daemon for Hyprland on Wayland" license = "MIT" diff --git a/README.md b/README.md index 1e577e8..c823661 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Minimal status bar and notification daemon for [Hyprland](https://hyprland.org/) on Wayland. -A single Rust binary that provides a full-width top bar and a standards-compliant D-Bus notification daemon, with no system tray, no launcher, and no wallpaper logic. +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 @@ -10,7 +10,7 @@ A single Rust binary that provides a full-width top bar and a standards-complian - 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 +- Right: CPU%, RAM, power draw (W), battery level + AC indicator, Bluetooth state, WiFi SSID with signal strength, system tray (SNI) **Notification daemon**: @@ -105,6 +105,7 @@ Example — change the font size: | `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 | diff --git a/src/bar/mod.rs b/src/bar/mod.rs index 24c75b5..7b0d41d 100644 --- a/src/bar/mod.rs +++ b/src/bar/mod.rs @@ -1,3 +1,4 @@ pub mod clock; pub mod stats; +pub mod tray; pub mod workspaces; diff --git a/src/bar/tray.rs b/src/bar/tray.rs new file mode 100644 index 0000000..421b7c8 --- /dev/null +++ b/src/bar/tray.rs @@ -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 }, + Name(String), +} + +#[derive(Debug)] +pub enum TrayUpdate { + Add { id: String, icon: Option, title: String }, + Remove { id: String }, +} + +struct WatcherState { + items: Vec, +} + +struct Watcher { + state: Arc>, + 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 { + 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, 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::("Title").await.unwrap_or_default(); + (icon, title) +} + +async fn read_icon(proxy: &zbus::Proxy<'_>) -> Option { + let pixmaps: Vec<(i32, i32, Vec)> = + 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) { + 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>>> = 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 { + 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) +} diff --git a/src/main.rs b/src/main.rs index 10cca56..f1d77b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,8 @@ pub struct App { wifi_img: gtk4::Image, // Pre-loaded textures indexed by constant pointer values. wifi_textures: std::collections::HashMap, + tray_box: gtk4::Box, + tray_items: std::collections::HashMap, } #[derive(Debug)] @@ -42,6 +44,7 @@ pub enum AppInput { ActiveWorkspace(WorkspaceId), ClockTick, StatsUpdate(bar::stats::Stats), + TrayUpdate(bar::tray::TrayUpdate), } #[relm4::component(pub)] @@ -153,6 +156,8 @@ impl SimpleComponent for App { wifi_lbl: wifi_lbl.clone(), wifi_img: wifi_img.clone(), wifi_textures, + tray_box: gtk4::Box::new(gtk4::Orientation::Horizontal, 4), + tray_items: std::collections::HashMap::new(), }; let widgets = view_output!(); model.workspace_box = widgets.workspace_box.clone(); @@ -179,12 +184,15 @@ impl SimpleComponent for App { wifi_pair.append(&wifi_img); wifi_pair.append(&wifi_lbl); 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)); theme::apply(); bar::workspaces::spawn_watcher(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(); osd::spawn(); @@ -228,6 +236,26 @@ impl SimpleComponent for App { 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); + } + } } } } diff --git a/src/notifications/mod.rs b/src/notifications/mod.rs index b197038..b6c60b2 100644 --- a/src/notifications/mod.rs +++ b/src/notifications/mod.rs @@ -70,7 +70,7 @@ impl NotifServer { ( "breadbar".into(), "breadway".into(), - "0.1.0".into(), + env!("CARGO_PKG_VERSION").into(), "1.2".into(), ) } diff --git a/src/osd.rs b/src/osd.rs index 61f7ea4..74f0ec7 100644 --- a/src/osd.rs +++ b/src/osd.rs @@ -35,7 +35,7 @@ fn volume_watcher(tx: mpsc::Sender) { let stdout = child.stdout.take().unwrap(); let reader = BufReader::new(stdout); - for line in reader.lines().flatten() { + 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); From 50bb249b3a7e4046d245a40810bd4b01cc7be74e Mon Sep 17 00:00:00 2001 From: Breadway Date: Sat, 13 Jun 2026 12:12:41 +0800 Subject: [PATCH 06/18] Add packaging/arch PKGBUILD and Forgejo Actions workflows - packaging/arch/PKGBUILD: builds and publishes breadbar to [breadway] repo - .forgejo/workflows/mirror.yml: mirrors every push/tag to GitHub - .forgejo/workflows/package.yml: builds on tag, publishes to Forgejo registry Requires FORGEJO_TOKEN and GITHUB_MIRROR_TOKEN secrets in Forgejo. --- .forgejo/workflows/mirror.yml | 20 +++++++++++++++ .forgejo/workflows/package.yml | 46 ++++++++++++++++++++++++++++++++++ packaging/arch/PKGBUILD | 32 +++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 .forgejo/workflows/mirror.yml create mode 100644 .forgejo/workflows/package.yml create mode 100644 packaging/arch/PKGBUILD diff --git a/.forgejo/workflows/mirror.yml b/.forgejo/workflows/mirror.yml new file mode 100644 index 0000000..1ff2822 --- /dev/null +++ b/.forgejo/workflows/mirror.yml @@ -0,0 +1,20 @@ +name: Mirror to GitHub + +on: + push: + branches: ['**'] + tags: ['**'] + +jobs: + mirror: + runs-on: [self-hosted, hestia] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Push to GitHub + run: | + git remote add github \ + "https://x-access-token:${{ secrets.GITHUB_MIRROR_TOKEN }}@github.com/Breadway/breadbar.git" + git push github --mirror diff --git a/.forgejo/workflows/package.yml b/.forgejo/workflows/package.yml new file mode 100644 index 0000000..43b2d97 --- /dev/null +++ b/.forgejo/workflows/package.yml @@ -0,0 +1,46 @@ +name: Build and publish package + +on: + push: + tags: ['v*'] + +jobs: + package: + runs-on: [self-hosted, hestia] + container: + image: archlinux:latest + options: --privileged + + steps: + - uses: actions/checkout@v4 + + - name: Set version + run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV + + - name: Install build dependencies + run: pacman -Syu --noconfirm base-devel git rust cargo gtk4 gtk4-layer-shell libpulse iw + + - name: Create builder user + run: useradd -m builder + + - name: Prepare source + run: | + 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 + cp -r . /home/builder/src + chown -R builder:builder /home/builder/src + + - name: Build package + run: su builder -c "cd /home/builder/src/packaging/arch && makepkg -sf --noconfirm" + + - name: Publish to Forgejo registry + run: | + PKG=$(find /home/builder/src/packaging/arch -name '*.pkg.tar.zst' | head -1) + curl -fsS -X PUT \ + -H "Authorization: token ${{ secrets.FORGEJO_TOKEN }}" \ + --upload-file "${PKG}" \ + "https://git.breadway.dev/api/packages/breadway/arch/push?distrib=breadway" diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD new file mode 100644 index 0000000..32d8c5a --- /dev/null +++ b/packaging/arch/PKGBUILD @@ -0,0 +1,32 @@ +# Maintainer: Breadway + +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') +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" +} From 00dbb1df5f6075b775b662bb88fabcf45c5474d3 Mon Sep 17 00:00:00 2001 From: Breadway Date: Sat, 13 Jun 2026 16:02:07 +0800 Subject: [PATCH 07/18] Fix Forgejo workflows for the actual server capabilities - package.yml: correct Arch registry upload (octet-stream + binary body), drop --privileged, manual shell clone (archlinux image has no Node), built-in Actions token, --nocheck - mirror.yml: clone --mirror + explicit refs push with --prune --- .forgejo/workflows/mirror.yml | 17 ++++++------ .forgejo/workflows/package.yml | 48 +++++++++++++++------------------- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/.forgejo/workflows/mirror.yml b/.forgejo/workflows/mirror.yml index 1ff2822..2d9ec37 100644 --- a/.forgejo/workflows/mirror.yml +++ b/.forgejo/workflows/mirror.yml @@ -9,12 +9,13 @@ jobs: mirror: runs-on: [self-hosted, hestia] steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Push to GitHub + - name: Mirror to GitHub run: | - git remote add github \ - "https://x-access-token:${{ secrets.GITHUB_MIRROR_TOKEN }}@github.com/Breadway/breadbar.git" - git push github --mirror + set -euo pipefail + git clone --mirror "${GITHUB_SERVER_URL}/${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.GITHUB_MIRROR_TOKEN }}@github.com/Breadway/breadbar.git" \ + '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*' diff --git a/.forgejo/workflows/package.yml b/.forgejo/workflows/package.yml index 43b2d97..e18c4bb 100644 --- a/.forgejo/workflows/package.yml +++ b/.forgejo/workflows/package.yml @@ -9,38 +9,32 @@ jobs: runs-on: [self-hosted, hestia] container: image: archlinux:latest - options: --privileged - steps: - - uses: actions/checkout@v4 - - - name: Set version - run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV - - - name: Install build dependencies - run: pacman -Syu --noconfirm base-devel git rust cargo gtk4 gtk4-layer-shell libpulse iw - - - name: Create builder user - run: useradd -m builder - - - name: Prepare source + # 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.GITHUB_TOKEN }} run: | - git archive --format=tar.gz \ - --prefix=breadbar-${VERSION}/ \ - HEAD > packaging/arch/breadbar-${VERSION}.tar.gz + 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 \ + "${GITHUB_SERVER_URL}/${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 - cp -r . /home/builder/src chown -R builder:builder /home/builder/src - - - name: Build package - run: su builder -c "cd /home/builder/src/packaging/arch && makepkg -sf --noconfirm" - - - name: Publish to Forgejo registry - run: | + # --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 ${{ secrets.FORGEJO_TOKEN }}" \ - --upload-file "${PKG}" \ - "https://git.breadway.dev/api/packages/breadway/arch/push?distrib=breadway" + -H "Authorization: token ${PUBLISH_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary "@${PKG}" \ + "https://git.breadway.dev/api/packages/Breadway/arch/os" From 7c23265eab873d6e3723fc63aa75eb7ccb75ca9f Mon Sep 17 00:00:00 2001 From: Breadway Date: Sat, 13 Jun 2026 16:10:49 +0800 Subject: [PATCH 08/18] Rename mirror secret to MIRROR_TOKEN (GITHUB_ prefix is reserved) Forgejo/gitea rejects user secret names starting with GITHUB_. --- .forgejo/workflows/mirror.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/mirror.yml b/.forgejo/workflows/mirror.yml index 2d9ec37..bc3249a 100644 --- a/.forgejo/workflows/mirror.yml +++ b/.forgejo/workflows/mirror.yml @@ -17,5 +17,5 @@ jobs: # 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.GITHUB_MIRROR_TOKEN }}@github.com/Breadway/breadbar.git" \ + "https://x-access-token:${{ secrets.MIRROR_TOKEN }}@github.com/Breadway/breadbar.git" \ '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*' From 4b4d222784ea5b41545539b61ba0c1df5ae38a78 Mon Sep 17 00:00:00 2001 From: Breadway Date: Sat, 13 Jun 2026 16:14:13 +0800 Subject: [PATCH 09/18] Clone from public URL, not GITHUB_SERVER_URL (resolves to localhost in runner) The Forgejo runner injects GITHUB_SERVER_URL as http://localhost:3002, which is unreachable from inside the job container. Use the public URL instead. --- .forgejo/workflows/mirror.yml | 2 +- .forgejo/workflows/package.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/mirror.yml b/.forgejo/workflows/mirror.yml index bc3249a..ac655db 100644 --- a/.forgejo/workflows/mirror.yml +++ b/.forgejo/workflows/mirror.yml @@ -12,7 +12,7 @@ jobs: - name: Mirror to GitHub run: | set -euo pipefail - git clone --mirror "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" repo.git + 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. diff --git a/.forgejo/workflows/package.yml b/.forgejo/workflows/package.yml index e18c4bb..89cb69f 100644 --- a/.forgejo/workflows/package.yml +++ b/.forgejo/workflows/package.yml @@ -22,7 +22,7 @@ jobs: useradd -m builder git config --global --add safe.directory '*' git clone --branch "${GITHUB_REF_NAME}" --depth 1 \ - "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" /home/builder/src + "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 From 289fc1c827f97b572f5ad5fc670e0ac905b992a6 Mon Sep 17 00:00:00 2001 From: Breadway Date: Sat, 13 Jun 2026 17:06:54 +0800 Subject: [PATCH 10/18] Disable LTO in PKGBUILD (vendored ring/mlua static libs vs makepkg -flto) --- packaging/arch/PKGBUILD | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index 32d8c5a..d2eb2dd 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -7,6 +7,10 @@ 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) depends=('gtk4' 'gtk4-layer-shell' 'libpulse' 'iw') optdepends=( 'hyprland: workspace and window data integration' From ea441a2de38767f7148edd87523484836c6dad94 Mon Sep 17 00:00:00 2001 From: Breadway Date: Sat, 13 Jun 2026 22:55:40 +0800 Subject: [PATCH 11/18] Use REGISTRY_TOKEN (scoped write:package) for registry publish --- .forgejo/workflows/package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/package.yml b/.forgejo/workflows/package.yml index 89cb69f..2895a76 100644 --- a/.forgejo/workflows/package.yml +++ b/.forgejo/workflows/package.yml @@ -14,7 +14,7 @@ jobs: # actions require. Everything runs as shell steps and clones manually. - name: Build and publish env: - PUBLISH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PUBLISH_TOKEN: ${{ secrets.REGISTRY_TOKEN }} run: | set -euo pipefail VERSION="${GITHUB_REF_NAME#v}" From ec24ed63714dede4eb3595e3678aac4d8f50caaf Mon Sep 17 00:00:00 2001 From: Breadway Date: Sat, 13 Jun 2026 23:00:49 +0800 Subject: [PATCH 12/18] Disable debug package so the main package publishes correctly makepkg's debug split produced a -debug pkg; the upload's head -1 could grab it instead of the main package. !debug yields a single package. --- packaging/arch/PKGBUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index d2eb2dd..c021698 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -10,7 +10,7 @@ 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) +options=(!lto !debug) depends=('gtk4' 'gtk4-layer-shell' 'libpulse' 'iw') optdepends=( 'hyprland: workspace and window data integration' From b09817805874522ece1996685b3e00a580260d39 Mon Sep 17 00:00:00 2001 From: Breadway Date: Sun, 14 Jun 2026 19:36:38 +0800 Subject: [PATCH 13/18] Embed SVG assets and rasterise with resvg The packaged binary panicked on startup ("svg load: Unrecognized image file format"): asset SVGs were referenced by their build-time CARGO_MANIFEST_DIR path (absent on an installed system, so read_to_string returned empty bytes), and gdk::Texture::from_bytes can no longer decode SVG since librsvg dropped its gdk-pixbuf loader. - include_str! the SVGs into the binary (no runtime asset files) - rasterise via resvg/tiny-skia into a gdk::MemoryTexture (no system loader) --- Cargo.lock | 278 +++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 5 +- src/bar/stats.rs | 27 +++-- src/main.rs | 35 ++++-- 4 files changed, 317 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd6d212..3ba78d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,30 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "async-broadcast" version = "0.7.2" @@ -70,6 +88,18 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.0" @@ -89,7 +119,7 @@ dependencies = [ [[package]] name = "breadbar" -version = "0.1.2" +version = "0.1.5" dependencies = [ "bread-theme", "futures-lite", @@ -97,6 +127,7 @@ dependencies = [ "gtk4-layer-shell", "hyprland", "relm4", + "resvg", "serde", "serde_json", "tokio", @@ -109,6 +140,12 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "bytes" version = "1.11.1" @@ -121,7 +158,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cairo-sys-rs", "glib", "libc", @@ -172,12 +209,27 @@ dependencies = [ "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]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + [[package]] name = "derive_more" version = "2.1.1" @@ -271,6 +323,15 @@ dependencies = [ "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]] name = "event-listener" version = "5.4.1" @@ -301,6 +362,15 @@ dependencies = [ "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]] name = "field-offset" version = "0.3.6" @@ -311,6 +381,22 @@ dependencies = [ "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]] name = "flume" version = "0.12.0" @@ -591,7 +677,7 @@ version = "0.22.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a" dependencies = [ - "bitflags", + "bitflags 2.13.0", "futures-channel", "futures-core", "futures-executor", @@ -720,7 +806,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4069987ff4793699511a251028cc336b438e46565b463f111250148d574752a" dependencies = [ - "bitflags", + "bitflags 2.13.0", "gdk4", "glib", "glib-sys", @@ -835,6 +921,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + [[package]] name = "indexmap" version = "2.14.0" @@ -871,6 +963,17 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "leb128fmt" version = "0.1.0" @@ -928,6 +1031,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.1" @@ -939,6 +1052,15 @@ dependencies = [ "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]] name = "once_cell" version = "1.21.4" @@ -997,6 +1119,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1009,6 +1137,19 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "prettyplease" version = "0.2.37" @@ -1103,6 +1244,35 @@ dependencies = [ "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]] name = "rustc_version" version = "0.4.1" @@ -1118,7 +1288,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -1216,6 +1386,27 @@ dependencies = [ "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]] name = "slab" version = "0.4.12" @@ -1247,6 +1438,25 @@ dependencies = [ "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]] name = "syn" version = "2.0.117" @@ -1310,6 +1520,32 @@ dependencies = [ "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]] name = "tokio" version = "1.52.3" @@ -1449,6 +1685,28 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "usvg" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" @@ -1563,7 +1821,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.13.0", "hashbrown 0.15.5", "indexmap", "semver", @@ -1723,7 +1981,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.13.0", "indexmap", "log", "serde", @@ -1759,6 +2017,12 @@ version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "zbus" version = "5.16.0" diff --git a/Cargo.toml b/Cargo.toml index 4594c93..ddcf280 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "breadbar" -version = "0.1.2" +version = "0.1.5" edition = "2021" description = "Minimal status bar and notification daemon for Hyprland on Wayland" license = "MIT" @@ -20,6 +20,9 @@ zbus = { version = "5", default-features = false, features = ["tokio"] } tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "process", "signal", "sync"] } serde = { version = "1", features = ["derive"] } 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" diff --git a/src/bar/stats.rs b/src/bar/stats.rs index 8a6b5d1..2d4908b 100644 --- a/src/bar/stats.rs +++ b/src/bar/stats.rs @@ -15,22 +15,25 @@ static BT_CONN: AsyncOnce = AsyncOnce::const_new(); static BT_CACHE: LazyLock> = LazyLock::new(|| Mutex::new(BT_OFF)); static BT_TICK: AtomicU8 = AtomicU8::new(0); -pub const WIFI_STRONG: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Strong.svg"); -pub const WIFI_MEDIUM: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Medium.svg"); -pub const WIFI_WEAK: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Weak.svg"); -pub const WIFI_OFF: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Disconnect.svg"); +// 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 = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Battery 3 Bars.svg"); -pub const BAT_MID: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Battery 2 Bars.svg"); -pub const BAT_LOW: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Battery 1 Bar.svg"); -pub const AC_POWER: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/AC Power.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 = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Bluetooth Off.svg"); -pub const BT_ON: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Bluetooth.svg"); -pub const BT_CONNECTED: &str = concat!( +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)] pub struct Stats { diff --git a/src/main.rs b/src/main.rs index f1d77b3..877490c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,10 @@ +// 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 { ($n:literal) => { - concat!(env!("CARGO_MANIFEST_DIR"), "/assets/", $n) + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/", $n)) }; } @@ -274,24 +278,39 @@ impl App { } } -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, 0); pair.add_css_class("stat-pair"); - let img = gtk4::Image::from_paintable(Some(&svg_texture(icon_path))); + let img = gtk4::Image::from_paintable(Some(&svg_texture(icon_svg))); img.add_css_class("stat-icon"); pair.append(&img); pair.append(label); pair } -fn svg_texture(path: &str) -> gtk4::gdk::Texture { +// Rasterise an (embedded) SVG to a texture. Done in pure Rust with resvg +// because librsvg dropped its gdk-pixbuf SVG loader, so gdk::Texture::from_bytes +// can no longer decode SVG on a stock system. +fn svg_texture(svg_src: &str) -> gtk4::gdk::Texture { + use resvg::{tiny_skia, usvg}; let fg = theme::fg_color(); - let svg = std::fs::read_to_string(path) - .unwrap_or_default() + let svg = svg_src .replace("currentColor", &fg) .replace(r#"width="24" height="24""#, r#"width="16" height="16""#); - let bytes = gtk4::glib::Bytes::from_owned(svg.into_bytes()); - gtk4::gdk::Texture::from_bytes(&bytes).expect("svg load") + 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() -> gtk4::Label { From 1959a86157594cc719f5d8f646131c85fc51f7df Mon Sep 17 00:00:00 2001 From: Breadway Date: Mon, 15 Jun 2026 18:51:42 +0800 Subject: [PATCH 14/18] Default workspace-button rounding to 0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/theme.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ba78d5..1970c7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,7 +119,7 @@ dependencies = [ [[package]] name = "breadbar" -version = "0.1.5" +version = "0.1.6" dependencies = [ "bread-theme", "futures-lite", diff --git a/Cargo.toml b/Cargo.toml index ddcf280..755020b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "breadbar" -version = "0.1.5" +version = "0.1.6" edition = "2021" description = "Minimal status bar and notification daemon for Hyprland on Wayland" license = "MIT" diff --git a/src/theme.rs b/src/theme.rs index 62ce751..747ca1d 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -14,7 +14,7 @@ fn load_css() -> String { window.breadbar {{ background-color: {bg_rgba}; border-radius: 0; }}\ label {{ color: {fg}; }}\ .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; border: none; outline: none; box-shadow: none;\ min-width: 24px; padding: 4px 8px; }}\ .workspace-btn:hover {{ opacity: 0.8; }}\ .workspace-btn.active {{ background: {accent}; opacity: 1; }}\ From 0cb27ec1c47b647e42bc1e5830b68be63bacf59c Mon Sep 17 00:00:00 2001 From: Breadway Date: Tue, 16 Jun 2026 16:56:52 +0800 Subject: [PATCH 15/18] theme: load the shared bread-theme stylesheet Call bread_theme::gtk::apply_shared() before breadbar's own rules so fonts, palette, and generic widgets come from the one ecosystem stylesheet (and recolour live). Keep only breadbar-specific CSS (bar window, workspace buttons, stats, notifications, OSD). Bump bread-theme dep to v0.2.6. --- Cargo.toml | 2 +- src/theme.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 755020b..ec2f241 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["wayland", "hyprland", "bar", "status-bar", "gtk4"] categories = ["gui"] [dependencies] -bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.1.0", features = ["gtk"] } +bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.6", features = ["gtk"] } gtk4 = { version = "0.11", features = ["v4_12"] } gtk4-layer-shell = "0.8" relm4 = { version = "0.11", features = ["macros"] } diff --git a/src/theme.rs b/src/theme.rs index 747ca1d..820b69a 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -9,9 +9,10 @@ thread_local! { fn load_css() -> String { let p = load_palette(); + // breadbar-specific rules only — fonts, base colours, and generic widgets + // come from the shared ecosystem stylesheet (applied first in `apply()`). format!( - "* {{ 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}; }}\ .workspace-btn {{ background: transparent; color: {fg}; opacity: 0.45;\ border-radius: 0; border: none; outline: none; box-shadow: none;\ @@ -50,6 +51,10 @@ pub fn fg_color() -> String { /// 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 + // so breadbar's own rules below layer on top. + bgtk::apply_shared(); + let css = load_css(); PROVIDER.with(|cell| bgtk::apply_css(&css, cell)); From 37b9a342e169dac97a68e139605c695f2279ccfe Mon Sep 17 00:00:00 2001 From: Breadway Date: Tue, 16 Jun 2026 18:31:18 +0800 Subject: [PATCH 16/18] Release 0.1.7: shared bread-theme stylesheet Pin bread-theme v0.2.6 and load the shared ecosystem stylesheet. --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1970c7a..cb922a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,8 +108,8 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bread-theme" -version = "0.1.0" -source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.1.0#6b5f4f475f66a645b08cb865e6dda8228d23679b" +version = "0.2.3" +source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.6#0c8c5c00e435fedff4f81e36d603424c153519a9" dependencies = [ "dirs", "gtk4", @@ -119,7 +119,7 @@ dependencies = [ [[package]] name = "breadbar" -version = "0.1.6" +version = "0.1.7" dependencies = [ "bread-theme", "futures-lite", diff --git a/Cargo.toml b/Cargo.toml index ec2f241..f647dba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "breadbar" -version = "0.1.6" +version = "0.1.7" edition = "2021" description = "Minimal status bar and notification daemon for Hyprland on Wayland" license = "MIT" From 0893724e5fd62481dde735aeb12cced30f6d12ca Mon Sep 17 00:00:00 2001 From: Breadway Date: Wed, 17 Jun 2026 12:40:52 +0800 Subject: [PATCH 17/18] Fix illegible text on light pywal palettes + hot-reload Use bread-theme 0.2.7's luminance-picked ink (@on-*) for text on coloured backgrounds: the active workspace pill and notification cards previously kept the pywal foreground, which vanished when those slots came out light. Drop the blanket label colour rule (it overrode the per-surface ink on child labels). Switch to bread_theme::gtk::apply_app_css so the bar recolours live on `bread-theme reload` instead of only at startup. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/theme.rs | 56 +++++++++++++++++++++++++++++----------------------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb922a6..4f918a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,7 +109,7 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bread-theme" version = "0.2.3" -source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.6#0c8c5c00e435fedff4f81e36d603424c153519a9" +source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.7#ea87083c0615fc9141b0ae4c99f833748a0189d1" dependencies = [ "dirs", "gtk4", diff --git a/Cargo.toml b/Cargo.toml index f647dba..a05db04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["wayland", "hyprland", "bar", "status-bar", "gtk4"] categories = ["gui"] [dependencies] -bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.6", features = ["gtk"] } +bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.7", features = ["gtk"] } gtk4 = { version = "0.11", features = ["v4_12"] } gtk4-layer-shell = "0.8" relm4 = { version = "0.11", features = ["macros"] } diff --git a/src/theme.rs b/src/theme.rs index 820b69a..e02ed01 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,9 +1,8 @@ -use bread_theme::{gtk as bgtk, hex_to_rgba, load_palette}; +use bread_theme::{gtk as bgtk, hex_to_rgba, ink_on, load_palette}; use gtk4::CssProvider; use std::cell::RefCell; thread_local! { - static PROVIDER: RefCell> = const { RefCell::new(None) }; static USER_PROVIDER: RefCell> = const { RefCell::new(None) }; } @@ -11,52 +10,59 @@ fn load_css() -> String { let p = load_palette(); // breadbar-specific rules only — fonts, base colours, and generic widgets // come from the shared ecosystem stylesheet (applied first in `apply()`). + // Colour is set on each surface (bar, active workspace pill, notification + // card) and child labels inherit it, so text stays legible whatever lightness + // pywal hands a given slot. `on_*` are luminance-picked ink (black/white) for + // that background — the pywal hues themselves are untouched. format!( - "window.breadbar {{ background-color: {bg_rgba}; border-radius: 0; }}\ - label {{ color: {fg}; }}\ - .workspace-btn {{ background: transparent; color: {fg}; opacity: 0.45;\ + "window.breadbar {{ background-color: {bg_rgba}; color: {on_bg}; border-radius: 0; }}\ + .workspace-btn {{ background: transparent; opacity: 0.45;\ border-radius: 0; border: none; outline: none; box-shadow: none;\ min-width: 24px; padding: 4px 8px; }}\ .workspace-btn:hover {{ opacity: 0.8; }}\ - .workspace-btn.active {{ background: {accent}; opacity: 1; }}\ + .workspace-btn.active {{ background: {accent}; color: {on_accent}; opacity: 1; }}\ .stats-box {{ margin-right: 8px; }}\ .stat-pair {{ margin-right: 12px; }}\ .stat-icon {{ margin-right: 5px; }}\ .bt-icon {{ margin-right: 12px; }}\ - window.breadbar-notification {{ background-color: alpha({bg_plain}, 0.95); }}\ - .notification-card {{ background: {surface}; border-radius: 8px;\ + window.breadbar-notification {{ background-color: alpha({bg_plain}, 0.95); color: {on_bg}; }}\ + .notification-card {{ background: {surface}; color: {on_surface}; border-radius: 8px;\ padding: 12px; margin-bottom: 8px; }}\ - .notification-summary {{ font-weight: bold; color: {fg}; }}\ - .notification-app {{ color: {fg}; opacity: 0.6; }}\ - .notification-body {{ color: {fg}; }}\ - window.breadbar-osd {{ background-color: alpha({bg_plain}, 0.95); border-radius: 8px; }}\ - .osd-kind {{ color: {fg}; opacity: 0.75; font-size: 12px; }}\ - .osd-pct {{ color: {fg}; font-weight: bold; font-size: 12px; }}\ + .notification-summary {{ font-weight: bold; }}\ + .notification-app {{ opacity: 0.6; }}\ + window.breadbar-osd {{ background-color: alpha({bg_plain}, 0.95); color: {on_bg}; border-radius: 8px; }}\ + .osd-kind {{ opacity: 0.75; font-size: 12px; }}\ + .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, - fg = p.foreground, - accent = p.color4, - trough = hex_to_rgba(&p.color4, 0.25), + 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), ) } -/// Returns the current foreground colour (used for icon tinting in the stats bar). +/// Returns the ink colour for icon tinting in the stats bar — the same +/// luminance-picked colour the bar's text uses, so icons stay legible on the bar +/// whatever lightness pywal gives the background. pub fn fg_color() -> String { - load_palette().foreground + ink_on(&load_palette().background).to_string() } /// 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 - // so breadbar's own rules below layer on top. + // (and self-reloading) so breadbar's own rules below layer on top. bgtk::apply_shared(); - let css = load_css(); - PROVIDER.with(|cell| bgtk::apply_css(&css, cell)); + // 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")); From aa34aa13198fc1099c61648a71b97e7bf5cb462f Mon Sep 17 00:00:00 2001 From: Breadway Date: Wed, 17 Jun 2026 12:55:47 +0800 Subject: [PATCH 18/18] Bump bread-theme to v0.2.8 (live-reload fix) --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f918a3..aa34dc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,7 +109,7 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bread-theme" version = "0.2.3" -source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.7#ea87083c0615fc9141b0ae4c99f833748a0189d1" +source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.8#77417d552130281ff787e07d52541eb25e9d533b" dependencies = [ "dirs", "gtk4", diff --git a/Cargo.toml b/Cargo.toml index a05db04..0950f3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["wayland", "hyprland", "bar", "status-bar", "gtk4"] categories = ["gui"] [dependencies] -bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.7", features = ["gtk"] } +bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.8", features = ["gtk"] } gtk4 = { version = "0.11", features = ["v4_12"] } gtk4-layer-shell = "0.8" relm4 = { version = "0.11", features = ["macros"] }