The mail program has a folder tree on the left, the list of messages in the center, and the current message on the right. The IDE has all these tool windows that need showing, in addition to the actual editor. Websites also like it if the window size is a bit more.
Back when I was using Emacs and xterm, mainly, it was nice to show Emacs in the left half and then two xterms on the right.
So instead of tiling, I've come to realize that I only need a couple of window positions and sizes: Mail program and IDE are full screen. The browser occupies 70% width and height, in the top right corner, and the terminal is in the bottom left corner, 200 columns by 44 rows or so. (Lazygit works better if the terminal is a bit larger.) The chat program is full height, 60% width, left edge.
In this way, while the IDE is building or running tests, I can summon the web browser and still see at the bottom and on the left what is the progress of build or test. Also, when I use the software through the browser, I can see a couple of lines of log messages, which is enough to tell me whether to switch.
So I'm now happy with hotkeys in Hammerspoon that reposition and resize the current window to one of these presets, and to jump to a specific app with a keypress. I use a modal for this.
I dig the idea of having multi-level modals, somehow this idea never occurred to me.
- Dumping all open Safari tabs to an Obsidian doc
- Adding 'hyper' (Ctrl-Opt-Cmd) keybinds to pop a new window for:
- Safari
- Finder
- Terminal / Ghostty
- VS Code
- Notes
- Editing Hammerspoon/AeroSpace/Sketchybar config
- Reloading Hammerspoon config
- Reloading Sketchybar
- Quitting all Dock apps except Finder
- Screen lock
- System sleep
- Opening front Finder folder in VS Code
- Opening front Safari URL on Archive.today
- Showing front Safari window tab count
- Showing front app bundle ID
- Posting notification about current Music track
- Controlling my Logi Litra light (various color temps/brightnesses)
- Starting/stopping a client work timer
- Tying it to AeroSpace for:
- Pushing a window to another monitor
- Performing a two-up window layout
- Swapping those two windows
- Closing all other workspace windows
- Gathering all windows to first workspace
- Ensuring some background apps stay running if they crash
- Prompting to unmount disk images if trashed
- Binding into Skim to jump to specific sections of spec PDFs using terse Markdown URLsI can't even work on Mac without it. It let's you do stuff like "alt+spc a b" (apps -> browser) or "alt+spc m j/k" (media -> vol up/down), or edit just about any text of any app in your editor (Emacs atm) - with all the tools you have there - spellchecking, thesaurus, translation, LLMs, etc.
You can plug it to your favorite WM (I'm currently using Yabai) and do tons of other interesting things. Because it's all written in Fennel, one can develop things in a tight feedback loop with a connected REPL - e.g., I can ask Claude to inspect things in the running Slack app or Firefox and make interesting automations - all without ever leaving my editor.
-- resize based on ratios
function ratioResize(xr, yr, wr, hr)
return function ()
local win = hs.window.focusedWindow()
win:moveToUnit({x=xr,y=yr,w=wr,h=hr})
end
end
-- 4 corners, different sizes
hs.hotkey.bind({"cmd", "ctrl"}, "w", ratioResize(0, 0, 2/5, 2/3))
hs.hotkey.bind({"cmd", "ctrl"}, "e", ratioResize(2/5, 0, 3/5, 2/3))
hs.hotkey.bind({"cmd", "ctrl"}, "s", ratioResize(0, 2/3, 2/5, 1/3))
hs.hotkey.bind({"cmd", "ctrl"}, "d", ratioResize(2/5, 2/3, 3/5, 1/3))
And to throw windows to other monitors: -- send to next screen
hs.hotkey.bind({"cmd", "ctrl"}, ";", function()
local win = hs.window.focusedWindow()
local screen = win:screen()
local next_screen = screen:next()
win:moveToScreen(next_screen)
end) -- Hide Zoom's "share" windows so it doesn't come back on ESC keypress
local zoomWindow = nil
local originalFrame = nil
hs.hotkey.bind({"cmd", "ctrl", "alt"}, "H", function()
print("> trying to hide zoom")
if not zoomWindow then
print("> looking for window")
zoomWindow = hs.window.find("zoom share statusbar window")
end
if zoomWindow then
print("> found window")
if originalFrame then
print("> restoring")
zoomWindow:setFrame(originalFrame)
originalFrame = nil
zoomWindow = nil
else
print("> hiding")
originalFrame = zoomWindow:frame()
local screen = zoomWindow:screen()
local frame = zoomWindow:frame()
frame.x = screen:frame().w + 99000
frame.y = screen:frame().h + 99000
zoomWindow:setFrame(frame)
end
else
print("> window not found")
end
end) hs.hotkey.bind({"ctrl"}, "D", function()
hs.grid.show()
end)
i've tried all of the other fancy window managers and for me nothing has ever beat the ease of use of just(1) ctrl-d to see the grid, (2) type the letter where you want the top left corner of your window to be, (3) type the letter where you want the bottom right corner to be
window resized
I’ll have a hell of a time rewriting everything into Lua when I have soooo many node packages I leverage.
- Vim mode everywhere in macOS: https://github.com/dbalatero/VimMode.spoon
- Modifier keys + click/drag to resize or move windows: https://github.com/dbalatero/SkyRocket.spoon
- Show an overlay helper of all your keybinds when you hold modifier keys down: https://github.com/dbalatero/HyperKey.spoon
And my huge pile of random scripts/configs: https://github.com/dbalatero/nixpkgs/tree/main/home/modules/...
local usbWatcher = hs.usb.watcher.new(function(device)
if device.productName == "EMEET SmartCam C960" then
if device.eventType == "added" then
hs.execute("networksetup -setairportpower en0 off")
hs.notify.new({title="Wi-Fi", informativeText="Disabled (USB device connected)"}):send()
elseif device.eventType == "removed" then
hs.execute("networksetup -setairportpower en0 on")
hs.notify.new({title="Wi-Fi", informativeText="Re-enabled (USB device removed)"}):send()
end
end
end)
usbWatcher:start()It uses a swipe gesture detection spoon I found after searching for something similar[1].
Then I just figured out that I have Hammerspoon, it can control windows -> recreate one exactly how I like it. Been using it for a year now and it's 99% perfect. Some specific applications (coughFirefoxcough) sometimes get into a weird state that doesn't work, but I can live with that.
It can also pop all windows to a specific layout with a single shortcut by combining the active wifi + monitor setup to detect if I'm at home, at work, or working at home.
Hammerspoon seems like a superset and it’s probably better to just have one, instead of two tools warring about who gets the keypresses?
local appHotkeys = {}
local function remapAppHotkey(appName, fromMods, fromKey, toMods, toKey, delay)
if not appHotkeys[appName] then
appHotkeys[appName] = {}
end
local hotkey = hs.hotkey.new(fromMods, fromKey, function()
hs.eventtap.keyStroke(toMods, toKey, delay or 0)
end)
table.insert(appHotkeys[appName], hotkey)
end
local appWatcher = hs.application.watcher.new(function(appName, eventType)
local hotkeys = appHotkeys[appName]
if not hotkeys then return end
for _, hotkey in ipairs(hotkeys) do
if eventType == hs.application.watcher.activated then
hotkey:enable()
elseif eventType == hs.application.watcher.deactivated then
hotkey:disable()
end
end
end)
appWatcher:start()
-- Remap app hotkeys
remapAppHotkey("Finder", { "cmd" }, "q", { "cmd" }, "w", 0.5)
... etc ...Will be using it for more automation tools moving forward.
It's fun to combine with qmk [0], which gives you a bunch more options for hotkeys on your keyboard via layers. I've ended up with a layer where half the keyboard is Hammerspoon shortcuts directly to apps (e.g. go to Slack, to Chrome, etc.) and half of it is in-app shortcuts (like putting cmd-number on the home row, for directly addressing chrome tabs).
Between this and one of the tiling window manager-adjacent tools (I use Sizeup), I can do all my OS-level navigation directly. "Oh I want to go to Slack and go to this DM" is a few keystrokes away, and not dependent on what else I was doing.
[0] https://qmk.fm/
Yabai supports this perfectly (especially combined with instant, animation-free space switching) but it requires disabling system integrity protection--which is a non-starter on a work computer.
Aerospace solves it with their own spaces implementation.
I was able to put together a hammerspoon script that does the job decently enough for my purposes: https://gist.github.com/kcrwfrd/6f3dcaec0e08e0e77b2884588a34...
Can't live without AutoHotkey on Windows.
Thanks to everyone who contributed to both!
I'll be sad when it moves from Lua to JavaScript, but I guess that's better than moving to Tcl.
otherwise I'm slowly working on a Spoon that figures out if there is an active meeting in Zoom, Teams, Huddle, Google Meet and will allow for muting, video enable/disable and screen sharing etc
It's lua, so you can get creative with https://fennel-lang.org/
Some previous discussion:
KeyboardMaestro
Automator and AppleScript
Raycast