So you want i3wm on MacOS?
Recently I was forced to leave my beloved Linux setup because I got a Macbook from my new employer. I’m sure some people are laughing now, but the year 2021 is the first time in my life that I own an Apple product. Although MacOS is known to be very user-friendly and to offer a nice, clean and minimal UI, I actually had a lot of trouble switching my workflow at first.
I think I am not the only one in this situation. I have worked with window managers under Linux for years. Over time I have learned to appreciate i3 very much and nowadays I can’t work without it! Thanks to a great colleague (thanks Marcel aka @snowiow!) who went through the same pain, I got some great tips to get an i3wm
feeling on MacOS.
In fact, I’m very happy with the result, because it covers 90% of the features that made me love i3wm
for years.
yabai + skhd (i3 replacement)
yabai is a window management utility that is designed to work as an extension to the built-in window manager of macOS. yabai
allows you to control your windows, spaces and displays freely using an intuitive command line interface and optionally set user-defined keyboard shortcuts or other third-party software.
I recommend installing yabai
via brew
. This will allow us to conveniently update yabai
via our package manager in the future.
brew install koekeishiya/formulae/yabai
Next we activate the service. yabai
will ask you for permissions that you need to set. Next we’ll have to grant permissions under Accessibility
for yabai
, then start the service:
# start yabai
brew services start yabai
yabai
is managed by a configuration file. This configuration file is named .yabairc
and lives in your home directory (you can also change the path, but I prefer to keep my doftiles all inside my homedir). Create a file with the following command
touch ~/.yabairc
Here is my .yabairc
. I will go into details afterwards.
#!/usr/bin/env sh
# the scripting-addition must be loaded manually if
# you are running yabai on macOS Big Sur. Uncomment
# the following line to have the injection performed
# when the config is executed during startup.
#
# for this to work you must configure sudo such that
# it will be able to run the command without password
#
# see this wiki page for information:
# - https://github.com/koekeishiya/yabai/wiki/Installing-yabai-(latest-release)
#
# sudo yabai --load-sa
# yabai -m signal --add event=dock_did_restart action="sudo yabai --load-sa"
#!/usr/bin/env sh
# bar settings
yabai -m config top_padding 0
# global settings
yabai -m config mouse_follows_focus off
yabai -m config focus_follows_mouse autofocus
yabai -m config window_placement second_child
yabai -m config window_topmost off
yabai -m config window_opacity off
yabai -m config window_opacity_duration 0.0
yabai -m config window_shadow on
yabai -m config active_window_opacity 1.0
yabai -m config normal_window_opacity 0.90
yabai -m config split_ratio 0.50
yabai -m config auto_balance off
# Mouse support
yabai -m config mouse_modifier alt
yabai -m config mouse_action1 move
yabai -m config mouse_action2 resize
# general space settings
yabai -m config layout bsp
yabai -m config bottom_padding 0
yabai -m config left_padding 0
yabai -m config right_padding 0
yabai -m config window_gap 0
# float system preferences
yabai -m rule --add app='^System Information$' manage=off
yabai -m rule --add app='^System Preferences$' manage=off
yabai -m rule --add title='Preferences$' manage=off
# float settings windows
yabai -m rule --add title='Settings$' manage=off
# Some Goland settings, in case you are using it. float Goland Preference panes
yabai -m rule --add app='Goland IDEA' title='^$' manage=off
yabai -m rule --add app='Goland IDEA' title='Project Structure' manage=off
yabai -m rule --add app='Goland IDEA' title='Preferences' manage=off
yabai -m rule --add app='Goland IDEA' title='Edit configuration' manage=off
echo "yabai configuration loaded.."
The yabai
config shown above configures some useful things. I like it when I can move the mouse over windows and they are automatically selected. This is what the following section does:
# global settings
yabai -m config mouse_follows_focus off
yabai -m config focus_follows_mouse autofocus
I have also completely removed the distances between the individual windows. After all, we want to use all the space on our workspace. You can also adjust these settings to your liking and, for example, increase the spacing a little, if that should be visually more pleasant.
# general space settings
yabai -m config layout bsp
yabai -m config bottom_padding 0
yabai -m config left_padding 0
yabai -m config right_padding 0
yabai -m config window_gap 0
Of course, you can also add other programmes here. If you don’t use Goland, for example, you can remove the section below completely.
# float system preferences
yabai -m rule --add app='^System Information$' manage=off
yabai -m rule --add app='^System Preferences$' manage=off
yabai -m rule --add title='Preferences$' manage=off
# float settings windows
yabai -m rule --add title='Settings$' manage=off
# Some Goland settings, in case you are using it. float Goland Preference panes
yabai -m rule --add app='Goland IDEA' title='^$' manage=off
yabai -m rule --add app='Goland IDEA' title='Project Structure' manage=off
yabai -m rule --add app='Goland IDEA' title='Preferences' manage=off
yabai -m rule --add app='Goland IDEA' title='Edit configuration' manage=off
Next we need to make our .yabairc
executable:
chmod +x ~/.yabairc
Optional: Disable window animations. If the animations of MacOS bother you, you can additionally deactivate them. Unfortunately, there is still a short animation when windows are launched and integrated into the tiling. But at least all other animations can be turned off.
I have disabled the following two things, there are many more.
# Disable animations when opening and closing windows.
defaults write NSGlobalDomain NSAutomaticWindowAnimationsEnabled -bool false
# Accelerated playback when adjusting the window size.
defaults write NSGlobalDomain NSWindowResizeTime -float 0.001
Have a look at this issue for further settings.
After that, restart yabai
brew services restart yabai
If you’d like to keybind all your commands, you can pair yabai
with skhd
. skhd is a simple hotkey daemon for MacOS that focuses on responsiveness and performance. Hotkeys are defined in a text file through a simple DSL. skhd
is able to hotload its config file, meaning that hotkeys can be edited and updated live while skhd
is running.
We can also conveniently install skhd
via brew
. Then we also start this service
brew install koekeishiya/formulae/skhd
brew services start skhd
The first time skhd
is ran, it will request access to the accessibility API.
Next, we create a configuration file for skhd with the name .skhdrc
. Here too, we can either create the file under ~/.skhdrc
, or under a different path such as ~/.config/skhd/skhdrc
.
touch ~/.skhdrc
In this file we can now create some useful keybindings that are very close to our beloved i3
under Linux.
The first thing I configured is the ability to navigate between windows with the arrow keys. Alternatively, h
,j
,k
,l
can be used.
# change focus
alt - h : yabai -m window --focus west
alt - j : yabai -m window --focus south
alt - k : yabai -m window --focus north
alt - l : yabai -m window --focus east
# (alt) change focus (using arrow keys)
alt - left : yabai -m window --focus west
alt - down : yabai -m window --focus south
alt - up : yabai -m window --focus north
alt - right : yabai -m window --focus east
I would also like to move individual tiles back and forth. Here I have created a binding that allows to move tiles with alt + shift + arrow keys
or with alt + shift + <h j k l>
in any direction. If a tile borders the screen and is dragged outside the space, then the tile is moved to the next monitor.
# shift window in current workspace
alt + shift - h : yabai -m window --swap west || $(yabai -m window --display west; yabai -m display --focus west)
alt + shift - j : yabai -m window --swap south || $(yabai -m window --display south; yabai -m display --focus south)
alt + shift - k : yabai -m window --swap north || $(yabai -m window --display north; yabai -m display --focus north)
alt + shift - l : yabai -m window --swap east || $(yabai -m window --display east; yabai -m display --focus east)
# alternatively, use the arrow keys
alt + shift - left : yabai -m window --swap west || $(yabai -m window --display west; yabai -m display --focus west)
alt + shift - down : yabai -m window --swap south || $(yabai -m window --display south; yabai -m display --focus south)
alt + shift - up : yabai -m window --swap north || $(yabai -m window --display north; yabai -m display --focus north)
alt + shift - right : yabai -m window --swap east || $(yabai -m window --display east; yabai -m display --focus east)
Sometimes you want to set new windows above or below the one currently in focus. To do this, the key combination alt + ctrl + arrow keys
or alt + ctrl + <h j k l>
can be used to set an insertion point (displayed graphically).
# set insertion point in focused container
alt + ctrl - h : yabai -m window --insert west
alt + ctrl - j : yabai -m window --insert south
alt + ctrl - k : yabai -m window --insert north
alt + ctrl - l : yabai -m window --insert east
# (alt) set insertion point in focused container using arrows
alt + ctrl - left : yabai -m window --insert west
alt + ctrl - down : yabai -m window --insert south
alt + ctrl - up : yabai -m window --insert north
alt + ctrl - right : yabai -m window --insert east
I also use a simple shortcut (alt - b
) to quickly switch back and forth between windows.
# go back to previous workspace (kind of like back_and_forth in i3)
alt - b : yabai -m space --focus recent
# move focused window to previous workspace
alt + shift - b : yabai -m window --space recent; \
yabai -m space --focus recent
With the key combination alt + shift + n
we can easily move focused tiles to another workspace. n’ is in this case the number (1-9) to which the tile should be moved.
# move focused window to next/prev workspace
alt + shift - 1 : yabai -m window --space 1
alt + shift - 2 : yabai -m window --space 2
alt + shift - 3 : yabai -m window --space 3
alt + shift - 4 : yabai -m window --space 4
alt + shift - 5 : yabai -m window --space 5
alt + shift - 6 : yabai -m window --space 6
alt + shift - 7 : yabai -m window --space 7
alt + shift - 8 : yabai -m window --space 8
alt + shift - 9 : yabai -m window --space 9
#alt + shift - 0 : yabai -m window --space 10
We can also mirror the tiles in a workspace horizontally or vertically.
# # mirror tree y-axis
alt + shift - y : yabai -m space --mirror y-axis
# # mirror tree x-axis
alt + shift - x : yabai -m space --mirror x-axis
# balance size of windows
alt + shift - 0 : yabai -m space --balance
Or detach individual windows from the tiling so that we can move them freely around without attaching to other ones.
# change layout of desktop
alt - e : yabai -m space --layout bsp
alt - l : yabai -m space --layout float
alt - s : yabai -m space --layout stack
skhd
also supports stack mode alt - s
, so new windows will be created on top of each other and we can cycle trough them via shortcuts
# cycle through stack windows
# alt - p : yabai -m window --focus stack.next || yabai -m window --focus south
# alt - n : yabai -m window --focus stack.prev || yabai -m window --focus north
# forwards
alt - p : yabai -m query --spaces --space \
| jq -re ".index" \
| xargs -I{} yabai -m query --windows --space {} \
| jq -sre "add | map(select(.minimized != 1)) | sort_by(.display, .frame.y, .frame.x, .id) | reverse | nth(index(map(select(.focused == 1))) - 1).id" \
| xargs -I{} yabai -m window --focus {}
# backwards
alt - n : yabai -m query --spaces --space \
| jq -re ".index" \
| xargs -I{} yabai -m query --windows --space {} \
| jq -sre "add | map(select(.minimized != 1)) | sort_by(.display, .frame.y, .frame.y, .id) | nth(index(map(select(.focused == 1))) - 1).id" \
| xargs -I{} yabai -m window --focus {}
With alt + w
we can easily terminate a currently focused tile.
# close focused window
alt - w : yabai -m window --close
With alt + f
we can bring a focused tile into fullscreen mode. This is very practical if you want to show your colleagues some code or if you want to use the whole desktop quickly in a presentation.
# enter fullscreen mode for the focused container
alt - f : yabai -m window --toggle zoom-fullscreen
# toggle window native fullscreen
alt + shift - f : yabai -m window --toggle native-fullscreen
We are done with the yabai
+ skhd
part. We can still configure some stuff ourselves via the MacOS system settings to make our whole workflow a little more effective.
MacOS settings
The spotlight search is not so wrong! I know you miss dmenu
or rofi
as much as I do, but I have made my peace with spotlight
.
In the system preferences we can still change the Spotlight shortcut to alt + d
, then we always have our search conveniently at our side whenever we need it.
What is also very nice is the possibility to jump back and forth between workspaces via shortcut. Since I use a lot of shortcuts with alt
as a prefix anyway, I have adapted the binding so that I can easily jump to the corresponding, active workspaces with alt + <1-9>
.
What we want to do now is hide the menu bar and the status bar. We can also easily configure both in the system settings.
spacebar (i3status replacement)
Spacebar is a cool status bar, similar to i3status
. It shows everything you need, such as the time, user, battery status and our active workspaces.
We can also install Spacebar
again quite comfortably via brew
and then start it.
# install spacebar
brew install cmacrae/formulae/spacebar
# start spacebar
brew services start spacebar
Next we’ll have to grant permissions under Accessibility
for spacebar
again.
Spacebar uses fontawesome for the display of icons. To do this, we install the font via brew
brew install homebrew/cask-fonts/font-fontawesome
Like yabai
and skhd
, spacebar uses a configuration file that can be used to turn off or personalise information in the bar.
Create a config file at ~/.config/spacebar/spacebarrc
mkdir -p ~/.config/spacebar
touch ~/.config/spacebar/spacebarrc
chmod +x ~/.config/spacebar/spacebarrc
Here is the content of my spacebarrc
. Of course, everyone can tinker with the settings as they like.
#!/usr/bin/env sh
# Fixes a bug on the newest MacOS version, Thanks to Kamen Vakavchiev!
# see: https://github.com/cmacrae/spacebar/issues/101#issuecomment-1083252227
spacebar -m config right_shell off
spacebar -m config position top
spacebar -m config height 26
spacebar -m config title on
spacebar -m config spaces on
spacebar -m config clock on
spacebar -m config power on
spacebar -m config padding_left 20
spacebar -m config padding_right 20
spacebar -m config spacing_left 25
spacebar -m config spacing_right 15
spacebar -m config text_font "Helvetica Neue:Bold:12.0"
spacebar -m config icon_font "Font Awesome 5 Free:Solid:12.0"
spacebar -m config background_color 0xff202020
spacebar -m config foreground_color 0xffa8a8a8
spacebar -m config power_icon_color 0xffcd950c
spacebar -m config battery_icon_color 0xffd75f5f
spacebar -m config dnd_icon_color 0xffa8a8a8
spacebar -m config clock_icon_color 0xffa8a8a8
spacebar -m config power_icon_strip
spacebar -m config space_icon •
spacebar -m config space_icon_color 0xffffab91
spacebar -m config space_icon_color_secondary 0xff78c4d4
spacebar -m config space_icon_color_tertiary 0xfffff9b0
spacebar -m config space_icon_strip 1 2 3 4 5 6 7 8 9 10
spacebar -m config clock_icon
spacebar -m config dnd_icon
spacebar -m config clock_format "%d/%m/%y %R"
spacebar -m config right_shell on
spacebar -m config right_shell_icon
spacebar -m config right_shell_command "whoami"
echo "spacebar configuration loaded.."
The last thing we need to do to make our bar look nice is to tell yabai to keep some space above the screen.
We add the following to our .yabairc
:
# spacebar padding on top screen
SPACEBAR_HEIGHT=$(spacebar -m config height)
yabai -m config external_bar all:$SPACEBAR_HEIGHT:0
Now we restart the service once and we have a fancy new menu bar!
brew services restart spacebar
Terminals
Opinions differ on the choice of terminal application. Personally, I have tried the two tools kitty and alacritty with MacOS. I think both have advantages and disadvantages, but in the end I stuck with alacritty
.
Kitty
kitty
has many foundational features, such as: image support, superlative performance, various enhancements to the terminal protocol, etc. These features allow the development of rich terminal applications, such as Side-by-side diff and Unicode input.
To install kitty
via brew
run the following command:
brew install --cask kitty
Next we’ll have to grant permissions under Accessibility
for kitty
.
Furthermore, we want to create a skhd
shortcut to open a new terminal window with alt + return
. For this we can add the following to our .skhdrc
:
# open terminal
alt - return : open -n /Applications/kitty.app/Contents/MacOS/kitty --single-instance -d
Alacritty
Alacritty
is a modern terminal emulator that comes with sensible defaults, but
allows for extensive configuration. By integrating with other
applications, rather than reimplementing their functionality, it manages to
provide a flexible set of features with high performance.
To install alacritty
via brew
run the following command:
brew install --cask alacritty
Next we’ll have to grant permissions under Accessibility
for alacritty
.
Furthermore, we want to create a skhd
shortcut to open a new terminal window with alt + return
. For this we can add the following to our .skhdrc
:
# open terminal
alt - return : open -n /Applications/Alacritty.app
Cheatsheet: Everyday commands
alt + arrow keys
move focus between windows (or useh
,j
,k
,l
)alt + shift + arrow keys
move space in direction (or useh
,j
,k
,l
)alt + shift + n
move space to workspacen
alt + ctrl + arrow keys
set insertion point in focused container (or useh
,j
,k
,l
)alt + return
open terminalalt + w
close focused spacealt + f
toggle fullscreenalt + d
toggle spotlight search
Switching between modes:
alt + e
- normal mode (New windows will open next to each other)alt + s
- stack mode (New windows will open on top of each other)
cycle through windows while you’re in stack mode
alt - p
- next window on the stackalt - n
- previous window on the stack
Conclusion
Although I still love Linux over MacOS, I have to say that I am getting used to the change with this setup.
yabai
and skhd
(+spacebar
) offer a lot of features that allow me to bring my workflow as close as possible to my old i3 bindings. I do miss some things though, for example the convenient stacking of windows or the lack of deactivating animations when creating a new tile. Nevertheless, the setting works for me, despite some minor drawbacks.
I hope this blogpost helps some people who work with MacOS but don’t want to miss their old i3wm
setup.
You can find my .yabairc
, skhdrc
and spacebarrc
files here