Initial tips
Learn some quick tips to make development easier down the line.
Next ChapterBefore you dive into your first few projects, you’re going to get some initial tweaks out of the way that will make your life easier. Do each of these sections before you proceed to the next chapter. It should only take you 10-15 minutes to get it all out of the way, and you’ll be in a much better place to tackle the next chapters.
Modifier key symbols #
Throughout this course, I’ll use these symbols to represent these modifier keys:
Key | Symbol |
---|---|
Command (cmd) | ⌘ |
Control (ctrl) | ⌃ |
Alt/option (alt) | ⌥ |
Shift (shift) | ⇧ |
Hopefully these are already familiar, but if they aren’t, just come back here and reference this table as needed.
Add a super and hyper key #
A big part of Hammerspoon scripts is binding hotkeys. The hardest thing about binding hotkeys is finding a set of keys that won’t conflict with the default hotkeys in the applications you run. For example, you wouldn’t want to bind a key to ⌘Q, because that’s the standard key to Quit a program.
What we need is a modifier key that is convenient to press, but also won’t conflict with other key binds. Enter the super and hyper keys, which combine multiple modifiers under a single keypress:
Key | Modifiers when pressed |
---|---|
Super | ⌘⌥⌃ (cmd alt ctrl ) |
Hyper | ⌘⇧⌥⌃ (cmd shift alt ctrl ) |
Because there are so many modifiers being activated at once when you hold down either of these keys, there’s a good chance that any hotkeys you bind to either of these modifiers will not conflict with any running applications.
There’s a couple of ways to create these special “mega-modifier” keys. If you have a fancy keyboard with QMK firmware, you can skip down to the QMK section. If you just have a standard keyboard, use Karabiner Elements to create your modifiers.
I set up my super
and hyper
keys like so:
Physical key used | When key is held | When key is tapped |
---|---|---|
End | Acts as super (⌘⌥⌃) |
Types an End key |
Semicolon (; ) |
Acts as hyper (⌘⇧⌥⌃) |
Types a semicolon |
By having separate functions when the key is held and tapped, we can retain the original flavor of the key while giving it special powers when held down.
Creating the super and hyper key with Karabiner Elements #
Karabiner Elements is a powerful app for macOS that lets you extend your computer keyboard. Among other things, you can rebind your keyboard keys to whatever you want.
I’ll show you how to setup two keys:
- When Caps Lock is held, it will be
super
. When tapped, it will beesc
. - When semicolon (
;
) is held, it will behyper
. When tapped, it will be semicolon (;
).
To set this up:
- Download and install Karabiner Elements.
- Create a config file:

mkdir -p ~/.config/karabiner
touch ~/.config/karabiner/karabiner.json
Next, open the config file you just made and add this snippet:

{
"profiles": [
{
"complex_modifications": {
"parameters": {
"basic.simultaneous_threshold_milliseconds": 10,
"basic.to_delayed_action_delay_milliseconds": 500,
"basic.to_if_alone_timeout_milliseconds": 1000,
"basic.to_if_held_down_threshold_milliseconds": 500,
"mouse_motion_to_scroll.speed": 100
},
"rules": [
{
"description": "Change ; to hyper. (; if left alone)",
"manipulators": [
{
"from": {
"key_code": "semicolon"
},
"to": [
{
"key_code": "left_shift",
"modifiers": [
"left_command",
"left_control",
"left_option"
]
}
],
"to_if_alone": [
{
"key_code": "semicolon"
}
],
"type": "basic"
}
]
},
{
"description": "Change 'caps lock' to super. ('esc' if left alone)",
"manipulators": [
{
"from": {
"key_code": "caps_lock"
},
"to": [
{
"key_code": "left_control",
"modifiers": [
"left_command",
"left_option"
]
}
],
"to_if_alone": [
{
"key_code": "escape"
}
],
"type": "basic"
}
]
}
]
}
}
]
}
Finally, open Karabiner Elements from the /Applications
directory. You should now have a super
and hyper
key set up. You can now jump to the Testing section below.
Creating the super and hyper key with QMK #
If you have a fancy mechanical keyboard that uses the QMK firmware, you’re in luck! This firmware has first class support for super and hyper.
I do the following in my keymap.c
file:

// Create a hyper key at `HYPER_SEMI` that is hyper when held, and semicolon
// when tapped.
#define HYPER_SEMI MT(MOD_HYPR, KC_SCOLON)
// Create a custom keyboard for super
enum custom_keycodes = {
MOD_SUPER
};
bool handle_mod_super_key(keyrecord_t *record);
bool handle_mod_super_key(keyrecord_t *record) {
if (record->event.pressed) {
register_code(KC_LCTL);
register_code(KC_LGUI);
register_code(KC_LALT);
} else {
unregister_code(KC_LCTL);
unregister_code(KC_LGUI);
unregister_code(KC_LALT);
}
return false;
}
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case MOD_SUPER:
return handle_mod_super_key(record);
}
// Passthru all other keys
return true;
}
Then, when I go to bind my keys at the bottom of the QMK file, I can bind my desired keys to HYPER_SEMI
and MOD_SUPER
:

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
KC_Q, KC_W, KC_E, KC_R, KC_T, // ...
// You'll choose which key you want to bind.
MOD_SUPER, HYPER_SEMI,
}
Testing your super & hyper key #
Let’s add a small snippet to your Hammerspoon config to test out your new keys. Open up ~/.hammerspoon/init.lua
and add:

-- Make sure these variables are always at the _top_ of your init.lua file,
-- before you require() any extra files. Otherwise you might end up with other
-- config files trying to reference them before they are set.
super = {'cmd', 'ctrl', 'alt'}
hyper = {'cmd', 'ctrl', 'alt', 'shift'}
hs.hotkey.bind(super, 'h', function()
hs.alert.show('Hello from super!')
end)
hs.hotkey.bind(hyper, 'h', function()
hs.alert.show('Hello from hyper!')
end)
Save the file, and reload Hammerspoon.
- Try pressing
super + h
. You should see an alert that says Hello from super! - Try pressing
hyper + h
. You should see an alert that says Hello from hyper!
Add a reload key #
When developing in Hammerspoon, you often need to reload your config. One of the first things I do is bind this to a hotkey to make it really fast to reload my code. Personally, I like to bind this to hyper + h
:

hs.hotkey.bind(hyper, 'h', function()
hs.application.launchOrFocus("Hammerspoon")
hs.reload()
end)
Try it out by tapping hyper + h
. You should see the Hammerspoon console pop up with some log messages indicating a successful reload.
Add a hot config reloader #
Instead of always manually reloading your Hammerspoon config every time you make a change, you can add a file watcher that will detect when you save a Lua file, and reload it for you. We can achieve this with the hs.pathwatcher API, which lets you define a function to be called whenever a file changes inside a directory:

configWatcher = hs.pathwatcher.new(os.getenv("HOME") .. "/.hammerspoon/", hs.reload)
configWatcher:start()
Add a simple debug printer #
Sometimes you need to print some values to the console to test out your code. I like to have a little p()
function that will stringify any variable and dump it to the Hammerspoon console. Add this to your config file and reload Hammerspoon:

function p(variable)
-- hs.inspect.inspect() will print a human-readable version of any variable
-- you throw at it.
print(hs.inspect.inspect(variable))
end
Now, give it a try by pasting this in the Hammerspoon console:

p("Hello")
p(42)
p({ complex = true, table = true })
You should see the following output:
2021-08-19 01:29:07: "Hello"
2021-08-19 01:29:07: 42
2021-08-19 01:29:07: {
complex = true,
table = true
}