One-keystroke Unicode characters in QMK on macOS

Written using QMK 0.6.0 on macOS High Sierra 10.13.3

I recently picked up an ErgoDox EZ keyboard, and was excited to add some Unicode characters to it. Some may know that I dabble with Esperanto, so I wanted the ability to type characters like ŝ and ŭ with a single shortcut. Plus, ✨ emoji ✨! I know macOS has an emoji picker with Cmd + Ctrl + Space, but that’s kinda painful to do repeatedly.

So, I decided to dig into the QMK documentation and build my own keymap, including the nifty Unicode features I wanted. The documentation is great, don’t get me wrong. Starting from a copy of the default keymap for an ErgoDox EZ, I was easily able to set up multiple layers for Qwerty, Colemak and symbols, compile it, and get it flashed onto the keyboard. I even added in RGB backlighting for the current layer, for good measure. But the Unicode stuff was a bit trickier.

I created a new Unicode layer in my keymap, triggerable by holding the key. In this layer, I ended up needing three different techniques, for different things - Slack shortcodes, Esperanto characters, and real emoji.

Slack shortcodes via macros

I post a lot of trollfaces in Slack. (Who doesn’t?) I could just type :troll: all the time, but that’s annoying. Better to just use a keyboard shortcut to type that text for me - and I did this via a QMK macro.

The New Way for macros in the QMK docs worked well for this. I added TROLL into the custom keycode enum, specified TROLL in my keymap as part of my new Unicode layer, and then added the macro code in process_record_user:

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    if (record->event.pressed) {
        switch(keycode) {
            case TROLL:
                return false;

Worked perfectly. Now I can press ’ + N in Slack and I get trollfaces. Sweet.

Basic Unicode characters via UC

For any Unicode character up to 0xFFFF (which is a lot of characters, but not including emoji) QMK has your back. Unfortunately, the documentation is only a few lines long and leaves out some necessary bits and pieces, so here we go.

In my case, the Esperanto characters I wanted to use were in the valid range (less than 0xFFFF). QMK provides the UC function for these - so I can simply use UC(0x0108) in my keymap to write the character Ĉ, for example.

After compiling this and flashing it to my ErgoDox, pressing the key results in “º¡º•”, not “Ĉ”. So what gives?

This part is macOS-specific - you need to tell your OS that any hex codes received should be treated as Unicode codepoints, not as just a string of characters. To do this, you’ll need to go to System Preferences -> Keyboard -> Input Sources and use the “+” to add the Unicode Hex Input keyboard format (it’ll be listed under “Others” at the very bottom of the list.)

This won’t switch you to using that keyboard format - to do this I checked “Show input menu in menu bar” which adds a keyboard switcher to the top menu bar. Now from that menu, you can select Unicode Hex Input.

Once that’s done, you can pretty much set and forget it. (If you were using some other custom keyboard layout, well, you could easily just customize your keyboard to match what it was!) Now the keypress will work and insert the Unicode character correctly - in my case, I mapped the Esperanto characters to the same position as their ASCII equivalents, so pressing ’ + C gives me Ĉ. Perfect.

Emojis using macros

Now the cool part! Real emojis using their Unicode characters to use cross-program, not just in Slack!

Emojis have Unicode codepoints higher than 0xFFFF - the dancer emoji is 0x1F483, for example - so using UC won’t work, the keymap won’t compile. QMK claims to support higher codepoints with UNICODEMAP but I couldn’t get that to work (undeclared function X??), so I devised a different way.

I have to thank @FakeUnicode on Twitter for this one - they told me that emoji codepoints can be broken down into surrogate pairs - pairs of codepoints that are themselves both smaller than 0xFFFF.

I looked up the dancer emoji on - the UTF-32 codepoint matches what it actually is (0x0001F483), but it also can be broken down into two UTF-16 codepoints (0xD83D 0xDC83).

So what we can do, is write a macro that will type out the two UTF-16 codepoints while holding the Alt key, the way you might have typed out non-alphanumeric characters in the past. (If you hold down Alt, type 0108, then release Alt, you also get a Ĉ character.)

So like the TROLL macro, you can create another macro by adding a name for it to the custom keycodes enum, and then use that macro name in your keymap. The code for the macro is a little different though - SEND_STRING won’t just take a simple string, it’ll take a command to hold down the Alt key then type the characters for the two surrogate pair codes.

For the dancer, it looks like this as another case in the process_record_user function:

case DANCER:
    return false;

Compile, flash to the keyboard, and enjoy typing 💃 characters everywhere with a single keypress!

You can repeat this process for any emoji or Unicode character, by looking up the character in UTF-16 and creating a macro for it. Win!

Some issues you might run into

I ran into these, so you might too!

1. Pressing the key results in double output? I wanted one trollface, not two!

The default process_record_user function in the default keymap file is structured a little differently than the one in the macro documentation - one has the if (record->event.pressed) conditional inside the case statement, one has it outside.

If you’ve copied and pasted just parts of it, and your case doesn’t have the conditional either inside or outside it, then you’ll get the macro twice - once on key press and once on key release. Make sure you have a if (record->event.pressed) check in your function.

2. I get a weird string of characters, not the Unicode I wanted…

You might not have set your OS keyboard input to Unicode Hex Input, or might be on an OS other than macOS. I haven’t tested on any other OS, sorry!

3. I get some other weird behaviour when pressing the key, not Unicode!

This one stumped me for a while - on my keymap, one Esperanto character worked everywhere except in Atom - in Atom, it deleted a word instead! The problem was that part of the Unicode codepoint (the D in 0x015D) conflicted with an inbuilt shortcut in the program (Alt + D for deleting a word).

This isn’t a case that should come up too often, so in my case I simply disabled the inbuilt shortcut in Atom and then the Unicode worked as intended.

Hopefully this helps others who want to use QMK to do cool things - if you have any comments (or know how I could have gotten UNICODEMAP to work…) let me know on Twitter!