Or: How I Learned To Stop Worrying And Love Karabiner
So, back in November, I hurried on down to Apple Princes Street and picked up an M1 MacBook Air on launch day. (16 gigs / 1 TB / space grey, obviously.) Nifty wee machine. Blew my 2013 MacBook Pro out the water, naturally, all while both eerily quiet and downright supernaturally cool. The legends… they're all true?
Well anyway, I got it in Apple's UK keyboard layout because I didn't want to wait—another lockdown was just days away—and I could always use their extended new year return period if the layout drove me nuts. See, I'm used to ANSI. How hard could going back to ISO be?
This is what I'm used to on my laptop:
Apple's UK images still show American layout. But the layout over here is Apple's ISO style. The same as above, but with their skinny-assed Return key, the ever curious § key up above Tab and tilde tucked down by small left Shift, like this:
One thing their ISO keyboards do have going for them: icon mods. I do like these! Much more stylish than having things like "delete" spelled out on the keys. (That's Backspace you dimwits!) They're a throwback to my 12" PowerBook, only higher contrast and backlit. Indeed, my distant familiarity with this layout started giving me ideas…
As far as laptops go, the Air's keyboard is obviously much better than those from the horrific "Butterfly" era. Minimal keyfeel aside—thump-thump-thump—those butterfly laptops were aptly named when it came to reliability, so I skipped them entirely. The relevant comparison for me was with my 7 year old 15" MacBook Pro. I like these new ones much more than Apple's previous scissor-switch. They're less 'floppy', they've got tighter tolerances, and while they'll win no contests with Topre and all the other real keyboards I love, they're passable for a slim laptop, and just as reliable.
But this Apple/ISO layout keeps reminding me of my 2003 PowerBook, my first Mac. The old PowerBook's keyboard is sculpted—I still prefer the key shape to all these chiclets since—and has a lot more travel. But here's the oddball: back in those days, Mac laptops had an Enter key over to the right of the spacebar. I found myself hitting right Option when completing File > Save dialogs and the like. That old muscle memory was flowing back!
Alright already Mu, what's all this got to do with Karabiner?
Let's reprogram this thing!
I'm used to programmable keyboards. Soarer's, TMK in my HHKB, QMK in my Kishy. After hitting that accursed ` key when reaching for left Shift for the hundredth goddamn time, I knew this built in keyboard was in sore need of some modding. It was time for me to make my peace with Karabiner.
Usually, I do all my key-swapping in the keyboards themselves. MacOS does have some minimal functionality—you can reorder the modifiers on individual keyboards—but nothing like as complimacated as I was looking for:
So, here's what I've gotten up to in Karabiner. It's complex!
Karabiner calls simple key-swaps "simple modifications". You can do quite a bit with just those. But I'm a special flower, always…
Karabiner's also got a library of ready made "complex modifications" but even these weren't sufficiently complex for µ. So I rolled up my sleeves and cooked up my own in JSON. Karabiner's rule format is… clunky, but powerful. It did allow me to do quite a lot of trickery indeed, including all my usual staples like Shift + Shift > Caps Lock, and more besides.
For anyone interested, my rules come in 3 varieties, in 3 different files. First: the General Complex Rules which apply to all keyboards, including my laptop's own:
Code: Select all
{
"title": "General Complex Rules",
"rules": [
{
"description": "Shift + Shift ⇨ Caps Lock",
"manipulators": [
{
"from": {
"key_code": "left_shift",
"modifiers": {
"mandatory": [
"right_shift"
],
"optional": [
"any"
]
}
},
"to": [
{
"key_code": "caps_lock"
}
],
"type": "basic"
},
{
"from": {
"key_code": "right_shift",
"modifiers": {
"mandatory": [
"left_shift"
],
"optional": [
"any"
]
}
},
"to": [
{
"key_code": "caps_lock"
}
],
"type": "basic"
}
]
},
{
"description": "Shift + Backspace ⇨ Delete",
"manipulators": [
{
"conditions": [
{
"type": "frontmost_application_if",
"bundle_identifiers": [
"com.apple.finder"
]
}
],
"from": {
"key_code": "delete_or_backspace",
"modifiers": {
"mandatory": [
"shift"
],
"optional": [
"option"
]
}
},
"to": [
{
"key_code": "delete_forward"
}
],
"type": "basic"
},
{
"conditions": [
{
"type": "frontmost_application_unless",
"bundle_identifiers": [
"com.apple.finder"
]
}
],
"from": {
"key_code": "delete_or_backspace",
"modifiers": {
"mandatory": [
"shift"
],
"optional": [
"any"
]
}
},
"to": [
{
"key_code": "delete_forward"
}
],
"type": "basic"
}
]
},
{
"description": "Right Option alone ⇨ 🗣 Enter",
"manipulators": [
{
"type": "basic",
"from": {
"key_code": "right_option"
},
"to": [
{
"key_code": "right_option"
}
],
"to_if_alone": [
{
"key_code": "keypad_enter"
}
]
},
{
"from": {
"key_code": "right_option",
"modifiers": {
"mandatory": [
"right_command"
]
}
},
"to": [
{
"key_code": "s",
"modifiers": [
"right_command",
"right_control"
],
"repeat": true
}
],
"type": "basic"
}
]
},
{
"description": "Fine Volume: Command ± 🔉 ⇨ Shift + Option ± 🔉",
"manipulators": [
{
"from": {
"key_code": "f11",
"modifiers": {
"mandatory": ["command"]
}
},
"to": [
{
"key_code": "volume_decrement",
"modifiers": [
"left_shift",
"left_alt"
],
"repeat": true
}
],
"type": "basic"
},
{
"from": {
"key_code": "f12",
"modifiers": {
"mandatory": ["command"]
}
},
"to": [
{
"key_code": "volume_increment",
"modifiers": [
"left_shift",
"left_alt"
],
"repeat": true
}
],
"type": "basic"
}
]
},
{
"description": "Fine Brightness: Command ± 🔅 ⇨ Shift + Option ± 🔅",
"manipulators": [
{
"from": {
"key_code": "f1",
"modifiers": {
"mandatory": [
"command"
]
}
},
"to": [
{
"key_code": "display_brightness_decrement",
"modifiers": [
"left_shift",
"left_alt"
],
"repeat": true
}
],
"type": "basic"
},
{
"from": {
"key_code": "f2",
"modifiers": {
"mandatory": [
"command"
]
}
},
"to": [
{
"key_code": "display_brightness_increment",
"modifiers": [
"left_shift",
"left_alt"
],
"repeat": true
}
],
"type": "basic"
}
]
},
{
"description": "Contrast: Control ± 🔅 ⇨ Control + Option + Command ± .",
"manipulators": [
{
"from": {
"key_code": "f1",
"modifiers": {
"mandatory": [
"control"
]
}
},
"to": [
{
"key_code": "period",
"modifiers": [
"left_control",
"left_option",
"left_command"
],
"repeat": true
}
],
"type": "basic"
},
{
"from": {
"key_code": "f2",
"modifiers": {
"mandatory": [
"control"
]
}
},
"to": [
{
"key_code": "comma",
"modifiers": [
"left_control",
"left_option",
"left_command"
],
"repeat": true
}
],
"type": "basic"
}
]
}
]
}
What I've done here is give the right Option (Alt) key a second purpose as a (Numpad) Enter key, which I've gotten back into using as a quickly reachable confirm key. And, on top of that, I've also given it the 🗣 screen reader combo, as I used to have it set on my PowerBook. Yes, I like my computers talking to me! Well, it's a good way to hear typos in what I write. Those two keys make a great roll combo that I've missed for a decade. Now it's back!
Shift + Shift > Caps Lock, always. And Shift + Backspace for (forward) Delete is just as natural, too. I've gotten used to both of these from my programmable keyboards.
Oh, and I've made the shortcuts for fine brightness and volume control much more natural. Contrast is a handy thing to tweak sometimes too.
Here's the Built in Keyboard specific rules, just for my laptop's own keys:
Code: Select all
{
"title": "Built in Keyboard Rules",
"type": "device_if",
"rules": [
{
"description": "Built in Keyboard: Caps Lock ⇨ Right Control",
"manipulators": [
{
"conditions": [
{
"type": "device_if",
"identifiers": [
{
"vendor_id": 1452,
"product_id": 641
}
]
}
],
"from": {
"key_code": "caps_lock",
"modifiers": {
"optional": ["any"]
}
},
"to": [
{
"key_code": "right_control"
}
],
"type": "basic"
}
]
},
{
"description": "Built in Keyboard: Transform § ⇨ Backtick (Fn to override)",
"manipulators": [
{
"conditions": [
{
"type": "device_if",
"identifiers": [
{
"vendor_id": 1452,
"product_id": 641
}
]
}
],
"type": "basic",
"from": {
"key_code": "non_us_backslash",
"modifiers": {
"mandatory": ["fn"],
"optional": ["any"]
}
},
"to": [
{
"key_code": "non_us_backslash"
}
]
},
{
"type": "basic",
"from": {
"key_code": "non_us_backslash",
"modifiers": {
"optional": ["any"]
}
},
"to": [
{
"key_code": "grave_accent_and_tilde"
}
]
}
]
},
{
"description": "Built in Keyboard: Backtick ⇨ Right Shift or Backtick when alone",
"manipulators": [
{
"conditions": [
{
"type": "device_if",
"identifiers": [
{
"vendor_id": 1452,
"product_id": 641
}
]
}
],
"type": "basic",
"from": {
"key_code": "grave_accent_and_tilde",
"modifiers": {
"optional": ["any"]
}
},
"to": [
{
"key_code": "right_shift",
"lazy": true
}
],
"to_if_alone": [
{
"key_code": "grave_accent_and_tilde"
}
]
}
]
},
{
"description": "Built in Keyboard: ❌ Backslash ⇨ Return or Backslash with Right Option",
"manipulators": [
{
"conditions": [
{
"type": "device_if",
"identifiers": [
{
"vendor_id": 1452,
"product_id": 641
}
]
}
],
"from": {
"key_code": "backslash",
"modifiers": {
"mandatory": [
"right_option"
],
"optional": [
"any"
]
}
},
"to": [
{
"key_code": "backslash"
}
],
"type": "basic"
},
{
"from": {
"key_code": "backslash",
"modifiers": {
"optional": [
"any"
]
}
},
"to": [
{
"key_code": "return_or_enter"
}
],
"type": "basic"
}
]
}
]
}
I didn't want to lose ISO's extra key, but I did want to use it as left Shift whenever I mistakenly reach for it, tucked away down there where Shift alone should be. The rule here neatly transforms ` to left Shift on the fly, whenever it's pressed in combination with other keys. Otherwise, it works like normal: ````. See!
I'm used to ` being up top, left of 1, so I put it up there too. It's really useful for typing gràvè àccènts and for Command + ` window cycling. But I can reclaim the §/± key by throwing Fn at it when I want. Complex rules are pretty sweet!
One of these rules is inactive: "Built in Keyboard: Backslash ⇨ Return or Backslash with Right Option". The big red cross in its name is a note to self, seeing as there's no comments allowed in JSON. I did try this one initially, but my fingers are adapting back to skinny assed ISO Return now, so the extension into \ is more trouble than it's worth. I'll delete it eventually.
And finally a numpad layer to match my SSK, Realforce and indeed the PowerBook:
Code: Select all
{
"title": "🔢 Numpad Layer",
"rules":
[
{
"description": "🔢 Hold Fn + I square keys ⇨ 🔢 Numpad",
"manipulators": [
{
"type": "basic",
"from": {
"key_code": "j",
"modifiers": {
"mandatory": ["fn"]
}
},
"to": [
{
"key_code": "keypad_1"
}
]
},
{
"type": "basic",
"from": {
"key_code": "k",
"modifiers": {
"mandatory": ["fn"]
}
},
"to": [
{
"key_code": "keypad_2"
}
]
},
{
"type": "basic",
"from": {
"key_code": "l",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_3"
}
]
},
{
"type": "basic",
"from": {
"key_code": "u",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_4"
}
]
},
{
"type": "basic",
"from": {
"key_code": "i",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_5"
}
]
},
{
"type": "basic",
"from": {
"key_code": "o",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_6"
}
]
},
{
"type": "basic",
"from": {
"key_code": "7",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_7"
}
]
},
{
"type": "basic",
"from": {
"key_code": "8",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_8"
}
]
},
{
"type": "basic",
"from": {
"key_code": "9",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_9"
}
]
},
{
"type": "basic",
"from": {
"key_code": "0",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_0"
}
]
},
{
"type": "basic",
"from": {
"key_code": "m",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_0"
}
]
},
{
"type": "basic",
"from": {
"key_code": "comma",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_0"
}
]
},
{
"type": "basic",
"from": {
"key_code": "period",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_period"
}
]
},
{
"type": "basic",
"from": {
"key_code": "hyphen",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_hyphen"
}
]
},
{
"type": "basic",
"from": {
"key_code": "equal_sign",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_plus"
}
]
},
{
"type": "basic",
"from": {
"key_code": "slash",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_slash"
}
]
},
{
"type": "basic",
"from": {
"key_code": "semicolon",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_asterisk"
}
]
},
{
"type": "basic",
"from": {
"key_code": "return_or_enter",
"modifiers": {
"mandatory": [
"fn"
]
}
},
"to": [
{
"key_code": "keypad_enter"
}
]
}
]
},
{
"description": "🔢 Insert or Option + Command + Command + Option ⇨ Toggle Numpad Layer",
"manipulators": [
{
"from": {
"key_code": "insert",
"modifiers": {
"optional": ["any"]
}
},
"type": "basic",
"to": {
"set_variable": {
"name": "numeric_keypad_mode",
"value": 1
}
},
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 0
}
]
},
{
"from": {
"key_code": "insert",
"modifiers": {
"optional": ["any"]
}
},
"type": "basic",
"to": {
"set_variable": {
"name": "numeric_keypad_mode",
"value": 0
}
},
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"from": {
"key_code": "right_option",
"modifiers": {
"mandatory": ["left_option",
"left_command",
"right_command"]
}
},
"type": "basic",
"to": {
"set_variable": {
"name": "numeric_keypad_mode",
"value": 1
}
},
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 0
}
]
},
{
"from": {
"key_code": "right_option",
"modifiers": {
"mandatory": ["left_option",
"left_command",
"right_command"]
}
},
"type": "basic",
"to": {
"set_variable": {
"name": "numeric_keypad_mode",
"value": 0
}
},
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "j"
},
"to": [
{
"key_code": "keypad_1"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "k"
},
"to": [
{
"key_code": "keypad_2"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "l"
},
"to": [
{
"key_code": "keypad_3"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "u"
},
"to": [
{
"key_code": "keypad_4"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "i"
},
"to": [
{
"key_code": "keypad_5"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "o"
},
"to": [
{
"key_code": "keypad_6"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "7"
},
"to": [
{
"key_code": "keypad_7"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "8"
},
"to": [
{
"key_code": "keypad_8"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "9"
},
"to": [
{
"key_code": "keypad_9"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "0"
},
"to": [
{
"key_code": "keypad_0"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "m"
},
"to": [
{
"key_code": "keypad_0"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "comma"
},
"to": [
{
"key_code": "keypad_0"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "period"
},
"to": [
{
"key_code": "keypad_period"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "hyphen"
},
"to": [
{
"key_code": "keypad_hyphen"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "equal_sign"
},
"to": [
{
"key_code": "keypad_plus"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "slash"
},
"to": [
{
"key_code": "keypad_slash"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "semicolon"
},
"to": [
{
"key_code": "keypad_asterisk"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
},
{
"type": "basic",
"from": {
"key_code": "return_or_enter"
},
"to": [
{
"key_code": "keypad_enter"
}
],
"conditions": [
{
"type": "variable_if",
"name": "numeric_keypad_mode",
"value": 1
}
]
}
]
}
]
}
Something I'm still not settled on is the perfect "Num Lock" toggle key. I've thrown it on Insert for my external keyboards, just because they all have that (they're mostly TKLs) and it's easily found. On my Mac's built in keyboard, however, it's a trickier call. I put it on Option + Option for a while, but acc5denta3 presses are rea33y q45te d5sr4pt5ve0, so I hid it behind a bigger combo I'm less likely to trigger falsely. Guess my fingers are lazier on those mods than I think!
Anyway, a few months into having this Air now, and I'm well pleased. Karabiner's patched this pretty wee keyboard to the point it mostly stays off my tits—what a relief!—and my external keyboards are all getting some new tricks now too. I haven't even started using app-specific logic yet, something only a host-run software like Karabiner can even dream to do. That vortex awaits me, should I dare.
(If you want to complain about my sloppy, inconsistent formatting, feel free. I am not a programmer! This JSON stuff is all new to me, and I did a lot of trial and copypasta. That's a spicy meatball!)