How to make sway act like ratpoison

Posted on June 30, 2019

Sway is an i3-compatible Wayland compositor. Ratpoison is a tiling window manager for X11 that is largely modeled after GNU screen. After a lost couple of years in the WIMPy land of GNOME, I wanted to go back to a tiling window manager on my desktop system. I still wanted to stick to Wayland (X11 is soon becoming retrocomputing), which leaves sway as the only option. However, I have no prior love of i3, and I found the default key shortcuts bad. Specifically, sway assumes that it can be granted an entire modifier key (say, Alt) for key shortcuts. As an example, Alt-f makes a window full screen. A window manager making such assumptions is intolerable in the presence of software like GNU Emacs, which needs all the modifier keys it can get. Returning to Alt-f (or M-f in Emacs-speak), this shortcut is usually bound to the common command forward-word. One solution is to simply pick a more obscure modifier key, like Super or Hyper, typically in the form of the Windows- or Menu-keys on a modern keyboard. Unfortunately, I use a Unicomp reconstruction of a PS/2 Model M keyboard, which has no such fancy keys.

A much better model of keyboard shortcuts is the one used by programs such as GNU Screen, tmux, and ratpoison, where all commands are hidden behind a prefix key. For example, in ratpoison you open a new terminal by first pressing Ctrl-t and then pressing c (without holding down Ctrl). Giving up Ctrl-t (or Ctrl-b for tmux) is a much easier sacrifice than an entire modifier key. Further, the usual convention is to bind the non-modified key to send the captured keypress to the current application (so Ctrl-t t would send Ctrl-t).

Sway is quite configurable, but unfortunately does not seem to support multi-chord keybindings. Fortunately, sway does have a notion of “modes”, much like vi, although in the default configuration the only mode is for resizing windows. With some cleverness, we can use these modes to emulate a prefix key. We will bind Ctrl-t to a command that switches to the prefix mode, where we then bind the actual keys that we care about. However, there is a wrinkle: in sway, once you are in a mode, you stay in that mode. This not what we want: after starting a terminal with Ctrl-t c, we don’t want every subsequent c to start a new terminal! Fortunately, we can bind c to launch the terminal, and then switch back to the “default” mode. We will have to do this for every command, which looks a bit clumsy, but it works well in practice:

mode "prefix" {
    # Launch terminal.
    bindsym c exec $term; mode "default"

    # Send ctrl-t to focused window.
    bindsym t exec xdotool key ctrl+t; mode "default"

    # Kill focused window.
    bindsym k kill; mode "default"

    # return to default mode
    bindsym Control+g mode "default"
    bindsym Return mode "default"
    bindsym Escape mode "default"
bindsym Control+t mode "prefix"

The main difference compared to ratpoison is that if you are in the prefix mode and hit a key not associated with a command, that key will be passed to the focused window, and you will stay in prefix mode. Ratpoison would beep at you and exit prefix mode (although it’s strictly not a mode in ratpoison). There is no simple way to capture this behaviour in sway, since you cannot define a “default binding” that would exit the mode.

Still, if you are like me, and consider the early/mid-2000s to be the pinnacle of Unix UI design, then the above produces a quite tolerable experience. My full sway configuration has more bits and commands, but you should be able to flesh it out yourself. It is not an exact reproduction of ratpoison, nor is it supposed to be: I can accept some measure of progress, as long as I am allowed to keep decades of muscle memory intact.