-
-
Notifications
You must be signed in to change notification settings - Fork 39.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement double tap keys #426
Comments
I have an idea how to implement this nicely, that also includes the ability to run a custom function a'la With |
I have the beginnings done on my f/tap-dance branch. Not quite there yet, and there are a number of things I don't like about it, but it's a start. It also supports any number of taps, and makes it possible to do custom stuff, too. So you can have a hundred-tap action, for the most persistent folk. What it does, is that in
There is another important part of the functionality: I tried calling The next steps are:
Any hints about the naming, or why the matrix scan stuff doesn't work as I expected it would be much appreciated. I also made a branch for my own layout that uses this feature. |
Expanding on the hundred-tap thing: imagine an easter egg, where if you tap a certain key 100 times, fast, you get some nice feedback from your keyboard: sounds, crazy led dance, or backlight breathing furiously, and so on. Based on the above code, this is entirely possible with very little additional code. |
Looking at how the other macros like We'd have a single array of keycode pairs: We will also need a third macro, that the user can paste into their keymap, after the keymap itself, so that the tap pairs be available. We can't do that in a header, because those get included before the macros run. Then, in This way, the preprocessor would do most of the work, and changes could be contained in a few places. But doing this is a tad boring, and I don't need it, so I have little reason to work on it, now that I wrote it down :) |
Very cool work, @algernon -- but from reading your description and looking at the commit, I am uncertain as to where you landed in terms of the actual "interface". What's the current syntax your feature uses? (can just link to the spot where you're using it if that's easier) |
Myeah, the diff of the commit in my repo is hard to read, indeed. Here's how to use it:
And that's it. Not the friendliest thing, but it's a start. |
Hm, thinking further, I think I have an idea how to do the I'll go into details tomorrow (CEST), once I PoC'd it up. It's a fairly simple solution, friendlier than what I have now, but not as user-friendly as |
sounds good! Thank you :) |
Making progress... my issue currently is that Will push my code in a bit, and write a short guide on how to use it. |
Updated my branch, the current syntax is:
I have an idea how to extend this further, so one can have custom functions as callbacks, but I ran out of keyboarding time for today, and will have to do some work. The idea is to turn have an array of these: typedef enum {
QK_TAP_PAIR,
QK_TAP_FN
} qk_tap_type;
typedef void (*qk_tap_user_t) (td_state_t *state);
typedef struct {
qk_tap_type type;
union {
struct {
uint16_t kc1;
uint16_t kc2;
} pair;
qk_tap_user_t fn;
};
} qk_tap_action_t; If the type is pair, we call a function that does the right thing based on the tap count. If the type is fn, we call a user defined function with the current tap dance state, and that fn is free to do whatever it wants. I may be able to get this done tomorrow morning. Meanwhile, suggestions on naming, or other issues would be very useful to me. The next issue I have, is that for this to work, I need the array defined, and I can't easily do a default for that. This means that either we need to update all keymaps to have an empty array, or have the feature be disabled by default. I'm leaning towards the latter, that's less noise accross the board. |
So, last things first: Disabling by default is the way to go, I agree. Then we'll put it in some default/experimental keymaps and in the docs, and in the upcoming graphical configurator, and in time it'll find its way into more and more keymaps. As for the design of the code: I'm an MVP kinda guy. :) I see you're taking it one step further with So I guess my only suggestion here would be to target double tap first and then extend once people have had a chance to actually use it -- but of course this is just a suggestion. :) |
I need the fn stuff, for things like So I'm focusing on making it possible for me to do what I want, while still having a reasonably easy interface for others to use this feature. What do you think about |
Almost ready, but I'm working from home today, so I can't test if what I write actually works, only that it compiles. Will push the latest implementation to a branch, and do a detailed update / tour, in case someone is brave enough to test it over the weekend while I'm away :) Teaser: const qk_tap_dance_action_t tap_dance_actions[] = {
[CT_CLN] = ACTION_TAP_DANCE_FN (ang_tap_dance)
,[CT_SE] = ACTION_TAP_DANCE_DOUBLE(KC_SPC, KC_ENT)
}; It needs to be enablef with |
Pushed my latest changes, see algernon@852f95d85f9222aefeaa970ed0e931b8ca9135f1 for a detailed explanation, and an example. As I was not able to test this yet, there are likely bugs in it, I feel that some of the functionality will not work as one would expect, but this is as far as I can go without testing it on a real keyboard. I'll come back to the topic on Monday, when I'm at work, at the keyboard. If anyone wants to give it a go, there's an example in the commit message, and the f/e-experiment of my layout also uses this new feature. To test that, once flashed, press |
Couple of things I want to be able to do, all of which are reasonably easy to implement, based on the code I have now:
For this, we will need the pressed state, and a way to signal to the caller when to reset the counter (but we will need the latter anyway). Should be about half an hour of work to get this done, so will do that on monday. This would allow me to have a |
Tap-and-hold support would be great! I've got multiple use cases for this. For example, I'd like my
|
That scenario would be a bit harder to implement, I think, but with |
That is super cool, @algernon! And I totally see why you need the Fn stuff now -- the I am also dreaming of a "hyperspace cadet shift" where the Shift is a Shift key -- single tap sends parens (like now), but double shift key tap sends brackets. Insane :) not sure if that would be feasible with your new hold implementation, just a potential use case I'm excited about. |
I haven't had a chance to catch-up and dive into the magic behind everything yet, but this is awesome - exactly what I was imagining with the process_record stuff :) Just wanted to mention that I have the enum {
KC_CUSTOM = SAFE_RANGE,
KC_CUSTOM_2
}; for custom functions in the keymap. Obviously when we merge in the tap dance stuff, it'll have its own range defined in #define TD(n) (QK_TAP_DANCE + n) |
Updated my branch, the code is tested now, and after a few little fixes, it works like a charm. The last remaining issue is that I would like to hook the timeout stuff into I'll play with the implementation for a few more hours, and if all goes well, I'll open a PR. |
Darn. It looks like some - if not all - keyboards override the |
Branch updated again, this time PR coming soon! |
* quantum: Add a tap dance feature With this feature one can specify keys that behave differently, based on the amount of times they have been tapped, and when interrupted, they get handled before the interrupter. To make it clear how this is different from `ACTION_FUNCTION_TAP`, lets explore a certain setup! We want one key to send `Space` on single tap, but `Enter` on double-tap. With `ACTION_FUNCTION_TAP`, it is quite a rain-dance to set this up, and has the problem that when the sequence is interrupted, the interrupting key will be send first. Thus, `SPC a` will result in `a SPC` being sent, if they are typed within `TAPPING_TERM`. With the tap dance feature, that'll come out as `SPC a`, correctly. The implementation hooks into two parts of the system, to achieve this: into `process_record_quantum()`, and the matrix scan. We need the latter to be able to time out a tap sequence even when a key is not being pressed, so `SPC` alone will time out and register after `TAPPING_TERM` time. But lets start with how to use it, first! First, you will need `TAP_DANCE_ENABLE=yes` in your `Makefile`, because the feature is disabled by default. This adds a little less than 1k to the firmware size. Next, you will want to define some tap-dance keys, which is easiest to do with the `TD()` macro, that - similar to `F()`, takes a number, which will later be used as an index into the `tap_dance_actions` array. This array specifies what actions shall be taken when a tap-dance key is in action. Currently, there are two possible options: * `ACTION_TAP_DANCE_DOUBLE(kc1, kc2)`: Sends the `kc1` keycode when tapped once, `kc2` otherwise. * `ACTION_TAP_DANCE_FN(fn)`: Calls the specified function - defined in the user keymap - with the current state of the tap-dance action. The first option is enough for a lot of cases, that just want dual roles. For example, `ACTION_TAP_DANCE(KC_SPC, KC_ENT)` will result in `Space` being sent on single-tap, `Enter` otherwise. And that's the bulk of it! Do note, however, that this implementation does have some consequences: keys do not register until either they reach the tapping ceiling, or they time out. This means that if you hold the key, nothing happens, no repeat, no nothing. It is possible to detect held state, and register an action then too, but that's not implemented yet. Keys also unregister immediately after being registered, so you can't even hold the second tap. This is intentional, to be consistent. And now, on to the explanation of how it works! The main entry point is `process_tap_dance()`, called from `process_record_quantum()`, which is run for every keypress, and our handler gets to run early. This function checks whether the key pressed is a tap-dance key. If it is not, and a tap-dance was in action, we handle that first, and enqueue the newly pressed key. If it is a tap-dance key, then we check if it is the same as the already active one (if there's one active, that is). If it is not, we fire off the old one first, then register the new one. If it was the same, we increment the counter and the timer. This means that you have `TAPPING_TERM` time to tap the key again, you do not have to input all the taps within that timeframe. This allows for longer tap counts, with minimal impact on responsiveness. Our next stop is `matrix_scan_tap_dance()`. This handles the timeout of tap-dance keys. For the sake of flexibility, tap-dance actions can be either a pair of keycodes, or a user function. The latter allows one to handle higher tap counts, or do extra things, like blink the LEDs, fiddle with the backlighting, and so on. This is accomplished by using an union, and some clever macros. In the end, lets see a full example! ```c enum { CT_SE = 0, CT_CLN, CT_EGG }; /* Have the above three on the keymap, TD(CT_SE), etc... */ void dance_cln (qk_tap_dance_state_t *state) { if (state->count == 1) { register_code (KC_RSFT); register_code (KC_SCLN); unregister_code (KC_SCLN); unregister_code (KC_RSFT); } else { register_code (KC_SCLN); unregister_code (KC_SCLN); reset_tap_dance (state); } } void dance_egg (qk_tap_dance_state_t *state) { if (state->count >= 100) { SEND_STRING ("Safety dance!"); reset_tap_dance (state); } } const qk_tap_dance_action_t tap_dance_actions[] = { [CT_SE] = ACTION_TAP_DANCE_DOUBLE (KC_SPC, KC_ENT) ,[CT_CLN] = ACTION_TAP_DANCE_FN (dance_cln) ,[CT_EGG] = ACTION_TAP_DANCE_FN (dance_egg) }; ``` This addresses #426. Signed-off-by: Gergely Nagy <[email protected]> * hhkb: Fix the build with the new tap-dance feature Signed-off-by: Gergely Nagy <[email protected]> * tap_dance: Move process_tap_dance further down Process the tap dance stuff after midi and audio, because those don't process keycodes, but row/col positions. Signed-off-by: Gergely Nagy <[email protected]> * tap_dance: Use conditionals instead of dummy functions To be consistent with how the rest of the quantum features are implemented, use ifdefs instead of dummy functions. Signed-off-by: Gergely Nagy <[email protected]>
…#460) * non-working commit * working * subprojects implemented for planck * pass a subproject variable through to c * consolidates clueboard revisions * thanks for letting me know about conflicts.. * turn off audio for yang's * corrects starting paths for subprojects * messing around with travis * semicolon * travis script * travis script * script for travis * correct directory (probably), amend files to commit * remove origin before adding * git pull, correct syntax * git checkout * git pull origin branch * where are we? * where are we? * merging * force things to happen * adds commit message, adds add * rebase, no commit message * rebase branch * idk! * try just pull * fetch - merge * specify repo branch * checkout * goddammit * merge? idk * pls * after all * don't split up keyboards * syntax * adds quick for all-keyboards * trying out new script * script update * lowercase * all keyboards * stop replacing compiled.hex automatically * adds if statement * skip automated build branches * forces push to automated build branch * throw an add in there * upstream? * adds AUTOGEN * ignore all .hex files again * testing out new repo * global ident * generate script, keyboard_keymap.hex * skip generation for now, print pandoc info, submodule update * try trusty * and sudo * try generate * updates subprojects to keyboards * no idea * updates to keyboards * cleans up clueboard stuff * setup to use local readme * updates cluepad, planck experimental * remove extra led.c [ci skip] * audio and midi moved over to separate files * chording, leader, unicode separated * consolidate each [skip ci] * correct include * quantum: Add a tap dance feature (#451) * quantum: Add a tap dance feature With this feature one can specify keys that behave differently, based on the amount of times they have been tapped, and when interrupted, they get handled before the interrupter. To make it clear how this is different from `ACTION_FUNCTION_TAP`, lets explore a certain setup! We want one key to send `Space` on single tap, but `Enter` on double-tap. With `ACTION_FUNCTION_TAP`, it is quite a rain-dance to set this up, and has the problem that when the sequence is interrupted, the interrupting key will be send first. Thus, `SPC a` will result in `a SPC` being sent, if they are typed within `TAPPING_TERM`. With the tap dance feature, that'll come out as `SPC a`, correctly. The implementation hooks into two parts of the system, to achieve this: into `process_record_quantum()`, and the matrix scan. We need the latter to be able to time out a tap sequence even when a key is not being pressed, so `SPC` alone will time out and register after `TAPPING_TERM` time. But lets start with how to use it, first! First, you will need `TAP_DANCE_ENABLE=yes` in your `Makefile`, because the feature is disabled by default. This adds a little less than 1k to the firmware size. Next, you will want to define some tap-dance keys, which is easiest to do with the `TD()` macro, that - similar to `F()`, takes a number, which will later be used as an index into the `tap_dance_actions` array. This array specifies what actions shall be taken when a tap-dance key is in action. Currently, there are two possible options: * `ACTION_TAP_DANCE_DOUBLE(kc1, kc2)`: Sends the `kc1` keycode when tapped once, `kc2` otherwise. * `ACTION_TAP_DANCE_FN(fn)`: Calls the specified function - defined in the user keymap - with the current state of the tap-dance action. The first option is enough for a lot of cases, that just want dual roles. For example, `ACTION_TAP_DANCE(KC_SPC, KC_ENT)` will result in `Space` being sent on single-tap, `Enter` otherwise. And that's the bulk of it! Do note, however, that this implementation does have some consequences: keys do not register until either they reach the tapping ceiling, or they time out. This means that if you hold the key, nothing happens, no repeat, no nothing. It is possible to detect held state, and register an action then too, but that's not implemented yet. Keys also unregister immediately after being registered, so you can't even hold the second tap. This is intentional, to be consistent. And now, on to the explanation of how it works! The main entry point is `process_tap_dance()`, called from `process_record_quantum()`, which is run for every keypress, and our handler gets to run early. This function checks whether the key pressed is a tap-dance key. If it is not, and a tap-dance was in action, we handle that first, and enqueue the newly pressed key. If it is a tap-dance key, then we check if it is the same as the already active one (if there's one active, that is). If it is not, we fire off the old one first, then register the new one. If it was the same, we increment the counter and the timer. This means that you have `TAPPING_TERM` time to tap the key again, you do not have to input all the taps within that timeframe. This allows for longer tap counts, with minimal impact on responsiveness. Our next stop is `matrix_scan_tap_dance()`. This handles the timeout of tap-dance keys. For the sake of flexibility, tap-dance actions can be either a pair of keycodes, or a user function. The latter allows one to handle higher tap counts, or do extra things, like blink the LEDs, fiddle with the backlighting, and so on. This is accomplished by using an union, and some clever macros. In the end, lets see a full example! ```c enum { CT_SE = 0, CT_CLN, CT_EGG }; /* Have the above three on the keymap, TD(CT_SE), etc... */ void dance_cln (qk_tap_dance_state_t *state) { if (state->count == 1) { register_code (KC_RSFT); register_code (KC_SCLN); unregister_code (KC_SCLN); unregister_code (KC_RSFT); } else { register_code (KC_SCLN); unregister_code (KC_SCLN); reset_tap_dance (state); } } void dance_egg (qk_tap_dance_state_t *state) { if (state->count >= 100) { SEND_STRING ("Safety dance!"); reset_tap_dance (state); } } const qk_tap_dance_action_t tap_dance_actions[] = { [CT_SE] = ACTION_TAP_DANCE_DOUBLE (KC_SPC, KC_ENT) ,[CT_CLN] = ACTION_TAP_DANCE_FN (dance_cln) ,[CT_EGG] = ACTION_TAP_DANCE_FN (dance_egg) }; ``` This addresses #426. Signed-off-by: Gergely Nagy <[email protected]> * hhkb: Fix the build with the new tap-dance feature Signed-off-by: Gergely Nagy <[email protected]> * tap_dance: Move process_tap_dance further down Process the tap dance stuff after midi and audio, because those don't process keycodes, but row/col positions. Signed-off-by: Gergely Nagy <[email protected]> * tap_dance: Use conditionals instead of dummy functions To be consistent with how the rest of the quantum features are implemented, use ifdefs instead of dummy functions. Signed-off-by: Gergely Nagy <[email protected]> * Merge branch 'master' into quantum-keypress-process # Conflicts: # Makefile # keyboards/planck/rev3/config.h # keyboards/planck/rev4/config.h * update build script
i guess this can be closed now, because of |
@pvinis absolutely! |
…qmk#460) * non-working commit * working * subprojects implemented for planck * pass a subproject variable through to c * consolidates clueboard revisions * thanks for letting me know about conflicts.. * turn off audio for yang's * corrects starting paths for subprojects * messing around with travis * semicolon * travis script * travis script * script for travis * correct directory (probably), amend files to commit * remove origin before adding * git pull, correct syntax * git checkout * git pull origin branch * where are we? * where are we? * merging * force things to happen * adds commit message, adds add * rebase, no commit message * rebase branch * idk! * try just pull * fetch - merge * specify repo branch * checkout * goddammit * merge? idk * pls * after all * don't split up keyboards * syntax * adds quick for all-keyboards * trying out new script * script update * lowercase * all keyboards * stop replacing compiled.hex automatically * adds if statement * skip automated build branches * forces push to automated build branch * throw an add in there * upstream? * adds AUTOGEN * ignore all .hex files again * testing out new repo * global ident * generate script, keyboard_keymap.hex * skip generation for now, print pandoc info, submodule update * try trusty * and sudo * try generate * updates subprojects to keyboards * no idea * updates to keyboards * cleans up clueboard stuff * setup to use local readme * updates cluepad, planck experimental * remove extra led.c [ci skip] * audio and midi moved over to separate files * chording, leader, unicode separated * consolidate each [skip ci] * correct include * quantum: Add a tap dance feature (qmk#451) * quantum: Add a tap dance feature With this feature one can specify keys that behave differently, based on the amount of times they have been tapped, and when interrupted, they get handled before the interrupter. To make it clear how this is different from `ACTION_FUNCTION_TAP`, lets explore a certain setup! We want one key to send `Space` on single tap, but `Enter` on double-tap. With `ACTION_FUNCTION_TAP`, it is quite a rain-dance to set this up, and has the problem that when the sequence is interrupted, the interrupting key will be send first. Thus, `SPC a` will result in `a SPC` being sent, if they are typed within `TAPPING_TERM`. With the tap dance feature, that'll come out as `SPC a`, correctly. The implementation hooks into two parts of the system, to achieve this: into `process_record_quantum()`, and the matrix scan. We need the latter to be able to time out a tap sequence even when a key is not being pressed, so `SPC` alone will time out and register after `TAPPING_TERM` time. But lets start with how to use it, first! First, you will need `TAP_DANCE_ENABLE=yes` in your `Makefile`, because the feature is disabled by default. This adds a little less than 1k to the firmware size. Next, you will want to define some tap-dance keys, which is easiest to do with the `TD()` macro, that - similar to `F()`, takes a number, which will later be used as an index into the `tap_dance_actions` array. This array specifies what actions shall be taken when a tap-dance key is in action. Currently, there are two possible options: * `ACTION_TAP_DANCE_DOUBLE(kc1, kc2)`: Sends the `kc1` keycode when tapped once, `kc2` otherwise. * `ACTION_TAP_DANCE_FN(fn)`: Calls the specified function - defined in the user keymap - with the current state of the tap-dance action. The first option is enough for a lot of cases, that just want dual roles. For example, `ACTION_TAP_DANCE(KC_SPC, KC_ENT)` will result in `Space` being sent on single-tap, `Enter` otherwise. And that's the bulk of it! Do note, however, that this implementation does have some consequences: keys do not register until either they reach the tapping ceiling, or they time out. This means that if you hold the key, nothing happens, no repeat, no nothing. It is possible to detect held state, and register an action then too, but that's not implemented yet. Keys also unregister immediately after being registered, so you can't even hold the second tap. This is intentional, to be consistent. And now, on to the explanation of how it works! The main entry point is `process_tap_dance()`, called from `process_record_quantum()`, which is run for every keypress, and our handler gets to run early. This function checks whether the key pressed is a tap-dance key. If it is not, and a tap-dance was in action, we handle that first, and enqueue the newly pressed key. If it is a tap-dance key, then we check if it is the same as the already active one (if there's one active, that is). If it is not, we fire off the old one first, then register the new one. If it was the same, we increment the counter and the timer. This means that you have `TAPPING_TERM` time to tap the key again, you do not have to input all the taps within that timeframe. This allows for longer tap counts, with minimal impact on responsiveness. Our next stop is `matrix_scan_tap_dance()`. This handles the timeout of tap-dance keys. For the sake of flexibility, tap-dance actions can be either a pair of keycodes, or a user function. The latter allows one to handle higher tap counts, or do extra things, like blink the LEDs, fiddle with the backlighting, and so on. This is accomplished by using an union, and some clever macros. In the end, lets see a full example! ```c enum { CT_SE = 0, CT_CLN, CT_EGG }; /* Have the above three on the keymap, TD(CT_SE), etc... */ void dance_cln (qk_tap_dance_state_t *state) { if (state->count == 1) { register_code (KC_RSFT); register_code (KC_SCLN); unregister_code (KC_SCLN); unregister_code (KC_RSFT); } else { register_code (KC_SCLN); unregister_code (KC_SCLN); reset_tap_dance (state); } } void dance_egg (qk_tap_dance_state_t *state) { if (state->count >= 100) { SEND_STRING ("Safety dance!"); reset_tap_dance (state); } } const qk_tap_dance_action_t tap_dance_actions[] = { [CT_SE] = ACTION_TAP_DANCE_DOUBLE (KC_SPC, KC_ENT) ,[CT_CLN] = ACTION_TAP_DANCE_FN (dance_cln) ,[CT_EGG] = ACTION_TAP_DANCE_FN (dance_egg) }; ``` This addresses qmk#426. Signed-off-by: Gergely Nagy <[email protected]> * hhkb: Fix the build with the new tap-dance feature Signed-off-by: Gergely Nagy <[email protected]> * tap_dance: Move process_tap_dance further down Process the tap dance stuff after midi and audio, because those don't process keycodes, but row/col positions. Signed-off-by: Gergely Nagy <[email protected]> * tap_dance: Use conditionals instead of dummy functions To be consistent with how the rest of the quantum features are implemented, use ifdefs instead of dummy functions. Signed-off-by: Gergely Nagy <[email protected]> * Merge branch 'master' into quantum-keypress-process # Conflicts: # Makefile # keyboards/planck/rev3/config.h # keyboards/planck/rev4/config.h * update build script
* Add default keymap for Infinity60 * Add default keymap for idb 60
* Add files via upload * Update keyboards/keebio/bfo9000/keymaps/vial/rules.mk Co-authored-by: Less/Rikki <[email protected]> --------- Co-authored-by: Less/Rikki <[email protected]>
Tap ; once to send a semicolon. Tap twice to send a colon.
Based on #411 and the excellent code and discussion between @algernon and @piotr-dobrogost.
Package this into something like:
TAP(KC_SCLN, KC_CLN)
- first argument is the single tap, second arg is the double tap. And no, we're not going to support triple taps ;)The text was updated successfully, but these errors were encountered: