You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Kaleidoscope/README.md

7.2 KiB

Kaleidoscope-Qukeys

status Build Status

Concept

This Kaleidoscope plugin will allow you to overload keys on your keyboard so that they produce one keycode (i.e. symbol) when tapped, and a different keycode -- most likely a modifier (e.g. shift or alt) when held. There are already two Kaleidoscop plugins that provide this same functionality (DualUse and SpaceCadet), but those plugins were designed primarily to allow you to overload keys whose primary function is as a modifier, and use them to produce printable keycodes when tapped. The Qukey design is different; it's meant to overload letter keys that are usually tapped and let you use them as alternate modifier keys (though the design is flexible enough that any keycode can be the secondary one -- I just haven't thought of a realistic use for that yet).

Design goals

  • I want users to be able to type at fast pace on Qukeys, without accidentally triggering the secondary function, and preserving the order of input keystrokes.

  • I want users to be able to rapidly invoke the secondary function of a Qukey without having to wait for a timeout.

  • I want Qukeys to be useful as modifiers with other input devices (e.g. a mouse), without having to wait a long time for a timeout. I'm guessing that 200ms is acceptable, but not anything longer than that.

  • I want physical keys on the keyboard to be defined independently of each other. I don't want the plugin to act on keycodes; simply translating one keycode to another.

Schrödinger's Key

The idea is that a Qukey will work just like a particle in a superposition of quantum states until some event causes the "waveform" to "collapse" into just one of the two states. (The name "qukey" is a reference to "qubit", a term used in quantum computing.)

When a Qukey is pressed, its state will be indeterminate (like a superposition of quantum states); no keycode for that key will be transmitted to the host until some other event (or sequence of events) causes the state to be decided. Once the state is decided, the Qukey will stay in that state until the key is released. The possible triggers for "collapsing" the state of a Qukey are:

  • Timeout

  • Release of the qukey

  • Press and subsequent release of another key

Timeout

When a qukey times out, it falls into its secondary state (probably a modifier).The timeout should be fairly short ­ just a bit longer than the longest "reasonable" time of a tap, such that you very rarely (ideally never) hold a qukey long enough to trigger the secondary function when intending a tap, but short enough that you don't feel a need to wait a while to use it with a mouse.

Release of qukey

If the qukey is released before it times out, and before any other keys are pressed, it falls into its primary state. This is slightly different from the timeout case, because we then add the qukey's primary keycode to the HID report when the key toggles off (rather than when it toggles on, which is the behaviour of a normal key). It will get cleared at the end of the cycle, but there's still no need to send any extra HID reports.

Interactions with subsequent keys

This is where things get tricky. Because fast typists might have multiple keys pressed simultaneously, we need to keep track of a sequence of subsequent keypresses, starting with the first qukey to toggle on. Basically, if any subsequent key is released before a qukey that was pressed before it, that qukey should fall into its secondary state (e.g. a modifier).

In order to do this, we need to suppress the output of any subsequent keys, as well as the qukey. Basically, this means storing a list of keys that have been pressed, but which have not affected the report yet. This also means that when a key in that list toggles off, every qukey preceding it in the list has its state decided.

When a subsequent key is released, we now need to send a series of HID reports to preserve the order that the keypresses were detected by the keyboard. To do that, we add the one keycode at a time to the report and send it, until we reach the next qukey in the list that has an indeterminate state.

The really tricky part is that the key releases might come in any order, so if we have a sequence that goes like this, we could get into trouble:

  1. press qk(A,shift)
  2. press B
  3. press C
  4. press qk(D,ctrl)
  5. press space
  6. release B

In this case, B is released after the second qukey was pressed, but I don't think it's actually a problem. Maybe.

We might also send a series of reports when a qukey times out, of course, but this is much more straightforward.

Another way to address overlap

An alternative method is to only allow the qukey to fall into its secondary (modifier) state if there is only one non-qukey press following. So, in the previous example, once step 3 was reached, the qukey would become A. I don't think this works as well, but it would err more on the side of defaulting to the primary key, which would mean fewer unintended modifiers while typing, and enforce (slightly) more deliberate usage to get the secondary keycode. This would make for another interesting possibility, though divergent from the idea that I started with ­ the state could change while the key is pressed:

  1. press qk(A,shift)
  2. press B
  3. press C -> output: abc
  4. release B -> output: shift held?

It wouldn't require any additional data structures, so the algorithm to use could be user-configurable.

Data structures

I want to store a table, indexed by key id (layer, row & column). Each entry would be an object containing:

  • primary keycode (or Key object)
  • secondary keycode (or Key object)
  • time limit (set when key toggles on, based on timeout)
  • flags (bitfield ­ maybe useful)

I think I need the flags byte in order to signal which state the qukey has once its state has collapsed, but maybe I can get away with just using the time limit value. I could set that to zero (or maybe some other constant?) to signal that the qukey is in its primary state, and if the state collapses to secondary before the timeout, just reset the time limit to millis(). Or maybe I never need that, because it should only send the primary keycode as a "tap" in a single report.

In addition, I need to store a list of keypresses, ideally in something like std::queue, though I think that's not available to Arduino (it looks like someone has written a QueueArray library, though). This would just be a FIFO buffer of key ids (row & column ­ maybe layer, too, but that might even be undesirable, because we might try to expand Qukey to allow layer switching, too).