I am a Karabiner Elements user and had donated to it previously and even talked about some custom key mappings I use. Things had a been a bit spotty with it since macOS Big Sur but with the latest version 13.0 and macOS 11.0, 11.1, and 11.2 things were fine. However, yesterday I updated one of my Macs to 11.3 Beta and that broke Karabiner. In addition to it I noticed that my M1 Mac always reports that it restarted due to a kernel panic whenever I restart; and when I Googled for the panic report entries the first hit was Karabiner. Looks like things aren’t entirely with Karabiner and macOS Big Sur after all.
Part of me feels sorry for all macOS developers. Can’t be much fun having your software break due to ground breaking changes in macOS… it’s a game of catch-up with not much dividends, especially if you are a free product like Karabiner. Even paid products like Carbon Copy Cloner have a tough time I imagine as they have to spend time fixing things for Big Sur rather than working on new features. I started using Carbon Copy Cloner recently and was struck by this blog post:
Every year we spend hundreds of hours making changes to CCC to accommodate the new OS. Every year. CCC isn’t like other apps that can easily roll with the changes; our solution is tied so closely to the logistics of the startup process, and that happens to be something that Apple has been changing a lot since the introduction of APFS. The logic changes required to accommodate APFS volume groups alone are mind blowing. All of that time spent is subtracted from the time we can spend on feature work. To put it plainly, we spend about a quarter to half of our year just making CCC work with the next year’s OS. That’s not a shiny new feature that users can swoon about (and pay for!), it’s typically thankless work, and – fair or not – work that users have come to expect us to provide for free.
The blog post goes on to talk how things are better now with Big Sur, but it’s something I too as an end user often take for granted – that all a developer has to do is spend time creating new features (the fun and sexy part of being a developer), while in reality a major part of their time also goes into just keeping the product working across OS upgrades (the not so fun and sexy part of being a developer). I know I get irritated when a PowerShell script I’ve written stops working because something in the software (e.g. Exchange) it talks to has changed; I can only imagine how much more frustrating it must be for more complex software.
Anyways, enough digression. I decided to uninstall Karabiner as it wasn’t working at all on one machine and seemed to be causing kernel panics on the other. I use Karabiner mainly coz I have a Microsoft Sculpt keyboard and I’d like to set some keymappings for that. One of these – swapping Ctrl and Cmd is easy to do from macOS’s System Preferences itself – but the rest are what I chiefly needed Karabiner for (as I mentioned in the earlier post I want to swap the accent and \
keys as what’s shown on the keyboard is not what appears when I press it).
Hello hidutil
I remembered someone mentioned hidutil
in the Karabiner issues threads in the past so Googled on that and came across this Apple technical note. It’s pretty easy as per the article; all you need to do is run a command like this:
1 |
hidutil property --set '{"UserKeyMapping":[{"HIDKeyboardModifierMappingSrc":0x700000004,"HIDKeyboardModifierMappingDst":0x700000005},{"HIDKeyboardModifierMappingSrc":0x700000005,"HIDKeyboardModifierMappingDst":0x700000004}]}' |
This swaps the key with code 0x700000004
with the key with code 0x700000005
. You can find a table with these codes in the technical note (which in turns points to a missing page from the USB forum). I’ve take a screenshot it and will include here in case the Apple page too goes missing (but we don’t really need this screenshot as I’ll explain later).
Add 0x700000000
to the number in the table. For example the @
sign is code 0x1F
so in the hidutil
command above you will put it as 0x70000001F
. In my case I want to swap the “Grave Accent and Tilde” (0x35
) with “Non-US \ and |” (0x64
) so my command would be:
1 |
hidutil property --set '{"UserKeyMapping":[{"HIDKeyboardModifierMappingSrc":0x700000035,"HIDKeyboardModifierMappingDst":0x700000064},{"HIDKeyboardModifierMappingSrc":0x700000064,"HIDKeyboardModifierMappingDst":0x700000035}]}' |
This doesn’t return any output but I can see this keymapping (and any others) via the following command:
1 |
hidutil property --get "UserKeyMapping" |
In the --set
block above first I map 0x35
to 0x64
and then I map 0x64
to 0x35
(as I am swapping these) – that’s why I have two set of mappings. It is basically a JSON block that looks like this:
1 2 3 4 5 6 7 8 9 10 |
"UserKeyMapping": [ { "HIDKeyboardModifierMappingSrc":0x700000035, "HIDKeyboardModifierMappingDst":0x700000064 }, { "HIDKeyboardModifierMappingSrc":0x700000064, "HIDKeyboardModifierMappingDst":0x700000035 } ] |
Surviving Reboots
Turns out after a reboot the above mapping disappears. So you need to do something to make this survive that. I know macOS has an equivalent to systemd
and others called launchd
and I had dabbled with it in the past. When I Googled on creating a launchd
service file I came across another Apple note. Using the info in that and this site I made a service file that will run the above hidutil
command whenever macOS reboots.
I created the following as ~/Library/LaunchAgents/local.hidutilKeyMapping.plist
. (One of my Macs didn’t have the LaunchAgents
folder so I had to create that folder too).
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 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>local.hidutilKeyMapping</string> <key>ProgramArguments</key> <array> <string>/usr/bin/hidutil</string> <string>property</string> <string>--set</string> <string>{ "UserKeyMapping": [ { "HIDKeyboardModifierMappingSrc":0x700000035, "HIDKeyboardModifierMappingDst":0x700000064 }, { "HIDKeyboardModifierMappingSrc":0x700000064, "HIDKeyboardModifierMappingDst":0x700000035 } ] }</string> </array> <key>RunAtLoad</key> <true/> </dict> </plist> |
What I’ve done is split the earlier command into separate lines under the <array>
element. One difference is I removed the '
before and after the "UserKeyMapping"
block – I discovered that with it the file wasn’t working.
After saving I loaded it as follows:
1 |
launchctl load ~/Library/LaunchAgents/local.hidutilKeyMapping.plist |
If all went well you won’t see any output. I could confirm it had loaded though like this:
1 |
launchctl list | grep local |
My service name is local.hidutilKeyMapping
(it’s the <Label>
entry in the service file above) so that’s why I am searching for all services with the word local
in them. I could see local.hidutilKeyMapping
so that was good. Next I started it:
1 |
launchctl start local.hidutilKeyMapping |
Again, no output. But now after a reboot I can see the key mappings are created via
1 |
hidutil property --get "UserKeyMapping" |
Discovering the Generator
At this point I was pretty pleased with myself and thinking about creating this blog post. I wanted to see if there was a way of discovering the keycodes for any keys not in the above table. For instance, the Sculpt keyboard has these media keys and previously I was able to map them using Karabiner. If I could find the keycodes for them and the corresponding destination keycodes that macOS needs for controlling media I was golden. Googling on that however got me to this site: https://hidutil-generator.netlify.app/. Whoa!
This was a site where one could select source and destination keys and it will create the .plist
file I did above. That is so cool! Someone made a website to do that. I love it.
I was able to add the following mappings:
Sweet!
I then added the generated .plist
file into mine (this was just a case of me having a NIH syndrome – I could have just added the accent and \
keys to the website and replaced my .plist
file with the result). The end result is that I now have all the mappings I was using Karabiner for but without using Karabiner. :) I knew I wasn’t a heavy user of Karabiner but now I realize I didn’t even need it to begin with! This way I am using a native macOS tool for the keymapping and hopefully it is less prone to breaking between macOS updates.
That’s all for now!
Update: To reset the mappings:
1 |
hidutil property --set '{"UserKeyMapping":[]}' |