Using Karabiner Elements with BetterTouchTool

I had this draft sitting in Bear for around nine months now and never published it. I no longer use the crazy fn + modifier shortcuts I describe, but the info about how to trigger BetterTouchTool commands via Karabiner is still useful so I’m publishing this now.

This is a story about how I figured out how to set up modifiers in Karabiner Elements to trigger actions in BetterTouchTool.

For years I’ve used BetterTouchTool keyboard shortcuts for window management. To keep from overcomplicating this, let’s just say that I have four shortcuts for four common window management commands. If I hold down ⌃⌥⌘ I can then tap the four arrow keys to resize/reposition windows:

Shortcut Action
⌃⌥⌘← Fill left half of screen
⌃⌥⌘→ Fill right half of screen
⌃⌥⌘↑ Fill entire screen1
⌃⌥⌘↓ Center window on screen

Like I said, I’ve done this for years, and it works fine. But I wanted to try something different and get a little crazy. I wondered if it was possible to hold down the fn key to enter a sort of “Window Management Mode.” The idea was bigger than that, really. It was more along the lines of “I’ve got this fn key. Why don’t I add a bunch of new shortcuts that use it?” We’ve got the Command, Option, and Control keys and holding down each one gives you an entire new layer of commands. Why not do the same with the fn key?

My trimmed down idea was to set up the following shortcuts2:

Shortcut Action
fn+⌃ Fill left half of screen
fn+⌘ Fill right half of screen
fn+⇧ Fill entire screen
fn+⌥ Center window on screen

So while holding down the fn key I could tap any of the other modifier keys on the left side of the keyboard to control windows. The upside of this is two-fold. First, it’s a single-handed operation. Second, it frees up the ⌃⌥⌘+arrow shortcuts for something more useful, probably in my editor, but I haven’t yet figure out what magic to invoke with that very special combination.

But how to set up those key combos? Things are complicated by the fact that I want to trigger a command by tapping a modifier key (like command or control) rather than a standard key. Now it is sort of possible to set up these shortcuts in BetterTouchTool by setting up a Key Sequence. But there is an issue with it. With BTT Key Sequences you set a maximum delay between keystrokes. So it would be possible for the command to not fire if too much time passed between holding down fn and tapping the other modifier key. The bigger issue is that I would not be able to move a window to the right side of the screen and then immediately center it, which is something I like to do all the time. I mean I could, but I’d have to lift my finger off of the fn key after I tapped the ⌘ key and then press fn again before tapping ⌥. And that rubs me the wrong way.

Enter Karabiner Elements.

With Karabiner I can set up shortcuts (or “modifications,” in Karabiner-speak) exactly how I want for this situation. If I tap the left ⌘ key while fn is held down, it will do something. Period. The end. Perfect. The problem is that Karabiner, unlike BTT, doesn’t know anything about managing windows. But Karabiner can trigger shell scripts. And BTT can be scripted with Apple Script.

Here’s my first attempt at getting Karabiner to fire a trigger defined in a BetterTouchTool :

{
  "description": "fn + Left ⌃ | Maximize Window Left",
  "manipulators": [
    {
      "type": "basic",
      "from": {
        "key_code": "left_control",
        "modifiers": {
          "mandatory": [
            "fn"
          ]
        }
      },
      "to": [
        {
          "shell_command": "osascript -e 'tell application \"BetterTouchTool\" to execute_assigned_actions_for_trigger \"14915871-78D8-49F7-A95E-F292366825EA\"'"
        }
      ]
    }
  ]
}

In plain English, this says: “when you tap the left Control key while the fn key is held down, run this shell command: osascript -e 'tell application "BetterTouchTool" to execute_assigned_actions_for_trigger "14915871-78D8-49F7-A95E-F292366825EA"'

Where did that UUID come from? You can right-click on a trigger in BTT and copy that trigger’s UUID.

That was my first attempt: using Apple Script to run the actions assigned to a specific trigger. And it works. BUT, I had to keep that trigger in BTT which means having that keyboard shortcut set up in BTT and a big reason I wanted to go through this exercise was to free up my precious ⌃⌥⌘← shortcut for something else!

Attempt two is nearly identical to my first attempt. The only difference is the shell command that’s getting run:

"shell_command": "osascript ~/Dropbox/scripts/window-management/fill-left-side-of-screen.scpt"

Rather than running an inline Apple Script, it’s running one saved in an external file. Here’s the contents of that script:

var BTT = Application('BetterTouchTool');
var action = {
  "BTTPredefinedActionType" : 19
}

BTT.trigger_action(JSON.stringify(action));

You can find the value of "BTTPredefinedActionType" by right-clicking on the action in BTT and selecting Copy (or Copy JSON in older version of BTT). That copies a lot of data you don’t need, so paste it all in an editor window and pull out only what you need.

Now Karabiner is running an external Apple Script that contains all the info BTT needs to resize the window so you’re free to delete the shortcut from BTT!


  1. Fill the screen without going into full-screen mode. Not that there’s anything wrong with full-screen mode, that’s just not what I wanted in this case. 
  2. There’s actually logic to the shortcuts I came up with. ⌃ is the left-most modifier (not counting the fn key) so that acts the same as my previous ⌃⌥⌘← command. Likewise the ⌘ key is the right-most modifier so it takes on the action I had assigned to the right arrow. 

Making symlinks with LaunchBar

I always forget the exact syntax for making a symbolic link from the command line. It’s quick enough to look it up, especially when using Fish’s history search capabilities, but ever since I noticed you could make symlinks with LaunchBar, that has become my preferred method.

The animated GIF below shows making a symlink of a folder in my LaunchBar actions folder (~/Library/Application Support/LaunchBar/Actions/beats-x-launchbar-actions) in a subfolder in my Projects folder (~/projects/launchbar-actions).

The workflow goes like so:

  1. Select the folder you want the symlink to point to. In this case I selected it in the Finder and sent it to LaunchBar with Instant Send.
  2. Hit tab to invoke Send To…
  3. Select the target folder, where you want the new symlink to live. In this case I:
    • Navigated to my ~/projects folder by typing p
    • Tapped the right arrow key to navigate into the folder
    • Started typing the name of the target folder laun
    • Hit enter to get the list of actions
  4. Select “Make Symbolic Link (Absolute)” from the list of file actions.

LaunchBar: Quick Calculations In-Place

This is a real case I ran into the other day while posting to my blog. The image embed code generated by WordPress set the image dimensions to twice what I wanted them to be. My intellectual laziness (not wanting to do some simple math in my head) is your gain because you’re going to learn how to do some quick calculations “in-place” with LaunchBar’s calculator and Instant Send feature.

Method One

  1. Select the number you want to do a calculation on.
  2. Send that number to LaunchBar with Instant Send. In my case that’s done by double-tapping the Command (⌘) key.
  3. Type /2 to divide the number by two.
  4. Type ShiftEnter to paste the resulting number back into the app you initially copied from.

Method Two

  1. Type the math equation in place. In this case by turning 628 into 628/2.
  2. Select that equation and send it to LaunchBar with Instant Send (double-tap Command).
  3. LaunchBar automatically detects you gave it a math problem so it instantly solves it for you. So type ShiftEnter to paste the result back to where it came from.

`e` and `ee`

How many times have you typed something like atom .gitignore or code my-project to open a specific file or folder in your editor? And how many times have you typed code . to open the current folder in your editor? I personally do that one a ton.

Not only is that too many characters to type, what happens when you decide that Atom is slow and it’s time to move to Visual Studio Code? You’ve got to retrain yourself to use a different four character command each time you open a file or directory.

Screw that. Set up an alias:

Fish: alias e "code"
Bash: alias e="code"

Now you can type e my-project or e .gitignore to open a file or folder. And when you decide to give a new editor a try you can change your alias to point to you editor du jour and continuing just typing e at the command line. With this alias, opening your current directory is as easy as typing e .. But that’s not easy enough for me. It’s three whole characters when you count the space. And the E key and period key are on different sides of the keyboard! Wahhh! So set up a second alias:

Fish: alias ee "e ."
Bash: alias ee="e ."

Every time I type ee to open the current directory, I smile. At least on the inside. And you will, too.

Code Helper CPU Usage

Thanks to iStats Menus I noticed there was a process constantly using just enough CPU to attract attention. Activity Monitor showed that one of the VS Code helpers was hovering at around 7%. Not terrible, but it was annoying me.

There is now a Process Explorer in VSCode that makes it much easier to determine exactly which VSCode process is hogging the CPU

After a little trial and error, disabling and reenabling extensions, I discovered it was the Settings Sync extension. Once that was disabled things calmed down. I dug a little deeper and discovered that it was turning on the sync.autoUpload setting (which is off by default) that was really doing it. With that set back to false the extension ran fine without using a bit too much CPU for my taste.

Switching Shells

I’m not sure how I made it this far into my career without knowing I could quickly switch between fish and bash and zsh and any other shell I have on my system by simply typing the name of the shell I want to use.

Since I switched from zsh to fish around a year and a half ago, I’ve kept the stock macOS Terminal app (I normally use iTerm) set up to use zsh just in case I wanted to test something in a POSIX-compatible shell. All this time I’ve thought I’d have to do the following if I wanted to switch from fish to zsh:

  1. Type chsh -s (which zsh)
  2. Enter my account password
  3. Open a new terminal tab

Nope.

Just type zsh. Done.

A Modest Proposal on Naming Files

Trying to decide if you should replace spaces in your files names with dashes or underscores? Choose dashes. Why? If you use dashes, when you are editing the file name you can jump from word to word by tapping the left or right arrow key while holding down Option. Said another way, macOS recognizes the dash as a word separator; not so with the underscore.

VS Code Quick Tip: Exit Preview Mode

If you’ve got VS Code’s enablePreview options turned on, and they are by default, you might like to know that you can just hit ⌘-S to take it out of “Preview Mode” to prevent it from being replaced by the next file you open. In my opinion this is better than the other methods to make a file stay open: double-clicking the file’s tab (eww, touching your mouse or trackpad) or making the file “dirty” by entering some text and immediately deleting it.

I’ve never used this “preview” feature in any editor. I’ve recently gotten sick of ending up with dozens of tabs open in a project so I’ve been giving it a shot. This little tip is making me actually enjoy the feature.

Speaking of preview modes in text editors, I’d kill for Sublime Text’s legit preview mode the would instantly show you the contents of the file you had selected in the Quick Open menu. So good!

Global npm Module Weirdness

I just installed node 8.9.1 with nodenv. Then I wanted to get my global packages installed to more-or-less match what I had with my previous version of node (v6.11.3).

Running npm list -g --depth=0 with node 6.11.3 active gives me:

❯ npm list -g --depth=0
/Users/erikhansen/.nodenv/versions/6.11.3/lib
├── eslint@4.7.1
├── eslint-config-airbnb-base@12.0.0
├── eslint-config-prettier@2.5.0
├── eslint-plugin-import@2.7.0
├── eslint-plugin-json@1.2.0
├── eslint-plugin-prettier@2.3.1
├── generator-code@1.1.20
├── gulp@3.9.1
├── npm@3.10.10
├── prettier@1.7.3
├── prettier-eslint@8.2.0
├── vsce@1.31.1
└── yo@2.0.0

So with node 8.9.1 active I ran npm i -g eslint gulp prettier prettier-eslint yo generator-code to start installing a bunch of — but not all of — the global packages that I wanted.

Now for the weird part: Running npm list -g --depth=0 to see the current state of the world gives me this:

❯ ng # <-- a handy alias for `npm list -g --depth=0`
/Users/erikhansen/.nodenv/versions/8.9.1/lib
├── generator-code@1.1.22
├── gulp@3.9.1
├── npm@5.5.1
├── prettier-eslint@8.2.2
└── yo@2.0.0

eslint isn’t listed as a global package! But I have it installed:

❯ which eslint; and eslint -v
/Users/erikhansen/.nodenv/shims/eslint
v4.11.0

I looked a little deeper and found eslint installed as a dependency of prettier-eslint (which, by the way, is not a module that I actually wanted to have installed globally):

❯ npm list -g --depth=1
...
├─┬ prettier-eslint@8.2.2
│ ├── ...
│ ├── eslint@4.11.0
...

And further, digging into where nodenv actually stores the globally installed modules, in this case ~/.nodenv/versions/8.9.1/lib/node_modules, I see that eslint is indeed there at the top level!

Questions…

So the question is:

Why doesn’t npm list eslint as a top-level globally installed module when it seems as though it is one?

The follow up question is: Why does it bother me as much as it does that eslint doesn’t show up in that list of global modules even though everything works as though it is a top-level global module?

The Fix

How to fix this “problem”? One option is to just blow away all of the globally installed packages by running this a couple of times:

npm ls -gp --depth=0 | awk -F/ '/node_modules/ && !/\/npm$/ {print $NF}' | xargs npm -g rm

That’s extreme and is likely something that you wouldn’t want to do in most situations.

The other option is to remove the package that has eslint as a dependency, which is a package I didn’t want to have installed in the first place: npm -g rm prettier-eslint

Now the world is happy again:

❯ ng
/Users/erikhansen/.nodenv/versions/8.9.1/lib
├── eslint@4.11.0
├── generator-code@1.1.22
├── gulp@3.9.1
├── npm@5.5.1
├── prettier@1.8.2
└── yo@2.0.0

Final Note

At this point reinstalling prettier-eslint works and eslint still shows up as a global package.