fluxo
fluxo is a high-performance system metrics daemon and client for status bars. It entirely replaces standard shell scripts with a compiled Rust binary that collects data via a background polling loop and serves it over a Unix socket.
With its 100% Native, Content-Based Event-Driven Architecture, it consumes effectively 0% CPU while idle and pushes updates only when the rendered UI text or icons physically change.
It speaks two dialects, both driven by that same change detection:
- Waybar — the daemon sends
SIGRTMIN+Nand the bar re-runsfluxo <module>. See Waybar Configuration. - Any other bar (Quickshell, ags, eww, …) — hold one
fluxo stream <modules...>child open and read newline-delimited JSON from its stdout. See Streaming.
Key Features
- 100% Native Architecture: Zero shell-outs or subprocesses. Uses
bluerfor Bluetooth,libpulse-bindingfor audio,zbusfor MPRIS/DND, andnotifyfor backlight. - Push-Based for Any Bar:
fluxo streamturns the daemon into a push source, so bars that keep long-lived child processes never have to poll or re-exec a client. - Content-Based Event Signaling:
fluxoevaluates your custom configuration formats internally. It only sends aSIGRTMIN+Xsignal to Waybar if the resulting string or CSS class has actually changed, eliminating pointless re-renders from raw polling fluctuations. - Zero-Latency Interactions: Direct library bindings mean that when you change your volume or connect a Bluetooth device via the CLI, the daemon updates instantly.
- Circuit Breaker (Failsafe): Automatically detects failing modules and enters a "Cool down" state, preventing resource waste and log spam. Fallback caching keeps your bar looking clean even during brief failures.
- Multi-threaded Polling: Decoupled Tokio subsystem threads ensure that a hang in one system (e.g., a slow GPU probe) never freezes your Waybar.
Modules
| Command | Description | Tokens |
|---|---|---|
cpu |
CPU usage and temperature | {usage}, {temp}, {model} |
mem |
Memory usage | {used}, {total} |
net |
Network status & speeds | {interface}, {ip}, {rx}, {tx} |
sys |
System load and uptime | {uptime}, {load1}, {load5}, {load15}, {procs} |
disk |
Disk usage | {mount}, {used}, {total} |
pool |
Btrfs aggregate storage | {used}, {total} |
gpu |
GPU usage & thermals | {usage}, {vram_used}, {vram_total}, {temp} |
vol |
Audio output (sink) | {name}, {volume}, {icon} |
mic |
Audio input (source) | {name}, {volume}, {icon} |
bt |
Bluetooth status & plugins | {alias}, {mac}, {left}, {right}, {anc} |
power |
Battery and AC status | {percentage}, {icon} |
game |
Hyprland Gamemode status | active/inactive strings |
mpris |
Media Player status | {artist}, {title}, {album}, {status_icon} |
backlight |
Display Brightness | {percentage}, {icon} |
kbd |
Keyboard Layout | {layout} |
dnd |
Do Not Disturb (SwayNC) | active/inactive strings |
Installation
From Source
cargo build --release
cp target/release/fluxo ~/.cargo/bin/
Debian/Ubuntu (.deb)
cargo install cargo-deb
cargo deb
sudo dpkg -i target/debian/fluxo-rs_*.deb
The .deb package installs the binary to /usr/bin/fluxo, the systemd user service to /usr/lib/systemd/user/fluxo.service, and documentation to /usr/share/doc/fluxo/.
Setup
- Configure: Create
~/.config/fluxo/config.toml(seeexample.config.toml). Ensure you map your[signals]. - Start the daemon via systemd (recommended) or manually:
systemd (recommended)
If installed from the .deb, the service file is already in place. For manual installs:
mkdir -p ~/.config/systemd/user
cp dist/fluxo.service ~/.config/systemd/user/
If your binary is not at ~/.cargo/bin/fluxo, edit the ExecStart= path in the service file.
Then enable and start:
systemctl --user daemon-reload
systemctl --user enable --now fluxo
Check status:
systemctl --user status fluxo
journalctl --user -u fluxo -f
Manual
fluxo daemon
Waybar Configuration
To achieve zero-latency updates and zero-polling CPU usage, set interval: 0 on your modules and rely entirely on Waybar Signals mapped in your config.toml:
"custom/volume": {
"exec": "fluxo vol",
"return-type": "json",
"interval": 0,
"signal": 8, // Must match the value in config.toml [signals]
"on-click": "fluxo vol mute",
"on-scroll-up": "fluxo vol up 1",
"on-scroll-down": "fluxo vol down 1",
"on-click-right": "fluxo vol cycle"
},
"custom/bluetooth-audio": {
"format": "{}",
"return-type": "json",
"exec": "fluxo bt",
"on-click": "fluxo bt menu",
"on-click-right": "fluxo bt cycle_mode",
"signal": 9,
"interval": 0,
"tooltip": true
}
Streaming
Waybar's model is signal the bar, the bar re-runs a command, which is why the
one-shot client exists. Other bars have no equivalent of a signal, so they end up
emulating one by polling — spawning a fluxo <module> client per module per
interval, which means an exec and a dynamic link per reading, and values that are
still up to one interval stale.
fluxo stream is the push-based path for those bars. Subscribe once, and the
daemon writes a JSON line whenever a module's rendered output actually changes:
$ fluxo stream cpu mem net
{"module":"cpu","text":"7.4|41.0","tooltip":"AMD Ryzen 7 PRO 3700U","class":"normal","percentage":7}
{"module":"mem","text":"9.41|15.49","class":"normal","percentage":60}
{"module":"cpu","text":"6.9|40.5","tooltip":"AMD Ryzen 7 PRO 3700U","class":"normal","percentage":6}
Each line is one complete JSON object: a module key naming the source, plus the
module's usual text and — when set — tooltip, class and percentage. A full
snapshot of every subscribed module is sent immediately on connect, so a bar
renders correctly the moment it attaches rather than after the first change.
Two deliberate differences from the one-shot client:
textis not padded with figure-spaces and zero-width spaces. That padding exists to stop Waybar's proportional font from reflowing, and is noise to a consumer doing its own layout.- Modules without a watch channel (
power,game,pool) are re-evaluated on a 5 second sweep rather than event-driven. Unchanged output is never written, so a quiet sweep costs nothing on the wire.
Quickshell, for example, needs one Process for the whole bar:
Process {
running: true
command: ["fluxo", "stream", "cpu", "mem", "gpu", "net", "sys"]
stdout: SplitParser {
onRead: line => {
const ev = JSON.parse(line);
// dispatch on ev.module
}
}
onExited: reconnectTimer.restart()
}
The connection dies when the daemon restarts, so drive running back to true
from a timer — that is a reconnect, not a poll, and only ever fires while the
stream is actually down.
Debugging
Use --loglevel to control log verbosity (trace, debug, info, warn, error):
fluxo daemon --loglevel debug
Or via the RUST_LOG environment variable:
RUST_LOG=debug fluxo daemon
For module help and available arguments:
fluxo help # overview of all modules
fluxo help vol # detailed help for a specific module