Over-customising with Hammerspoon
Hammerspoon is a free, open-source program that gives you deep access to macOS APIs via Lua scripting. In the Before Times, the barrier to entry would be your ability/willingness to write Lua, but now we have Claude Code as a non-deterministic compiler that turns English into functioning code (h/t yomismoaqui) so a quick read of the docs is sufficient to do whatever you'd like, even if you are only semi-technical like me.
It was incredibly satisfying to replace a few 3rd party dependencies that I kept around for a tiny subset of their features, as well as to go crazy and create some bespoke solutions to very specific problems. You can check out the config file and/or read the longer explanation below.
Hyper key
This is the foundation for all of the rest of the shortcuts – it is a key that you remap to a key or modifier combination unused by the system, usually Ctrl-Cmd-Opt-Shift. This ensures that your new shortcuts will never collide with combinations used by the system or other apps, allowing you to create keybindings for various things with abandon.
In the past I used Karabiner-Elements for this. It uses a driver extension to intercept keystrokes at a very low level and passes them through a virtual keyboard, which is where your remapped keys lie. It works, but it's prone to breakage with macOS updates and is generally a bit janky – it doesn't play nice with the British ISO keyboard layout, does not uninstall cleanly, and has the occasional memory leak. It's also by far the most under-utilised program on my computer. I'd prefer something more stable with a smaller scope, given that I'm just remapping a couple of keys.
Now, I use macOS' native command-line remapping tool, hidutil, which has virtually zero footprint and is very reliable, but unfortunately quite obscure – to persist your remapping across reboots, you need to create a launchd plist. Someone saintly has made a webtool to easily create remapping scripts, which obviates this issue. As this tool only allows one-to-one key swaps, I remap both Caps Lock and Right Command to F19 as I use neither for their regular function but use Hyper a lot, so it's nice to have opposable chords for shortcuts instead of having to contort my hand. This is one advantage of Hammerspoon: F19 is not a true motifier key, but hs.hotkey.modal allows you to pass any key as a modifier for commands within Hammerspoon.
Another advantage of having Hyper be a discrete key instead of a combo is that it unlocks some bonus ergonomic shortcuts when used with true modifiers, e.g. I have Cmd-F19 set up to open my clipboard history in Alfred and Shift-F19 to open Alfred Universal Actions.
App toggling
I find it irritating to wrangle with Command-Tab or the trackpad to switch between programs when I'm in flow. You either have to move away from the keyboard or tab your way through a long list of apps on the screen using visual search.
To solve this, I configured Hyper-[letter] hotkeys (e.g. Hyper-B launches my browser) to:
- Launch an app if it is quit
- Focus it if it is hidden or buried under other windows
- Hide it if it is currently visible and active
This means I don't really need to do 'window management'. I can just call up and dismiss apps as and when I need them with near-zero switching cost.
Hammerspoon can launch and toggle apps by app name, path, or bundle ID. I took the bundle ID route, as it seemed the most robust. You can find bundle IDs by running osascript -e 'id of app "AppName"'.
Sometimes I want to reassign these shortcuts on the fly. For instance, I switch LLM providers and browsers often. Dealing with bundle IDs and editing the config file manually can be annoying in these instances, so I configured Hyper-` to enter an assign mode where I can associate a Hyper-[letter] key-bind with the focused app.
Window management
There are tons of apps that handle window management, but my needs are very minimal: I only ever use windows maximised, in a 50/50 split, or a two-thirds/one-third split. Hammerspoon handles all of these very well, and rolling my own window manager let me set up some additional luxuries:
Hyper-Hsends the current window to next screen and warps the cursorHyper-Jtiles left half (left two-thirds on repetition)Hyper-KmaximisesHyper-Ltiles right half (right one-third on repetition)Hyper-;maximises all open windows, helpful when connecting/disconnecting from external monitors leaves windows in disarrayHyper-Uresizes all windows and changes tiling bounds to leave a 400px gutter on the right side of the screen, in case there is a small window I want to see persistently while I switch between other apps, such as a video call
This integrates nicely with the app toggling shortcuts, as it means I can launch an app and tile it with the same modifier held down.
Toggling Bluetooth devices
It was at this point that, drunk on power, I unleashed my true excesses. My Sony headphones have unreliable multipoint, so I turn it off and manually connect to them from my phone and laptop. For some reason, the 3 seconds it takes me to interact with the bluetooth menu bar icon were incredibly grating, so I configured Hyper-1 to instantly pair/unpair the headphones by hooking into the blueutil CLI.
Blocks of work
And now, absurdity.
I am a big fan of David Cain's How To Do Things, which proposes a simplified Pomodoro system that optimises for your psychology instead of giga-productivity. You work in sets of uninterrupted 25-minute sessions he calls "blocks", each with a pre-meditated intent/finish line. The emphasis is on uninterrupted: any distraction or deviation invalidates the block. There is no system for breaks - they are ad-hoc and you don't need to always take them. I make no claims about the general efficacy of this way of working but it suits me well.
When I'm at home I just use a physical timer for this, but I don't particularly want to carry one around with me. A digital solution has to be dead simple: an unobtrusive but always-visible 25 minute timer with no option to pause, only to reset.
To solve this, I configured Hyper-2 to spawn a 25 minute countdown timer which displays in a translucent hs.canvas on the top-right of my screen. Repeating the shortcut while the timer is running just cancels it and displays the message 'Womp Womp: timer cancelled', which is sufficiently mild negative reinforcement for bailing. All I need to make some good blocks.
And nothing ‘gainst Time’s scythe can make defence
Displays a pie chart in the menu bar that shows how many hours are left in the day. A kind of diurnal memento mori.