OpenRiot v8.0.10: Three Cylinders, One Starter

“The engine had three cylinders and one starter. The starter turned the first cylinder, heard it catch, and went home. The other two sat there with the fuel and the spark and nobody to crank them. The car did not need a bigger engine. It needed the starter to keep turning.” – The OpenRiot Crew, v8.0.10


Release Overview

v8.0.9 split the bar into three panels. v8.0.10 makes sure they come back.

The panels now start on login, restart on an i3 restart, and recover individually when one crashes. A resolution change no longer kills a working bar for no reason, and when the screen really does change, the panels rescale before they are restarted instead of after.

Total changes: one watchdog that stops blocking, three launch lines that know when to skip, and a resolution path that stops restarting the world when nothing moved.


The Starter

The old watchdog loop was one line of shell with a trap in it:

pgrep -f "^polybar $b$" || polybar $b

The first missing bar started in the foreground. The loop then waited for that bar to exit, which is another way of saying it never checked the other two. On a normal boot the panels were already running, so nobody noticed. After an upgrade killed all three, the watchdog rescued one and went to sleep inside the process.

It now starts the missing bar detached:

pgrep -f "^polybar $b$" || { nohup polybar $b >/dev/null 2>&1 & }

The loop keeps moving, checks the next name, and starts what is missing. One watchdog is enough. The pidfile guard keeps a second i3 restart from stacking another loop on top of the first.


The Crank

The three launch lines are exec_always, so they run on login and on every i3 restart. Each one checks for its own bar before starting it:

pgrep -f "^polybar left$" || polybar left

That guard is the difference between a restart and a duplicate. Without it, a watchdog that recovered the clock first would be joined by a second clock when i3 restarted two seconds later. The anchored pattern matches the polybar process itself, not the shell command that launched it, so the guard does not trip over its own string.


The Idle

resolution.Apply() used to run xrandr, save the mode, kill polybar, and rebuild the desktop. It did this even when the requested mode was already the current mode. At login, the saved resolution was almost always the current resolution, so the restore step killed a perfectly good set of bars for no reason and trusted a watchdog that was about to block.

The function now reads the current mode first. If the display is already at the requested resolution and rate, it returns without touching anything. When a real change does happen, it runs --polybar-setup after xrandr and before the kill, so the new bar height and fonts are already on disk when the watchdog starts the panels again.

--polybar-setup also updates i3 directly now:

i3-msg gaps top all set <scaled height>

The static gaps top 28 is the 1080p value. On a taller scaled bar, the reserved strip follows the bar instead of letting the first line of every window hide under it.


The Gap

The left panel lost its padding-left. The workspaces already start where the panel starts, and the extra space was a leftover from the single-bar layout. The center and right panels keep their padding.


🧾 Files Changed

File Change
config/i3/config Guarded exec_always launches,
  non-blocking watchdog, pidfile guard
config/polybar/config.ini.tmpl Left padding removed,
  screenchange-reload enabled
source/polybar/polybar.go gaps top all set after scaling
source/resolution/backend.go Skip no-op resolution changes,
  rescale before restarting polybar
install/openriot Rebuilt with the changes

🗣️ Final Words

“Three cylinders, one starter, and a starter that keeps turning until all three catch. The engine did not get louder. It got reliable. That is the whole tune.” – The OpenRiot Crew, v8.0.10