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. |
||
|---|---|---|
| .forgejo/workflows | ||
| assets | ||
| ci | ||
| src | ||
| .gitignore | ||
| AGENTS.md | ||
| bakery.toml | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| README.md | ||
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
breadcrumbsprofiles for one-click switching - Shows nearby SSIDs from
breadcrumbs scan-list(saved networks are clickable to join) - Degrades gracefully if
breadcrumbsis 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.StatusNotifierWatcherappear 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
CloseNotificationandreplaces_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 withbreadbar --history(Hyprland:bind = SUPER, N, exec, breadbar --history) or D-Busdev.breadway.Bar.ToggleHistoryonorg.freedesktop.Notificationsat/dev/breadway/Bar.
Volume/brightness OSD:
- Overlay window at the bottom of the screen, auto-dismisses after 2 s
- Appears automatically on any
pactlsink-change event orsysfsbacklight change
Theming:
- Uses
bread-themefor 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
SIGHUPto reload the theme at runtime (integrates with wallpaper-change hooks)
Dependencies
Runtime (required):
- GTK4 (≥ 4.12)
gtk4-layer-shelliw— for WiFi SSID/signal (iw dev <iface> link)wpctl(WirePlumber) — volume read/writepactl(PipeWire-Pulse) — audio sink listing and OSD volume eventsbrightnessctl— 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 foundbreadcrumbs— WiFi popover enrichment (profiles, internet/Tailscale status); basic SSID/signal still shown without itblueman-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