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-config and doctor commands.

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 1 for the selected profile's eligible IPv4/IPv6 settings and reapplies it to active connections. Conflicting wired, Wi-Fi, or mobile profiles with metric 0/1 are moved to 2 as needed; other priorities are preserved. IPv6 metric 0 retains 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.
Description
No description provided
Readme MIT 217 KiB
Languages
Rust 99.1%
Shell 0.9%