Floating on a River

Migration from XMonad to River

This summer, I got a new laptop for my job. It came pre-installed by the sysadmin team with Fedora 42 (upon request; Ubuntu LTS is the norm). Since it came with Wayland already, I decided to use it as a forcing function to finally take the leap to Wayland. However, I had not yet set up my dotfiles to actually support running River. So I forced myself to use GNOME until I was done. This took far longer than it should have, but such is life.

Getting Hooked on Tiling

My dotfiles were started back in early 2010 when I decided to build things up from a “I am at a TTY. What is next?” situation. I had been presenting on an eeePC 900 and the OOM killer visited X due to memory consumption. At the time, I was using KDE as I had since I started with Linux back in 2006, when I was “onboarded” by Kevin Kofler from the Fedora KDE team through my connections in the TI calculator community. Since I wanted the same setup on all of my machines, I decided to start from the ground up and add things incrementally. Luckily my course load was such that I had enough time to do so.

Of course, one of the first things to do is to start X. But the default TWM leaves a lot to be desired. My first “barebones” window manager was ratpoison. This is a tiling window manager that aims to “kill the rodent” by making the mouse unnecessary. It was, indeed, good at that, as I once had my T61 dotfiles (where the trackpad is disabled due to having a trackpoint) “leak” onto my netbook where the trackpad was all I had. It took a few days for me to discover what was going on and get the trackpad working again. But I still managed to get by without a mouse at all during that time.

One thing that ratpoison did get me hooked on was “submaps”. Basically, instead of “naked” keybindings, window manager actions are more like tmux or screen where they are sequences of keys. I ended up with <Ctrl>i as my prefix, so banishing the mouse (setting its location to the lower-right corner) was <Ctrl>i followed by z.

Eventually, I desired some more…advanced tiling support and landed on XMonad. It helped that I was also on a Haskell kick around that time. I even wrote and contributed a few modules to support my desired configuration. My xmonad.hs ended up fairly long at around 650 lines, and that’s with a bunch of list comprehensions making a few dozen bindings at a time.

The main benefits of XMonad, to me, were:

  • stability: rarely did my configuration need major reworks due to decisions I didn’t make
  • per-workspace layout decisions: each workspace had its own layout memory instead of such changes affecting all workspaces at once
  • rich API for actions: I was able to make bindings for things like “swap workspaces on two monitors” and “send this window to another workspace” using absolute or relative commands
  • monitor behavior: each monitor viewed a workspace and could be swapped at will
  • support for status bar per monitor: with (contributed) support to manage them as monitors came and went

Other than that, it was a pretty standard tiling window manager setup.

GNOME Thoughts

Fast forward to July of this year when, after 15 years of using XMonad, I was using GNOME. It was…fine. Some things I found annoying, but it was nice to at least see what kinds of things are available when starting to build up my actual river setup (notably the multi-finger tap support on the trackpad).

One of my biggest annoyances is with the app-centric model. As one might imagine, I live in a terminal most of the time. This means each workspace I use has two applications: Firefox and foot (it was urxvt256c in X). If I switched to Firefox on one workspace, it would “pop” to the top in other workspaces as well. Sometimes there was even a race when changing workspaces quickly that would put me “back” at the old workspace. For someone who is more task-centered, this was quite jarring.

Notifications being in the center of the screen was also obnoxious. Since I was using tiling, if a terminal was on the right side, a prompt at the top of the window would be obscured. And I never did find a keybinding to dismiss them, so I had to use the mouse.

There were other annoyances, but they’re “standard” behaviors. I can say that it behaves better than Windows or Apple’s window management at least.

Using river

Getting river set up took some fiddling to integrate with my systemd user session. In 2013, I started using systemd to manage my user session. I don’t know how early I was to the “party”, but I think I was definitely one of the first based on the issues I ended up running into. Unfortunately, this meant that display managers are not really suitable for me because they start the session directly instead of “just” letting systemd handle everything using unit files. It also means that I have some different .target units compared to what most programs provide on their own, so I end up ignoring system user units for the most part and writing my own.

So I had to ditch gdm and instead start back at the TTY again. No big deal. However, I did have to discover some new unit features to make things truly work. Unlike X where the X server starts and the window manager sort of “hooks in” later and a simple After=xorg.target would suffice to make sure that anything wanting X didn’t start before an X socket was ready, river needs to make sure that everything waits until it is done. This means using Type=notify to make sure that systemd doesn’t start other services before river has made everything available.

Since river’s configuration is “just” an executable file that calls riverctl, systemd-notify --ready is the obvious solution to this. However, systemd is clever and can detect when something is trying to “impersonate” the service and ignores it unless NotifyAccess=all is used. Without this, systemd would “timeout” river.service and kill it, taking the whole session down. But I’d get glimpses of it for up to a minute.

Benefits

There have been two main benefits of starting to use river: battery life and latency. There’s also the “everything is tiled again”, but that was something I expected.

Battery Life

One thing about the new laptop is that its battery life is fantastic. The BIOS (or what does one call it when it is UEFI-based these days?) allows me to cap the battery charge to 80%, which has, so far, kept the battery nice and fresh. We’ll see after 3 years when the battery life in this brand tend to take a nosedive based on my previous 4 laptops of theirs.

GNOME reports a battery life from this 80% max of around 8 or 9 hours. Not the best on the market, but it definitely feels nice. However, this is when the machine is idle. Once you start running Firefox with a few chat app tabs (Google Chat, Slack, Discord, etc.), it definitely starts to get worse.

With river? I have seen 11 hour estimates at 49% now. Granted, this is idle, but even with streaming music, watching GitLab logs in Firefox, and editing, I’m seeing over 2.5 hours left at 21% (it goes to 3.5 when I change to an idle Firefox tab in the foreground). This is on par with arm64 macOS laptops. Now, it does assume some powertop tweak applications (something I do by default on all machines now), but that is not too hard for distributions to enable by default.

I don’t know what was taking up so much in GNOME, but with this knowledge, I’m glad that I am able to use a tiling window manager like river. Perhaps GNOME can resolve whatever issue(s) are sapping battery life, but I imagine it means trimming down quite a few things.

Latency

Another major benefit latency. GNOME had this weird habit of lagging at times, which was definitely noticeable when a build was going on in the background (but that is nothing new for Linux users). It also (usually) has a nice workaround: use the -l (load average) flag on make or ninja and you’re a lot better off. Other times it would just be a “stutter” that would pause everything mid-animation for a second or two. These I could deal with. What I could not abide was keys stuttering in mpv. I don’t know what it was, but mpv would end up getting duplicate key events during these stutters. Which is really bad when the key is “double speed” or “half speed”. I’d end up with mpv slammed at either 100x or 0.01x. Luckily mpv accepts input on the terminal too which did not have such issues.

But in river, things feel more responsive. foot responds instantly and scrolls on demand. Firefox tab management also feels faster. mpv also feels faster. The lack of animation on workspace changing is also helpful in the subjective feel.

I don’t really use many apps beyond those for further comparison, but for my workflow, the improvement in responsiveness is significant.

Conclusion

I’ll definitely continue to use river, and I’ll need to do some development to truly replicate my XMonad setup. Supporting submap bindings and “toggle hooks” (flags that change the behavior of the next window) come to mind. I also probably need to do some work to mask the underlying “tag” model with workspaces. Luckily, I’m fine with “just” 9 of them. Perhaps all of these are doable with the new “river window management” protocol that just landed in the past week.