No description
Find a file
Breadway e52c507f32 capsule: keyboard open, query modes, sections, and search-state geometry (phase 6c)
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.
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 capsule: keyboard open, query modes, sections, and search-state geometry (phase 6c) 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 bar: implement spotlight's embedded launcher capsule (Phase 6b) 2026-08-31 15:45:25 +08:00
Cargo.toml bar: implement spotlight's embedded launcher capsule (Phase 6b) 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