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
Wow... that's... incredible. I've used Hammerspoon forever and never knew that existed.
Just messing around I found you can extend the grid size with `hs.grid.setGrid('4x4')`, which you also may then want to shrink the text size with `hs.grid.ui.textSize = 30`, and finally if you use an alternative keyboard layout (eg: Colemak), you can set the grid to use it with `hs.grid.HINTS`. They really thought of everything with this feature.
This is amazing. Thanks for sharing. Did you ever look into capturing the states of where all the windows are once you are done with resizing them? So as to restore them later back into position if they ever get out of alignment ?
Thinking of the usecase where every task or a project deserves a certain arrangement of windows and it would be good to summon them into existence as and when needed?
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 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.
Hammerspoon is the glue that holds my Mac together. For a starter list of things to do with this app, a partial list of the things that I'm using it for:
- 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 URLs
> - Dumping all open Safari tabs to an Obsidian doc
I'd love to do this too. Would you mind sharing how you do it? Or is it trivially easy and not worth explaining? (I haven't looked too deeply into HS yet.)
I pretty much only use it for two (related) things these days:
- check the list of open Teams windows; if there's a non-standard one, assume I'm in a meeting and webhook to HomeAssistant to select the "active"[2] preset on my meeting light[0].
- download my work ical[1] and, if there's a pending meeting (<~15m), webhook-HASS for the "pending" present on the meeting light.
[0] Just a short strip of WS2812B connected to an ESP32 running WLED.
[1] Originally this was a simple HTTP to my shared link on outlook.com but then they started requiring authentication (because that's exactly what you want on a SHARED link, you gufftarts); had a look at the Azure SDK and ... bag of milky spanners that is; ended up having to import my work ical into Apple Calendar and then use the ical link for that in Hammerspoon. Oh how we laughed. Especially when I realised it only has about 40% of the actual meetings because somehow "my calendar" is actually 4 or 5 bastardised conglomerations of pain and the ical for "my calendar" is actually just for one of those. AND NOT THE USEFUL ONE EITHER.
[2] There's various - "camera" for "the one meeting I'm forced to have my camera on", "active" is "I probably have to talk", "passive" is "I'm not going to be talking", and "silent" for things like company presentations where it's just watching a boring Powerpoint over Teams.
Tossing a couple things out mostly for the people getting ideas from these threads:
I've done something similar, but using the webcam watcher to hook on the webcam being enabled for any reason -- that way when I have that one external meeting on Google Meet or whatever the light still works.
(I also found it useful to have Hammerspoon flip a virtual switch in, well, Hubitat for me, and then automation based on that virtual switch, rather than triggering the light directly. Lets me hang other things off of that virtual switch instead of putting it in Hammerspoon.)
I have fond memories of this app. However, after many years, I have moved on. I am in the process of writing my own replacement for some of the various use cases that Hammerspoon once provided me. Though, Hammerspoon will always be a source of great inspiration.
I fake a tiling window manager on Mac with Hammerspoon, resizing to fit in specific corners/sizes:
-- 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)
I do this too, really happy with my setup - I use hyper+arrow keys to move windows around a monitor (split in thirds on 40”+ or halves on the built-in screen), or jump to another monitor, and hyper+enter to fullscreen. When you push against an edge in full screen it reduces the window size in stages, it all feels natural.
I use it to hide Zoom's screen sharing controls so they don't come back when pressing Esc:
-- 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)
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 ...
I tried to find a proper window control tool for macOS for a while, tested Rectangle and Magnet and dunno how many.
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.
I 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.
68 comments
[ 2.7 ms ] story [ 77.9 ms ] threadHammerspoon seems like a superset and it’s probably better to just have one, instead of two tools warring about who gets the keypresses?
(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
Just messing around I found you can extend the grid size with `hs.grid.setGrid('4x4')`, which you also may then want to shrink the text size with `hs.grid.ui.textSize = 30`, and finally if you use an alternative keyboard layout (eg: Colemak), you can set the grid to use it with `hs.grid.HINTS`. They really thought of everything with this feature.
Thinking of the usecase where every task or a project deserves a certain arrangement of windows and it would be good to summon them into existence as and when needed?
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
Still wouldn't work without it though (I run Niri at home)
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/
I'd love to do this too. Would you mind sharing how you do it? Or is it trivially easy and not worth explaining? (I haven't looked too deeply into HS yet.)
- check the list of open Teams windows; if there's a non-standard one, assume I'm in a meeting and webhook to HomeAssistant to select the "active"[2] preset on my meeting light[0].
- download my work ical[1] and, if there's a pending meeting (<~15m), webhook-HASS for the "pending" present on the meeting light.
[0] Just a short strip of WS2812B connected to an ESP32 running WLED.
[1] Originally this was a simple HTTP to my shared link on outlook.com but then they started requiring authentication (because that's exactly what you want on a SHARED link, you gufftarts); had a look at the Azure SDK and ... bag of milky spanners that is; ended up having to import my work ical into Apple Calendar and then use the ical link for that in Hammerspoon. Oh how we laughed. Especially when I realised it only has about 40% of the actual meetings because somehow "my calendar" is actually 4 or 5 bastardised conglomerations of pain and the ical for "my calendar" is actually just for one of those. AND NOT THE USEFUL ONE EITHER.
[2] There's various - "camera" for "the one meeting I'm forced to have my camera on", "active" is "I probably have to talk", "passive" is "I'm not going to be talking", and "silent" for things like company presentations where it's just watching a boring Powerpoint over Teams.
I've done something similar, but using the webcam watcher to hook on the webcam being enabled for any reason -- that way when I have that one external meeting on Google Meet or whatever the light still works.
(I also found it useful to have Hammerspoon flip a virtual switch in, well, Hubitat for me, and then automation based on that virtual switch, rather than triggering the light directly. Lets me hang other things off of that virtual switch instead of putting it in Hammerspoon.)
I use it for one thing only, as a window manager, and for that purpose it has made MacOS eminently more usable for me.
Can't live without AutoHotkey on Windows.
Thanks to everyone who contributed to both!
It's lua, so you can get creative with https://fennel-lang.org/
I use ShiftIt (a lovely project, but dead) reimplemented in Hammerspoon. It is very comprehensive.
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.
KeyboardMaestro
Automator and AppleScript
Raycast
Some previous discussion:
2021 https://news.ycombinator.com/item?id=29533495
2019 https://news.ycombinator.com/item?id=21801178
I 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.