Skip to main content

Using Norman keyboard layout with IME inputs

·3 mins

[Update Sept 3, 2024]: After using this for a few months, I’ve found that this solution has a strange N-key like bug. If I hit certain keys quickly back-to-back, the first key ends up getting repeated and the second key gets lost. For example, typing “t” (which is “k” in Norman) followed by “k” (which is “i” in Norman), results in “kk”. This particular combination is annoying, because it appears in words like “kick”. I haven’t found anything about this bug online.

I’ve been using the Norman keyboard layout for many years. It works great for me, and I love how it noticably reduces finger movement. Usually I use the official keyboard layout package for MacOS and Windows. However, in Sonoma (or thereabouts), MacOS changed the way that the keyboard layouts work with IME input types. Previously, as long as I selected the Norman layout first before switching to an IME layout (like Hindi or Chinese), the keyboard layout would continue to be Norman under-the-hood. That was exactly what I wanted, because I wanted to use the Norman layout in conjunction with the IME input. Since these changes, that’s no longer possible. Instead, it seems that the IME layout is hard-coded to a QWERTY layout inside as implied by the layout settings:

IME Layout Settings

I finally found a solution for this problem in an unlikely place. Instead of using the official layout package, I’m using hidutil and Hammerspoon to remap the keys into the Norman layout. Now my keyboard works perfectly with IME layouts. Even better, hidutil supports remapping keys for specific devices, so I’m only remapping keys for the internal laptop keyboard. My external keyboard, a Moonlander mechanical keyboard, continues to work using my custom firmware layout tuned for Norman. This means that I can type on my external keyboard or on the internal keyboard without needing to change the keyboard layout. Previously, the MacOS keyboard layout would need to be set to QWERTY while using my Moonlander to avoid double conversions: tapping “t” on my Moonlander would send a “t” to MacOS which would get remapped to “k” for Norman. I solved this problem before by using Hammerspoon to automatically change the keyboard layout when the Moonlander was attached/detached, but that meant that the internal keyboard was stuck on QWERTY as long as the Moonlander was attached. Not ideal.

Here are the steps I used:

  1. Allow hidutil to run with sudo without prompting for a password. This is required since Sonoma. Created /etc/sudoers.d/01-jdve-hidutil with the following contents:
jdve ALL = (ALL) NOPASSWD: /usr/bin/hidutil

This gives only my user the ability to run hidutil using sudo without prompting for a password.

  1. Forked foundation_remapping into my .hammerspoon configs to run hidutil with sudo. Also updated it to support additional filtering parameters that it didn’t support before.

  2. Remapped the keyboard according to the Norman layout using foundation_remapping. The most important part here is the device filtering rules to match the internal keyboard on my MacBook Pro:

local FRemap = require('foundation_remapping')
local remapper = FRemap.new({vendorID=0, productID=0, locationID=0x31, primaryUsagePage=1})
remapper:remap('e', 'd')
remapper:remap('r', 'f')
remapper:remap('t', 'k')
remapper:remap('y', 'j')
remapper:remap('i', 'r')
remapper:remap('o', 'l')
remapper:remap('p', ';')
remapper:remap('d', 'e')
remapper:remap('f', 't')
remapper:remap('h', 'y')
remapper:remap('j', 'n')
remapper:remap('k', 'i')
remapper:remap('l', 'o')
remapper:remap(';', 'h')
remapper:remap('n', 'p')
remapper:register()

Done!