summaryrefslogtreecommitdiff
path: root/quantum/process_keycode
diff options
context:
space:
mode:
authorStephen Ostermiller <stephenostermiller@users.noreply.github.com>2025-11-23 06:32:36 -0500
committerGitHub <noreply@github.com>2025-11-23 22:32:36 +1100
commit1a954e8da5dcbd81eeccb9d6ac41b6eda64d7b85 (patch)
tree4f09599d467a9c9bbb1dde722511139fa965ff03 /quantum/process_keycode
parentc7e17538eea98540dbee71de5d024db99f7786fe (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>
Diffstat (limited to 'quantum/process_keycode')
-rw-r--r--quantum/process_keycode/process_tap_dance.c122
-rw-r--r--quantum/process_keycode/process_tap_dance.h11
2 files changed, 93 insertions, 40 deletions
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
26static uint16_t active_td; 26static uint16_t active_td;
27
28#ifndef TAP_DANCE_MAX_SIMULTANEOUS
29# define TAP_DANCE_MAX_SIMULTANEOUS 3
30#endif
31
32static tap_dance_state_t tap_dance_states[TAP_DANCE_MAX_SIMULTANEOUS];
33
27static uint16_t last_tap_time; 34static uint16_t last_tap_time;
28 35
36static 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
63tap_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
29void tap_dance_pair_on_each_tap(tap_dance_state_t *state, void *user_data) { 67void 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
89static inline void process_tap_dance_action_on_each_tap(tap_dance_action_t *action) { 127static 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
99static inline void process_tap_dance_action_on_each_release(tap_dance_action_t *action) { 137static 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
103static inline void process_tap_dance_action_on_reset(tap_dance_action_t *action) { 141static 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
113static inline void process_tap_dance_action_on_dance_finished(tap_dance_action_t *action) { 152static 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
130bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { 169bool 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
153bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { 197bool 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
186void tap_dance_task(void) { 234void 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
197void reset_tap_dance(tap_dance_state_t *state) { 247void 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
36typedef void (*tap_dance_user_fn_t)(tap_dance_state_t *state, void *user_data); 38typedef void (*tap_dance_user_fn_t)(tap_dance_state_t *state, void *user_data);
37 39
38typedef struct tap_dance_action_t { 40typedef 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
81void reset_tap_dance(tap_dance_state_t *state); 82void reset_tap_dance(tap_dance_state_t *state);
82 83
84tap_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
85bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record); 88bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record);