When I set up a new computer, the first thing I do (after installing Dropbox and 1Password, of course) is to run a script that installs my favorite command line tools and configures my shell the way I like it. I also go into System Preferences and twiddle various settings. I’ve done it enough times that it doesn’t take me long, but the part that always slows me down is custom keyboard shortcuts.

These are located in the Keyboard preference pane, under the Shortcuts tab, and they allow you to override the keyboard shortcuts for menu commands in any app. My favorite is to map Command–Shift–O in the Finder to Show Package Contents, and I have a few more that I couldn’t live without. Unfortunately, setting up all my favorite keyboard shortcuts is tedious, and because I use them by memory, it’s difficult to remember what they all are outside the context in which I use them. So, I set about trying to figure out how to set custom keyboard shortcuts from a shell script, and have them show up in System Preferences as though they were added by hand. Read on to find out what I learned about how to script macOS keyboard shortcuts.

Diving In – Script macOS Keyboard Shortcuts

The most useful tool in my quest was fseventer. This incredible app hooks into the fsevents system introduced in OS X 10.4, and shows a real-time visualization of every file that is changed on your system. Even if you have no practical use for it, it can be eye-opening to see how much stuff is going on at once inside a typical computer.

[Update April 2017: unfortunately, fseventer is now defunct, but a new app has emerged to take its place. If you’re interested in this kind of tool, check out FSMonitor.]

I fired up fseventer, started recording, and then changed a keyboard shortcut in System Preferences. Unsurprisingly, one of the files that changed was the preference file for the app in question. For the Finder, this is ~/Library/Preferences/com.apple.finder.plist. I saved a copy of that file, reverted the change to the keyboard shortcut, and then compared the two files.

From this, I found the NSUserKeyEquivalents element, which contains a dictionary mapping menu items to keyboard shortcuts. Letter and number keys are represented as letters and numbers, as you would expect. However, modifier keys such as ⌘ (command) and ⌥ (option) do not use their normal symbols. Instead, easier-to-type substitute characters are used. If you want to find the symbol for a given modifier key, create a custom keyboard shortcut that uses that key. Then, open the corresponding app’s preference file and look in the NSUserKeyEquivalents dictionary. I’ll give you the starter list of symbols, based on the modifier keys I use. Command, Control, Option, and Shift have convenient short codes, but Tab, the arrow keys, and others require the use of Unicode code points. I’ve given you the code for the Tab key below; finding the codes for other characters is left as an exercise to the reader. (Post any useful ones you find in the comments!)

Common Modifier Key Symbols Command: @ Control: ^ Option: ~ Shift: $ Tab: \U21e5 (Unicode code point for ⇥ character) 1 2 3 4 5 Command: @ Control: ^ Option: ~ Shift: $ Tab: \U21e5 (Unicode code point for ⇥ character)

If you use these symbols in a shell script, I recommend creating named variables so that you can have a hope of reading the code later. Note that the backslash in the Tab key code may need to be escaped if you use it in a shell string. Here is how to use what we have learned so far to set a shortcut of Command–Shift–O for Show Package Contents in the Finder:

TAB_KEY_SYMBOL="\\U21e5" # Note the backslash-escaped backslash. COMMAND_KEY_SYMBOL="@" SHIFT_KEY_SYMBOL="$" defaults write com.apple.finder NSUserKeyEquivalents "{ 'Show Package Contents' = '${COMMAND_KEY_SYMBOL}${SHIFT_KEY_SYMBOL}O'; }" # You may need to restart the Finder or kill the preferences daemon for the change to take effect: killall Finder killall cfprefsd 1 2 3 4 5 6 7 8 TAB_KEY_SYMBOL = "\\U21e5" # Note the backslash-escaped backslash. COMMAND_KEY_SYMBOL = "@" SHIFT_KEY_SYMBOL = "$" defaults write com .apple .finder NSUserKeyEquivalents "{ 'Show Package Contents' = '${COMMAND_KEY_SYMBOL}${SHIFT_KEY_SYMBOL}O'; }" # You may need to restart the Finder or kill the preferences daemon for the change to take effect: killall Finder killall cfprefsd

If you run this code in the Terminal, you will then be able to use your new keyboard shortcut in Finder. Hooray! However, there’s a problem: if you go to System Preferences, you won’t see your new keyboard shortcuts in the “App Shortcuts” section of the Shortcuts tab of the Keyboard preference pane. This puzzled me when I first encountered it, and it took a little more poking around in fseventer to figure out what I was missing.

Getting Custom Shortcuts to Show Up

It turns out that another file is touched when you set a custom keyboard shortcut for an app: ~/Library/Preferences/com.apple.universalaccess.plist. Inside the com.apple.custommenu.apps element, you’ll find an array of the reverse-domain bundle identifiers of the apps that have custom keyboard shortcuts defined in System Preferences. You have to add apps to this file if you want them to show up in the list. But use caution! If you add an app to this list more than once, System Preferences will crash when you try to load the Keyboard pane. In the code below, the addCustomMenuEntryIfNeeded function takes care of this safety check for you.

In the following shell script, I have combined the ideas described in this post into a starting point for you to customize the keyboard shortcuts instantly whenever you are setting up a new Mac. Copy and paste it into a .sh file or your shell rc file, and then have fun making your own dream keyboard shortcut setup script!

fixKeyboardShortcuts.sh #!/bin/sh function addCustomMenuEntryIfNeeded { if [[ $# == 0 || $# > 1 ]]; then echo "usage: addCustomMenuEntryIfNeeded com.company.appname" return 1 else local contents=`defaults read com.apple.universalaccess "com.apple.custommenu.apps"` local grepResults=`echo $contents | grep $1` if [[ -z $grepResults ]]; then # does not contain app defaults write com.apple.universalaccess "com.apple.custommenu.apps" -array-add "$1" else # contains app already, so do nothing fi fi } function fixKeyboardShortcuts { local COMMAND_KEY_SYMBOL="@" local CONTROL_KEY_SYMBOL="^" local OPTION_KEY_SYMBOL="~" local SHIFT_KEY_SYMBOL="$" local TAB_KEY_SYMBOL="\\U21e5" # Finder # Show Package Contents: Command-Shift-O defaults write com.apple.finder NSUserKeyEquivalents "{ 'Show Package Contents' = '${COMMAND_KEY_SYMBOL}${SHIFT_KEY_SYMBOL}O'; }" addCustomMenuEntryIfNeeded "com.apple.finder" # Terminal # Select Next Tab: Control-Tab # Select Previous Tab: Control-Shift-Tab defaults write com.apple.Terminal NSUserKeyEquivalents "{ 'Select Next Tab' = '${CONTROL_KEY_SYMBOL}${TAB_KEY_SYMBOL}'; 'Select Previous Tab' = '${CONTROL_KEY_SYMBOL}${SHIFT_KEY_SYMBOL}${TAB_KEY_SYMBOL}'; }" addCustomMenuEntryIfNeeded "com.apple.Terminal" # OmniGraffle # Fit in Window: Command-0 # Grid Lines: Option-Command-G defaults write com.omnigroup.OmniGraffle6 NSUserKeyEquivalents "{ 'Fit in Window' = '${COMMAND_KEY_SYMBOL}0'; 'Grid Lines' = '${OPTION_KEY_SYMBOL}${COMMAND_KEY_SYMBOL}G'; }" addCustomMenuEntryIfNeeded "com.omnigroup.OmniGraffle6" # Restart cfprefsd and Finder for changes to take effect. # You may also have to restart any apps that were running # when you changed their keyboard shortcuts. There is some # amount of voodoo as to what you do or do not have to # restart, and when. killall cfprefsd killall Finder } # Run the function fixKeyboardShortcuts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #!/bin/sh function addCustomMenuEntryIfNeeded { if [ [ $ # == 0 || $# > 1 ]]; then echo "usage: addCustomMenuEntryIfNeeded com.company.appname" return 1 else local contents = ` defaults read com .apple .universalaccess "com.apple.custommenu.apps" ` local grepResults = ` echo $ contents | grep $ 1 ` if [ [ - z $ grepResults ] ] ; then # does not contain app defaults write com .apple .universalaccess "com.apple.custommenu.apps" - array - add "$1" else # contains app already, so do nothing fi fi } function fixKeyboardShortcuts { local COMMAND_KEY_SYMBOL = "@" local CONTROL_KEY_SYMBOL = "^" local OPTION_KEY_SYMBOL = "~" local SHIFT_KEY_SYMBOL = "$" local TAB_KEY_SYMBOL = "\\U21e5" # Finder # Show Package Contents: Command-Shift-O defaults write com .apple .finder NSUserKeyEquivalents "{ 'Show Package Contents' = '${COMMAND_KEY_SYMBOL}${SHIFT_KEY_SYMBOL}O'; }" addCustomMenuEntryIfNeeded "com.apple.finder" # Terminal # Select Next Tab: Control-Tab # Select Previous Tab: Control-Shift-Tab defaults write com .apple .Terminal NSUserKeyEquivalents " { 'Select Next Tab' = '${CONTROL_KEY_SYMBOL}${TAB_KEY_SYMBOL}' ; 'Select Previous Tab' = '${CONTROL_KEY_SYMBOL}${SHIFT_KEY_SYMBOL}${TAB_KEY_SYMBOL}' ; } " addCustomMenuEntryIfNeeded "com.apple.Terminal" # OmniGraffle # Fit in Window: Command-0 # Grid Lines: Option-Command-G defaults write com .omnigroup .OmniGraffle6 NSUserKeyEquivalents " { 'Fit in Window' = '${COMMAND_KEY_SYMBOL}0' ; 'Grid Lines' = '${OPTION_KEY_SYMBOL}${COMMAND_KEY_SYMBOL}G' ; } " addCustomMenuEntryIfNeeded "com.omnigroup.OmniGraffle6" # Restart cfprefsd and Finder for changes to take effect. # You may also have to restart any apps that were running # when you changed their keyboard shortcuts. There is some # amount of voodoo as to what you do or do not have to # restart, and when. killall cfprefsd killall Finder } # Run the function fixKeyboardShortcuts

One More Thing: Global Shortcuts

You can also set global keyboard shortcuts that work across all applications. These are stored in ~/Library/Preferences/.GlobalPreferences.plist, which you can write to by passing -g (for “Global”) to the defaults command.

Interested in joining the Raizlabs team making great software? We’re hiring developers in Boston and SF.