yabai

Automatic setup of spaces with yabai

Sharing my yabai configuration for automatically setting up workspaces in macOS. I walk through how to create a consistent set of labelled spaces, clean up extras, and automatically move applications to specific workspaces on startup. Whilst tiling window managers aren't for everyone, this setup helps maintain an organised workflow by ensuring applications always land in their designated spaces.

The idea behind tiling window managers is brilliant - frames on the screen are organised in a non-overlapping fashion. In practice, that means a productivity boost because (1) all non-hidden frames are always visible and (2) all resizing and movement is done automatically by the window manager whenever a frame becomes visible or hidden. It might take some time to get used to this approach, but in the end, it's a love-it-or-hate-it relationship without a position in between.

My story with tiling window managers started in macOS, where the window manager can't be changed, but… you have applications that imitate them either in a non-intrusive manual manner (à la Spectacle) or in an automatic manner (à la Amethyst and yabai). So around four years ago I started using chunkwm (former yabai) and I've been a happy user since then.

Since I'm too lazy to set up macOS spaces manually, I want yabai to enforce a specific configuration on startup - a constant number of spaces (with labels), meaning that missing ones are created and extra ones are removed. In addition, I want some applications to start on specific spaces (e.g. I want my browser to always be in the third space) and to start some applications silently (without stealing focus).

In this article, we're going to learn how to achieve these goals.

#1Prerequisites

In order to achieve these goals, you need to complete the following steps:

  1. Disable System Integrity Protection.
  2. Install yabai (unfortunately, preferably from brew) and enable scripting additions.
  3. Create a configuration file.
  4. Make sure that yabai is running and operational.
  5. Install jq (JSON processor).

#1Setting up preferred spaces

In my setup, I have six spaces: one for Emacs, one for terminal emulators, one for web, one for social stuff, one for media stuff, and one for various other things. The yabai configuration file must be an executable, and in order to make things easy, let it be a shell executable.

#!/usr/bin/env sh # load scripting additions (optional) sudo yabai --load-sa yabai -m signal --add event=dock_did_restart action="sudo yabai --load-sa" # config (tweak it to your liking) yabai -m config layout bsp yabai -m config top_padding 8 yabai -m config bottom_padding 8 yabai -m config left_padding 8 yabai -m config right_padding 8 yabai -m config window_gap 8 yabai -m config auto_balance off yabai -m config split_ratio 0.5 yabai -m config window_shadow off

So let's define a function called setup_space that takes two arguments - space index and its label (unless you're using widget software like Übersicht, labels have little meaning). We're going to use it like this:

setup_space 1 emacs setup_space 2 code setup_space 3 web setup_space 4 social setup_space 5 media setup_space 6 other

So what should it do in order to make sure that these 6 spaces exist and properly set?

Yabai comes with query command that allows to query information about all spaces or specific spaces. For example, yabai -m query --spaces returns information about all spaces, and yabai -m query --spaces --space 1 returns information about space with index 1 if it exists.

There are space commands allowing to create spaces and label them.

This should be enough for us to define setup_space.

function setup_space { local idx="$1" local name="$2" local space= echo "setup space $idx : $name" space=$(yabai -m query --spaces --space "$idx") if [ -z "$space" ]; then yabai -m space --create fi yabai -m space "$idx" --label "$name" } setup_space 1 emacs setup_space 2 code setup_space 3 web setup_space 4 social setup_space 5 media setup_space 6 other

You can remove all spaces except for 1 via Mission Control and run yabairc:

$ ~/.config/yabai/yabairc

All extra spaces should be created.

On the internet, you may find a solution that uses the space focus command - yabai -m space --focus "$idx" || yabai -m space --create - but keep in mind that this doesn't work as intended and leads to creation of extra spaces on each run of yabairc. Aside from producing an undesired result, this approach also has two extra drawbacks. First, it's slower because focus switching isn't free. Secondly, focus switching leads to screen flickering. That's why you should avoid this command in the setup script as much as possible.

#1Cleaning up extra spaces

This is completely optional, but I like to remove extra spaces on startup. First of all, I don't have key bindings to quickly switch to spaces with an index bigger than 6. Secondly, if for some reason I'm using a macOS full-screen application, it places the application too far away.

In order to achieve this goal, we have two approaches:

  1. Check if a space with index MAX_SPACES + 1 (for me it's 7) exists, and if it does - remove it. Repeat the procedure until you run out of spaces with index MAX_SPACES + 1. This works because removing a space in the middle changes the index of all that comes after.
  2. Just query all spaces with index >=MAX_SPACES and remove them.

Since I want to reduce the number of invocations of the yabai client, I'm going with the second approach. In order to query spaces with an index bigger than MAX_SPACES, we're going to use jq select capabilities. If you want to learn more about that, just take a break and use the jq manual.

for idx in $(yabai -m query --spaces | jq '.[].index | select(. > 6)' | sort -nr); do yabai -m space --destroy "$idx" done

Keep an eye on sort -nr. We want to remove spaces in reversed order; otherwise, indices are reassigned. Another approach would be to ignore idx and always remove the space with index 7:

for _ in $(yabai -m query --spaces | jq '.[].index | select(. > 6)'); do yabai -m space --destroy 7 done

In that case, you don't need to rely on sort, and the jq selector just acts as a way to repeat the action SPACES_COUNT - MAX_SPACES times.

#1Moving applications on start

As I said, I love having my browser start on a specific space. What makes yabai so wonderful is its system of events and rules. You can read more about rules and signals by running man yabai or by checking the official wiki on rules and signals.

yabai -m rule --add app="^Safari$" space=^3 yabai -m rule --add app="^FireFox$" space=^3 yabai -m rule --add app="^Telegram$" space=4 yabai -m rule --add app="^Music$" space=5 yabai -m rule --add app="^Spotify$" space=5

If you put "^" before the space number, the space will be focused after the application is started. Personally, I don't use that because (a) I often start applications without the intention to use them right now and (b) I'm forced to reload the configuration on Emacs initialisation, so it's being picked up by yabai.

In general, rules and signals are quite powerful, so I urge you to play around with them. I've yet to discover more possibilities. Would love to hear from you if you have ideas to share.

#1Full configuration

#!/usr/bin/env bash # load scripting additions sudo yabai --load-sa yabai -m signal --add event=dock_did_restart action="sudo yabai --load-sa" # config yabai -m config layout bsp yabai -m config top_padding 8 yabai -m config bottom_padding 8 yabai -m config left_padding 8 yabai -m config right_padding 8 yabai -m config window_gap 8 yabai -m config auto_balance off yabai -m config split_ratio 0.5 yabai -m config window_shadow off # # setup spaces # for _ in $(yabai -m query --spaces | jq '.[].index | select(. > 6)'); do yabai -m space --destroy 7 done function setup_space { local idx="$1" local name="$2" local space= echo "setup space $idx : $name" space=$(yabai -m query --spaces --space "$idx") if [ -z "$space" ]; then yabai -m space --create fi yabai -m space "$idx" --label "$name" } setup_space 1 emacs setup_space 2 code setup_space 3 web setup_space 4 social setup_space 5 media setup_space 6 other # move some apps automatically to specific spaces yabai -m rule --add app="^Safari$" space=^3 yabai -m rule --add app="^Firefox$" space=^3 yabai -m rule --add app="^Telegram$" space=4 yabai -m rule --add app="^Music$" space=5 yabai -m rule --add app="^Spotify$" space=5

Thank you for bearing with me till the end! Safe travels!