diff --git a/docs/ChangeLog/20250831/pr25415.md b/docs/ChangeLog/20250831/pr25415.md new file mode 100644 index 00000000000..ffa29e2ec4b --- /dev/null +++ b/docs/ChangeLog/20250831/pr25415.md @@ -0,0 +1,3 @@ +# Tap dance state removed from `tap_dance_action_t` + +Code that accessed the tap dance state as a field in the tap dance action should now call `tap_dance_get_state(int tap_dance_idx)` instead. That function may return `NULL` if many tap dance keys are held together. Add a `NULL` check before using the returned state. diff --git a/docs/features/tap_dance.md b/docs/features/tap_dance.md index d533e41aaaf..f0e5ddb1742 100644 --- a/docs/features/tap_dance.md +++ b/docs/features/tap_dance.md @@ -209,11 +209,13 @@ tap_dance_action_t tap_dance_actions[] = { bool process_record_user(uint16_t keycode, keyrecord_t *record) { tap_dance_action_t *action; + tap_dance_state_t* state; switch (keycode) { - case TD(CT_CLN): // list all tap dance keycodes with tap-hold configurations - action = &tap_dance_actions[QK_TAP_DANCE_GET_INDEX(keycode)]; - if (!record->event.pressed && action->state.count && !action->state.finished) { + case TD(CT_CLN): + action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(keycode)); + state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(keycode)); + if (!record->event.pressed && state != NULL && state->count && !state->finished) { tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)action->user_data; tap_code16(tap_hold->tap); } diff --git a/quantum/process_keycode/process_tap_dance.c b/quantum/process_keycode/process_tap_dance.c index 11df62763dd..3b5c9576800 100644 --- a/quantum/process_keycode/process_tap_dance.c +++ b/quantum/process_keycode/process_tap_dance.c @@ -24,8 +24,38 @@ #include "keymap_introspection.h" static uint16_t active_td; + +#ifndef TAP_DANCE_MAX_SIMULTANEOUS +# define TAP_DANCE_MAX_SIMULTANEOUS 3 +#endif + +static tap_dance_state_t tap_dance_states[TAP_DANCE_MAX_SIMULTANEOUS]; + static uint16_t last_tap_time; +tap_dance_state_t *tap_dance_get_state(uint8_t tap_dance_idx) { + uint8_t i; + if (tap_dance_idx >= tap_dance_count()) { + return NULL; + } + // Search for a state already used for this keycode + for (i = 0; i < TAP_DANCE_MAX_SIMULTANEOUS; i++) { + if (tap_dance_states[i].in_use && tap_dance_states[i].index == tap_dance_idx) { + return &tap_dance_states[i]; + } + } + // Search for the first available state + for (i = 0; i < TAP_DANCE_MAX_SIMULTANEOUS; i++) { + if (!tap_dance_states[i].in_use) { + tap_dance_states[i].index = tap_dance_idx; + tap_dance_states[i].in_use = true; + return &tap_dance_states[i]; + } + } + // No states are available, tap dance won't happen + return NULL; +} + void tap_dance_pair_on_each_tap(tap_dance_state_t *state, void *user_data) { tap_dance_pair_t *pair = (tap_dance_pair_t *)user_data; @@ -86,58 +116,64 @@ static inline void _process_tap_dance_action_fn(tap_dance_state_t *state, void * } } -static inline void process_tap_dance_action_on_each_tap(tap_dance_action_t *action) { - action->state.count++; - action->state.weak_mods = get_mods(); - action->state.weak_mods |= get_weak_mods(); +static inline void process_tap_dance_action_on_each_tap(tap_dance_action_t *action, tap_dance_state_t *state) { + state->count++; + state->weak_mods = get_mods(); + state->weak_mods |= get_weak_mods(); #ifndef NO_ACTION_ONESHOT - action->state.oneshot_mods = get_oneshot_mods(); + state->oneshot_mods = get_oneshot_mods(); #endif - _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_each_tap); + _process_tap_dance_action_fn(state, action->user_data, action->fn.on_each_tap); } -static inline void process_tap_dance_action_on_each_release(tap_dance_action_t *action) { - _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_each_release); +static inline void process_tap_dance_action_on_each_release(tap_dance_action_t *action, tap_dance_state_t *state) { + _process_tap_dance_action_fn(state, action->user_data, action->fn.on_each_release); } -static inline void process_tap_dance_action_on_reset(tap_dance_action_t *action) { - _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_reset); - del_weak_mods(action->state.weak_mods); +static inline void process_tap_dance_action_on_reset(tap_dance_action_t *action, tap_dance_state_t *state) { + _process_tap_dance_action_fn(state, action->user_data, action->fn.on_reset); + del_weak_mods(state->weak_mods); #ifndef NO_ACTION_ONESHOT - del_mods(action->state.oneshot_mods); + del_mods(state->oneshot_mods); #endif send_keyboard_report(); - action->state = (const tap_dance_state_t){0}; + // Clear the tap dance state and mark it as unused + memset(state, 0, sizeof(tap_dance_state_t)); } -static inline void process_tap_dance_action_on_dance_finished(tap_dance_action_t *action) { - if (!action->state.finished) { - action->state.finished = true; - add_weak_mods(action->state.weak_mods); +static inline void process_tap_dance_action_on_dance_finished(tap_dance_action_t *action, tap_dance_state_t *state) { + if (!state->finished) { + state->finished = true; + add_weak_mods(state->weak_mods); #ifndef NO_ACTION_ONESHOT - add_mods(action->state.oneshot_mods); + add_mods(state->oneshot_mods); #endif send_keyboard_report(); - _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_dance_finished); + _process_tap_dance_action_fn(state, action->user_data, action->fn.on_dance_finished); } active_td = 0; - if (!action->state.pressed) { + if (!state->pressed) { // There will not be a key release event, so reset now. - process_tap_dance_action_on_reset(action); + process_tap_dance_action_on_reset(action, state); } } bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { tap_dance_action_t *action; + tap_dance_state_t * state; if (!record->event.pressed) return false; if (!active_td || keycode == active_td) return false; - action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); - action->state.interrupted = true; - action->state.interrupting_keycode = keycode; - process_tap_dance_action_on_dance_finished(action); + action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); + state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(active_td)); + if (state == NULL) { + return false; + } + state->interrupted = true; + state->interrupting_keycode = keycode; + process_tap_dance_action_on_dance_finished(action, state); // Tap dance actions can leave some weak mods active (e.g., if the tap dance is mapped to a keycode with // modifiers), but these weak mods should not affect the keypress which interrupted the tap dance. @@ -151,8 +187,9 @@ bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { } bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { - int td_index; + uint8_t td_index; tap_dance_action_t *action; + tap_dance_state_t * state; switch (keycode) { case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: @@ -161,16 +198,19 @@ bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { return false; } action = tap_dance_get(td_index); - - action->state.pressed = record->event.pressed; + state = tap_dance_get_state(td_index); + if (state == NULL) { + return false; + } + state->pressed = record->event.pressed; if (record->event.pressed) { last_tap_time = timer_read(); - process_tap_dance_action_on_each_tap(action); - active_td = action->state.finished ? 0 : keycode; + process_tap_dance_action_on_each_tap(action, state); + active_td = state->finished ? 0 : keycode; } else { - process_tap_dance_action_on_each_release(action); - if (action->state.finished) { - process_tap_dance_action_on_reset(action); + process_tap_dance_action_on_each_release(action, state); + if (state->finished) { + process_tap_dance_action_on_reset(action, state); if (active_td == keycode) { active_td = 0; } @@ -185,16 +225,18 @@ bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { void tap_dance_task(void) { tap_dance_action_t *action; + tap_dance_state_t * state; if (!active_td || timer_elapsed(last_tap_time) <= GET_TAPPING_TERM(active_td, &(keyrecord_t){})) return; action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); - if (!action->state.interrupted) { - process_tap_dance_action_on_dance_finished(action); + state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(active_td)); + if (state != NULL && !state->interrupted) { + process_tap_dance_action_on_dance_finished(action, state); } } void reset_tap_dance(tap_dance_state_t *state) { active_td = 0; - process_tap_dance_action_on_reset((tap_dance_action_t *)state); + process_tap_dance_action_on_reset(tap_dance_get(state->index), state); } diff --git a/quantum/process_keycode/process_tap_dance.h b/quantum/process_keycode/process_tap_dance.h index 5cccbdf439a..5a972cee5ab 100644 --- a/quantum/process_keycode/process_tap_dance.h +++ b/quantum/process_keycode/process_tap_dance.h @@ -28,15 +28,16 @@ typedef struct { #ifndef NO_ACTION_ONESHOT uint8_t oneshot_mods; #endif - bool pressed : 1; - bool finished : 1; - bool interrupted : 1; + bool pressed : 1; + bool finished : 1; + bool interrupted : 1; + bool in_use : 1; + uint8_t index; } tap_dance_state_t; typedef void (*tap_dance_user_fn_t)(tap_dance_state_t *state, void *user_data); typedef struct tap_dance_action_t { - tap_dance_state_t state; struct { tap_dance_user_fn_t on_each_tap; tap_dance_user_fn_t on_dance_finished; @@ -80,6 +81,8 @@ typedef struct { void reset_tap_dance(tap_dance_state_t *state); +tap_dance_state_t *tap_dance_get_state(uint8_t tap_dance_idx); + /* To be used internally */ bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record); diff --git a/tests/tap_dance/examples.c b/tests/tap_dance/examples.c index 4b6bdb20908..6aaf0082323 100644 --- a/tests/tap_dance/examples.c +++ b/tests/tap_dance/examples.c @@ -81,11 +81,13 @@ typedef struct { bool process_record_user(uint16_t keycode, keyrecord_t *record) { tap_dance_action_t *action; + tap_dance_state_t* state; switch (keycode) { case TD(CT_CLN): action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(keycode)); - if (!record->event.pressed && action->state.count && !action->state.finished) { + state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(keycode)); + if (!record->event.pressed && state != NULL && state->count && !state->finished) { tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)action->user_data; tap_code16(tap_hold->tap); }