No description
Find a file
Breadway df6b568cc1 bar: implement glass-workbench's three module variants (Phase 5)
Wires modules.workspaces.style/modules.clock.style into the bar for real
(Phase 3 shipped the schema but only ever consumed trail/flip), and adds
cpu/ram as bar modules, so glass-workbench (bread-theme) renders correctly
while liquid-motion's default path is untouched:

- workspaces: "pill" never calls WorkspaceTrail::place/stretch (the trail
  overlay stays invisible) and honours modules.workspaces.show_empty for
  real, rendering unoccupied non-active workspaces dimmed via CSS instead
  of filtering them out of the row. "trail" keeps the exact pre-existing
  filter/place/stretch behaviour regardless of show_empty.
- clock: "plain" registers a plain date+time label (date_lbl, built but
  never parented until now, plus a new clock_plain_lbl) instead of the
  per-digit flip box. "flip" is untouched.
- cpu/ram: two new bar chips (bar_cpu_pair/bar_ram_pair, bar_cpu_lbl/
  bar_ram_lbl) fed by the same AppInput::StatsUpdate data as the control
  panel's cpu_pair/mem_pair. Separate instances rather than reparenting
  the panel's own pair — a GTK widget can only have one parent, and
  reparenting would pull them out of the control panel's sys-grid, which
  no theme asked to change.

theme.rs::load_css() now branches on tokens.bar_border() (full-border
island vs. flush bar's bottom-only hairline) and modules.workspaces.style
(dimmed/translucent trail-fill buttons vs. solid-accent-fill pills), and
unconditionally gains .clock-plain/.clock-plain-time rules. The trail/flip
branches reproduce today's CSS byte-for-byte.

Verification (bread-capture, isolated headless-Sway harness):
- Noise floor: same baseline binary against itself varies from AE=0 (same
  capture minute) up to ~1900px (an in-flight digit-flip/clock-tick or a
  live Wi-Fi scan straddling two captures) and ~39000px on control-panel
  (live hardware sensors) — all pre-existing, not introduced by this change.
- Regression: this binary vs. the pre-Phase-5 baseline, captured in the
  same clock-minute to remove the dominant noise source, is AE=0 (bit-for-
  bit identical) on the bar view under the default liquid-motion theme.
- glass-workbench (BREAD_SHELL_THEME=glass-workbench): bar view is
  1920x36 (flush, no margin) vs. liquid-motion's 1920x56 (12px margin +
  44px island), square corners vs. liquid-motion's rounded island, plain
  "Mon 24/08 23:24" clock, and cpu/ram/wifi/battery/control chips with no
  media widget — matches demo 02. The isolated Sway capture harness has no
  live Hyprland IPC, so it reports zero workspaces under both themes
  (pre-existing, unrelated to this change) — the pill fill/dim CSS itself
  is exercised by code review and the trail-untouched regression result,
  not by a captured pixel with visible buttons.

cargo test: 18/18 passing (breadbar), no new clippy/cargo-check warnings
beyond the two pre-existing ones this task named up front.
2026-08-31 15:45:25 +08:00
.forgejo/workflows CI: refuse unsigned bakery index on stable tag releases 2026-08-16 00:50:22 +08:00
assets Include GPU.svg so the bar release build can compile 2026-08-16 13:49:52 +08:00
ci ci: port breadbar onto shared bread-ecosystem build system 2026-08-15 22:34:12 +08:00
src bar: implement glass-workbench's three module variants (Phase 5) 2026-08-31 15:45:25 +08:00
.gitignore gitignore: exclude graphify-out local tool cache 2026-08-23 15:07:51 +08:00
AGENTS.md Add notification history; pin ecosystem crates to v0.7.2 2026-08-15 22:54:04 +08:00
bakery.toml Pin ecosystem crates off floating main; bakery pulls bread 2026-08-15 21:40:24 +08:00
Cargo.lock Drive bar/window geometry and CSS tokens from the shell theme manifest 2026-08-31 15:45:25 +08:00
Cargo.toml Drive bar/window geometry and CSS tokens from the shell theme manifest 2026-08-31 15:45:25 +08:00
CONTRIBUTING.md CONTRIBUTING.md: document single-trunk + RC-tag release model 2026-07-31 11:08:41 +08:00
LICENSE Prepare repo for public GitHub release 2026-05-19 12:30:12 +08:00
README.md Persist notification history to XDG state 2026-08-15 23:11:28 +08:00

breadbar

Minimal status bar and notification daemon for Hyprland on Wayland.

A single Rust binary that provides a full-width top bar, a D-Bus notification daemon, a volume/brightness OSD, and an SNI system tray housed in a control panel popover.

Features

Status bar (anchored to the top of every monitor via gtk4-layer-shell):

  • Left: live workspace buttons sourced from Hyprland IPC, active workspace highlighted
  • Centre: media widget (track/artist from playerctl, click to open prev/play-pause/next controls; hidden when no player is active, lingers up to 30 minutes after the last pause) + clock (HH:MM, updates at the top of each minute)
  • Right: CPU%, RAM, power draw (W), battery level + AC indicator, Bluetooth icon (click to open blueman-manager), WiFi SSID with signal-strength icon (click for details popover), hamburger control panel button

WiFi popover (click the WiFi area):

  • Shows current SSID, IP address, and internet/Tailscale connectivity status via breadcrumbs status
  • Lists saved breadcrumbs profiles for one-click switching
  • Shows nearby SSIDs from breadcrumbs scan-list (saved networks are clickable to join)
  • Degrades gracefully if breadcrumbs is not installed

Control panel (hamburger button on the right):

  • Volume slider (reads/writes via wpctl, up to 150%)
  • Brightness slider (reads/writes via brightnessctl)
  • Live CPU%, GPU%, and network throughput (download/upload)
  • Audio output selector (lists PulseAudio sinks via pactl, switching takes effect immediately)
  • System tray (SNI): apps that register with org.kde.StatusNotifierWatcher appear as icon buttons
  • Power buttons: lock (breadlock), suspend, reboot, poweroff

Notification daemon:

  • Implements org.freedesktop.Notifications (D-Bus) — works with any standard sender (notify-send, etc.)
  • Popups appear top-right, stack vertically, auto-dismiss after the sender-specified timeout (default 5 s)
  • Supports CloseNotification and replaces_id
  • History of the last 50 notifications (app, summary, truncated body, time). Loaded from and saved to $XDG_STATE_HOME/breadbar/history.json (typically ~/.local/state/breadbar/history.json). Toggle with breadbar --history (Hyprland: bind = SUPER, N, exec, breadbar --history) or D-Bus dev.breadway.Bar.ToggleHistory on org.freedesktop.Notifications at /dev/breadway/Bar.

Volume/brightness OSD:

  • Overlay window at the bottom of the screen, auto-dismisses after 2 s
  • Appears automatically on any pactl sink-change event or sysfs backlight change

Theming:

  • Uses bread-theme for palette loading; reads ~/.cache/wal/colors.json (pywal) if present, falls back to a Catppuccin Mocha palette
  • User CSS override: ~/.config/breadbar/style.css
  • Send SIGHUP to reload the theme at runtime (integrates with wallpaper-change hooks)

Dependencies

Runtime (required):

  • GTK4 (≥ 4.12)
  • gtk4-layer-shell
  • iw — for WiFi SSID/signal (iw dev <iface> link)
  • wpctl (WirePlumber) — volume read/write
  • pactl (PipeWire-Pulse) — audio sink listing and OSD volume events
  • brightnessctl — brightness read/write
  • A running Hyprland compositor
  • D-Bus session bus

Runtime (optional, degrade gracefully if absent):

  • playerctl — media widget; hidden if no player is found
  • breadcrumbs — WiFi popover enrichment (profiles, internet/Tailscale status); basic SSID/signal still shown without it
  • blueman-manager — opened when the Bluetooth icon is clicked; Bluetooth state still shown without it

Bluetooth state is read from /sys/class/rfkill and BlueZ D-Bus and degrades gracefully if unavailable.

Building

cargo build --release

The binary is at target/release/breadbar.

Requirements: Rust 1.77+ (uses LazyLock), a GTK4 development environment (libgtk-4-dev / gtk4 package).

On Arch Linux:

sudo pacman -S gtk4 gtk4-layer-shell wireplumber pipewire-pulse brightnessctl iw
cargo build --release

Running

./target/release/breadbar

Typically launched from your Hyprland config:

exec-once = /path/to/breadbar

breadbar claims org.freedesktop.Notifications on the session D-Bus on startup. If another notification daemon is already running, startup will fail — stop the other daemon first.

Theming

pywal integration

breadbar reads ~/.cache/wal/colors.json automatically (via bread-theme). To reload after a wallpaper change:

pkill -HUP breadbar

Or hook it into your wallpaper script:

wal -i /path/to/wallpaper.jpg
pkill -HUP breadbar

Custom CSS

Drop a ~/.config/breadbar/style.css file and send SIGHUP to reload. This CSS is applied at a higher priority than the generated palette so you can override anything.

Example — change the font size:

* {
    font-size: 13px;
}

Architecture

Module Responsibility
src/main.rs GTK4 app entry point, widget tree, relm4 component
src/bar/workspaces.rs Hyprland IPC event stream, workspace buttons
src/bar/clock.rs Minute-tick clock
src/bar/stats.rs Polling loop: CPU, RAM, power, battery, Bluetooth, WiFi
src/bar/media.rs playerctl polling, media widget and controls popover
src/bar/wifi.rs WiFi details popover, breadcrumbs profile/scan integration
src/bar/control.rs Control panel data: volume (wpctl), brightness (brightnessctl), sinks (pactl)
src/bar/tray.rs org.kde.StatusNotifierWatcher D-Bus service, SNI item rendering
src/notifications/mod.rs org.freedesktop.Notifications zbus service + dev.breadway.Bar history IPC
src/notifications/popup.rs Layer-shell popup window and card stack
src/notifications/history.rs Bounded history (last 50, persisted under XDG state) and layer-shell history window
src/osd.rs Volume/brightness on-screen display
src/widgets/ Live Lua widgets from breadd (BreadClient + WidgetSpec)
src/theme.rs bread-theme palette loading, GTK CSS provider injection

Stats are polled every 2 seconds. Bluetooth and WiFi are sampled every 16 seconds and cached in between to avoid hammering D-Bus and iw.

License

MIT