Hi everyone,
Recently, I’ve set up a new Guix System on a Thinkpad and I decided to migrate over to Wayland and sway after being a longtime bspwm user on X11. For a top bar/status bar on X11, I use polybar, which is similar to waybar on Wayland.
I installed waybar and tinkered with it for a few minutes, but I wasn’t too keen on fiddling with CSS and trying to emulate my polybar configuration, all while wrangling and learning Guix to make it usable for me.
Once I remembered (from my i3 days) that there is a swaybar protocol (see i3blocks) I decided that I would take a more interesting approach and write my own version of a swaybar generator using Guile Scheme – Gubar (pronounced “goober” and named by @daviwil)
High-level design overview
After looking at the swaybar-protocol spec, I realized that I would need guile-json for serializing updated bar components (blocks), and also for deserializing click events that are captured by sway and dumped on stdin of the swaybar command (in this case, gubar).
The bare minimum bar usually sleeps for one second and then runs one or more shell command and parses the outputs into a nice long string, but that becomes quite inefficient if you have many blocks or a command that can take a bit longer to run. You could instead sleep for one minute, but then you don’t get instant updates on things like volume, cpu, battery, etc – IF those things are important to you.
I thought it would be more appropriate to use fibers and have one fiber per block. The fibers are kinda like greenthreads or goroutines, but based on “Concurrent ML”. Andy Wingo has a really nice write-up on it in the fibers manual.
With this design, I can sleep each block for different amounts of time, having them run asynchronously, and then message back to the main scheduler when they update so we know when to output the fresh set of blocks.
Configuration
Unlike i3blocks, I want gubar to be configured fully in Guile. Right now, it just calls load
on the config.scm file, which I’m not entirely sure is correct, but it works well enough. This way made sense to me since it’s as if the config is just another module, that may or may not be present. If you have tips on this, I’d love to know!
I really like the ergonomics of the syntax that Guix uses for defining system or home configs. They have a custom record type that is, in my opinion, more robust than srfi-9 records. I don’t want to depend on Guix or copy paste out the definition, so right now the configuration is a bit clunky. If you have any ideas or would like to help make it more comfortable, I’m open to suggestions and collaboration.
Simple DateTime Block
As an example, here is a module for displaying the current time:
DateTime block code
(define-module (gubar blocks date-time)
#:use-module (gubar gublock)
#:use-module (gubar swaybar-protocol)
#:use-module (ice-9 textual-ports)
#:export (date-time))
(define* (date-time #:key (format "%c") (interval 1))
"Creates a new date-time gublock."
(gublock
#:interval interval
#:procedure
(lambda (block)
(scm->block
`(("full_text" .
,(strftime format (localtime (current-time)))))))))
Due to guile-json not exposing modifiers on the generated srfi-9 record types, I opted to use an assoc-list and then convert it to the record type (scm->block
). Eventually, I will have to implement the functional modifiers manually to avoid this.
Checkout the other modules for some (slightly) more interesting examples. I am slowly hacking on the ones that I will be using in my daily driver config.
Screenshot
It’s just the regular swaybar with some nerd font icons, but here’s a screenshot with the config:
Still needs a lot of work, but it’s getting there!
Happy Hacking!