I got it working a bit quicker than expected: I’m calling it Kaleidoscope-Qukeys, as an homage to “qubits” from quantum computing. The description in the readme was written before the code, so I’m sure it needs updating. Also, it depends on a change not yet incorporated in Kaleidoscope: #255, and one just merged in KeyboardioHID.
Just read the README. Very interesting approach. What’s your experience? Does it work as expected?
So far, yes. But I’m not a fast typist, and that’s putting it mildly. I have tried most of the test-cases I can think of, and it works like I hoped it would. I don’t think I’ve tried overflowing the queue yet… =)
Once it’s a little easier to build, I should ask for a volunteer who can type at least 100 wpm on a Model01 to try it out with a few keys configured to be
shift as an alternate on commonly-used letter keys and see if it works well.
…and before I do that I should try out using
Kaleidoscope-Macros to toggle
Qukeys on and off, and make sure that works.
I’d be happy to give it a go! “Once it’s a little easier to build” = when the next bit of KeyboardHID changes are merged in?
Gave it a quick test and I wasn’t able to see any aberrant behaviour (I usually type around 90WPM, my best on the Model 01 is 104). Seems pretty cool!
It was actually Kaleidoscope #255 that hadn’t been merged yet. But it is now, and there’s a new version of Arduino-Boards that has all the necessary changes, so we are definitely at “easier to build” already.
That’s very gratifying to hear; thanks for testing it out! Can you tell me which keys you configured? (I completely failed to describe how to give proper coordinates in any of the documentation…)
I should note that I haven’t made it easy to configure the timeout yet; it’s currently set at 500ms, but I was hoping to play with it and make it as short as possible while keeping the typing error rate close to zero. (It’s a balancing act between typing errors and using modifiers with external pointing devices, though it occurs to me that one could tap a key that does nothing (
Key_NoKey) to force it to the alternate state. That’s probably not worthwhile in practice, though.)
I just put shift on the “e” key (using Dvorak, so the key labeled ‘D’) and tried to type.
I was able to figure out how to configure it by copying the example in the project directory
When I was using Karabiner to get dual use “SHFT/e” behaviour, it was possible to tweak things so that the modifier didn’t fire unless there was more than N milliseconds of overlap. That would mean, that if we have the following sequence of events:
D/U down, letter down, D/U up, letter up
Then the firmware would wait a few milliseconds before sending the “letter down” even (or until the dual use key up event, whichever is sooner).
I found that once I got the delay tuned right, it was pretty much undetectable and massively improved my typing accuracy. It’s kind of a more sophisticated debouncing (no, autocorrect, not “denouncing”) algorithm (it would probably be possible to have a universal “de-overlap” plugin that would do this sort of thing for all the keys rather than special casing for dual use).
For the sequence in your example, the algorithm that I came up with will always give you the correct result (that is, not the modifier), without worrying about how much overlap there is. It only cares about the sequence of events (and the timeout, which is much longer – probably 200-500ms). The only tuning you’d need would be if you were concerned about getting modified clicks with an external pointing device.
(I’m pretty sure using keys for mouse clicks and layer changes will work the same way as typing, but I haven’t tested that or thought it through completely.)
So if your dualUse key down and up is ⇩ and ⇧, and your secondary key ︎ (:downwards_black_arrow:) and ︎ (:upwards_black_arrow:), and nothing has happened to make you sure that ⇩ is a shift, then you should be safe sending:
⇩⬇︎⇧⬆︎ as ⇩⇧⬇︎⬆︎, and leaving ⇩⬇︎⬆︎⇧ alone. My gut says that, for typing at least, you’re only going to have to worry about disambiguating the last two keys; once you’ve got three keys held down together, the first one is definitely a modifier, but I confess I’ve not thought through all the ramifications.
This is great until you’re in a land where latency matters and accuracy of keyDown timings is king. But the only thing I can think of where that matters all that much is gaming, and if you’re serious about that, you’d be advised to have a dedicated gaming layer that does away with pretty much anything that ‘cooks’ the key events in this kind of fashion.
[WTF Unicode? Why do filled arrows work in one place, but not another?]
That’s what I thought until @algernon and @noseglasses both reported that they have data showing instances of four or more keyswitches active simultaneously during normal typing. That’s why I adjusted the algorithm, and defaulted to a limit of eight keys in the queue, since we have only ten fingers, and if we’re using them all, I don’t think that can be called “typing”.
In fact, in your first example, I don’t convert the sequence ⇩,︎,⇧,︎ to ⇩,⇧,︎,︎; it becomes ⇩,︎,[⇧ ︎] (commas separate HID reports from each other). The resulting overlap is dealt with by the host just like it deals with normal overlap from typing on a “dumb” keyboard.
And, yes, this is clearly not intended for gaming. But I did include a way to toggle the whole plugin off (and on).
I’ve actually dug into the code now. That’s clever stuff.
What happens when you get a layer shift halfway through the sequence?
I wish there was a way to test this stuff without requiring stupid agility.
The layer shift should apply before the next keypress is sent. If not, that’s an unintended bug.
Indeed. I did set the timeout high enough to confirm things worked as expected, but I’ll need people who can actually type fast to confirm that it really works in practice.
You could try my python scripted Kaleidoscope simulator.
It is not yet released but could neverheless be useful for your testing. It’s supposed to make it much more easy to generate test cases with multiple keys active at the same time.
The README provides info about how to build and run but is still in progress like the whole project.
Build the Python API doc (html) with
make doc. It is generated in the
doc/kaleidoscope directory of the build tree.
Of course, feel free to contact me if you need any help.
EDIT 1: Updated the name of the module from Kaleidoscope-Python-Wrapper to Kaleidoscope-Python.
Here’s a version of Arduino-Boards that supports everything necessary.
I will try it out sometime, thanks! Before I spend much time on that, though, I’ve got two other Kaleidoscope projects that I think are a higher priority: writing doxygen comments in the code, and exploring your plugin interface changes thoroughly. And finishing writing at least the first draft of the repository-management guide for contributors that I started.