10 KiB
Tap Hold Custom Tap Dance
This custom tap dance functions similarly to the single tap and hold functionality of 'Quad Function Tap-Dance' by DanielGGordon with a reduced size per tap dance declared.
Motivation
I first discovered tap dancing through Ben Vallack and was interested in the functionality of tap dancing demonstrated in his tap dancing video. And so I set off to implement my own tap dances emulating this functionality.
Custom Tap Dance Action
Similar to the the action definitions in process_tap_dance.h
; I have created a custom macro and data structure used to declare tap dance actions:
typedef struct {
uint16_t t;
uint16_t h;
bool tapped;
} qk_tap_dance_tap_hold_t;
#define ACTION_TAP_HOLD(t, h) \
{ .fn = {qk_tap_dance_tap_hold_on_each_tap, qk_tap_dance_tap_hold_on_finish, qk_tap_dance_tap_hold_on_reset}, .user_data = (void *)&((qk_tap_dance_tap_hold_t){t, h, true}), }
This allows the user to set the keycode registered when tapping t
, the keycode registered when holding h
, as well as prepares a boolean storing logic allowing the the reset function to determine whether a tap or hold was registered tapped
.
Custom Tap Dance Functions
The three handlers backing Tap Hold are really simple.
The on_each_tap
handler triggers a tap if a second tap is made within the tapping term. Following that it performs a pseudo reset, setting the count back to 1 and resetting the timer.
void qk_tap_dance_tap_hold_on_each_tap(qk_tap_dance_state_t *state, void *user_data) {
qk_tap_dance_tap_hold_t *kc = (qk_tap_dance_tap_hold_t *)user_data;
if (state->count == 2) {
tap_code16(kc->t);
state->count = 1;
state->timer = timer_read();
}
}
The on_finished
handler determines whether the dance was a tap or a hold, saving the result in kc->tapped
for on_reset
later. After, the handler registers the respctive keycode.
void qk_tap_dance_tap_hold_on_finish(qk_tap_dance_state_t *state, void *user_data) {
qk_tap_dance_tap_hold_t *kc = (qk_tap_dance_tap_hold_t *)user_data;
kc->tapped = state->interrupted || !state->pressed;
if (kc->tapped) {
register_code16(kc->t);
} else {
register_code16(kc->h);
}
}
Finally, the on_reset
handler unregisters the corresponding keycode, and resets kc->tapped
for subsequent tap dances.
void qk_tap_dance_tap_hold_on_reset(qk_tap_dance_state_t *state, void *user_data) {
qk_tap_dance_tap_hold_t *kc = (qk_tap_dance_tap_hold_t *)user_data;
if (kc->tapped) {
unregister_code16(kc->t);
} else {
unregister_code16(kc->h);
}
kc->tapped = true;
}
Tap: keycode; Hold: control + keycode (or any modifier + keycode)
The macro ACTION_TAP_HOLD
allows a user to select the keycode for both the tap and hold action of the tap dance. It goes without saying that you can also send keycodes with modifiers so instead of having to write out ACTION_TAP_HOLD(kc, C(kc))
for each keycode, we can use more macros:
#define ACTION_TAP_HOLD_CTL(t) ACTION_TAP_HOLD(t, LCTL(t))
Macros are rock.
The Journey to Lower Sized Tap Dancing
As I said earlier, I had set out to create my own tap dancing functions with little knowledge of how QMK works. Just as a bonus, I thought I'd share my journey through making my own custom tap dance.
Research
When looking through the tap dance documentation, it was apparent to me that complex example 4 by DanielGGordon, had the functionality I so desired, I would have to do at least three things.
However, in order to make all the tap dances for at least all of the alpha keys I would have to do the following three things:
- Write a
on_dance_finished
function - Write a
on_reset
function - And create a static struct instance storing a
boolean
andtd_state_t
The most outrageous part was that I would have to repeat these three steps for each and every tap dance!
Unable to see how I could reduce the number of functions and data structures. I decided to follow through with making functions for each keycode.
Naive Implementation 1: Macros with ACTION_TAP_DANCE_FN_ADVANCED
For my initial implementation, I set out to make use of macros to reduce the amount of apparent duplicate code in my keymap files.
Copying the functions by DanielGGordon, reducing its functionality to single tap and single hold, and converting them into macros, I was able to create all the necessary functions for each tap dance without having to write out the same functions dozens of times.
My issues with this implementation were that, when compiling, this was no different from me writing the same function dozens of time. This meant that the resulting firmware was HUGE decreasing the amount of additional features the user is able to enable.
Naive Implementation 2: Custom Quantum Keycode
Searching for another solution I searched through the files in the qmk repository and stumbled across the implementation of several quantum keycode processing files in process_keycode
, namely the files process_unicode.h
, process_unicode.c
, and process_unicode_common.h
.
And so I set off to implement my own custom quantum keycodes overriding Unicode keycode ranges and implementing process_record_user()
.
Upon initial testing with a single key, it appeared functional save for the fact that keys would only register when releasing the key. Additionally it saved plenty of space due to reuse of functions when processing input.
I was really proud of my implementation and had even shown it off to several of my friends.
Unfortunately, when testing it out on all alpha, everything started to come apart. Because keystrokes would only register when releasing the switch, faster typists would actually actuate some keystrokes in the incorrect order; particularly with space appearing before the final letter of most words.
Current Implementation: Custom Tap Dance Actions
Upset that I would have to go back to naive implementation 1, I went back to digging for answers. Maybe there was something I was missing, some extra details on how tap dancing gets processed.
Looking again in process_keycode
, I found process_tap_dance.h
and process_tap_dance.c
. And in those files, I found the miracle working struct qk_tap_dance_action_t
.
Looking more closely, each tap dance action a user defines constructs a qk_tap_dance_action_t
with the following:
- Three handler functions
- An
on_each_tap
function that runs when the tap dance key is pressed within theTAPPING_TERM
- An
on_dance_finished
function that runs when theTAPPING_TERM
is complete - An
on_reset
function that runs after finishing the dance
- An
- A
custom_tapping_term
for the tap dance action - Last but not least, the my saving grace:
void *user_data
. A user defined struct that gets passed to each of the handler functions.
With this discovery, I set out to implement my own custom tap dance action in my personal userspace as seen tap_dance.c
and tap_dance.h
which I named ACTION_TAP_HOLD.
Updates and Thoughts
08/08/2021
Initially, I thought of allowing the user to hold the hold tap dance action (KC_LCTL
+ KC_<keycode>
). Unfortunately, I ran into several issues (likely due to my lack of understanding on the runtime flow) causing control to be held indefinitely until the computer is restarted. This is why, I had handler functions perform tap_code16
opposed to register_code16
and unregister_code16
.
When handling a double tap within the TAPPING_TERM
, the tap keycode gets sent with tap_code16
, the status timer gets reset, and the counter gets set back to 1 in case the user wishes to tap again or to hold the second tap.
09/08/2021
Generalized tap and hold user_data
struct to store two keycodes; one for the tap action, the other for the hold action.
This way the user can define the tap and hold functionality separately.
09/08/2021
Reworked utilizing, register_code16
and unregister_code16
. The issues previously experienced were due to my ignorance of the runtime flow for tap dancing.
Previously in both the on_dance_finished
and on_reset
functions, I checked if the key was being tapped or held using this statement:
state->interrupted || !state->pressed
In the case of a hold, when accessing the on_dance_finished
function, state->interrupted
would be false and state->pressed
would be true making the above statement false making the statement work as intended.
However, when it comes to on_reset
the statement no longer works.
In the runtime flow for tap dancing, on_reset
gets called in the function reset_tap_dance
. In order for the on_reset
function to be called, state->pressed must be false.
This means that the above statement is no longer reliable in determining if a key has been held in this function. In fact, the function will always act as though a tap occured, meaning the hold reset will never be reached.
There were signs of this in complex examples that I hadn't taken into mind when designing this custom tap dance action (mainly through the static variables used to store the state instead of using state
in both functions).
As such, the fix was as simple as adding a boolean to the user_data
struct that stores the state of the tap dance, thus allowing us to now be able to properly hold the hold key without any worry of the hold action of a key being stuck on.