CapsLock-Control Confusion

2024-06-10

We had a keyboard running QMK firmware, where the Mod-Tap behavior seemed to be misbehaving: the configuration “tap for CapsLock, hold for Ctrl” only ever registered as Ctrl.

After some experimentation, I found that the MAGIC_CAPS_LOCK_AS_CTRL Magic Keycodes bit had been set; toggling that off resolved the issue. I suspect clearing the EEPROM would have done the same.

Lesson learned: When starting a “fresh” keyboard layout, clear out the EEPROM explicitly!

Issue report

Here’s my narrative of what happened; see below for a deeper understanding.

Note that this isn’t a bug report for QMK. As far as I can tell, everything was working as intended – just not as I naively expected!

Symptoms

We had a Ristretto keyboard sitting on the shelf for a while. Recently, the user pulled it, configured a layout with the QMK Configurator tool, and programmed it onto the keyboard.

For the most part, the layout worked fine, except for one key. The user had configured that key with MT(MOD_LCTL, KC_CAPSLOCK): using the Mod-Tap feature, a tap (short press) should toggle CapsLock, while a hold (long press) would register as “left Ctrl”.

The actual behavior was that the key always behaved as “left Ctrl” (MOD_LCTL). This wasn’t an operating-system level rebinding; we saw it on distinct Windows and Linux computers.

Debugging and suspicions

For debugging, we set up the QMK command-line environment to get more Ctrl. We exported the keymap.json file from the QMK Configurator, and used the qmk json2c command command to get a buildable keymap.

The issue reproduced when using a local build; it wasn’t a configurator problem. We changed a couple of #defines related to the Mod-Tap feature, but they also didn’t introduce any fix.

I wound up trying MT(MOD_LCTL, KC_TAB), which gave our first big clue: that key combo worked fine! This suggested there was something about CapsLock that was behaving specially.

I probably should have guessed that sooner, really. “Replace CapsLock with Ctrl” is a pretty typical keyboard customization, even without running custom firmware – I have configured my laptop to swap them.

I recognized the behavior as being equivalent to “CapsLock acts as Ctrl”: regardless of “hold” or “tap”, the key came up Ctrl.

Discovery and mitigation

A little searching turned up this Reddit thread; which doesn’t say much, but points at the Bootmagic feature.

That page notes that Bootmagic is deprecated in favor of Magic Keycodes – of which the first several deal with “CapsLock as Ctrl”. Found it!

I configured a key for QK_MAGIC_CAPS_LOCK_AS_CONTROL_OFF, flashed the keyboard, and pressed the key – and the Mod-Tap key worked as it should. This also persisted after flashing a new layout, moving to a different computer, etc.

Analysis: Persistent state in QMK

So what happened here? The keyboard had some sort of persistent state that said “treat CapsLock as Control”, that wasn’t surfaced aside from the behavior itself; that persisted even after re-flashing.

QMK can use EEPROM to store persistent settings. Looking at the source, we can see this feature is used for the Magic Keycodes: The keymap_config is read from eeconfig, updated, and written back.

As far as I can tell from the datasheet, the atmega32u4 controller used by Ristretto does not have any ECC protections. (If you know otherwise, let me know!)

So at some point, either I had set this configuration bit, or that specific bit of EEPROM had been corrupted by noise,1 or…something. And the configuration stuck in the EEPROM until cleared.

What I’ve learned

QMK is pretty easy to use, and quite powerful! I’ve mostly been using graphical configurators for my keyboards, but I might switch over to get that lower-level control.

When notionally starting a new layout, explicitly clear the EEPROM to clean out any persistent state.


  1. As noted, this keyboard had been unplugged for quite a while – probably over a year. The datasheet claims retention for “100 years at 25°C”, but I don’t know if that’s assuming the chip is online or offline. ↩︎