Photo by Fernando Arcos from Pexels

Solve Shortcut Hell in MacOS — Building a Hyper Key

Jan-Hendrik Kuperus
Mac O’Clock
Published in
5 min readApr 21, 2020

--

If you like shortcuts and try to use them in every application, you will run into it eventually: Shortcut Hell. Two (or more) applications wanting to use the same shortcut and no way to change them.

Well, what if I told you there’s a way to create a completely new layer on your keyboard? What if I told you this way no application will ever be able to steal your shortcuts? All you need to sacrifice is one tiny button on your keyboard. Best of all: you get to choose which button you sacrifice.

Introducing: the Hyper key!

The Hyper key is a custom sibling of Shift, Option, Control and Command. It acts as a new modifier to all the other keys on your keyboard. Hammerspoon enables this behaviour by defining a completely new “modal” key, over which you have complete control. While it is most natural to enable and disable this mode in tandem with a key-press and -release, you can actually enable it whenever you like.

Hammerspoon does require a little help to get this done. That help comes from Karabiner, a tool for reassigning keys on your keyboards. Karabiner can do a whole lot more, but right now this is all I use it for (and for remapping Windows-keyboards to proper Mac-layout 😇).

Setting up Karabiner

You can begin by downloading Karabiner-Elements from its website. Once installed, start it and make sure it starts when you login. Then, you can start remapping keys in the Simple modifications tab, view your currently connected devices in the Devices tab (and later also disconnected devices, since Karabiner remembers them).

To start setting up the Hyper key, create a new item on the Simple modifications tab and remap the right_option to f18 for all devices. Of course this is where you can choose which key should be your Hyper key.

Remapping a key to F18 for the Hyper key

Why F18 you ask? Well, it’s a key that’s (probably) not on your keyboard and yet MacOS supports function keys up to 20. That means anything above F12 is free to use for these things. F18 is just the value that was used in an example I once found, which led to this setup. It kinda stuck.

Setting up Hammerspoon

Hammerspoon is an interface between a Lua scripting engine and various MacOS APIs. For more information on its capabilities and ways to install it, please see my previous post about it.

If you are not familiar with Hammerspoon and/or Lua, I suggest you put the coming code snippets in a file ~/.hammerspoon/hyper.lua and import it from a file ~/.hammerspoon/init.lua. This will allow you to follow the post below exactly and keep your init.lua clean so you can more easily add other features to it later.

Now that right_option is causing keypress/-release events for f18, we can use Hammerspoon to tap into the MacOS keyboard event system and create our own layer of keys.

The feature we will use for this is called hs.hotkey.modal. It allows us to create a keyboard mode, which we can programmatically turn on and off. To turn it on and off, we use the events from the f18 key we remapped in Karabiner.

How Hammerspoon’s modal works to create a new keyboard modifier state

Let’s see what this would look like in the Lua script (put it in ~/.hammerspoon/hyper.lua (or if you’re impatient, download it here):

local This = {}-- Hyper mode needs to be bound to a key. Here we are binding
-- it to F17, because this is yet another key that's unused.
-- Why not F18? We will use key-events from F18 to turn hyper
-- mode on and off. Using F17 as both the modal and source of key
-- events will result in a very jittery Hyper mode.
This.hyperMode = hs.hotkey.modal.new({}, 'F17')-- Enter Hyper Mode when Hyper-key is pressed
function enterHyperMode()
This.hyperMode:enter()
end
-- Leave Hyper Mode when Hyper-key is pressed
function exitHyperMode()
This.hyperMode:exit()
end

This is the base of the Hyper mode. The new modal that is created, which can be turned on and off using the enterHyperMode and exitHyperMode functions. Now we still need to bind to some keyboard events to actually enable the Hyper key.

-- Binds the enter/exit functions of the Hyper modal to all
-- combinations of modifiers
function This.install(hotKey)
hs.hotkey.bind({}, hotKey, enterHyperMode, exitHyperMode)
hs.hotkey.bind({"shift"}, hotKey, enterHyperMode, exitHyperMode)
hs.hotkey.bind({"ctrl"}, hotKey, enterHyperMode, exitHyperMode)
hs.hotkey.bind({"ctrl", "shift"}, hotKey, enterHyperMode,
exitHyperMode)
hs.hotkey.bind({"cmd"}, hotKey, enterHyperMode, exitHyperMode)
hs.hotkey.bind({"cmd", "shift"}, hotKey, enterHyperMode,
exitHyperMode)
hs.hotkey.bind({"cmd", "ctrl"}, hotKey, enterHyperMode,
exitHyperMode)
hs.hotkey.bind({"cmd", "ctrl", "shift"}, hotKey, enterHyperMode,
exitHyperMode)
hs.hotkey.bind({"alt"}, hotKey, enterHyperMode, exitHyperMode)
hs.hotkey.bind({"alt", "shift"}, hotKey, enterHyperMode,
exitHyperMode)
hs.hotkey.bind({"alt", "ctrl"}, hotKey, enterHyperMode,
exitHyperMode)
hs.hotkey.bind({"alt", "ctrl", "shift"}, hotKey, enterHyperMode,
exitHyperMode)
hs.hotkey.bind({"alt", "cmd"}, hotKey, enterHyperMode,
exitHyperMode)
hs.hotkey.bind({"alt", "cmd", "shift"}, hotKey, enterHyperMode,
exitHyperMode)
hs.hotkey.bind({"alt", "cmd", "ctrl"}, hotKey, enterHyperMode,
exitHyperMode)
hs.hotkey.bind({"alt", "cmd", "shift", "ctrl"}, hotKey,
enterHyperMode, exitHyperMode)
end
return This

The install function will bind to all of the events of the key passed in through the parameterhotkey. It binds both a key press and a key release function, to turn the Hyper-mode on and off.

Now we have the basics and we can use this to bind arbitrary Lua-functions to keys combined with our Hyper-key. Here is an example init.lua file which imports the Hyper mode and binds a few keys:

-- Load and install the Hyper key extension. Binding to F18
local hyper = require('hyper')
hyper.install('F18')
-- Quick Reloading of Hammerspoon
hyper.bindKey('r', hs.reload)
-- Global Application Keyboard Shortcuts
hyper.bindKey('p', function()
am.switchToAndFromApp("com.spotify.client")
end)
hyper.bindShiftKey('p', function()
hs.spotify.displayCurrentTrack()
end)
hyper.bindKey(']', function()
am.switchToAndFromApp("com.googlecode.iterm2")
end)

These are just a few examples of what you can do. The am.switchToAndFromApp is part of another extension, which I will be sharing shortly in a follow-up post. If you’re ready to get to know Hammerspoon, browse the docs and find something fun to do.

Congratulations, you have unlocked your very own Hyper-key. Thanks for reading and please come back again soon for some inspiration to use it (for serious stuff and fun 😇).

— JH

--

--

Jan-Hendrik Kuperus
Mac O’Clock

Hi! I’m the Founder and Director of Yoink. I love writing code, tweaking it, beautifying it. I'm an all-round coder and a Professional Amateur Baker 😁🎂