10 KiB
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;
- wired, Wi-Fi, and mobile broadband connections, Wi-Fi scanning and passwords, and saved internet priority through NetworkManager;
- 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.
Network
The network module uses NetworkManager's system D-Bus service. NetworkManager 1.44 or newer is required for guarded profile updates. The bar follows its primary connection; the popup reports the actual IPv4 and IPv6 defaults separately, including defaults provided by a VPN. Internet connectivity status comes from NetworkManager's connectivity check, when enabled.
Left-click the network module to see wired LAN, Wi-Fi, and mobile broadband devices. Select a device to see its addresses, saved connections, and controls:
- Scan for Wi-Fi networks requests a scan and updates the list when it completes. Networks show signal strength, security, and saved/connected state.
- Select a saved connection to activate it. New open, enhanced-open, WPA/WPA2 Personal, and WPA3 Personal networks can be joined directly. Password entry is masked and sent over D-Bus to NetworkManager for normal profile storage.
- Disconnect disconnects that device. Wi-Fi and mobile broadband radio switches appear in the overview; hardware blocks are shown separately.
- Prefer for internet saves route metric
1for the selected profile's eligible IPv4/IPv6 settings and reapplies it to active connections. Conflicting wired, Wi-Fi, or mobile profiles with metric0/1are moved to2as needed; other priorities are preserved. IPv6 metric0retains its kernel meaning.
This preference uses NetworkManager configuration, without a separate Lightbar
preference file. It does not change autoconnect priority. Default indicators
continue to reflect NetworkManager's current routes: an unavailable gateway,
IPv6 availability, or VPN policy can produce a different default. VPNs, virtual
connections, and never-default settings are preserved. Explicit default routes
and policy routing require the connection editor. Failed priority updates attempt
to restore the previous metrics and report any rollback failure in the popup.
Use Open connection editor (nm-connection-editor) for enterprise Wi-Fi,
hidden networks, legacy WEP, password changes on saved profiles, SIM/APN setup,
and advanced routing. Existing mobile broadband profiles can be activated in
the popup. NetworkManager's permissions and secret-agent policies still apply;
authorization failures and connection failures appear in the popup.
Use Tab/Shift+Tab or Up/Down to move focus, Enter/Space to activate controls, Backspace to edit a password, and Escape to close. Lists support scrolling and Previous/More buttons. Devices, defaults, and saved settings update from D-Bus events without idle polling. Controls remain available while disconnected.
format_connected accepts {icon}, {kind}, {connection}, {interface},
and {ssid}. An optional interface limits the bar's displayed connection;
the popup continues to show all supported devices. The example uses
{icon} {connection} so wired and mobile defaults have appropriate icons.
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.