Lightbar
Lightbar is a small native Wayland panel for Sway. It draws directly into shared-memory layer-shell surfaces and updates only when module state changes or the compositor grants a frame. There is no GTK widget tree, CSS engine, browser, or free-running render loop.
The initial target is Ubuntu 26.04 and Sway 1.11. The implementation checklist and the deliberately deferred stress tests are tracked in TOOD.md.
What works
- one top or bottom bar on every selected output, with output-local workspaces;
- left, centered, and right module groups with hover/click/scroll hit testing;
- Sway workspaces, binding mode, scratchpad list and direct IPC actions;
- exact-boundary clock and calendar popup;
- NetworkManager, PipeWire audio, battery, backlight, and hwmon temperature;
- default output and microphone selection, volume and mute controls, backlight sliders, and external advanced settings launchers;
- interval and streaming custom commands with limits, timeout, stale state, and explicit shell opt-in;
- StatusNotifier watcher/host, validated ARGB pixmaps, attention state, activation, secondary activation, item context menus, and scrolling;
- strict TOML validation and atomic config/theme hot reload;
check-configanddoctorcommands.
Tray items that only publish an icon-theme name currently use a dot fallback.
Pixmap icons are rendered directly. DBusMenu is also still delegated to the tray
item through ContextMenu; native nested DBusMenu rendering is tracked in the
checklist.
Build and try it
Rust 1.93 or newer is required. On Ubuntu 26.04, install the normal Rust build
toolchain and pkg-config; the Wayland and D-Bus protocol clients themselves are
pure Rust.
cargo test --all-targets
cargo run -- check-config --config examples/config.toml
cargo run -- doctor --config examples/config.toml
cargo run -- --config examples/config.toml
The example uses UbuntuMono Nerd Font. Install that font for the intended icon
glyphs; cosmic-text/fontconfig will otherwise select an installed fallback such
as Noto Sans.
Install without replacing Waybar
./scripts/install.sh
This builds a locked release, installs ~/.local/bin/lightbar, and copies the
example files only when the destination does not already exist. It also writes
~/.config/lightbar/bar.conf, a generated Sway fragment containing the
current user's absolute binary and configuration paths. It does not edit your
Sway or Waybar configuration.
Validate the installed configuration:
~/.local/bin/lightbar check-config
~/.local/bin/lightbar doctor
Then remove or comment out your existing top-level bar { ... } block, add the
portable include below at top level, and reload Sway:
include ~/.config/lightbar/bar.conf
The included file is generated separately for each user, so a shared Sway
configuration never contains a username or /home/... path. Absolute paths in
that generated file also avoid depending on the graphical Sway session's
PATH, which does not always include ~/.local/bin. If XDG_CONFIG_HOME is
customized, use the absolute include line printed by the installer instead.
Sway appends -b lightbar; the CLI accepts that automatically. To roll back,
remove the include, restore the old bar block, and reload. For a Waybar setup the
bar block can contain:
bar {
swaybar_command waybar
}
Lightbar never edits or removes Waybar's files.
Configuration
The configuration defaults to $XDG_CONFIG_HOME/lightbar/config.toml (or
~/.config/lightbar/config.toml). theme is resolved relative to that file. Both
files reject unknown fields, and an invalid live edit is logged while the last
valid configuration remains active.
Module actions live below common.actions and support four explicit types:
[modules.example]
kind = "command"
mode = "interval" # or "stream"
argv = ["date", "+%s"] # no shell interpretation
interval = "30s"
timeout = "2s"
max_output_bytes = 65536
[modules.example.common.actions.left]
type = "exec"
argv = ["notify-send", "clicked"]
Use type = "shell" only when shell expansion is intentional. Command output
may be plain text or a JSON object:
{"text":"42","tooltip":"answer","state":"warning","visible":true}
See examples/config.toml and examples/theme.toml for every built-in used by the current layout.
Audio
Audio requires PipeWire, WirePlumber (wpctl), and pw-dump on PATH.
The bar shows the current default output and microphone independently. With
the example actions, middle-click toggles mute and scrolling changes volume
for the speaker or microphone segment under the pointer. Left-click opens
the audio popup.
Click a device name in the popup to choose a default output or microphone. Each default has its own volume slider and mute button. Click the slider or scroll over it to adjust volume between 0% and 150%; changing volume preserves mute state. External changes and device connections update the bar and open popup automatically. A missing default is shown as unavailable while the other endpoint remains usable. If the audio service disconnects, the popup closes and the module reconnects automatically.
Use Tab/Shift+Tab or Up/Down to focus controls, Enter/Space to activate buttons, Left/Right to adjust a focused slider, Home/End for its limits, and Escape to close. Long device lists support scrolling and Previous/More buttons.
format and muted_format configure the output segment;
microphone_format and microphone_muted_format configure the microphone
segment. All four accept {volume}, {device} (description), and {name}
(PipeWire node name). Existing output formats remain valid. The complete
example configuration includes both microphone formats.
The popup's Open pavucontrol button provides per-application routing and
device profile controls when pavucontrol is installed. Default-device
selection uses WirePlumber's set-default command;
routing of existing streams follows the session manager's policy.
CPU and memory comparison
Build release binaries, run one bar configuration at a time, and sample the process for at least ten minutes:
./scripts/sample-process.sh $(pgrep -n lightbar) 600 > lightbar-core.tsv
./scripts/sample-process.sh $(pgrep -n waybar) 600 > waybar.tsv
Repeat with the tray removed from each layout, then interact with workspaces,
audio, popups, and tray items. Compare the samples and inspect whether RSS or
CPU trends upward. An eight-hour soak and event-storm testing remain release
gates in TOOD.md; a short local sample is not evidence that those gates pass.
Design guardrails
- Module events are dirty-compared before drawing.
- Frame callbacks prevent drawing faster than the compositor.
- D-Bus tray signal queues, process output, IPC replies, and pixmaps are bounded.
- Module workers back off or wait on events; low-frequency hardware values use sparse polling.
- Dropping a configuration generation stops its workers and monitored children.