keyboard stuff
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

authored by

Stephen Ostermiller
Sergey Vlasov
and committed by
GitHub
1a954e8d c7e17538

+152 -44
+47
docs/ChangeLog/20250831/pr25415.md
··· 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 + ```
+9 -3
docs/features/tap_dance.md
··· 40 40 41 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 47 ## Implementation Details {#implementation} 44 48 45 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 213 210 214 bool process_record_user(uint16_t keycode, keyrecord_t *record) { 211 215 tap_dance_action_t *action; 216 + tap_dance_state_t* state; 212 217 213 218 switch (keycode) { 214 - case TD(CT_CLN): // list all tap dance keycodes with tap-hold configurations 215 - action = &tap_dance_actions[QK_TAP_DANCE_GET_INDEX(keycode)]; 216 - if (!record->event.pressed && action->state.count && !action->state.finished) { 219 + case TD(CT_CLN): 220 + action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(keycode)); 221 + state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(keycode)); 222 + if (!record->event.pressed && state != NULL && state->count && !state->finished) { 217 223 tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)action->user_data; 218 224 tap_code16(tap_hold->tap); 219 225 }
+86 -36
quantum/process_keycode/process_tap_dance.c
··· 24 24 #include "keymap_introspection.h" 25 25 26 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 34 static uint16_t last_tap_time; 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 + } 28 66 29 67 void tap_dance_pair_on_each_tap(tap_dance_state_t *state, void *user_data) { 30 68 tap_dance_pair_t *pair = (tap_dance_pair_t *)user_data; ··· 86 124 } 87 125 } 88 126 89 - static inline void process_tap_dance_action_on_each_tap(tap_dance_action_t *action) { 90 - action->state.count++; 91 - action->state.weak_mods = get_mods(); 92 - action->state.weak_mods |= get_weak_mods(); 127 + static inline void process_tap_dance_action_on_each_tap(tap_dance_action_t *action, tap_dance_state_t *state) { 128 + state->count++; 129 + state->weak_mods = get_mods(); 130 + state->weak_mods |= get_weak_mods(); 93 131 #ifndef NO_ACTION_ONESHOT 94 - action->state.oneshot_mods = get_oneshot_mods(); 132 + state->oneshot_mods = get_oneshot_mods(); 95 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) { 100 - _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_each_release); 137 + static inline void process_tap_dance_action_on_each_release(tap_dance_action_t *action, tap_dance_state_t *state) { 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) { 104 - _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_reset); 105 - del_weak_mods(action->state.weak_mods); 141 + static inline void process_tap_dance_action_on_reset(tap_dance_action_t *action, tap_dance_state_t *state) { 142 + _process_tap_dance_action_fn(state, action->user_data, action->fn.on_reset); 143 + del_weak_mods(state->weak_mods); 106 144 #ifndef NO_ACTION_ONESHOT 107 - del_mods(action->state.oneshot_mods); 145 + del_mods(state->oneshot_mods); 108 146 #endif 109 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) { 114 - if (!action->state.finished) { 115 - action->state.finished = true; 116 - add_weak_mods(action->state.weak_mods); 152 + static inline void process_tap_dance_action_on_dance_finished(tap_dance_action_t *action, tap_dance_state_t *state) { 153 + if (!state->finished) { 154 + state->finished = true; 155 + add_weak_mods(state->weak_mods); 117 156 #ifndef NO_ACTION_ONESHOT 118 - add_mods(action->state.oneshot_mods); 157 + add_mods(state->oneshot_mods); 119 158 #endif 120 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 162 active_td = 0; 124 - if (!action->state.pressed) { 163 + if (!state->pressed) { 125 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 169 bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { 131 170 tap_dance_action_t *action; 171 + tap_dance_state_t * state; 132 172 133 173 if (!record->event.pressed) return false; 134 174 135 175 if (!active_td || keycode == active_td) return false; 136 176 137 - action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); 138 - action->state.interrupted = true; 139 - action->state.interrupting_keycode = keycode; 140 - process_tap_dance_action_on_dance_finished(action); 177 + action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); 178 + state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(active_td)); 179 + if (state == NULL) { 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 186 // Tap dance actions can leave some weak mods active (e.g., if the tap dance is mapped to a keycode with 143 187 // modifiers), but these weak mods should not affect the keypress which interrupted the tap dance. ··· 151 195 } 152 196 153 197 bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { 154 - int td_index; 198 + uint8_t td_index; 155 199 tap_dance_action_t *action; 200 + tap_dance_state_t * state; 156 201 157 202 switch (keycode) { 158 203 case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: ··· 161 206 return false; 162 207 } 163 208 action = tap_dance_get(td_index); 164 - 165 - action->state.pressed = record->event.pressed; 209 + state = tap_dance_get_or_allocate_state(td_index, record->event.pressed); 210 + if (state == NULL) { 211 + return false; 212 + } 213 + state->pressed = record->event.pressed; 166 214 if (record->event.pressed) { 167 215 last_tap_time = timer_read(); 168 - process_tap_dance_action_on_each_tap(action); 169 - active_td = action->state.finished ? 0 : keycode; 216 + process_tap_dance_action_on_each_tap(action, state); 217 + active_td = state->finished ? 0 : keycode; 170 218 } else { 171 - process_tap_dance_action_on_each_release(action); 172 - if (action->state.finished) { 173 - process_tap_dance_action_on_reset(action); 219 + process_tap_dance_action_on_each_release(action, state); 220 + if (state->finished) { 221 + process_tap_dance_action_on_reset(action, state); 174 222 if (active_td == keycode) { 175 223 active_td = 0; 176 224 } ··· 185 233 186 234 void tap_dance_task(void) { 187 235 tap_dance_action_t *action; 236 + tap_dance_state_t * state; 188 237 189 238 if (!active_td || timer_elapsed(last_tap_time) <= GET_TAPPING_TERM(active_td, &(keyrecord_t){})) return; 190 239 191 240 action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); 192 - if (!action->state.interrupted) { 193 - process_tap_dance_action_on_dance_finished(action); 241 + state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(active_td)); 242 + if (state != NULL && !state->interrupted) { 243 + process_tap_dance_action_on_dance_finished(action, state); 194 244 } 195 245 } 196 246 197 247 void reset_tap_dance(tap_dance_state_t *state) { 198 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 }
+7 -4
quantum/process_keycode/process_tap_dance.h
··· 28 28 #ifndef NO_ACTION_ONESHOT 29 29 uint8_t oneshot_mods; 30 30 #endif 31 - bool pressed : 1; 32 - bool finished : 1; 33 - bool interrupted : 1; 31 + bool pressed : 1; 32 + bool finished : 1; 33 + bool interrupted : 1; 34 + bool in_use : 1; 35 + uint8_t index; 34 36 } tap_dance_state_t; 35 37 36 38 typedef void (*tap_dance_user_fn_t)(tap_dance_state_t *state, void *user_data); 37 39 38 40 typedef struct tap_dance_action_t { 39 - tap_dance_state_t state; 40 41 struct { 41 42 tap_dance_user_fn_t on_each_tap; 42 43 tap_dance_user_fn_t on_dance_finished; ··· 79 80 #define TAP_DANCE_KEYCODE(state) TD(((tap_dance_action_t *)state) - tap_dance_actions) 80 81 81 82 void reset_tap_dance(tap_dance_state_t *state); 83 + 84 + tap_dance_state_t *tap_dance_get_state(uint8_t tap_dance_idx); 82 85 83 86 /* To be used internally */ 84 87
+3 -1
tests/tap_dance/examples.c
··· 81 81 82 82 bool process_record_user(uint16_t keycode, keyrecord_t *record) { 83 83 tap_dance_action_t *action; 84 + tap_dance_state_t* state; 84 85 85 86 switch (keycode) { 86 87 case TD(CT_CLN): 87 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 91 tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)action->user_data; 90 92 tap_code16(tap_hold->tap); 91 93 }