Level up keyboard shortcuts - Part 2. Hammerspoon and the Home Row
After my initial experiments using Karabiner to bind a keyboard shortcut to an image-based cheatsheet and leverage a hyper key to avoid application level key binding conflicts and finger twister, I was super excited. I had combed through many blogs and git repositories.
Having an extra meta key with a whole slew of nonconflicting keybinding slots seemed awesome. I like keyboard shortcuts bound to keys based on a pnemonic, so binding my global spectacle keyboard shortcut cheatsheet to hyper+k ['k' for keys] seemed like a good, quick win that would help me memorize those Spectacle shortcuts.
Incremental and Safe . . . time to test it out! 👍
The Home Row
Then, mid-way through patting myself on the back, I discovered global vi mode with a hyper key and Karabiner. With this Karabiner recipe enabled, you press a hyper key and then h/j/k/l to move the cursor left/down/up/right. Additionally, tab can be rigged up to act as a modifier in conjunction with the hyper key to enable quick access to home/end/pgdn/pgup – which is especially awesome when you are on a laptop keyboard.
In order to to try out global vi mode w/Karabiner, I had to sacrifice hyper+k from the Spectacle cheatsheet and switch that to be right-option+k.
Hey, another key I never use. right-option. hyper2?
Fast forward a bit. I dug deeper and deeper into the Hammerspoon ecosystem, looked at more custom Karabiner and Hammerspoon settings, and heard some feedback from my first post.
Hammerspoon is an application you run in the background that loads custom Lua scripts to interact with your system, allowing you to script behaviors to react to system events. One kind of system event is a key press. For our purposes here, we're exploring Hammerspoon primarily in the context of using it to react to keyboard shortcuts and trigger something in response. It has many other uses though. Keep in mind we're talking about system-wide key bindings here – not something isolated to a single application. 🤯
A unit of re-usable scripting in Hammerspoon is called a 'spoon'.
After trying out many different spoons and customizations, two projects really caught my eye:
- FryJay's MenuHammer - "A Spacemacs inspired menu system". A quick, customizable, nestable, menu system you can access via a system-wide keyboard shortcut.
- Jason Rudolph's Karabiner+Hammerspoon setup which focuses on home-row centric shortcuts for cursor navigation and window managment (ht: @gregvaughn)
Thinking about these in terms of the capabilities they enable relative to the caps-lock hyper key and Spectacle cheat sheet via a shortcut that we put together in the previous post, it seems there is a lot of potential.
Let's explore.
Key Chords
Hammerspoon can enable key chords*.
Karabiner can enable key chords too, but I found the nuances of getting it implemented well in Karabiner, for alpha keys, with the current version, to be painful. **
Wait wait . . . What is a key chord you say?
So, the aforementioned JR's setup has a 'super duper' behavior where pressing the 's' and 'd' keys at the same time acts almost like another new modifier key. waaaaaaaat? That new modifier is then leveraged for some home row centric key bindings that can substitute for moving your right hand or stretching the right pinky down to the arrow keys.
Here's how it works: while 's' and 'd' are pressed and held with the left hand, you use the right hand to press h to move the cursor left. 'h' for left, 'j' for down, 'k' for up, 'l' for right. You may recognize these commonly used vi cursor movement keys. With hand in place, pressing down the 's' and 'd', you can press 'a' to add the option/alt modifier, 'f' to add cmd modifier, or spacebar to add shift modifier. Those keys are all lying right under the fingertips of where the left hand is already positioned, minimizing the needed movement.
I thought it would be nice to form the muscle memory for those h/j/k/l vi cursor movement keys as a side benefit for when I might occasionally run vi on a remote server. Mostly though, I'm thinking about not having to pinky stretch down to those tiny cursor keys or reposition the whole right hand each time I want to navigate the cursor from a laptop keyboard.
What really pushed me over the line of I have to try this now was seeing those same home-row keys used for cursor movement -and- window positioning in a consistent way.
Modes and Menus
Where key chords are generally more temporary states, enabling a behavior only if all the right keys are pressed in close proximity spatially and temporally, a 'mode' let's you toggle into a state where keys react differently until it is dismissed. Menus are similar and dismiss automatically upon the selection of an action.
So in the context of JR's setup, when you hit control-S, you enable window layout mode. Then, you press one of the h/j/k/l/i/o/,/. keys while focused on a particular window, and it will resize&move the window to left/down/up/right/top-left/top-right/bottom-left/bottom-right. This re-uses some familar home row mappings (i.e. - 'h' = left whether it is for cursor movement or shifting a window to the left side of the screen). A few other window resize/move keys are added (i/o/,/.) in a place that makes sense spatially for shifting windows to corners. For example, out of the i/o/,/. keys, the 'i' is the top left key on the keyboard and it arranges the window to the top left position. After you make a selection the window layout mode dismisses itself.
There is a 'showHelp' flag built in to the lua script that drives window layout mode. When you set that flag to true, a built in cheatsheet displays when in window layout mode. Note: I re-bound this mode to hyper-W to preserve ctrl-S for application-level bindings.
It's worth noting that since I have had this working, I've been using Spectacle less and less. At the moment, it does not have feature parity with Spectacle, but it does seem possible to get there. 🤔
Another variation of the idea of a 'mode' is a shortcut driven menu. Once activated, you get a list of options, along with the keys to quickly navigate and select from those options.
This is where the Menuhammer project comes in. Menuhammer allows you to set up a totally customizable menu you can access system-wide via shortcut. From that menu, you can trigger any action hammerspoon can initiate – launching applications, macros, running scripts, laying out multiple windows, etc.
It looks like a great place to house things that are launched less frequently like occasional use macros, or toggling Wi-fi on and off. I'm thinking this a good landing spot for things which either (1) are not used frequently enough to give up an immediate action keybinding slot or (2) are not used frequently enough that it is worth memorizing. I bound it to hyper-space to try it out.
After using it for a while, I found it useful to bind the application submenu to Hyper-A and the Finder submenu to Hyper-F to jump directly to those as needed.
Note the really cool feature of those hyper-F menu items: Several of them launch Finder -and- send specific key to it, immediately selecting the 'Download's directory for instance.
I'm still experimenting with what feels most comfortable for applications I use regularly vs less often . . . especially in regards to putting applications on the shortlist in MenuHammer vs binding a key to launch them directly via hammerspoon.
Hyper key -and- Super Duper key chord?
Given what we did in part 1 of this series, where caps lock became a hyper key, with tab acting as a modifier when held, how does how does super duper mode compare to that and can these two worlds live together? If not, which one is more comfortable and effective?
With very few changes, I was able to fully enable Super Duper mode, and keep in place many of the caps-lock based hyper key things I already had. This is for both cursor movement and window management (Spectacle vs Hammerspoon scripted). This allowed me to compare the comfort level of each to see which one feels right. After using super duper mode for a bit, I'm finding it a lot more comfortable, and have swapped out caps lock to be a ctrl key on hold instead of being my hyper key which maps to ctrl-alt-shift-cmd. I have moved that hyper key down to the right-command key position, which I'm finding convenient.
I'm also experimenting with an 'a'h 'f'udge 😜 mode which maps home/pgdn/pgup/end keys to h/j/k/l when the a+f keychord is held. As always, all my curent setup can be found in my dotfiles repo.
* This is what I like to call them at the moment.
** Karabiners key chord pains: dupe keys, missed key presses, missed dropping and enabling of chord state, tried many solutions including stuff currently marked as 'working' in Karabiner recipes. All had problems with either messing up my normal typing of words or not working consistently enough.