New Gesture Plugin Ideas

I think there are a couple of gestures that would be useful to have as plugins, but I want to get an idea of how many people would be interested.

First, I would love to have an easy way to specify an arpeggio across two keys. Spacemacs does this by default for an escape bindings where if you roll your pointer and middle fingers over the f and d key it will eat the key presses and send an escape instead. This is awesome because if you pick your letters well so that they don’t come after each other often, you basically get key bindings for free!

I have manually built this into my firmware here:

Of course my version is hard coded to work with the f and d keys located in a normal qwerty location, but I suspect that the same technique could be used in plugin form. I’m super bad at c so it’ll take me a while to do it, but I plan on working on it in the coming weeks.

Second, I think some way to specify that accidental presses of a particular key should be ignored. For example, I have rebound the tab key on my layout to be a parenthesis key. This key uses tap dance to insert different kinds of brackets: first press (, second press {, third [. The problem is that to achieve this the firmware has to input a shift key so if I accidentally press the paren key while pressing g it does: (G(( which is super not ideal. I think this is partially a bug in tap dance, but is also symptomatic of a type of gesture which I think would be useful: a key which requires time on other side to get pressed. This would be mostly useful for mode switch keys such as the default num pad key which you will only want when you are in between typing prose etc. The idea would be that the key wouldn’t be accidentally triggered by sloppy typing. I’m not sure how useful this would be but it might be worth thinking about.

Lastly, I think coming up with a cleaner way to write these gestures would be worth while. I’m still getting oriented in the way most of these plugins are built, but I think there is a fair amount of overlap which could be extracted out either into a support plugin for event processing such as @noseglasses’s papageno library, or just a library for better emulating key strokes. I had to go out of my way to save a key and the state of the keyboard so that I could send it later, and I believe that this is useful functionality that shouldn’t have to be repeated in every plugin.

What are your thoughts?


I like the idea of requiring a key to be held for a minimum duration in order for it to be processed. I believe this is basically what Apple does for CapsLock by default on macOS (of course, there it’s done on the host, not in the keyboard). Doing that for for certain classes of keys (e.g. LockLayer()) or certain keycodes would be only slightly tricky — as is almost always the case, rollover makes things complicated. Another thing that would complicate matters would be interactions with other plugins that do similar things — like Qukeys, which also delays keypresses, but preserves their order.

Speaking of Qukeys, I had a quick look at your code, and I see that you and I had very similar approaches to dealing with the HID reports and loop prevention. One thing jumped out at me, though: when you use a timer like that (computing end_time, and comparing millis() to it, you have a (very small, in this case) risk of getting an error when the integer overflows. It’s about once every two months, I think, in this case, so it’s not a big deal, but you can eliminate that overflow problem by storing start_time instead, and comparing that to current_time - timeout. Then you can safely truncate the output of millis(), and cast all the time values to 16-bit integers (which would have a problem ~once/minute otherwise).

Oops. I got that wrong, actually: you have to compare (current_time - start_time) > timeout to avoid the overflow problem. Both integers in the comparison have to be guaranteed to be smaller than the largest integer the type can represent.

(Sorry for nit-picking your code more, but I noticed another thing that might cause you trouble at some point):

Because you’re checking for the timeout in the post-clear loop hook, if you have any other keycodes in the report (e.g. holding down another key), those keycodes will get dropped from the report when you send it because you’re copying the current report (which is empty) instead of the previous report. It’s probably better to send a modified version of the previous report instead, even in the event handler hooks, because at that point, the report is still (maybe) incomplete.

Very interesting. I actually think I filched the timing code from the duel use plugin: Might be worth while filing a bug…

As for the HID reports, there is a reason they are similar. I took the idea from QuKeys :).

I very much appreciate your critiques. I believe at one point I was modifying the previous report just as you suggested (and as the comments say in your QuKeys) but I think I swapped it out for the current report at one point to test something and forgot to put it back. You are totally correct and I have swapped it back.

There are still a couple of bugs in my existing solution that I have not quite figured out. First: pressing and holding the f key will cause the delay (expected) but then type 2 fs before delaying for the repeated key as is normal on windows. I’m not sure what is going on here, but a similar issue can be reproduced by holding down multiple keys before holding down f. With normal keys holding down a new key will stop repeating the old key, and start repeating the new key. With my f key however it will start repeating the sequence of all keys currently held down. I have yet to come to a case where this causes me problems, but the inconsistency bugs me :stuck_out_tongue:. I think it has to do with how I am sending hid reports, but I don’t fully understand it as I stole it from the QuKeys repo, so I’m still working on it.

How fast are those keys repeating? And does it happen with all the letter/number keys?

It almost certainly happens because reports are getting sent that are missing whatever the held key is (and then normal reports are sent with the held key added back in). This usually results in very fast repeats. And it’s usually scan order dependent; if it affects keys on the row below the F, but not ones above it, then it’s because you’re sending a partial report before the scan is finished.