Workspace layouts with Hammerspoon Grid

10 Jan 2024 in Odds and Ends

Since I switched from Arch to MacOS there’s been an i3 shaped hole in my life. I’ve used apps like Divvy to approximate it, but it’s not the same.

I tried out some of the MacOS tiling window managers, but couldn’t get to grips with them. Between having to trigger things using external apps (Yabai and skhd) and having to disable system integrity protection, things just didn’t click. One of the things I loved about i3 was its simplicity.

After many years of searching, I think I’ve found a solution in Hammerspoon.

Hammerspoon?

Hammerspoon is an automation framework for MacOS that allows you to script various things using Lua.

I’ve written about Hammerspoon before, and have been using it for window management since I abandoned Divvy a couple of years ago (when I configured a dedicated hyper key on my keyboard).

I was recently browsing through Jakub’s Hammerspoon config and spotted his usage of hs.grid to configure predefined layouts.

Predefined layouts were one of my big use cases for i3, and so I went digging.

hs.grid

hs.grid splits your screen into an x by y grid. You can place windows by specifying a width/height along with an offset for the x and y values.

I have split my screen in to an 8x2 grid, with zero margin. I also have a shortcut to show the grid overlay on screen in case I want to position a single window in a non-standard layout.

lua
hs.grid.setMargins(hs.geometry.size(0,0))
hs.grid.setGrid('8x2')
hs.hotkey.bind(hyper,'g',function()
hs.grid.show()
end)

To align a window to the grid, you call hs.grid.set and pass an instance of hs.window as the first parameter. The window is being placed on the right hand side of the screen (4 cells offset on the x-axis, 0 on the y-axis, and it covers a 4x2 space on an 8x2 grid).

lua
hs.grid.set(window, '4,0 4x2')

Automatic layouts

One of the things that I loved about Jakub’s Hammerspoon config is that he had a helper function to discover all of the windows for a certain app before reflowing windows in to the grid.

I borrowed this idea and built some convenience functions which allow you to define layouts like this:

lua
defineLayout("Writing", 1, {
{'Bear', '0,0 2x2'},
{'Arc', '2,0 4x2', true}
});
defineLayout("Code", 2, {
{'Arc', '0,0 2x2'},
{'Code - Insiders', '2,0 4x2', true},
{'iTerm', '6,0 2x2'}
})

Now I can press hyper+1 to have Bear on the left 1/4 of the screen and Arc in the middle half. If I’m writing code, I can press hyper+2 to have VSCode in the middle taking up half of the screen, with Arc on the left and a terminal on the right.

Applications will only be positioned if they are already launched. Hammerspoon provides a hs.application.launchOrFocus() method, but I chose to implement focusIfLaunched as sometimes I don’t have all the apps open (e.g. Bear is optional when I’m writing).

Give it a go

Hammerspoon is wonderful, and I recommend giving it a go if you haven’t already tried it. My Hammerspoon config is publicly available, and whilst I’m not a power user, it saves me a ton of time every single day.

If you’ve a Hammerspoon user and have tips and tricks you want to share, I’m @[email protected]. I’d love to hear them!

Bonus content

I’ve recently stumbled across aerospace, which bills itself as “an i3-like tiling window manager for macOS”. I haven’t given it a go yet, but it looks promising based on the YouTube video.