Skip to content

birdayz/ezbar

Repository files navigation

ezbar

A status bar for Sway. GPU-rendered with iced on wlr-layer-shell — no GTK, no config DSL. Widgets are native Rust, or sandboxed WASM you install from a registry.

ezbar — lilac islands (default)

The default look: lilac islands — square panels floating over your wallpaper, filled GPU sparklines, a dark base. Square, not rounded; dark, not busy. Drop in a preset to reskin the whole thing in seconds.

Highlights

  • GPU-rendered, and it shows. Filled sparklines, eased workspace cross-fades, live graphs — drawn on the GPU via iced/wgpu, never pixmaps.
  • A real plugin platform — two ways. A widget is just Rust (one trait, no IPC, no DSL). Or ship a sandboxed WASM plugin: it runs in a wasmtime sandbox, can only do read-only, user-granted things, hot-reloads, and installs from a git-backed registry with consent pinned to the binary's content hash. No other status bar has this.
  • Live everything. Theme, presets, and config reload instantly — no restart; reskin from the on-bar .
  • Real popups. Hover/click panels are actual layer-shell surfaces, not floating overlays.
  • One static binary. No daemon zoo, no GTK; config.toml is plain TOML and fully optional.

Built-in widgets: workspaces · window title · clock · CPU / temperature / memory / ping graphs · volume · battery · media (MPRIS) · GitHub · Google Calendar · Spotify · kubectl · stock ticker · live Claude-usage · and a no-code custom command widget. Clicking, scrolling, and hover popups all work.

Build

Needs stable Rust (≥ 1.88) and a Wayland + Vulkan stack.

Arch / AUR:

yay -S ezbar

Arch:

sudo pacman -S --needed rust wayland libxkbcommon vulkan-icd-loader fontconfig
# + a driver: vulkan-radeon | vulkan-intel | nvidia-utils

Debian/Ubuntu:

sudo apt install pkg-config libwayland-dev libxkbcommon-dev libvulkan1 \
                 mesa-vulkan-drivers libfontconfig1-dev

Then:

cargo build --release        # -> target/release/ezbar

Run

./target/release/ezbar

Add it to your Sway config — ezbar install does this for you (idempotent, and it never edits or removes an existing line; it backs up the config and appends one exec_always), or add it by hand:

exec_always /path/to/ezbar

ezbar is a thin launcher: it respawns the bar if the output disappears (monitor sleep / hotplug). For a single foreground instance (debugging):

EZBAR_CHILD=1 ./target/release/ezbar

Configure

Everything lives under ~/.config/ezbar/ and is optional — a widget with no config just stays quiet. Nothing is hardcoded; no secrets ship in the binary. Every module's [modules.<id>] options are listed in docs/config-reference.md.

Widget Reads from
Calendar calendar_url (your secret iCal URL) or $GOOGLE_CALENDAR_ICAL_URL
GitHub $GH_TOKEN / $GITHUB_TOKEN / gh auth token; optional github_config.json (reasons, exclude_repos)
Spotify spotify_config.json (client_id, client_secret); or $SPOTIFY_ACCESS_TOKEN
Stock $EZBAR_STOCK_SYMBOL (default NQ=F), $EZBAR_STOCK_API_KEY (optional)
Ping [modules.ping].target (default 8.8.8.8)

Theme it

Theming is live — edit [theme] in ~/.config/ezbar/config.toml and the bar re-skins instantly, no restart:

[theme]
style   = "islands"   # islands (default, floating square panels) | solid (one slab)
primary = "#cba6f7"   # the accent — workspace chip, pill borders, highlights (graphs are per-widget, see below)
background = { base = "#1e1e2e", weak = "#313244", strong = "#45475a" }
text = "#cdd6f4"; ok = "#a6e3a1"; warn = "#f9e2af"; urgent = "#f38ba8"

Six ready palettes ship in presets/ezbar-dark (the lilac-islands default), noir (the original flat black slab), catppuccin-mocha, gruvbox-dark, nord, tokyo-night. Drop them into ~/.config/ezbar/presets/, then switch live from the on-bar , or from a keybind:

ezbar msg preset gruvbox-dark   # or: preset next | prev
ezbar msg reload                # popup <kind> | volume <up|down|mute>

Presets are theme-only TOML bundles with a [palette] variable layer (primary = "$mauve"), selected via a state file that never edits your config.toml. Workspace chips come in four square styles (boxed · filled · outlined · underbar). Full design: RFC 0002.

Graph colors (per-widget)

The inline sparklines (cpu · temperature · memory · ping) are functional by default: they run green→red by load, so a pinned core looks hot. That colour carries meaning, so it isn't a global theme token — it's a per-widget knob under [modules.<id>.graph], not [theme]:

line_color line
"threshold" (default, or omitted) green→yellow→orange→red by load
"accent" "ok" "warn" "urgent" "fg" "fg_dim" that theme token, flat
"#rrggbb" / "#rrggbbaa" that literal colour, flat
# cpu keeps the functional default — leave it out entirely for this
[modules.cpu.graph]
line_color = "threshold"   # green when idle, red when pinned

[modules.temperature.graph]
line_color = "accent"      # flat lilac, matches the bar accent

[modules.memory.graph]
line_color = "#89b4fa"     # a fixed blue

Want every graph flat-accent (the r/unixporn ricer look)? Set line_color = "accent" on each — it's deliberately opt-in per widget, so going aesthetic never silently drops the load signal on a monitor you forgot about. A typo falls back to threshold, so a bad value can't blank a graph.

Interactions

Widget Action
cpu / temp / mem / ping click the label to toggle its graph
volume click to mute, scroll to change
media click to play/pause, scroll to skip; hides when nothing's playing
kubectl left-click clears the context, right-click opens the picker
calendar click for today's meetings; blinks when one is imminent/ongoing
github click for the grouped list; click a row to open + mark read, right-click to dismiss, [clear all] to mark all
spotify click to play/pause (or authorize), scroll to skip; long titles marquee

Plugins — two ways

1 · Compile-in modules (Rust). A widget is a Module: just iced — a view, an update, an optional background subscription and popup. No IPC, no DSL, no config schema. Develop one in a normal desktop window with the harness — no bar, no sway needed:

cargo run -p ezbar-harness --example counter   # a complete starter widget
cargo run --example harness -- github          # preview a built-in module

Guide: ezbar-plugin-author · Design: RFC 0001.

2 · Sandboxed WASM plugins. Ship a widget anyone can run safely. It compiles to a wasm32-wasip2 component, runs in a wasmtime sandbox, hot-reloads, and can only do user-granted, fine-grained things — default-deny, scoped per [modules.<id>]:

cap grant what it allows
network ["api.example.com", "*"] HTTP-GET to allow-listed hosts (* = any)
feeds ["cpu", "*"] host-sampled cpu/memory/temperature/battery/net
sway true read-only workspace list + focused title
fs [{ path = "~/notes", mode = "rw" }] preopened dirs (WASI-jailed; r/rw)
exec ["kubectl"] run allow-listed programs (any args)

Or flip [plugins] yolo = true to grant every plugin everything ("I trust my plugins," like Claude Code's bypass). Even in yolo the resource sandbox holds (cpu/mem/epoch) — a plugin can read your files but can't hang or OOM the bar. fs/exec are a dangerous tier: never silently activated by ezbar add — you grant them by hand (or yolo). Design: RFC 0015.

Consent is bound to the plugin's content hash, not its name — a swapped binary inherits nothing (it runs sandboxed until you re-approve). The whole lifecycle is a few commands:

ezbar search <term> --registry <dir|git-url>   # discover plugins in a registry
ezbar add <id> --registry <dir|git-url>   # resolve newest in-window version → verify sha256 → install → print grant block
ezbar update [<id>]                       # re-install newer versions (skips already-latest; remembers each plugin's source)
ezbar list                                # installed plugins: version, consent state, declared caps
ezbar inspect plugin.wasm                 # what it declares + the exact [modules.<id>] block to paste
ezbar grant <id>                          # approve its current bytes (re-run after a legit rebuild/update)
ezbar remove <id>                         # uninstall (.wasm + consent + pin — never your config.toml)
ezbar package plugin.wasm                 # author: embed an ezbar:manifest + print the registry entry

A plugin may embed a manifest declaring what it needs; ezbar inspect reads it and prints the grant block to paste (it never writes your config), and the host warns if a plugin wants a capability you didn't grant. The registry is just plugins/<id>/<version>.toml index files in a git repo (host it anywhere — --registry takes a local dir or any git URL); installs are sha256-verified, WIT-version-negotiated, and TOFU publisher-pinned. Guide: ezbar-wasm-plugin-author · Design: RFC 0014.

Layout

src/                    the bar: state, update, view, subscriptions, launcher
src/sources/            one module per data source (off-thread I/O via spawn_blocking)
src/modules/            built-in widgets (RFC 0001) + the inline-markup renderer
src/{grants,registry,   the CLI: capability consent + the plugin registry
     package,install}.rs   (grant/inspect/add/list/remove/package/install)
crates/ezbar-plugin       the module SDK — the Module trait, stable API
crates/ezbar-harness      standalone dev harness for modules
crates/ezbar-wasm         the wasmtime host — sandboxes + drives WASM plugins (the reactor)
crates/ezbar-plugin-wasm  the WASM guest SDK — write a plugin in Rust (TinyGo too)

Elm architecture: one State, one Message, update, view, and one Subscription stream per data source. Popups are extra layer-shell surfaces (the multi-window daemon pattern). WASM plugins run on a single shared wasmtime reactor, capability-gated and epoch-bounded.

License

MIT © Johannes Brüderl

About

Simple statusbar for Sway.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors