Completes the spotlight capsule (theme 04), THEME_SYSTEM_PLAN.md §7: - Keyboard open (item A): a new launcher_command module subscribes to bread.command.box.open — only when the active theme's launcher is Embedded — and focuses launcher_entry on receipt, which the existing EventControllerFocus::connect_enter already turns into an open_fn() call, same path a mouse click into the entry takes. breadbox's own keybind-triggered launch now redirects to this same event under an embedded theme instead of stacking its overlay window on top (see the breadbox commit). - Click-away scrim (item B): open_fn/close_fn now show/hide panel.rs's dismiss surface via the new show_capsule_dismiss/hide_dismiss, and panels.set_on_dismiss(close_fn) wires a click-away into the capsule's own close path. The scrim's clickable region starts at a fixed offset (capsule row height + the drawer's own maximum content height, DRAWER_MAX_HEIGHT_PX) rather than the drawer's live height, so it can never geometrically overlap a rendered result row regardless of breadbar-dismiss's overlay layer always rendering above the bar's own. - Query modes (item C): connect_changed now runs bread_launcher::parse_query against the entry text and, when the parsed prefix is listed in [launcher].modes, swaps launcher_results.scroller for a new mode_list populated by populate_mode_list (calc result / filtered commands / an "open this URL" prompt). key_ctrl routes Up/Down/Return at whichever list is active; a `>`/`.` row's action runs through the new ModeAction enum (RunShell for a command's own fixed exec string, OpenUrl straight to xdg-open) so arbitrary typed text is never passed through a shell. - Sections (item D): ResultsList::new's new `sections` bool is threaded from [launcher].sections. - Search-state geometry (item E): root's own width now spring-animates between [launcher].width and .search_width (animate_capsule_width, same set_size_request-driven technique animate_drawer_height already uses), and a `.searching` class on root switches border-radius to .search_radius via a new CSS rule/transition in theme.rs. Also fixes a real bug this surfaced: drawer_target_height was measuring drawer_box itself, whose size_request is continuously overwritten by animate_drawer_height's own tick callback — GTK clamps a widget's measure() up to at least its own explicit size request, so switching from the (tall) app list to a one-row calc result measured the PREVIOUS frame's forced height instead of the new content's actual size. It now sums each visible child's own natural height directly, which also fixes the same latent issue for narrowing app-search results. screenshot.rs gains two new views (capsule-sections, capsule-calc) to exercise D and C's calc path. |
||
|---|---|---|
| .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