DEV Community

Cover image for Better shortcuts with Karabiner Elements and Hammerspoon
ccedacero(Cristian Cedacero)
ccedacero(Cristian Cedacero)

Posted on

Better shortcuts with Karabiner Elements and Hammerspoon

Do you ever find yourself wishing you could open your favorite mac os applications with a keyboard shortcut? Tired of pressing 3 to 4 keys to manage windows? Need to easily move a window to another monitor? Well, stop wishing because I’m gonna teach you how you can make this simple.

As a keyboard shortcut lover, I’ve always enjoyed sharing my favorite shortcuts, and I’m always on the lookout for new ones. When I learned about the Karabiner Elements and Hammerspoon combo, I knew I needed to set it up, but my initial set up process ended up taking much longer than a simple installation, so I decided to outline the steps here in case this helps anyone.

Karabiner Elements

Karabiner Elements allows you to customize normal key behavior, and it’s complex modifications allow you to map multiple key combinations into one key. We will be taking advantage of this ability to change the behavior of the Caps Lock key.

Hyper Key

We’re going to be mapping the Caps Lock to command+control+option+shift. This type of key combination is usually referred to as the “hyper key”. We use this four key combination because it is unlikely to interfere with existing shortcuts. Most developerers wouldn’t expect you to press five keys to run a shortcut. We're mapping it to the Caps Lock key because it’s use is limited as you can achieve the same functionality by holding down the shift key. You can also customizer it to another key after trying this out. If you quit Karabiner elements, your Caps Lock key will return to it's normal state.

We can install Karabiner elements by running:

brew cask install karabiner-elements

Other installation notes

You will be prompted to allow apps by the developer, and after installation is complete, you will be prompted for input monitoring permission. This can be allowed in Security and Privacy. Make sure to allow karabiner_grabber and karabiner_observer.
Alt Text
For detailed installation instructions, you can see the installation docs here

Hammerspoon

Hammerspoon is a desktop automation tool for mac os and it job is to serve as the intermediary between the operating system and a Lua scripting engine, allowing you to interact with multiple mac os APIs. If you just install it, hammerspoon will do nothing,but what makes it so powerful is that you can write your own lua scripts or use one that has been written by the great open source community.The main reason I started using it is because I wanted a simple way to resize windows and move them between multiple monitors. Most shortcut apps require you to press 3 or 4 keys each to resize windows and move between monitors. With the combination of hammerspoon and karabiner, we can do it with two simple keys. You can install hammerspoon with.

brew cask install Hammerspoon

The Set Up

Step1. Open Karabiner element and under complex modifications, select the option to import more from the internet. You’ll be redirected to the karabiner complex modifications page, where you can choose from a long list of combinations. Download the Change Caps Lock key(rev4) option.

Step2. Once downloaded, enable the Change Caps Lock to command+control+option+shift complex modification.

Step3. Verify that it’s working properly by opening Karabiner-Event-Viewer. When you press Caps Lock, you should see your four key combination instead of the Caps Lock key.

Step4. Open Hammerspoon and right click on the icon to open the configuration file. This is a lua script where you can write your own scripts or use it to connect to one of the many open source configurations available here.

If you’d like to write your own, you can see the documentation and start with your hello world here.

Window Management

For my window management, I’m using “MiroWindowsManager” which can download from it's repo here. Instructions for installing directly from the repo are below.

  1. Extract the zip file, containing MiroWindowsManager.spoon in ~/.hammerspoon/Spoons
  2. Now you need to configure Hammerspoon in order to load this spoon in ~/.hammerspoon/Spoons/MiroWindowsManager.spoon adding the following snippet of code in your init.lua file:

local hyper = {"ctrl", "alt", "cmd"}
hs.loadSpoon("MiroWindowsManager")
hs.window.animationDuration = 0.3
spoon.MiroWindowsManager:bindHotkeys({
up = {hyper, "up"},
right = {hyper, "right"},
down = {hyper, "down"},
left = {hyper, "left"},
fullscreen = {hyper, "f"}
})

For our set up, you can change the local hyper key variable to our hyper combination:

local hyper = {"cmd", "alt", "ctrl","shift"}

You can also adjust the animation duration time up or down as you'd like. Reload your configuration file on hammerspoon and you should now be able to use your Capslock key and an arrow key to move your window to your desired location on your current desktop. Yay!

Multiple Monitors

For easy navigating between multiple monitors, you can use the following code written by Karsten S on stack overflow.
function moveWindowToDisplay(d)
return function()
local displays = hs.screen.allScreens()
local win = hs.window.focusedWindow()
win:moveToScreen(displays[d], false, true)
end
end
hs.hotkey.bind({"ctrl", "alt", "cmd"}, "1", moveWindowToDisplay(1))
hs.hotkey.bind({"ctrl", "alt", "cmd"}, "2", moveWindowToDisplay(2))
hs.hotkey.bind({"ctrl", "alt", "cmd"}, "3", moveWindowToDisplay(3))

Make sure you replace the key combination with our hyper key combination.You could also place our hyper key definition at the top of the lua file so that you can just call hyper in all other functions instead of writing the key combination.

Hyper key definition

Less repetition

hs.hotkey.bind(hyper, "1", moveWindowToDisplay(1))


Now, to switch between multiple monitors, all you have to do is use Caps Lock 1, 2, 3 respectively. I don’t see why this can’t work for more monitors if you edit accordingly.

Opening apps

To open any app with your own shortcut, you can add the function below to your lua file. As you can tell, the function checks to see if the app is open and being displayed. If it is, it hides the app. If it's not being displayed, but it's open, it navigates the app. If it's neither open nor hidden, it opens the app. Not sure who the original author is, but thank you whomever you are!

-- open hide app
function openApp(name)
local app = hs.application.get(name)
log.i("open App")
log.i(name)
log.i(app)
if app then
if app:isFrontmost() then
log.i("app hiding")
app:hide()
else
log.i("app focusing ")
app:mainWindow():focus()
end
else
log.i("app launch ")
hs.application.launchOrFocus(name)
end
end

After you have this function loaded, you can use it to create your own shortcuts to open any app. Some of personal favorites are below:

-- VSC
function vscode()
openApp("Visual Studio Code")
end
hs.hotkey.bind(hyper, 'a', vscode)

-- Chrome
function chrome()
openApp("Google Chrome")
end
hs.hotkey.bind(hyper, 'c', chrome)

-- Evernote
function evernote()
openApp("Evernote")
end
hs.hotkey.bind(hyper, 'e', evernote)

I know that this a lot of work compared to just being able to download a shortcut app for window management, but having the ability to simplify and define your own keyboard shortcuts is amazing! As developers, we're alway dealing with window management and multiple monitors. And think! This is just the beginning. The possibilities with hammerspoon are endless. If You invest a few days to pick up lua scripting, you’ll be able to write funtions to interact with your system directly! You could be the master of your mac domain.

Troubleshooting

If you’re unable to download Karabiner Elements complex modifications, ensure folder permissions are set to write under the karabiner complex modifications folder which is located at ~/.config/karabiner/assets/complex_modifications/. If the folder does not exist, create it, ensure permissions, and try again.

If you’re able to download and enable complex modifications, but your keystrokes are not registering,and you'd rather not spend time throubleshooting, you can use an alternative workaround by downloading the .json file located below and placing it at ~/.config/karabiner/ under the name karabiner.json.

This will load the hyper key complex modification of command+control+option+shift as default in Karabiner elements. The code is nearly identical to the original config file from karabiner, and you can edit the file to add other combinations from the karabiner website.

If you add more combinations,take note that this file automatically loads and enables all combinations, so you have to edit accordingly. This file is from Brett Terpstra's 2017 blog post about karabiner elements here.

Thanks for reading and happy coding + shortcuting!
{
"global": {
"check_for_updates_on_startup": true,
"show_in_menu_bar": true,
"show_profile_name_in_menu_bar": false
},
"profiles": [
{
"complex_modifications": {
"rules": [
{
"manipulators": [
{
"description": "Change caps_lock to command+control+option+shift.",
"from": {
"key_code": "caps_lock",
"modifiers": {
"optional": [
"any"
]
}
},
"to": [
{
"key_code": "left_shift",
"modifiers": [
"left_command",
"left_control",
"left_option"
]
}
],
"to_if_alone": [
{
"key_code": "escape"
}
],
"type": "basic"
}
]
}
]
},
"devices": [
{
"disable_built_in_keyboard_if_exists": false,
"identifiers": {
"is_keyboard": true,
"is_pointing_device": false,
"product_id": 610,
"vendor_id": 1452
},
"ignore": false
},
{
"disable_built_in_keyboard_if_exists": false,
"identifiers": {
"is_keyboard": true,
"is_pointing_device": false,
"product_id": 597,
"vendor_id": 1452
},
"ignore": false
}
],
"fn_function_keys": {
"f1": "vk_consumer_brightness_down",
"f10": "mute",
"f11": "volume_down",
"f12": "volume_up",
"f2": "vk_consumer_brightness_up",
"f3": "vk_mission_control",
"f4": "vk_launchpad",
"f5": "vk_consumer_illumination_down",
"f6": "vk_consumer_illumination_up",
"f7": "vk_consumer_previous",
"f8": "vk_consumer_play",
"f9": "vk_consumer_next"
},
"name": "Default profile",
"one_to_many_mappings": {},
"selected": true,
"simple_modifications": {},
"standalone_keys": {},
"virtual_hid_keyboard": {
"caps_lock_delay_milliseconds": 0,
"keyboard_type": "ansi",
"standalone_keys_delay_milliseconds": 200
}
}
]
}

Top comments (1)

Collapse
 
neighneighneigh profile image
NeighNeighNeigh • Edited

Update:

Never mind, I have it working now! I accidentally missed out including the following at the top of my script:
local log = hs.logger.new('hammerspoon','debug') 😬


Hey Cristian, I'm very interested in the app launcher you've implemented here. The automatic hiding is genius!

I've ran into an issue though and am getting an error. Hoping you might be able to spare a moment to take a look?

This is the contents of AppLauncher.lua, which I am successfully loading into Hammerspoon via AppLauncher = require('AppLauncher') in my init.lua

-- open hide app
function openApp(name)
  local app = hs.application.get(name)

  log.i("open App")
  log.i(name)
  log.i(app)

  if app then
    if app:isFrontmost() then
      log.i("app hiding")
      app:hide()
    else
      log.i("app focusing ")
      app:mainWindow():focus()
    end
  else
    log.i("app launch ")
    hs.application.launchOrFocus(name)
  end
end


function reminders()
  openApp("Reminders")
end
hs.hotkey.bind({}, 'F14', reminders)
Enter fullscreen mode Exit fullscreen mode

But when I issue the shortcut key, I get the following error

ERROR:   LuaSkin: hs.hotkey callback: /Users/nathan/.hammerspoon/AppL2.lua:5: attempt to index a nil value (global 'log')
stack traceback:
    /Users/nathan/.hammerspoon/AppL2.lua:5: in function 'openApp'
    /Users/nathan/.hammerspoon/AppL2.lua:25: in function 'reminders'
Enter fullscreen mode Exit fullscreen mode