diff options
| author | Stephen Ostermiller <stephenostermiller@users.noreply.github.com> | 2025-11-23 06:32:36 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-23 22:32:36 +1100 |
| commit | 1a954e8da5dcbd81eeccb9d6ac41b6eda64d7b85 (patch) | |
| tree | 4f09599d467a9c9bbb1dde722511139fa965ff03 | |
| parent | c7e17538eea98540dbee71de5d024db99f7786fe (diff) | |
Reduce tap dance memory usage, move state out of data (#25415)
* Use less tap dance memory.
Use dynamically allocated sparse array for tap dance state, dynamically allocate tap dance state when needed and free it when the tap dance is done.
* new approach
* Use null, check for null
* Reformat with docker
* Use uint8 with idx rather than uint16 with keycode in state
* fix accidental change
* reformat
* Add null check
* add documentation tip suggested by tzarc
* Only allow tap dance state allocation on key down, not on key up
Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
* Only allow tap dance allocation on key down, not on key up
Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
* add user action required section
---------
Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
| -rw-r--r-- | docs/ChangeLog/20250831/pr25415.md | 47 | ||||
| -rw-r--r-- | docs/features/tap_dance.md | 12 | ||||
| -rw-r--r-- | quantum/process_keycode/process_tap_dance.c | 122 | ||||
| -rw-r--r-- | quantum/process_keycode/process_tap_dance.h | 11 | ||||
| -rw-r--r-- | tests/tap_dance/examples.c | 4 |
5 files changed, 152 insertions, 44 deletions
diff --git a/docs/ChangeLog/20250831/pr25415.md b/docs/ChangeLog/20250831/pr25415.md new file mode 100644 index 0000000000..54eb8c737e --- /dev/null +++ b/docs/ChangeLog/20250831/pr25415.md | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | # Tap dance state removed from `tap_dance_action_t` | ||
| 2 | |||
| 3 | The tap dance state has been separated from the action structure. Custom tap dance functions now receive the state as a separate parameter instead of accessing it through `action->state`. | ||
| 4 | |||
| 5 | ## User Action Required | ||
| 6 | |||
| 7 | If your keymap uses custom tap dance functions that access the tap dance state, you need to update your code. | ||
| 8 | |||
| 9 | - You can't use `action->state`. Instead you need to call `tap_dance_state_t *tap_dance_get_state(uint8_t tap_dance_idx)` to get the state. | ||
| 10 | - You now get a pointer to the state, so use `->` notation rather than `.` notation to get fields from it. | ||
| 11 | |||
| 12 | ### Before: | ||
| 13 | ```c | ||
| 14 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { | ||
| 15 | tap_dance_action_t *action; | ||
| 16 | |||
| 17 | switch (keycode) { | ||
| 18 | case TD(CT_CLN): | ||
| 19 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(keycode)); | ||
| 20 | if (!record->event.pressed && action->state.count && !action->state.finished) { | ||
| 21 | tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)action->user_data; | ||
| 22 | tap_code16(tap_hold->tap); | ||
| 23 | } | ||
| 24 | |||
| 25 | } | ||
| 26 | return true; | ||
| 27 | } | ||
| 28 | ``` | ||
| 29 | |||
| 30 | ### After: | ||
| 31 | ```c | ||
| 32 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { | ||
| 33 | tap_dance_action_t *action; | ||
| 34 | tap_dance_state_t* state; | ||
| 35 | |||
| 36 | switch (keycode) { | ||
| 37 | case TD(CT_CLN): | ||
| 38 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(keycode)); | ||
| 39 | state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(keycode)); | ||
| 40 | if (!record->event.pressed && state != NULL && state->count && !state->finished) { | ||
| 41 | tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)action->user_data; | ||
| 42 | tap_code16(tap_hold->tap); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | return true; | ||
| 46 | } | ||
| 47 | ``` | ||
diff --git a/docs/features/tap_dance.md b/docs/features/tap_dance.md index d533e41aaa..688241a16b 100644 --- a/docs/features/tap_dance.md +++ b/docs/features/tap_dance.md | |||
| @@ -40,6 +40,10 @@ Similar to the first option, the second and third option are good for simple lay | |||
| 40 | 40 | ||
| 41 | For more complicated cases, like blink the LEDs, fiddle with the backlighting, and so on, use the fourth or fifth option. Examples of each are listed below. | 41 | For more complicated cases, like blink the LEDs, fiddle with the backlighting, and so on, use the fourth or fifth option. Examples of each are listed below. |
| 42 | 42 | ||
| 43 | ::: tip | ||
| 44 | If too many tap dances are active at the same time, later ones won't have any effect. You need to increase `TAP_DANCE_MAX_SIMULTANEOUS` by adding `#define TAP_DANCE_MAX_SIMULTANEOUS 5` (or higher) to your keymap's `config.h` file if you expect that users may hold down many tap dance keys simultaneously. By default, only 3 tap dance keys can be used together at the same time. | ||
| 45 | ::: | ||
| 46 | |||
| 43 | ## Implementation Details {#implementation} | 47 | ## Implementation Details {#implementation} |
| 44 | 48 | ||
| 45 | Well, that's the bulk of it! You should now be able to work through the examples below, and to develop your own Tap Dance functionality. But if you want a deeper understanding of what's going on behind the scenes, then read on for the explanation of how it all works! | 49 | Well, that's the bulk of it! You should now be able to work through the examples below, and to develop your own Tap Dance functionality. But if you want a deeper understanding of what's going on behind the scenes, then read on for the explanation of how it all works! |
| @@ -209,11 +213,13 @@ tap_dance_action_t tap_dance_actions[] = { | |||
| 209 | 213 | ||
| 210 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { | 214 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { |
| 211 | tap_dance_action_t *action; | 215 | tap_dance_action_t *action; |
| 216 | tap_dance_state_t* state; | ||
| 212 | 217 | ||
| 213 | switch (keycode) { | 218 | switch (keycode) { |
| 214 | case TD(CT_CLN): // list all tap dance keycodes with tap-hold configurations | 219 | case TD(CT_CLN): |
| 215 | action = &tap_dance_actions[QK_TAP_DANCE_GET_INDEX(keycode)]; | 220 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(keycode)); |
| 216 | if (!record->event.pressed && action->state.count && !action->state.finished) { | 221 | state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(keycode)); |
| 222 | if (!record->event.pressed && state != NULL && state->count && !state->finished) { | ||
| 217 | tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)action->user_data; | 223 | tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)action->user_data; |
| 218 | tap_code16(tap_hold->tap); | 224 | tap_code16(tap_hold->tap); |
| 219 | } | 225 | } |
diff --git a/quantum/process_keycode/process_tap_dance.c b/quantum/process_keycode/process_tap_dance.c index 11df62763d..36a94d8d28 100644 --- a/quantum/process_keycode/process_tap_dance.c +++ b/quantum/process_keycode/process_tap_dance.c | |||
| @@ -24,8 +24,46 @@ | |||
| 24 | #include "keymap_introspection.h" | 24 | #include "keymap_introspection.h" |
| 25 | 25 | ||
| 26 | static uint16_t active_td; | 26 | static uint16_t active_td; |
| 27 | |||
| 28 | #ifndef TAP_DANCE_MAX_SIMULTANEOUS | ||
| 29 | # define TAP_DANCE_MAX_SIMULTANEOUS 3 | ||
| 30 | #endif | ||
| 31 | |||
| 32 | static tap_dance_state_t tap_dance_states[TAP_DANCE_MAX_SIMULTANEOUS]; | ||
| 33 | |||
| 27 | static uint16_t last_tap_time; | 34 | static uint16_t last_tap_time; |
| 28 | 35 | ||
| 36 | static tap_dance_state_t *tap_dance_get_or_allocate_state(uint8_t tap_dance_idx, bool allocate) { | ||
| 37 | uint8_t i; | ||
| 38 | if (tap_dance_idx >= tap_dance_count()) { | ||
| 39 | return NULL; | ||
| 40 | } | ||
| 41 | // Search for a state already used for this keycode | ||
| 42 | for (i = 0; i < TAP_DANCE_MAX_SIMULTANEOUS; i++) { | ||
| 43 | if (tap_dance_states[i].in_use && tap_dance_states[i].index == tap_dance_idx) { | ||
| 44 | return &tap_dance_states[i]; | ||
| 45 | } | ||
| 46 | } | ||
| 47 | // No existing state found; bail out if new state allocation is not allowed | ||
| 48 | if (!allocate) { | ||
| 49 | return NULL; | ||
| 50 | } | ||
| 51 | // Search for the first available state | ||
| 52 | for (i = 0; i < TAP_DANCE_MAX_SIMULTANEOUS; i++) { | ||
| 53 | if (!tap_dance_states[i].in_use) { | ||
| 54 | tap_dance_states[i].index = tap_dance_idx; | ||
| 55 | tap_dance_states[i].in_use = true; | ||
| 56 | return &tap_dance_states[i]; | ||
| 57 | } | ||
| 58 | } | ||
| 59 | // No states are available, tap dance won't happen | ||
| 60 | return NULL; | ||
| 61 | } | ||
| 62 | |||
| 63 | tap_dance_state_t *tap_dance_get_state(uint8_t tap_dance_idx) { | ||
| 64 | return tap_dance_get_or_allocate_state(tap_dance_idx, false); | ||
| 65 | } | ||
| 66 | |||
| 29 | void tap_dance_pair_on_each_tap(tap_dance_state_t *state, void *user_data) { | 67 | void tap_dance_pair_on_each_tap(tap_dance_state_t *state, void *user_data) { |
| 30 | tap_dance_pair_t *pair = (tap_dance_pair_t *)user_data; | 68 | tap_dance_pair_t *pair = (tap_dance_pair_t *)user_data; |
| 31 | 69 | ||
| @@ -86,58 +124,64 @@ static inline void _process_tap_dance_action_fn(tap_dance_state_t *state, void * | |||
| 86 | } | 124 | } |
| 87 | } | 125 | } |
| 88 | 126 | ||
| 89 | static inline void process_tap_dance_action_on_each_tap(tap_dance_action_t *action) { | 127 | static inline void process_tap_dance_action_on_each_tap(tap_dance_action_t *action, tap_dance_state_t *state) { |
| 90 | action->state.count++; | 128 | state->count++; |
| 91 | action->state.weak_mods = get_mods(); | 129 | state->weak_mods = get_mods(); |
| 92 | action->state.weak_mods |= get_weak_mods(); | 130 | state->weak_mods |= get_weak_mods(); |
| 93 | #ifndef NO_ACTION_ONESHOT | 131 | #ifndef NO_ACTION_ONESHOT |
| 94 | action->state.oneshot_mods = get_oneshot_mods(); | 132 | state->oneshot_mods = get_oneshot_mods(); |
| 95 | #endif | 133 | #endif |
| 96 | _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_each_tap); | 134 | _process_tap_dance_action_fn(state, action->user_data, action->fn.on_each_tap); |
| 97 | } | 135 | } |
| 98 | 136 | ||
| 99 | static inline void process_tap_dance_action_on_each_release(tap_dance_action_t *action) { | 137 | static inline void process_tap_dance_action_on_each_release(tap_dance_action_t *action, tap_dance_state_t *state) { |
| 100 | _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_each_release); | 138 | _process_tap_dance_action_fn(state, action->user_data, action->fn.on_each_release); |
| 101 | } | 139 | } |
| 102 | 140 | ||
| 103 | static inline void process_tap_dance_action_on_reset(tap_dance_action_t *action) { | 141 | static inline void process_tap_dance_action_on_reset(tap_dance_action_t *action, tap_dance_state_t *state) { |
| 104 | _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_reset); | 142 | _process_tap_dance_action_fn(state, action->user_data, action->fn.on_reset); |
| 105 | del_weak_mods(action->state.weak_mods); | 143 | del_weak_mods(state->weak_mods); |
| 106 | #ifndef NO_ACTION_ONESHOT | 144 | #ifndef NO_ACTION_ONESHOT |
| 107 | del_mods(action->state.oneshot_mods); | 145 | del_mods(state->oneshot_mods); |
| 108 | #endif | 146 | #endif |
| 109 | send_keyboard_report(); | 147 | send_keyboard_report(); |
| 110 | action->state = (const tap_dance_state_t){0}; | 148 | // Clear the tap dance state and mark it as unused |
| 149 | memset(state, 0, sizeof(tap_dance_state_t)); | ||
| 111 | } | 150 | } |
| 112 | 151 | ||
| 113 | static inline void process_tap_dance_action_on_dance_finished(tap_dance_action_t *action) { | 152 | static inline void process_tap_dance_action_on_dance_finished(tap_dance_action_t *action, tap_dance_state_t *state) { |
| 114 | if (!action->state.finished) { | 153 | if (!state->finished) { |
| 115 | action->state.finished = true; | 154 | state->finished = true; |
| 116 | add_weak_mods(action->state.weak_mods); | 155 | add_weak_mods(state->weak_mods); |
| 117 | #ifndef NO_ACTION_ONESHOT | 156 | #ifndef NO_ACTION_ONESHOT |
| 118 | add_mods(action->state.oneshot_mods); | 157 | add_mods(state->oneshot_mods); |
| 119 | #endif | 158 | #endif |
| 120 | send_keyboard_report(); | 159 | send_keyboard_report(); |
| 121 | _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_dance_finished); | 160 | _process_tap_dance_action_fn(state, action->user_data, action->fn.on_dance_finished); |
| 122 | } | 161 | } |
| 123 | active_td = 0; | 162 | active_td = 0; |
| 124 | if (!action->state.pressed) { | 163 | if (!state->pressed) { |
| 125 | // There will not be a key release event, so reset now. | 164 | // There will not be a key release event, so reset now. |
| 126 | process_tap_dance_action_on_reset(action); | 165 | process_tap_dance_action_on_reset(action, state); |
| 127 | } | 166 | } |
| 128 | } | 167 | } |
| 129 | 168 | ||
| 130 | bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { | 169 | bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { |
| 131 | tap_dance_action_t *action; | 170 | tap_dance_action_t *action; |
| 171 | tap_dance_state_t * state; | ||
| 132 | 172 | ||
| 133 | if (!record->event.pressed) return false; | 173 | if (!record->event.pressed) return false; |
| 134 | 174 | ||
| 135 | if (!active_td || keycode == active_td) return false; | 175 | if (!active_td || keycode == active_td) return false; |
| 136 | 176 | ||
| 137 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); | 177 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); |
| 138 | action->state.interrupted = true; | 178 | state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(active_td)); |
| 139 | action->state.interrupting_keycode = keycode; | 179 | if (state == NULL) { |
| 140 | process_tap_dance_action_on_dance_finished(action); | 180 | return false; |
| 181 | } | ||
| 182 | state->interrupted = true; | ||
| 183 | state->interrupting_keycode = keycode; | ||
| 184 | process_tap_dance_action_on_dance_finished(action, state); | ||
| 141 | 185 | ||
| 142 | // Tap dance actions can leave some weak mods active (e.g., if the tap dance is mapped to a keycode with | 186 | // Tap dance actions can leave some weak mods active (e.g., if the tap dance is mapped to a keycode with |
| 143 | // modifiers), but these weak mods should not affect the keypress which interrupted the tap dance. | 187 | // modifiers), but these weak mods should not affect the keypress which interrupted the tap dance. |
| @@ -151,8 +195,9 @@ bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { | |||
| 151 | } | 195 | } |
| 152 | 196 | ||
| 153 | bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { | 197 | bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { |
| 154 | int td_index; | 198 | uint8_t td_index; |
| 155 | tap_dance_action_t *action; | 199 | tap_dance_action_t *action; |
| 200 | tap_dance_state_t * state; | ||
| 156 | 201 | ||
| 157 | switch (keycode) { | 202 | switch (keycode) { |
| 158 | case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: | 203 | case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: |
| @@ -161,16 +206,19 @@ bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { | |||
| 161 | return false; | 206 | return false; |
| 162 | } | 207 | } |
| 163 | action = tap_dance_get(td_index); | 208 | action = tap_dance_get(td_index); |
| 164 | 209 | state = tap_dance_get_or_allocate_state(td_index, record->event.pressed); | |
| 165 | action->state.pressed = record->event.pressed; | 210 | if (state == NULL) { |
| 211 | return false; | ||
| 212 | } | ||
| 213 | state->pressed = record->event.pressed; | ||
| 166 | if (record->event.pressed) { | 214 | if (record->event.pressed) { |
| 167 | last_tap_time = timer_read(); | 215 | last_tap_time = timer_read(); |
| 168 | process_tap_dance_action_on_each_tap(action); | 216 | process_tap_dance_action_on_each_tap(action, state); |
| 169 | active_td = action->state.finished ? 0 : keycode; | 217 | active_td = state->finished ? 0 : keycode; |
| 170 | } else { | 218 | } else { |
| 171 | process_tap_dance_action_on_each_release(action); | 219 | process_tap_dance_action_on_each_release(action, state); |
| 172 | if (action->state.finished) { | 220 | if (state->finished) { |
| 173 | process_tap_dance_action_on_reset(action); | 221 | process_tap_dance_action_on_reset(action, state); |
| 174 | if (active_td == keycode) { | 222 | if (active_td == keycode) { |
| 175 | active_td = 0; | 223 | active_td = 0; |
| 176 | } | 224 | } |
| @@ -185,16 +233,18 @@ bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { | |||
| 185 | 233 | ||
| 186 | void tap_dance_task(void) { | 234 | void tap_dance_task(void) { |
| 187 | tap_dance_action_t *action; | 235 | tap_dance_action_t *action; |
| 236 | tap_dance_state_t * state; | ||
| 188 | 237 | ||
| 189 | if (!active_td || timer_elapsed(last_tap_time) <= GET_TAPPING_TERM(active_td, &(keyrecord_t){})) return; | 238 | if (!active_td || timer_elapsed(last_tap_time) <= GET_TAPPING_TERM(active_td, &(keyrecord_t){})) return; |
| 190 | 239 | ||
| 191 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); | 240 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); |
| 192 | if (!action->state.interrupted) { | 241 | state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(active_td)); |
| 193 | process_tap_dance_action_on_dance_finished(action); | 242 | if (state != NULL && !state->interrupted) { |
| 243 | process_tap_dance_action_on_dance_finished(action, state); | ||
| 194 | } | 244 | } |
| 195 | } | 245 | } |
| 196 | 246 | ||
| 197 | void reset_tap_dance(tap_dance_state_t *state) { | 247 | void reset_tap_dance(tap_dance_state_t *state) { |
| 198 | active_td = 0; | 248 | active_td = 0; |
| 199 | process_tap_dance_action_on_reset((tap_dance_action_t *)state); | 249 | process_tap_dance_action_on_reset(tap_dance_get(state->index), state); |
| 200 | } | 250 | } |
diff --git a/quantum/process_keycode/process_tap_dance.h b/quantum/process_keycode/process_tap_dance.h index 5cccbdf439..5a972cee5a 100644 --- a/quantum/process_keycode/process_tap_dance.h +++ b/quantum/process_keycode/process_tap_dance.h | |||
| @@ -28,15 +28,16 @@ typedef struct { | |||
| 28 | #ifndef NO_ACTION_ONESHOT | 28 | #ifndef NO_ACTION_ONESHOT |
| 29 | uint8_t oneshot_mods; | 29 | uint8_t oneshot_mods; |
| 30 | #endif | 30 | #endif |
| 31 | bool pressed : 1; | 31 | bool pressed : 1; |
| 32 | bool finished : 1; | 32 | bool finished : 1; |
| 33 | bool interrupted : 1; | 33 | bool interrupted : 1; |
| 34 | bool in_use : 1; | ||
| 35 | uint8_t index; | ||
| 34 | } tap_dance_state_t; | 36 | } tap_dance_state_t; |
| 35 | 37 | ||
| 36 | typedef void (*tap_dance_user_fn_t)(tap_dance_state_t *state, void *user_data); | 38 | typedef void (*tap_dance_user_fn_t)(tap_dance_state_t *state, void *user_data); |
| 37 | 39 | ||
| 38 | typedef struct tap_dance_action_t { | 40 | typedef struct tap_dance_action_t { |
| 39 | tap_dance_state_t state; | ||
| 40 | struct { | 41 | struct { |
| 41 | tap_dance_user_fn_t on_each_tap; | 42 | tap_dance_user_fn_t on_each_tap; |
| 42 | tap_dance_user_fn_t on_dance_finished; | 43 | tap_dance_user_fn_t on_dance_finished; |
| @@ -80,6 +81,8 @@ typedef struct { | |||
| 80 | 81 | ||
| 81 | void reset_tap_dance(tap_dance_state_t *state); | 82 | void reset_tap_dance(tap_dance_state_t *state); |
| 82 | 83 | ||
| 84 | tap_dance_state_t *tap_dance_get_state(uint8_t tap_dance_idx); | ||
| 85 | |||
| 83 | /* To be used internally */ | 86 | /* To be used internally */ |
| 84 | 87 | ||
| 85 | bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record); | 88 | 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 4b6bdb2090..6aaf008232 100644 --- a/tests/tap_dance/examples.c +++ b/tests/tap_dance/examples.c | |||
| @@ -81,11 +81,13 @@ typedef struct { | |||
| 81 | 81 | ||
| 82 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { | 82 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { |
| 83 | tap_dance_action_t *action; | 83 | tap_dance_action_t *action; |
| 84 | tap_dance_state_t* state; | ||
| 84 | 85 | ||
| 85 | switch (keycode) { | 86 | switch (keycode) { |
| 86 | case TD(CT_CLN): | 87 | case TD(CT_CLN): |
| 87 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(keycode)); | 88 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(keycode)); |
| 88 | if (!record->event.pressed && action->state.count && !action->state.finished) { | 89 | state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(keycode)); |
| 90 | if (!record->event.pressed && state != NULL && state->count && !state->finished) { | ||
| 89 | tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)action->user_data; | 91 | tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)action->user_data; |
| 90 | tap_code16(tap_hold->tap); | 92 | tap_code16(tap_hold->tap); |
| 91 | } | 93 | } |
