Polybar State Management Architecture
Overview
Polybar modules fall into two categories: state modules (user-triggered) and polling modules (external state). State modules read from a cached state file instead of spawning the binary on each poll.
State File
Location: ~/.cache/openriot/states.toml
Contents:
[night-light]
enabled = false
[volume]
percent = 75
muted = false
Categories
State Modules (No Polling)
These are user-triggered state changes. Polybar reads state file at startup and on click.
| Module | State File Key | Init on Startup |
|---|---|---|
| volume | volume.muted, volume.percent |
Yes |
| night-light | night-light.enabled |
Yes |
Polybar config:
[module/volume]
type = custom/script
exec-init = openriot --init-states
exec = grep "^muted" ~/.cache/openriot/states.toml | cut -d= -f2
click-left = openriot --volume toggle
scroll-up = openriot --volume inc
scroll-down = openriot --volume dec
[module/night-light]
type = custom/script
exec-init = openriot --init-states
exec = grep "^enabled" ~/.cache/openriot/states.toml | cut -d= -f2
click-left = openriot --night-light
Polling Modules (Binary Exec)
External or unpredictable state. Binary spawns at interval.
| Module | Interval | Reason |
|---|---|---|
| battery | 60 sec | Hardware state changes |
| wireguard | 30 sec | Network state changes |
| transmission | 30 sec | Daemon state changes |
| weather | 1800 sec | External API |
| update-status | 3600 sec | External API |
Flow
Startup
- X session starts
- Polybar loads, runs
exec-init→openriot --init-states - Writes current state to
~/.cache/openriot/states.toml
Click Action
- User clicks module
- Binary runs
--volume toggleor--night-light - Binary updates state file
- Binary performs action (toggle mute, toggle redshift)
- Polybar re-reads state file on next poll
Binary Flags
--init-states
Reads current hardware/software state, writes to ~/.cache/openriot/states.toml.
func initStates() {
// Read night-light state (check if redshift is running)
// Read volume state (check sndioctl)
// Write to states.toml
}
--volume toggle|inc|dec
- Updates
volume.mutedorvolume.percentin state file - Performs actual action (sndioctl)
- Sends notification
--night-light
- Updates
night-light.enabledin state file - Toggles redshift service
- Sends notification
Performance Impact
Before
| Type | Calls/sec | |——|———–| | State modules (2 modules) | ~0.4/sec | | Polling modules | ~0.5/sec | | Workspace/Title | ~2.5/sec | | Total | ~3.4/sec |
After
| Type | Calls/sec | |——|———–| | State modules | ~0/sec (file read) | | Polling modules | ~0.1/sec | | Workspace/Title | ~2.5/sec | | Total | ~2.6/sec |
Reduction: ~25% overall, ~100% for state modules
Files Impacted
| File | Change |
|---|---|
source/main.go |
Add --init-states flag |
source/volume/volume.go |
Add state file write |
source/nightlight/nightlight.go |
Add state file write |
config/polybar/config.ini |
Update volume/night-light modules |
source/polybar/polybar.go |
Remove status-only functions |
AGENTS.md |
Document architecture |
Implementation Order
- Create
source/state/state.go- TOML read/write utilities - Add
--init-statesto main.go - Modify volume/nightlight to write state file
- Update polybar config to use file reads
- Remove old binary status flags (or deprecate)
- Update AGENTS.md
Note on WireGuard
WireGuard state can change unexpectedly (network drop, VPN disconnect). It requires polling but interval can be increased to 30s without UX impact since VPN state is relatively stable.