Make Your Least Used Key (Caps Lock) 10x More Useful
My external keyboard has a hyper key (altshiftctrlcmd) that I use for global shortcuts like launching or switching between my regularly used apps (like a faster command tab) and running some automations.
I have now configured my caps lock to behave as the hyper key while pressed or esc when tapped.
This post shows you how to configure your laptop to do the same.
Why#
You probably use Shift for capitalization, while caps-lock just sits there taking up valuable real estate on your keyboard.
Esc#
caps lock is easier to reach then esc on the Macbook Pro keyboard!
Hyper#
For instance I have the following shortcuts setup:
- No more constant Cmd+tabbing. Instead for switching between constantly used apps I have:
- caps locki -> launch/switch to IntelliJ IDEA
- caps lockt -> launch/switch to iTerm
- caps lockb -> launch/switch to browser (currently Firefox or Vivaldi)
- caps lockg -> launch/switch to Spotify (g is closer to the hyper key on my external keyboard)
- caps locks -> switch to Slack
- caps lock0 -> switch to Obsidian
- Automating tasks:
- caps lock1 … 4 -> update Slack status
- caps lock5 -> put computer to sleep
How#
Hammerspoon#
Hammerspoon is a powerful automation utility for macOS with configuration written in Lua. You don’t need to know Lua to understand the instructions in this post.
Install Hammerspoon#
First, install Hammerspoon if you don’t already have it. You can follow the “Setup” instructions outlined in their Getting Started guide.
Once installed, open the configuration file by clicking on the Hammerspoon icon in your menubar and selecting the “Open config” option. The config file is located at ~/.hammerspoon/init.lua
by default if you want to open it in another editor.
Remapping#
A common way to accomplish this is via Karabiner-Elements, but I have found that to be buggy 1, or it might not be on your organization’s allowed list of apps. Additionally, I would prefer to not have another app using my compute resources for such a minor utility.
Instead, a better and easier method is to use hidutil – a key remapping tool built into MacOS.
You can call the hidutil
command right from the Hammerspoon config by adding the following to the top of your file
-- Remap Caps Lock to escape using hidutil
local remap_output, remap_status = hs.execute [["/usr/bin/hidutil" "property" "--set" "{\"UserKeyMapping\":[{\"HIDKeyboardModifierMappingSrc\":0x700000039,\"HIDKeyboardModifierMappingDst\":0x70000006D}]}"]]
Save your configuration file and reload it in Hammerspoon by clicking on the Hammerspoon icon in your menubar and selecting the “Reload Config” option.
If you tap caps lock now, the LED indicator should no longer light up.
Configuring shortcuts with Hammerspoon#
Now let’s configure Hammerspoon to trigger caps lock as esc or hyper
.
Configure hyper mode#
First, let’s setup the “hyper” mode.
-- A global variable for the Hyper Mode
hyper = hs.hotkey.modal.new({}, nil)
-- Enter Hyper Mode when F18 (Hyper/Capslock) is pressed
function enterHyperMode()
hyper.triggered = false
hyper:enter()
end
-- Leave Hyper Mode when F18 (Hyper/Capslock) is pressed,
-- send ESCAPE if no other keys are pressed.
function exitHyperMode()
hyper:exit()
if not hyper.triggered then
-- BONUS! caps-lock acts as esc with a short tap
hs.eventtap.keyStroke({}, 'ESCAPE')
end
end
-- Bind the Hyper key
f18 = hs.hotkey.bind({}, 'F18', enterHyperMode, exitHyperMode)
This gets you setup to enter hyper mode while caps-lock is pressed and exit it when it is released.
Shortcuts#
Next, lets setup some shortcuts to switch between regularly used apps. As an example, let’s start with IntelliJ
hyper:bind({}, 'i', function()
-- trigger the hyper key
hs.eventtap.keyStroke({ "alt", "shift", "ctrl", "cmd" }, 'i')
hyper.triggered = true
end)
-- Use the hyber key combo to configure the actual actions
-- so the external keyboard can take advantage of it as well
hs.hotkey.bind({ "alt", "shift", "ctrl", "cmd" }, "i", function()
hs.application.launchOrFocus("IntelliJ IDEA Ultimate")
end)
The first line in this code block defines what to do when i
is pressed while hyper
mode is active. Here we tell Hammerspoon to simulate tapping the hyper key 2 along with i
.
The next block of code tells Hammerspoon to launch or focus to IntelliJ when it detects the hyper key combo is pressed with i
. I am not doing this in the previous block of code because I use another programmable keyboard with a standalone hyper key – and I want this configuration to work with that keyboard.
You can replace this block with a simpler loop to setup these shortcuts for multiple apps like so
-- Shortcuts to launch or switch focus to specific apps
keyApps = {
['i'] = 'Intellij IDEA Ultimate',
['b'] = 'Firefox', -- replace with your favorite browser
['t'] = 'iTerm',
['m'] = 'Telegram',
['s'] = 'Slack',
['g'] = 'Spotify',
['o'] = 'Obsidian'
}
for key, app in pairs(keyApps) do
-- First, bind the `key` when pressed in `hyper` mode to simulate tapping the hyper key
hyper:bind({}, key, function()
hs.eventtap.keyStroke({ "alt", "shift", "ctrl", "cmd" }, key)
hyper.triggered = true
end)
-- Then setup the app to launch or focus when configured key is pressed along with hyper key
-- This is useful if your programmable keyboard has a standalone hyper key
hs.hotkey.bind({ "alt", "shift", "ctrl", "cmd" }, key, function ()
hs.application.launchOrFocus(app)
end)
end
The keyApps
table contains mappings for keys to their respective apps. We then loop over these mappings to setup appropriate bindings like before.
Completing the configuration#
At the end your Hammerspoon file should look like this
-- Remap Caps Lock to escape using hidutil
local remap_output, remap_status = hs.execute [["/usr/bin/hidutil" "property" "--set" "{\"UserKeyMapping\":[{\"HIDKeyboardModifierMappingSrc\":0x700000039,\"HIDKeyboardModifierMappingDst\":0x70000006D}]}"]]
-- A global variable for the Hyper Mode
hyper = hs.hotkey.modal.new({}, nil)
-- Enter Hyper Mode when F18 (Hyper/Capslock) is pressed
function enterHyperMode()
hyper.triggered = false
hyper:enter()
end
-- Leave Hyper Mode when F18 (Hyper/Capslock) is pressed,
-- send ESCAPE if no other keys are pressed.
function exitHyperMode()
hyper:exit()
if not hyper.triggered then
-- BONUS! caps-lock acts as esc with a short tap
hs.eventtap.keyStroke({}, 'ESCAPE')
end
end
-- Bind the Hyper key
f18 = hs.hotkey.bind({}, 'F18', enterHyperMode, exitHyperMode)
-- Shortcuts to launch or switch focus to specific apps
keyApps = {
['i'] = 'Intellij IDEA Ultimate',
['b'] = 'Firefox', -- replace with your favorite browser
['t'] = 'iTerm',
['m'] = 'Telegram',
['s'] = 'Slack',
['g'] = 'Spotify',
['o'] = 'Obsidian'
}
for key, app in pairs(keyApps) do
-- First, bind the `key` when pressed in `hyper` mode to simulate tapping the hyper key
hyper:bind({}, key, function()
hs.eventtap.keyStroke({ "alt", "shift", "ctrl", "cmd" }, key)
hyper.triggered = true
end)
-- Then setup the app to launch or focus when configured key is pressed along with hyper key
-- This is useful if your programmable keyboard has a standalone hyper key
hs.hotkey.bind({ "alt", "shift", "ctrl", "cmd" }, key, function ()
hs.application.launchOrFocus(app)
end)
end
Save this file and then reload your configuration from Hammerspoon’s menu bar icon. You should be able to launch or switch apps when you hit your configured keys while holding down caps lock. Just tapping caps lock will trigger esc.
I haven’t covered the use case of automating tasks here – I plan to do that in a future post.
After waking the computer from sleep, my keyboard is completely unresponsive until I restart Karabiner-Elements – regardless of how I setup my computer. ↩︎
Mentioned earlier in the post the hyper key is a combination of “alt”, “shift”, “ctrl”, “cmd”. This can be used for global shortcuts because no other application is likely to shortcuts that use all the modifier keys. ↩︎