Skip to content

Releasing keys pressed in a different layer

Yuri Khan edited this page Oct 13, 2013 · 3 revisions

There is an issue #3 where media keys behave erratically if pressed and released in a certain way.

The problem is caused by the following sequence of events:

  • Press Fn. This activates the Fn layer.
  • Press a key that generates a media keycode, e.g. F12 mapped to Volume+. The Volume+ key is marked as down and starts generating reports.
  • Now release Fn while holding down F12. This activates the main layer, while the Volume+ key is still marked as down.
  • Now release F12. Since the main layer is active, it is the normal F12 key that is released. (This has no effect, because it is not down.) The Volume+ key stays down.

Of course the workaround is to hold Fn for a little longer and release F12 first. But it’s not satisfactory.

The problem, in general, affects any keys that are different in the main and Fn layers. Additionally, it seems possible to have the same problem with internal Num Lock.

Solution scheme 1

For each key, store which modifiers were active at the time when that key went down, in bit arrays. When the key is released, use these stored flags instead of the current flags to determine the key code.

Data memory cost: 2 arrays of 144 bits each. (Internally, the keyboard pretends to have 144 keys, although only 88 are connected.) This adds up to 36 bytes.

Code cost: clumsy bit shifts and bitwise operations when decoding.

Additional drawback: does not scale to more modifiers. (The two bit arrays just took the last bytes of available data memory.)

Solution scheme 2

Same idea, different data structure. Instead of bit arrays, use an associative array. When a key is pressed, find a free slot and store the key index and the current layer number. When a key is released, look it up, use the layer number from that lookup, and clear out the slot.

Data memory cost: Per each slot, one byte for the key index, another byte for the layer number. There must be enough slots to accommodate any keys that can be pressed simultaneously. In a keyboard implementing the Boot HID protocol, this means 8 shift keys (LSuper, LAlt, LShift, LCtrl and their right-hand counterparts) and 6 normal keys. Additionally, up to one media key can be reported through the second endpoint. All that adds up to 30 bytes.

What happens if the user manages to overflow the array? Well, for example, we can just drop the new entry on the floor, and still assume current layer when releasing a key whose index is not in the associative array. This will exhibit the same problem, but in addition to releasing keys in the wrong order, the user will have to hold down 15 keys. Serves them right, I’d say.

Code cost: Easier arithmetics, but loops in the associative array lookup code. (If lookup performance proves to be an issue, it can be made faster by taking the key column number as the initial index, turning the data structure into a hash table with open addressing. But I doubt it is worth the trouble.)

This approach scales to up to 8 modifier bits, or up to 256 layers.

Solution scheme 3

Instead of storing the layer number as in scheme 2, we can store the actual key code that the key resolved to at the time of pressing. For 8-bit key codes, the data cost is the same.

This simplifies implementation a little, but will not scale if we later want 16-bit or 24-bit key codes.

Solution scheme 4

The above approaches assumed that we want to release logical keys at the time their physical keys are released, and just need to remember the mapping.

A different idea is to watch for layer changes and forcibly release all keys that are mapped differently. In the above case, when Fn is released, Volume+ will be released forcibly.

Let’s analyze the above storages in this light:

  • Bit arrays are clumsy. Will need a long loop over all bits, with a check of different mapping for each.
  • Layer number associative array is OK. A quick loop with a check for different mapping.
  • Key code associative array is also OK. Same loop, check if new layer mapping is different from stored mapping.