In this post, I will focus on the possibilities of using an external keyboard as an additional input device for your Mac that goes beyond just typing key by key. This is super useful if you want to be more productive as a programmer or tester.

Requirements:

Optional but useful:

  • CheatSheet app.
  • Text editor with syntax highlighting (I use Visual Studio Code).

We are all familiar with at least a couple simple keyboard shortcuts that we use all the time in our daily lives – Command+C for copy, Command+V for paste, and possibly some more. I personally often use Mac built-in screenshot function (Shift+Control+Command+4), so the captured picture is saved in clipboard. And this is not a simple shortcut anymore, because it requires both of your hands to use it properly.

Another problem is the sheer amount of different keyboard shortcuts – system-wide as well as app-specific, that if you don’t use multiple times a day, most likely won’t be remembered. And the third problem is that some often used menu commands simply don’t have a keyboard shortcut assigned at all. This post is going to address all three of these problems with a lot of useful extras on top of it.

First, since we are going to be working with keyboard shortcuts, we will start with assigning a shortcut for a menu item that does not have a shortcut assigned already. If you don’t have any menu items without an assigned shortcut, skip this part.

A useful application to avoid shortcut conflicts is CheatSheet. When installed, it will find and display all keyboard shortcuts for the application in focus, so you can make sure you don’t re-use shortcuts that are already in use, otherwise unexpected results may occur.

You will need to navigate to your Keyboard Preferences, open Shortcuts tab and find App Shortcuts in the leftmost list. Here you can add a shortcut to your menu item, and specify whether this will be for a single application, or system-wide. For example, I will add a shortcut to “Check for Updates…”, leave it as system-wide by not choosing any specific application, and set the keyboard shortcut as Control+Option+Command+U, since neither system, nor any of my installed apps use this shortcut. Repeat the process for all items you need a shortcut for.

A custom keyboard shortcut that originally doesn’t exist.

At this point you should have  all the necessary shortcuts set, and we can move on to the interesting part – using Karabiner-Elements. It is a very powerful tool that allows all kinds of input modifications in any way you can imagine. However, in this article we are going to look mostly at keyboard key modifications. When you have connected your external keyboard and started Karabiner-Elements, you want to  make sure that your keyboard is shown in Devices tab. Here you will need to remember Vendor ID and Product ID, because with Simple Modifications, we will not change all inputs, but only the ones for that specific keyboard.

Finding Vendor and Product ID’s for the external keyboard.

You might have noticed that you can’t add multiple options to your Simple Modifications, just a single From key and single To key. This is where text editor I mentioned in the beginning will come in handy. Of course, instead of using a text editor with syntax-highlighting you can edit the file in the built-in text editor or even just terminal, but that’s a bit more of a hassle.

You will need to navigate to this folder first: ~/.config/karabiner/ and open your karabiner.json file. And this is where all the magic happens. If you’re not familiar with JSON files, their structure is quite simple – the whole file consists of key-value pairs, and in this Karabiner configuration file every key and value is also easily readable and understandable. The syntax is strict, and text editors that understand json file format will warn you if syntax is not correct.

Simple modifications – trigger a keyboard shortcut with a single key

In the configuration file there is a “devices” section under which we need to find

the Vendor ID and Product ID identifiers that we noted previously and edit only the device which has those exact identifiers. This way the keys you modify will only apply to that specific keyboard and your main typing keyboard will stay unaffected. If you can’t find your device identifiers in the configuration you can add another entry and change the ID’s.

"devices": [
               {
                   "disable_built_in_keyboard_if_exists": false,
                   "fn_function_keys": [],
                   "identifiers": {
                       "is_keyboard": true,
                       "is_pointing_device": false,
                       "product_id": 34,
                       "vendor_id": 3727
                   },
                   "ignore": false,
                   "manipulate_caps_lock_led": true,
                   "simple_modifications": [
                       {
                           ...
                       }
                   ]
               }

Note that the Product ID and Vendor ID’s are the same as in the previous image.

Next, we look for simple_modifications key where all of the shortcuts will go.

Every shortcut contains a from key (that is the button on your keyboard, that will trigger the shortcut) and a to key (that corresponds to the shortcut you want to trigger).

In this situation, the from key will always be a single keyboard button, without any modifiers, and to key will have one or more modifier buttons.

To give a couple of examples, here I have set buttons 1 and 2 to trigger a screenshot command and save it either to clipboard or desktop respectively; my a button triggers Command+Option+L keyboard shortcut, that I use to reformat code in IntelliJ IDEA; and r button triggers Shift+fn+F6, that triggers a Rename also in IntelliJ IDEA.

Note: in the last example, fn modifier is necessary only on MacBooks that have touch-bar, since fn is used to show function keys instead of touch buttons.

"simple_modifications": [
                      {
                          "from": {
                              "key_code": "1"
                          },
                          "to": {
                              "key_code": "4",
                              "modifiers": [
                                  "shift",
                                  "control",
                                  "command"
                              ]
                          }
                      },
                      {
                          "from": {
                              "key_code": "2"
                          },
                          "to": {
                              "key_code": "4",
                              "modifiers": [
                                  "shift",
                                  "command"
                              ]
                          }
                      },
                      {
                          "from": {
                              "key_code": "a"
                          },
                          "to": {
                              "key_code": "l",
                              "modifiers": [
                                  "command",
                                  "option"
                              ]
                          }
                      },
                      {
                          "from": {
                              "key_code": "r"
                          },
                          "to": {
                              "key_code": "f6",
                              "modifiers": [
                                  "shift",
                                  "fn"
                              ]
                          }
                      }
                  ]

When you install Karabiner, you also get an additional helper tool called EventViewer. This tool shows event types and more importantly – names of all buttons you tap. Use this to easily find obscure names you might want to reassign.


The key I usually call “tilde” here has a much longer name – “grave_accent_and_tilde”

These examples should be enough to show you how to create all the simple keyboard shortcuts you might need. But the Karabiner Elements has a lot more to it than just the keyboard shortcuts, and this is where the complex modifications come in play with a plethora of capabilities.

Complex modifications – more than just shortcuts

This is where things quickly get complicated. There are a couple small differences in how the complex modifications are structured versus simple. First, you need to find complex_modifications key, inside of which there is a rules key, and second – manipulators.

One of the options with complex modifications is that you are able to trigger any terminal command with the shell_command key. For now, I use this simply to launch some applications, but if you are familiar with terminal and use it all the time, you can create some complex actions with just a key tap.

"complex_modifications": {
               "parameters": {
                   "basic.simultaneous_threshold_milliseconds": 50,
                   "basic.to_delayed_action_delay_milliseconds": 500,
                   "basic.to_if_alone_timeout_milliseconds": 1000,
                   "basic.to_if_held_down_threshold_milliseconds": 500
               },
               "rules": [
                   {
                       "manipulators": [
                           {
                               "conditions": [
                                   {
                                       "identifiers": [
                                           {
                                               "product_id": 34,
                                               "vendor_id": 3727
                                           }
                                       ],
                                       "type": "device_if"
                                   }
                               ],
                               "description": "Open Discord",
                               "from": {
                                   "key_code": "z",
                                   "modifiers": {
                                       "optional": [
                                           "any"
                                       ]
                                   }
                               },
                               "to": [
                                   {
                                       "shell_command": "open -a 'Discord.app'"
                                   }
                               ],
                               "type": "basic"
                           },
                           {
                               "conditions": [
                                   {
                                       "identifiers": [
                                           {
                                               "product_id": 34,
                                               "vendor_id": 3727
                                           }
                                       ],
                                       "type": "device_if"
                                   }
                               ],
                               "description": "Open Desktop",
                               "from": {
                                   "key_code": "x",
                                   "modifiers": {
                                       "optional": [
                                           "any"
                                       ]
                                   }
                               },
                               "to": [
                                   {
                                       "shell_command":
                                           "open ~/Desktop"
                                   }
                               ],
                               "type": "basic"
                           }
                       ]
                   }
               ]
           }

In the example above, the manipulation starts with a condition that determines if the key is pressed on the specified keyboard, and only then triggers the terminal command, which opens Discord application. The "type": "basic" line is mandatory for every entry, even though it is not currently used.

Another case of complex modifications is the ability to completely change the behavior of a key if it’s tapped versus if it is held down. A good example is spacebar – you never really need to hold down the spacebar to type a 100 whitespace characters, so the holding behavior is essentially empty. If you have to enter just a couple of symbols or capital letters while typing, you usually would hold down shift key and then press the needed button, but even though we are used to it, the shift key is in an awkward position. Here we just have to add another key to the configuration with the name to_if_alone which will be the same as from key, because we don’t want to change the spacebar if it’s pressed on its own, and to key is changed to shift.

{
                               "description": "Change space to shift if held",
                               "from": {
                                   "key_code": "spacebar",
                                   "modifiers": {
                                       "optional": [
                                           "any"
                                       ]
                                   }
                               },
                               "to": [
                                   {
                                       "key_code": "left_shift"
                                   }
                               ],
                               "to_if_alone": [
                                   {
                                       "key_code": "spacebar"
                                   }
                               ],
                               "type": "basic"
                           }

Now, there are many, many other possible modifications – trigger an action if key is held down, type strings of characters if a single key is tapped, trigger an action if multiple specific keys are pressed at the same time, work with mouse events and even create variables in your configuration.

While writing this post, I experimented with the possibilities this application provides, and a couple of code snippets might not be enough, which is why below you will find a karabiner.json file that I currently use. It’s not complete, because I keep adding to it every once in a while, but it should provide enough examples to start building your own personal configuration to make your life a bit easier.

Download archive of my karabiner.json file.