If you’re obsessed with productivity hacks, you’ve probably heard of Karabiner – the ultimate tool for keyboard customization on macOS. But maintaining Karabiner’s config is a pain. In this post I’ll show you a simpler, Kotlin-powered way to wrangle your Karabiner setup, so you don’t have to wrestle with massive, unwieldy JSON.
On Karabiner
Here’s the gist: Karabiner lets you intercept any keystroke and remap it to… well, pretty much anything. Want to turn Caps Lock into a Hyper key? Make your keyboard launch confetti? It’s all possible.
Take the classic usecase: remap Caps Lock to Hyper (⌘⌥⌃⇧) when held, Escape when tapped. That’s easy to do but just scratches the surface. With Karabiner, you can build many more customizations that supercharge your workflow.
Maintaining that karabiner.json
I was watching a video by Max Stoiber where he uses Raycast1 & Karabiner. But what struck me was how he customized and maintained his karabiner setup.
Karabiner runs off a single .json
config file. But that .json configuration can become unwieldy and difficult to manage, as your rules grow in complexity. For example: I require a ~2700 line .json
for all my hacks. It’s impossible to maintain it as pure json.
Max uses TypeScript to maintain his rules -which then compiles down to a .json file- that Karabiner can consume. I really like this approach. If you read my previous blog post, I also ran into a very similar problem and used goku, which in turn used the very esoteric and terse edn format.
I like TypeScript over edn but you know what I’d like even better? Kotlin! So on a ✈️ ride back home, I decided to whip up karabiner-kt.
I’m really happy with my solution. Kotlin affords a much more pleasant DSL than most other2 languages.
Here’s what a Karabiner rule looks like in Kotlin :
karabinerRule {
description = "Right Cmd -> Ctrl (Enter alone)"
mapping {
fromKey = RightCommand
toKey = RightControl
toKeyIfAlone = KeyCode.ReturnOrEnter
forDevice { identifiers = DeviceIdentifier.APPLE_KEYBOARDS }
}
},
Clean, expressive, and type-safe!
Or here’s a fun one. Hold the “O” key and tap “0” for showing the Raycast Confetti:
karabinerRuleSingle {
description = "O + 0 -> Raycast Confetti"
layerKey = KeyCode.O
fromKey = KeyCode.Num0
shellCommand = "open raycast://extensions/raycast/raycast/confetti"
},
Here’s a slightly more complex modification: If I hold the “f” key and tap j/k, it types out an open and closed bracket respectively.
karabinerRule {
description = "F-key layer mappings"
layerKey = KeyCode.F
// J K
// ( )
mapping {
fromKey = KeyCode.J
toKey = KeyCode.Num9
toModifiers = listOf(LeftShift)
}
mapping {
fromKey = KeyCode.K
toKey = KeyCode.Num0
toModifiers = listOf(LeftShift)
}
// M ,
// [ ]
mapping {
fromKey = KeyCode.M
toKey = KeyCode.OpenBracket
}
mapping {
fromKey = KeyCode.Comma
toKey = KeyCode.CloseBracket
}
// . /
// { }
mapping {
fromKey = KeyCode.Period
toKey = KeyCode.OpenBracket
toModifiers = listOf(LeftShift)
}
mapping {
fromKey = KeyCode.Slash
toKey = KeyCode.CloseBracket
toModifiers = listOf(LeftShift)
}
}
Installation
The repo is open source, so feel free to take a look and customize.
Getting started is easy. Here’s how to set it up from scratch:
# install karabiner-elements
brew install --cask karabiner-elements
# app uses a simple gradle app
brew install gradle
# let's clean up the karabiner folder if they existed
rm -rf ~/.config/karabiner
mkdir -p ~/.config/karabiner
# clone the repo
git clone https://github.com/kaushikgopal/karabiner-kt.git ~/.config/karabiner/karabiner-kt
Run the configurator
Now every time you want to run the configurator:
cd ~/.config/karabiner/karabiner-kt
make
# you might have to do this once a while (if you restart your mac etc.)
make restart-karabiner
Try your own Rules!
All your customizations live in Rules.kt. My own config is about 300 lines of Kotlin. Compare that to the monstrous 2736-line JSON it generates. 😅
You should start with a really simple setup and build your Rules over time. Give it a shot and let me know if you run into issues!
Comments via 🦋