[HN Gopher] Hammerspoon
___________________________________________________________________
Hammerspoon
Author : tosh
Score : 161 points
Date : 2026-03-13 18:34 UTC (4 hours ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| golem14 wrote:
| Has anyone worked on making a config replicating aerospace?
|
| Hammerspoon seems like a superset and it's probably better to
| just have one, instead of two tools warring about who gets the
| keypresses?
| hirvi74 wrote:
| What features are you trying to replicate from Aerospace?
| golem14 wrote:
| Well, a tiling window and workspace manager. But as I am
| typing this, I'm realizing they hammerspoon can probably do
| some of the window placement, but maybe not handling
| workspaces and global state.
|
| I was hoping I could be lazy and ask, and a not-lazy person
| could give a ready made answer :)
| hirvi74 wrote:
| If workspace management is an another term for managing
| desktop/spaces, then you are going to be hard pressed to
| find anything that is not a brittle hack.
|
| I am writing a window manager bundled with other knick-
| knacks for myself. I have a "solution" for moving windows
| between spaces, but in the most vile way possible.
|
| The only way I have managed to move windows between spaces
| is by, and this is no joke, recording the mouse position,
| moving the mouse to an app's titlebar, automating the
| 'click and hold' on the window's titlebar, then having the
| keybindings for "Switch to Next/Previous Space" fire off,
| and then moving the mouse back to the original position.
|
| Because of the animations, all of junk requires carefully
| timed, short sleeps, which are also not likely consistent
| across various hardware/OS versions (can't test it myself).
|
| Also, I have no idea what happens if my solution is tried
| on apps with pop-up windows, 'headless' apps (no title
| bar), electron apps, etc..
|
| Apple's support for spaces is notoriously atrocious. There
| is no clean way to move windows from one space to another
| or to create/delete spaces. Though there was a built in way
| in OSX Snow Leopard, IIRC. Why it was removed? I have no
| idea.
|
| Aerospace creates its own virtual desktops/spaces instead
| of trying to fight against the OS. I have never used
| Aerospace, so I cannot comment on its efficacy. But that is
| probably the cleanest solution we currently have available.
| golem14 wrote:
| Sadly, I think you're right.
|
| Aerospace is pretty cool, i recommend it, but I have not
| really worked out how full screen interacts with spaces.
| It's a mess with and without aerospace.
| r5Khe wrote:
| It can definitely handle virtual workspaces and global
| state (if I'm understanding what you mean). I have an
| Aerospace-like implementation here:
| https://github.com/mybuddymichael/Helm.spoon
|
| It has several features from Aerospace, but Hammerspoon's
| window management performance is not nearly as good as
| Aerospace's (not surprising!).
|
| Overall, I've found it easier to just fork Aerospace and
| add various extra features to it, so that's what I'm doing
| now.
| pjm331 wrote:
| here is my entire config
| 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
| stackghost wrote:
| Not that I insert EOFs very often, but does that conflict with
| CTRL+D in the terminal?
| xyzzy_plugh wrote:
| I use EOF all the time to end terminal sessions.
| commandertso wrote:
| Great handle, btw.
| pjm331 wrote:
| yeah the CTRL+D definitely gives me problems from time to
| time but thus far i have been too lazy to fix it
| theshrike79 wrote:
| Make caps lock a hyperkey (shift+ctrl+option+cmd) and use
| that for non-overlapping shortcuts
| 1-more wrote:
| make caps lock control on hold, double quote on tap. Make
| control hyperkey on hold, angle bracket on tap. My keyboard
| firmware is very odd. This is not easily done with soft
| remaps to the point that I don't bother trying.
| elAhmo wrote:
| This is amazing! I have a slightly more elaborate setup that
| allows me to resize from one or another side, similar to what
| Apple added recently but with more flexibility, but this is
| super interesting, thanks for sharing!
| hrmtst93837 wrote:
| Neat until you need to sync configs or keep multiple machines
| in harmony, at which point dotfile headaches stack up with
| Hammerspoon and Lua. Adding complex logic like window rules,
| app-specific behavior, or handling monitor changes strips away
| some of that hotkey simplicity and leads to endless tweaking.
| Still, for avoiding the mouse, it's one of the few flexible
| options left on macOS that doesn't feel ancient. Tradeoffs
| everywhere but nowhere else really compares in control.
| dbalatero wrote:
| Syncing configs is a pretty solved problem with dotfile
| repos. I even made a starter repo anyone can fork & use:
| https://github.com/dbalatero/dotfiles-starter
| john-tells-all wrote:
| I'd love to have a global "toggle Teams mute" button.
| hirvi74 wrote:
| What do you mean? Like muting the entire application so no
| sound comes from Teams or muting yourself while on a call? For
| the latter, I thought 'Option + Space' worked (or used to)?
| roxolotl wrote:
| ```
|
| hs.loadSpoon("MicMute")
|
| binding = { toggle = { {"ctrl", "alt"}, "m" } }
|
| spoon.MicMute:bindHotkeys(binding)
|
| ```
|
| You'll have to add the MicMute spoon which just mean
| downloading the zip here, unzipping, and opening the .spoon.
| https://www.hammerspoon.org/Spoons/MicMute.html
| jjmiv wrote:
| is there a particular reason this was shared?
|
| 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
| al_borland wrote:
| My guess is someone just found it. When I was moving to macOS
| for my work laptop, I was looking for something to replace
| AutoHotKey. It took me a long time and a lot of digging to
| actually find HammerSpoon.
| rolymath wrote:
| Is paperwm jittery for everyone?
| trjordan wrote:
| I utterly love Hammerspoon.
|
| 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/
| henrebotha wrote:
| My QMK Tmux "layer" is still one of my favourite
| customisations. Prepends Ctrl-B to everything I type.
| incanus77 wrote:
| 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
| piskov wrote:
| Keyboard stuff is better handled with Karabiner elements
| dbalatero wrote:
| Karabiner can _create_ new keys like hyper, but you _bind_
| them with Hammerspoon.
| joemi wrote:
| > - 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.)
| mwagstaff wrote:
| Can't live without Hammerspoon on Mac.
|
| Can't live without AutoHotkey on Windows.
|
| Thanks to everyone who contributed to both!
| hirvi74 wrote:
| 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.
| weitzj wrote:
| I love hammerspoon. That's it :D
|
| It's lua, so you can get creative with https://fennel-lang.org/
| hmokiguess wrote:
| what's your favourite spoon?
| zdw wrote:
| 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)
| comboy wrote:
| I highly recommend Aerospace[1], went through a few approaches,
| I cared about not completely compromising security either, it
| works really well if you come from something like i3
|
| 1. https://github.com/nikitabobko/AeroSpace
| idle_zealot wrote:
| Seconding this. I find MacOS unusable without it. I'll ask
| here because websearch is failing me: is there a way to fix
| the focus stealing that happens when you have multiple
| windows of an application on different displays?
| Specifically, say workspace 1 and 2 are on monitor Left,
| while 3 and 4 are on Right. Application A has a window on
| workspace 1, B has one window on 2 and another on 3, and C
| has a window on 4. Workspace 1 is active on display Left,
| workspace 4 is active on Right. If I switch to workspace 3
| the following happens:
|
| - the switch goes through, Left displays workspace 1, right
| displays 3 (desired state)
|
| - Application B is focused, presumably because its window on
| 3 becomes active (also desired)
|
| - Display Left switches to display workspace 2, presumably
| because it contains a window belonging to the newly focused
| application B? (I don't want this)
|
| - the window of application B on workspace 2 steals focus
| from the one on workspace 3 (???)
| airstrike wrote:
| Thirding the recommendation, and I also have this same
| issue. It's quite frustrating--but still better than no
| Aerospace!
| JSR_FDED wrote:
| So what you're saying is:
|
| Charlie's paternal grandfather Reginald married twice--
| first to Mildred, mother of Charlie's father Arthur and his
| siblings Beatrice (a nun with spiritual godchildren) and
| Cecil (whose widow Dorothy married Charlie's maternal uncle
| Edward). What is the name of Charlie's goddaughter?
| jeberle wrote:
| I use it similarly, but I add spots for side x side as well as
| left, center, right. I only use Hammerspoon for this and a
| couple tiny things, but it's completely worth it for this
| alone. Use math to specify window sizes & location. Insanity.
| local mode = hs.screen.primaryScreen():currentMode()
| local mods = {"ctrl", "alt", "cmd"} -- mash those keys
| -- regular app windows do local w = 1094 -- no
| clip on GitHub, HN local h = 1122 -- tallish
| local x_1 = 0 -- left edge
| local x_2 = math.max(0, (mode.w - w - w) / 2) -- left middle
| local x_3 = (mode.w - w) / 2 -- middle
| local x_4 = math.min(mode.w - w, x_2 + w + 1) -- right middle
| local x_5 = mode.w - w -- right edge
| local y = 23 -- top of screen below menu bar
| hs.hotkey.bind(mods, "2", function() move_win( 0, y, mode.w,
| mode.h) end) -- max hs.hotkey.bind(mods, "3",
| function() move_win(x_1, y, w, h) end)
| hs.hotkey.bind(mods, "4", function() move_win(x_2, y, w, h)
| end) hs.hotkey.bind(mods, "5", function() move_win(x_3,
| y, w, h) end) hs.hotkey.bind(mods, "6", function()
| move_win(x_4, y, w, h) end) hs.hotkey.bind(mods, "7",
| function() move_win(x_5, y, w, h) end) end
| function move_win(x, y, w, h)
| hs.window.focusedWindow():setFrame(hs.geometry.rect(x, y, w,
| h)) end
| piskov wrote:
| Moom is really the best
| apazzolini wrote:
| I like the miro windows manager plugin:
| https://github.com/miromannino/miro-windows-manager
|
| It's nice to be able to iterate through the halves/thirds
| configurations for different cases.
| jmcguckin wrote:
| I use it to give me focus-follows-mouse and to have a large
| circle surrounding the mouse when i move it, to aid finding it.
| juancn wrote:
| 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)
| henrebotha wrote:
| Oh my god, immediately worth installing just for this
| overflowy wrote:
| I use this to remap app keys: 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 ...
| alexfortin wrote:
| I use it to enable/disable the wifi when I disconnec/connect the
| macbook to a specific usb hub with ethernet connection:
| 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()
| jmarcher wrote:
| That should not be required if the Ethernet adapter has a
| higher priority. That being said, you might different reason.
|
| https://support.apple.com/en-ca/guide/mac-help/mchlp2711/mac
| swiftcoder wrote:
| I always confuse "hammerspoon" and "rowhammer"
| iLemming wrote:
| So, the trivia behind the name is interesting. It all starts
| with a project called "Mjolnir" (Thor's Hammer). The original
| idea was for a lightweight automation engine with pluggable
| architecture. Someone wished to have "batteries included"
| version, the author said they're not interested in that
| direction, so the fork was born. What's meant to be a hard-
| fork, so really not a fork, but rather a "spoon", hence the
| name.
| selectnull wrote:
| Love hammerspoon. I use it to map double CMD to swap between the
| terminal and the browser.
| theshrike79 wrote:
| 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 (
| _cough_ Firefox _cough_ ) 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.
| piskov wrote:
| Moom works like a charm
| regus wrote:
| How does this compare to AppleScript?
| dbalatero wrote:
| Insanely easier to use, way better programming language, a
| kitchen sink of macOS APIs, and you can still call out to
| AppleScript when you need it:
| https://www.hammerspoon.org/docs/hs.osascript.html#applescri...
| cjbarber wrote:
| See also:
|
| KeyboardMaestro
|
| Automator and AppleScript
|
| Raycast
| ChrisArchitect wrote:
| https://www.hammerspoon.org/
|
| Some previous discussion:
|
| 2021 https://news.ycombinator.com/item?id=29533495
|
| 2019 https://news.ycombinator.com/item?id=21801178
| iLemming wrote:
| Shameless plug/proud self-promotion -
| https://github.com/agzam/spacehammer "Spacemacs|Doom inspired
| Hammerspoon modal toolkit"
|
| 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.
| jedbrooke wrote:
| > "alt+cmd m j/k" (media -> vol up/down)
|
| if only keyboards came with built in buttons for adjusting the
| volume... oh wait. Unless of course you are suffering on a
| touch bar mac, then I completely understand.
| iLemming wrote:
| It's not about "having" or "not having" keys for specific
| actions, it's all about freedom and feeling of control. When
| you take and apply the idea of modality, you quickly realize
| that you are no longer constrained with the number of
| combinations you can have or the type of keyboard you're
| using. Everything can be controlled by (mostly) using home-
| row keys - h/j/k/l - without having to memorize weird
| combinations of modifiers and keys - "was it Ctrl+Alt+Cmd F,
| or just Ctrl+Cmd F?"
|
| alt+cmd (was a typo, I meant to say alt+space), which is
| configurable - I myself prefer using cmd+space. That opens
| the "main" modal, from where you can configure "conditional
| branching" - e.g. "m" - for "media", or "a" - for "apps", so
| with "alt+space m j/k" you can do volume up/down, while
| pressing h/l could be "previous/next song". Then, "alt+spc a
| b" activates the browser, and "alt+spc a t" - could be bind
| to activate "terminal", etc.
|
| It only looks like you have to press more keys to achieve
| anything, in practice - you quickly develop muscle memory.
| Then switching between the apps, moving windows around and
| resizing them, controlling playback, etc. - it all gains
| incredible productivity without affecting the focus point.
| You don't need to keep moving your hand for the mouse, you
| don't need to memorize and deal with myriad of modifier-
| driven key combinations.
| gedy wrote:
| If you are on macOS but miss Paperwm or niri, etc this is a good
| compromise thanks to Hammerspoon:
| https://github.com/mogenson/PaperWM.spoon
| ifh-hn wrote:
| It's this like Mac equivalent to autohotkey on windows?
| dbalatero wrote:
| I have an unreleased pile of tutorial projects with Hammerspoon.
| I think not all chapters are finished, so I need to clean up and
| finish it at some point soon: https://learnhammerspoon.com
| kcrwfrd_ wrote:
| I despair at not being able to easily send a window to another
| space with a keyboard shortcut on macOS.
|
| 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...
| dbalatero wrote:
| Some stuff I've made in Hammerspoon you can use:
|
| - 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/...
___________________________________________________________________
(page generated 2026-03-13 23:00 UTC)