How to do an em-dash in QMK and ZMK keyboard firmware
keyboardsIt’s time to reclaim the em-dash.
A common refrain is that the use of an em-dash in writing is a clear sign sign that it was written by an AI (or an LLM). This kills me. Up until really very recently, the use of an em-dash in writing was a clear sign that someone knew how to use punctuation (as was the use of a semi-colon).
I have always been a big user of em-dashes, mostly for parenthetical comments, which I tend to do a lot because that’s just how my brain works. Actually the more accurate truth is that I use a lot of en-dashes as em-dashes, because typographically I like how they look better, but I do this by preference, not because I don’t know what is correct.
Inserting an em-dash (shift-option-hyphen) or an en-dash (option-hyphen) on a Mac is both easy and mnemonically-consistent. Doing so on Windows is neither of those things. As such, I’ve always programmed my keyboards to make it easy. Here is how you do it (or how I do it, anyway) on the two major open-source keyboard firmwares: qmk and zmk.
The approach in both is pretty similar – you want a macro that outputs alt-0,1,5,0 for an en-dash and alt-0,1,5,1 for an em-dash. Technically those are supposed to be keypad numbers, but I’m not sure how much difference it makes in paractice.
Both of these firmwares (sounds weird pluralising it like that) also have graphical configurators, which I have mostly not touched. You can probably also use those to create the macros and assign it to a key.
ZMK
Most of the keyboards I commonly use are wireless so mostly I use ZMK these days.
To create the macros you need the following in your .keymap file.
macros {
ZMK_MACRO(en_dash,
wait-ms = <40>;
tap-ms = <40>;
bindings
= <¯o_press &kp LALT>
, <¯o_tap &kp KP_N0 &kp KP_N1 &kp KP_N5 &kp KP_N0>
, <¯o_release &kp LALT>
;
)
ZMK_MACRO(em_dash,
wait-ms = <40>;
tap-ms = <40>;
bindings
= <¯o_press &kp LALT>
, <¯o_tap &kp KP_N0 &kp KP_N1 &kp KP_N5 &kp KP_N1>
, <¯o_release &kp LALT>
;
)
};
If you have a big enough keyboard, you can just use &em_dash and &em_dash as keys. Personally, I create a tap-dance behaviour and assign that to a key, so one tap is the normal key, two taps is an en-dash and three taps is an em-dash. To see this in context you can have a look at my keymap file for a BLE Corne.
QMK
I don’t use QMK nearly as much as I used to, so I’m not sure how current my method is here, but I still have boards that this basic approach works for, so hopefully it’s still good.
The QMK equivalent to a macro is a SEND_STRING command with a bunch of key tap commands. You want the following in your keymap.c file. Unless you have a very simple keymap, you’ll probably have more than just that in these sections of the file, so don’t just go copy-pasta without knowing what you’re doing (I think I’ve extracted out the relevant bits, but the full file is linked below if I’ve missed something important – I don’t know C well enough to know if this is complete).
enum custom_keycodes {
EM_DASH = SAFE_RANGE,
EN_DASH
};
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case EM_DASH:
if (record->event.pressed) {
// when keycode EM_DASH is pressed
SEND_STRING(SS_LALT(SS_TAP(X_KP_0) SS_TAP(X_KP_1) SS_TAP(X_KP_5) SS_TAP(X_KP_1)));
} else {
// when keycode EM_DASH is released
}
break;
case EN_DASH:
if (record->event.pressed) {
// when keycode EN_DASH is pressed
SEND_STRING(SS_LALT(SS_TAP(X_KP_0) SS_TAP(X_KP_1) SS_TAP(X_KP_5) SS_TAP(X_KP_0)));
} else {
// when keycode EN_DASH is released
}
break;
}
return true;
};
Then, again, you use EN_DASH and EM_DASH as key codes in your layout section.
Generally I’m using small split keyboards so I’m doing something a bit weird with my examples. On my Lulu I created an EX_DASH function which works like the Mac (en-dash when alt-hyphen and em-dash when shif-alt-hyphen). On my Unicorne I used tap_dance to make it work like the BLE Corne, above.