Monthly Archives: February 2024

Moving from I3 to macOS with Minimum Pain

I was a long time Linux user, most recently using I3 (Ubuntu/Regolith). The moment Apple released M1-based laptop, though, I wanted one. Because it’s fast and deservedly so.

The article linked above talks about the M1 architecture and it’s hardware accelerators, which explains why any Linux kernel that would use that hardware platform to full potential is very far away, so it looks like the only way to enjoy it today is to live with macOS.

Moving to macOS from I3? Dock? Ugh. Poking with a mouse moving and resizing windows? Thanks, no thanks.

Can macOS be made to work more like I3? Yabai, Amethyst – not slick. Rectangle? Way too much bother. Could a third solution be found, satisfying the requirements, simple and easy to use, setup and maintain?

The Requirements

I want maximized windows, easy keyboard-based app switch, keyboard shortcut to send to another screen, split screen occasionally.

The solution

  • Hide Dock
  • Launch apps via Command + Space
  • Maximize all windows
  • Send to another screen button
  • Split screen vertically

Hide dock / Launch apps via Command + Space

Hiding the dock is very easy – just hit Option-Command-D.

Switch between apps using Alt-Tab (and Shift-Alt-Tab).

Command+space for launching apps? It usually opens spotlight search. This could be changed in Settings: Keyboard -> Shortcuts -> Show Launch Pad: Command+Space. You might have to remove spotlight search shortcut by unchecking “Show Spotlight search” first. Note that the “3-rectangle/F3” keyboard button opens Launch Pad too, but we will use this button for more important things in the next section.

Maximize all Windows / Send to another screen button

Use Hammerspoon script to make the “3-rectangle/F3” button move the selected app to another screen (if such screen is present) and maximize it (always). At some point all of the apps are maximized and hitting Alt-Tab simply replaces one with another.

Why not use macOS-native maximized “full screen” workspaces, similar to I3? There are some issues with that and no benefits. First, the number and order of these workspaces change when the app windows open and close, so they can’t be precisely addressed with I3-like workspace shortcuts. Second, switching these workspaces triggers heavy animation. And it does not give you much extra real estate either: the “full screen” maximization does not include the top line with the camera notch on newer MacBooks – the area just becomes black and is not being used by the app window.

Split screen vertically

A small addition to the above Hammerspoon script lets us park a selected window to the left or right side of the screen using Control-Option-Command+Left or Right arrow button.

The Hammerspoon Script

function moveToNextScreen()
	hs.window.animationDuration = 0
    local win = hs.window.focusedWindow()
	screens = hs.screen.allScreens()
	local max_screen_index = 1
	local this_screen_index = 1
	for i, screen in pairs(screens) do
		max_screen_index = i
		if screen:name() == win:screen():name() then
			this_screen_index = i
		end
	end
	local next_screen_index = this_screen_index+1
	if next_screen_index == max_screen_index+1 then next_screen_index = 1 end
	local next_screen = screens[next_screen_index]
	win:moveToScreen(next_screen)
	win:maximize()
end
hs.hotkey.bind({"cmd","alt","shift"}, "S", moveToNextScreen)

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "Left", function()
	local win = hs.window.focusedWindow()
	local f = win:frame()
	local screen = win:screen()
	local max = screen:frame() 
	f.x = max.x
	f.y = max.y
	f.w = max.w / 2
	f.h = max.h
	win:setFrame(f)
end)

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "Right", function()
	local win = hs.window.focusedWindow()
	local f = win:frame()
	local screen = win:screen()
	local max = screen:frame() 
	f.x = max.x + (max.w / 2)
	f.y = max.y
	f.w = max.w / 2
	f.h = max.h
	win:setFrame(f)
end)

local keyboardHandler = function(event)
	local keyCode = event:getKeyCode()
	if keyCode == 160 then
		moveToNextScreen()
		return true
	end
end

keyboardEventListener = hs.eventtap.new({
	hs.eventtap.event.types.keyDown
}, keyboardHandler)
keyboardEventListener:start()

What else? Unrelated to I3: using Forklift gave me SSH mounts, something dearly missed from Linux otherwise.