aboutsummaryrefslogtreecommitdiff
path: root/quantum/process_keycode
diff options
context:
space:
mode:
authorPete Sevander <pete.sevander@gmail.com>2021-08-06 02:44:57 +0300
committerGitHub <noreply@github.com>2021-08-06 09:44:57 +1000
commit7e983796e18e7401c062c158f23966aeb7b1405b (patch)
tree88d6897d7ca4f6103f03c1941dac6736ba477361 /quantum/process_keycode
parent07553b41f0a03ca6549c09cecf9cd3dec7332346 (diff)
downloadqmk_firmware-7e983796e18e7401c062c158f23966aeb7b1405b.tar.gz
qmk_firmware-7e983796e18e7401c062c158f23966aeb7b1405b.zip
Process combos earlier & overlapping combos (#8591)
* Combo processing improvements. Now it is possible to use ModTap and LayerTap keys as part of combos. Overlapping combos also don't trigger all the combos, just exactly the one that you press. New settings: - COMBO_MUST_HOLD_MODS - COMBO_MOD_TERM - COMBO_TERM_PER_COMBO - COMBO_MUST_HOLD_PER_COMBO - COMBO_STRICT_TIMER - COMBO_NO_TIMER * Remove the size flags from combo_t struct boolean members. This in the end actually saves space as the members are accessed so many times. The amount of operations needed to access the bits uses more memory than setting the size saves. * Fix `process_combo_key_release` not called correctly with tap-only combos * Fix not passing a pointer when NO_ACTION_TAPPING is defined. * Docs for `COMBO_ONLY_FROM_LAYER` * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Update quantum/process_keycode/process_combo.c Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Add `EXTRA_SHORT_COMBOS` option. Stuff combo's `disabled` and `active` flags into `state`. Possibly can save some space. * Add more examples and clarify things with dict management system. - Simple examples now has a combo that has modifiers included. - The slightly more advanced examples now are actually more advanced instead of just `tap_code16(<modded-keycode>)`. - Added a note that `COMBO_ACTION`s are not needed anymore as you can just use custom keycodes. - Added a note that the `g/keymap_combo.h` macros use the `process_combo_event` function and that it is not usable in one's keymap afterwards. * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Change "the" combo action example to "email" example. * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Fix sneaky infinite loop with `combo_disable()` No need to call `dump_key_buffer` when disabling combos because the buffer is either being dumped if a combo-key was pressed, or the buffer is empty if a non-combo-key is pressed. * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> Co-authored-by: Drashna Jaelre <drashna@live.com>
Diffstat (limited to 'quantum/process_keycode')
-rw-r--r--quantum/process_keycode/process_combo.c548
-rw-r--r--quantum/process_keycode/process_combo.h36
2 files changed, 476 insertions, 108 deletions
diff --git a/quantum/process_keycode/process_combo.c b/quantum/process_keycode/process_combo.c
index 9e1629248..b4e698dec 100644
--- a/quantum/process_keycode/process_combo.c
+++ b/quantum/process_keycode/process_combo.c
@@ -16,114 +16,445 @@
16 16
17#include "print.h" 17#include "print.h"
18#include "process_combo.h" 18#include "process_combo.h"
19#include "action_tapping.h"
19 20
20#ifndef COMBO_VARIABLE_LEN 21
21__attribute__((weak)) combo_t key_combos[COMBO_COUNT] = {}; 22#ifdef COMBO_COUNT
23__attribute__((weak)) combo_t key_combos[COMBO_COUNT];
24uint16_t COMBO_LEN = COMBO_COUNT;
22#else 25#else
23extern combo_t key_combos[]; 26extern combo_t key_combos[];
24extern int COMBO_LEN; 27extern uint16_t COMBO_LEN;
25#endif 28#endif
26 29
27__attribute__((weak)) void process_combo_event(uint16_t combo_index, bool pressed) {} 30__attribute__((weak)) void process_combo_event(uint16_t combo_index, bool pressed) {}
28 31
29static uint16_t timer = 0; 32#ifdef COMBO_MUST_HOLD_PER_COMBO
30static uint16_t current_combo_index = 0; 33__attribute__((weak)) bool get_combo_must_hold(uint16_t index, combo_t *combo) { return false; }
31static bool drop_buffer = false; 34#endif
32static bool is_active = false; 35
33static bool b_combo_enable = true; // defaults to enabled 36#ifdef COMBO_MUST_TAP_PER_COMBO
37__attribute__((weak)) bool get_combo_must_tap(uint16_t index, combo_t *combo) { return false; }
38#endif
34 39
35static uint8_t buffer_size = 0; 40#ifdef COMBO_TERM_PER_COMBO
36#ifdef COMBO_ALLOW_ACTION_KEYS 41__attribute__((weak)) uint16_t get_combo_term(uint16_t index, combo_t *combo) { return COMBO_TERM; }
37static keyrecord_t key_buffer[MAX_COMBO_LENGTH]; 42#endif
43
44#ifdef COMBO_PROCESS_KEY_RELEASE
45__attribute__((weak)) bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) { return false; }
46#endif
47
48#ifndef COMBO_NO_TIMER
49static uint16_t timer = 0;
50#endif
51static bool b_combo_enable = true; // defaults to enabled
52static uint16_t longest_term = 0;
53
54typedef struct {
55 keyrecord_t record;
56 uint16_t combo_index;
57 uint16_t keycode;
58} queued_record_t;
59static uint8_t key_buffer_size = 0;
60static queued_record_t key_buffer[COMBO_KEY_BUFFER_LENGTH];
61
62typedef struct {
63 uint16_t combo_index;
64} queued_combo_t;
65static uint8_t combo_buffer_write= 0;
66static uint8_t combo_buffer_read = 0;
67static queued_combo_t combo_buffer[COMBO_BUFFER_LENGTH];
68
69#define INCREMENT_MOD(i) i = (i + 1) % COMBO_BUFFER_LENGTH
70
71#define COMBO_KEY_POS ((keypos_t){.col=254, .row=254})
72
73
74#ifndef EXTRA_SHORT_COMBOS
75/* flags are their own elements in combo_t struct. */
76# define COMBO_ACTIVE(combo) (combo->active)
77# define COMBO_DISABLED(combo) (combo->disabled)
78# define COMBO_STATE(combo) (combo->state)
79
80# define ACTIVATE_COMBO(combo) do {combo->active = true;}while(0)
81# define DEACTIVATE_COMBO(combo) do {combo->active = false;}while(0)
82# define DISABLE_COMBO(combo) do {combo->disabled = true;}while(0)
83# define RESET_COMBO_STATE(combo) do { \
84 combo->disabled = false; \
85 combo->state = 0; \
86}while(0)
38#else 87#else
39static uint16_t key_buffer[MAX_COMBO_LENGTH]; 88/* flags are at the two high bits of state. */
89# define COMBO_ACTIVE(combo) (combo->state & 0x80)
90# define COMBO_DISABLED(combo) (combo->state & 0x40)
91# define COMBO_STATE(combo) (combo->state & 0x3F)
92
93# define ACTIVATE_COMBO(combo) do {combo->state |= 0x80;}while(0)
94# define DEACTIVATE_COMBO(combo) do {combo->state &= ~0x80;}while(0)
95# define DISABLE_COMBO(combo) do {combo->state |= 0x40;}while(0)
96# define RESET_COMBO_STATE(combo) do {combo->state &= ~0x7F;}while(0)
40#endif 97#endif
41 98
42static inline void send_combo(uint16_t action, bool pressed) { 99static inline void release_combo(uint16_t combo_index, combo_t *combo) {
43 if (action) { 100 if (combo->keycode) {
44 if (pressed) { 101 keyrecord_t record = {
45 register_code16(action); 102 .event = {
46 } else { 103 .key = COMBO_KEY_POS,
47 unregister_code16(action); 104 .time = timer_read()|1,
48 } 105 .pressed = false,
106 },
107 .keycode = combo->keycode,
108 };
109#ifndef NO_ACTION_TAPPING
110 action_tapping_process(record);
111#else
112 process_record(&record);
113#endif
49 } else { 114 } else {
50 process_combo_event(current_combo_index, pressed); 115 process_combo_event(combo_index, false);
116 }
117 DEACTIVATE_COMBO(combo);
118}
119
120static inline bool _get_combo_must_hold(uint16_t combo_index, combo_t *combo) {
121#ifdef COMBO_NO_TIMER
122 return false;
123#elif defined(COMBO_MUST_HOLD_PER_COMBO)
124 return get_combo_must_hold(combo_index, combo);
125#elif defined(COMBO_MUST_HOLD_MODS)
126 return (KEYCODE_IS_MOD(combo->keycode) ||
127 (combo->keycode >= QK_MOMENTARY && combo->keycode <= QK_MOMENTARY_MAX));
128#endif
129 return false;
130}
131
132static inline uint16_t _get_wait_time(uint16_t combo_index, combo_t *combo ) {
133 if (_get_combo_must_hold(combo_index, combo)
134#ifdef COMBO_MUST_TAP_PER_COMBO
135 || get_combo_must_tap(combo_index, combo)
136#endif
137 ) {
138 if (longest_term < COMBO_HOLD_TERM) {
139 return COMBO_HOLD_TERM;
140 }
141 }
142
143 return longest_term;
144}
145
146static inline uint16_t _get_combo_term(uint16_t combo_index, combo_t *combo) {
147
148#if defined(COMBO_TERM_PER_COMBO)
149 return get_combo_term(combo_index, combo);
150#endif
151
152 return COMBO_TERM;
153}
154
155void clear_combos(void) {
156 uint16_t index = 0;
157 longest_term = 0;
158 for (index = 0; index < COMBO_LEN; ++index) {
159 combo_t *combo = &key_combos[index];
160 if (!COMBO_ACTIVE(combo)) {
161 RESET_COMBO_STATE(combo);
162 }
51 } 163 }
52} 164}
53 165
54static inline void dump_key_buffer(bool emit) { 166static inline void dump_key_buffer(void) {
55 if (buffer_size == 0) { 167 if (key_buffer_size == 0) {
56 return; 168 return;
57 } 169 }
58 170
59 if (emit) { 171 for (uint8_t key_buffer_i = 0; key_buffer_i < key_buffer_size; key_buffer_i++) {
60 for (uint8_t i = 0; i < buffer_size; i++) { 172
61#ifdef COMBO_ALLOW_ACTION_KEYS 173 queued_record_t *qrecord = &key_buffer[key_buffer_i];
62 const action_t action = store_or_get_action(key_buffer[i].event.pressed, key_buffer[i].event.key); 174 keyrecord_t *record = &qrecord->record;
63 process_action(&(key_buffer[i]), action); 175
176 if (IS_NOEVENT(record->event)) {
177 continue;
178 }
179
180 if (!record->keycode && qrecord->combo_index != (uint16_t)-1) {
181 process_combo_event(qrecord->combo_index, true);
182 } else {
183#ifndef NO_ACTION_TAPPING
184 action_tapping_process(*record);
64#else 185#else
65 register_code16(key_buffer[i]); 186 process_record(record);
66 send_keyboard_report();
67#endif 187#endif
68 } 188 }
189 record->event.time = 0;
69 } 190 }
70 191
71 buffer_size = 0; 192 key_buffer_size = 0;
72} 193}
73 194
74#define ALL_COMBO_KEYS_ARE_DOWN (((1 << count) - 1) == combo->state) 195#define NO_COMBO_KEYS_ARE_DOWN (0 == COMBO_STATE(combo))
75#define KEY_STATE_DOWN(key) \ 196#define ALL_COMBO_KEYS_ARE_DOWN(state, key_count) (((1 << key_count) - 1) == state)
76 do { \ 197#define ONLY_ONE_KEY_IS_DOWN(state) !(state & (state - 1))
77 combo->state |= (1 << key); \ 198#define KEY_NOT_YET_RELEASED(state, key_index) ((1 << key_index) & state)
199#define KEY_STATE_DOWN(state, key_index) \
200 do { \
201 state |= (1 << key_index); \
78 } while (0) 202 } while (0)
79#define KEY_STATE_UP(key) \ 203#define KEY_STATE_UP(state, key_index) \
80 do { \ 204 do { \
81 combo->state &= ~(1 << key); \ 205 state &= ~(1 << key_index); \
82 } while (0) 206 } while (0)
83 207
84static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record) { 208static inline void _find_key_index_and_count(const uint16_t *keys, uint16_t keycode, uint16_t *key_index, uint8_t *key_count) {
85 uint8_t count = 0; 209 while (true) {
86 uint16_t index = -1; 210 uint16_t key = pgm_read_word(&keys[*key_count]);
87 /* Find index of keycode and number of combo keys */ 211 if (keycode == key) *key_index = *key_count;
88 for (const uint16_t *keys = combo->keys;; ++count) {
89 uint16_t key = pgm_read_word(&keys[count]);
90 if (keycode == key) index = count;
91 if (COMBO_END == key) break; 212 if (COMBO_END == key) break;
213 (*key_count)++;
214 }
215}
216
217void drop_combo_from_buffer(uint16_t combo_index) {
218 /* Mark a combo as processed from the buffer. If the buffer is in the
219 * beginning of the buffer, drop it. */
220 uint8_t i = combo_buffer_read;
221 while (i != combo_buffer_write) {
222 queued_combo_t *qcombo = &combo_buffer[i];
223
224 if (qcombo->combo_index == combo_index) {
225 combo_t *combo = &key_combos[combo_index];
226 DISABLE_COMBO(combo);
227
228 if (i == combo_buffer_read) {
229 INCREMENT_MOD(combo_buffer_read);
230 }
231 break;
232 }
233 INCREMENT_MOD(i);
92 } 234 }
235}
236
237void apply_combo(uint16_t combo_index, combo_t *combo) {
238 /* Apply combo's result keycode to the last chord key of the combo and
239 * disable the other keys. */
93 240
94 /* Continue processing if not a combo key */ 241 if (COMBO_DISABLED(combo)) { return; }
95 if (-1 == (int8_t)index) return false;
96 242
97 bool is_combo_active = is_active; 243 // state to check against so we find the last key of the combo from the buffer
244#if defined(EXTRA_EXTRA_LONG_COMBOS)
245 uint32_t state = 0;
246#elif defined(EXTRA_LONG_COMBOS)
247 uint16_t state = 0;
248#else
249 uint8_t state = 0;
250#endif
251
252 for (uint8_t key_buffer_i = 0; key_buffer_i < key_buffer_size; key_buffer_i++) {
253
254 queued_record_t *qrecord = &key_buffer[key_buffer_i];
255 keyrecord_t *record = &qrecord->record;
256 uint16_t keycode = qrecord->keycode;
257
258 uint8_t key_count = 0;
259 uint16_t key_index = -1;
260 _find_key_index_and_count(combo->keys, keycode, &key_index, &key_count);
261
262 if (-1 == (int16_t)key_index) {
263 // key not part of this combo
264 continue;
265 }
98 266
99 if (record->event.pressed) { 267 KEY_STATE_DOWN(state, key_index);
100 KEY_STATE_DOWN(index); 268 if (ALL_COMBO_KEYS_ARE_DOWN(state, key_count)) {
269 // this in the end executes the combo when the key_buffer is dumped.
270 record->keycode = combo->keycode;
271 record->event.key = COMBO_KEY_POS;
101 272
102 if (is_combo_active) { 273 qrecord->combo_index = combo_index;
103 if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was pressed */ 274 ACTIVATE_COMBO(combo);
104 send_combo(combo->keycode, true); 275
105 drop_buffer = true; 276 break;
277 } else {
278 // key was part of the combo but not the last one, "disable" it
279 // by making it a TICK event.
280 record->event.time = 0;
281 }
282
283 }
284 drop_combo_from_buffer(combo_index);
285}
286
287static inline void apply_combos(void) {
288 // Apply all buffered normal combos.
289 for (uint8_t i = combo_buffer_read;
290 i != combo_buffer_write;
291 INCREMENT_MOD(i)) {
292
293 queued_combo_t *buffered_combo = &combo_buffer[i];
294 combo_t *combo = &key_combos[buffered_combo->combo_index];
295
296#ifdef COMBO_MUST_TAP_PER_COMBO
297 if (get_combo_must_tap(buffered_combo->combo_index, combo)) {
298 // Tap-only combos are applied on key release only, so let's drop 'em here.
299 drop_combo_from_buffer(buffered_combo->combo_index);
300 continue;
301 }
302#endif
303 apply_combo(buffered_combo->combo_index, combo);
304 }
305 dump_key_buffer();
306 clear_combos();
307}
308
309combo_t* overlaps(combo_t *combo1, combo_t *combo2) {
310 /* Checks if the combos overlap and returns the combo that should be
311 * dropped from the combo buffer.
312 * The combo that has less keys will be dropped. If they have the same
313 * amount of keys, drop combo1. */
314
315 uint8_t idx1 = 0, idx2 = 0;
316 uint16_t key1, key2;
317 bool overlaps = false;
318
319 while ((key1 = pgm_read_word(&combo1->keys[idx1])) != COMBO_END) {
320 idx2 = 0;
321 while ((key2 = pgm_read_word(&combo2->keys[idx2])) != COMBO_END) {
322 if (key1 == key2) overlaps = true;
323 idx2 += 1;
324 }
325 idx1 += 1;
326 }
327
328 if (!overlaps) return NULL;
329 if (idx2 < idx1) return combo2;
330 return combo1;
331}
332
333static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record, uint16_t combo_index) {
334 uint8_t key_count = 0;
335 uint16_t key_index = -1;
336 _find_key_index_and_count(combo->keys, keycode, &key_index, &key_count);
337
338 /* Continue processing if key isn't part of current combo. */
339 if (-1 == (int16_t)key_index) {
340 return false;
341 }
342
343 bool key_is_part_of_combo = !COMBO_DISABLED(combo);
344
345 if (record->event.pressed && !COMBO_DISABLED(combo)) {
346 uint16_t time = _get_combo_term(combo_index, combo);
347 if (!COMBO_ACTIVE(combo)) {
348 KEY_STATE_DOWN(combo->state, key_index);
349 if (longest_term < time) {
350 longest_term = time;
106 } 351 }
107 } 352 }
353 if (ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) {
354 /* Combo was fully pressed */
355 /* Buffer the combo so we can fire it after COMBO_TERM */
356
357#ifndef COMBO_NO_TIMER
358 /* Don't buffer this combo if its combo term has passed. */
359 if (timer && timer_elapsed(timer) > time) {
360 DISABLE_COMBO(combo);
361 return true;
362 } else
363#endif
364 {
365
366 // disable readied combos that overlap with this combo
367 combo_t *drop = NULL;
368 for (uint8_t combo_buffer_i = combo_buffer_read;
369 combo_buffer_i != combo_buffer_write;
370 INCREMENT_MOD(combo_buffer_i)) {
371
372 queued_combo_t *qcombo = &combo_buffer[combo_buffer_i];
373 combo_t *buffered_combo = &key_combos[qcombo->combo_index];
374
375 if ((drop = overlaps(buffered_combo, combo))) {
376 DISABLE_COMBO(drop);
377 if (drop == combo) {
378 // stop checking for overlaps if dropped combo was current combo.
379 break;
380 } else if (combo_buffer_i == combo_buffer_read && drop == buffered_combo) {
381 /* Drop the disabled buffered combo from the buffer if
382 * it is in the beginning of the buffer. */
383 INCREMENT_MOD(combo_buffer_read);
384 }
385 }
386
387 }
388
389 if (drop != combo) {
390 // save this combo to buffer
391 combo_buffer[combo_buffer_write] = (queued_combo_t){
392 .combo_index=combo_index,
393 };
394 INCREMENT_MOD(combo_buffer_write);
395
396 // get possible longer waiting time for tap-/hold-only combos.
397 longest_term = _get_wait_time(combo_index, combo);
398 }
399 } // if timer elapsed end
400
401 }
108 } else { 402 } else {
109 if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was released */ 403 // chord releases
110 send_combo(combo->keycode, false); 404 if (!COMBO_ACTIVE(combo) && ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) {
405 /* First key quickly released */
406 if (COMBO_DISABLED(combo) || _get_combo_must_hold(combo_index, combo)) {
407 // combo wasn't tappable, disable it and drop it from buffer.
408 drop_combo_from_buffer(combo_index);
409 key_is_part_of_combo = false;
410 }
411#ifdef COMBO_MUST_TAP_PER_COMBO
412 else if (get_combo_must_tap(combo_index, combo)) {
413 // immediately apply tap-only combo
414 apply_combo(combo_index, combo);
415 apply_combos(); // also apply other prepared combos and dump key buffer
416# ifdef COMBO_PROCESS_KEY_RELEASE
417 if (process_combo_key_release(combo_index, combo, key_index, keycode)) {
418 release_combo(combo_index, combo);
419 }
420# endif
421 }
422#endif
423 } else if (COMBO_ACTIVE(combo)
424 && ONLY_ONE_KEY_IS_DOWN(COMBO_STATE(combo))
425 && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index)
426 ) {
427 /* last key released */
428 release_combo(combo_index, combo);
429 key_is_part_of_combo = true;
430
431#ifdef COMBO_PROCESS_KEY_RELEASE
432 process_combo_key_release(combo_index, combo, key_index, keycode);
433#endif
434 } else if (COMBO_ACTIVE(combo)
435 && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index)
436 ) {
437 /* first or middle key released */
438 key_is_part_of_combo = true;
439
440#ifdef COMBO_PROCESS_KEY_RELEASE
441 if (process_combo_key_release(combo_index, combo, key_index, keycode)) {
442 release_combo(combo_index, combo);
443 }
444#endif
111 } else { 445 } else {
112 /* continue processing without immediately returning */ 446 /* The released key was part of an incomplete combo */
113 is_combo_active = false; 447 key_is_part_of_combo = false;
114 } 448 }
115 449
116 KEY_STATE_UP(index); 450 KEY_STATE_UP(combo->state, key_index);
117 } 451 }
118 452
119 return is_combo_active; 453 return key_is_part_of_combo;
120} 454}
121 455
122#define NO_COMBO_KEYS_ARE_DOWN (0 == combo->state)
123
124bool process_combo(uint16_t keycode, keyrecord_t *record) { 456bool process_combo(uint16_t keycode, keyrecord_t *record) {
125 bool is_combo_key = false; 457 bool is_combo_key = false;
126 drop_buffer = false;
127 bool no_combo_keys_pressed = true; 458 bool no_combo_keys_pressed = true;
128 459
129 if (keycode == CMB_ON && record->event.pressed) { 460 if (keycode == CMB_ON && record->event.pressed) {
@@ -144,62 +475,81 @@ bool process_combo(uint16_t keycode, keyrecord_t *record) {
144 if (!is_combo_enabled()) { 475 if (!is_combo_enabled()) {
145 return true; 476 return true;
146 } 477 }
147#ifndef COMBO_VARIABLE_LEN 478
148 for (current_combo_index = 0; current_combo_index < COMBO_COUNT; ++current_combo_index) { 479#ifdef COMBO_ONLY_FROM_LAYER
149#else 480 /* Only check keycodes from one layer. */
150 for (current_combo_index = 0; current_combo_index < COMBO_LEN; ++current_combo_index) { 481 keycode = keymap_key_to_keycode(COMBO_ONLY_FROM_LAYER, record->event.key);
151#endif 482#endif
152 combo_t *combo = &key_combos[current_combo_index]; 483
153 is_combo_key |= process_single_combo(combo, keycode, record); 484 for (uint16_t idx = 0; idx < COMBO_LEN; ++idx) {
154 no_combo_keys_pressed = no_combo_keys_pressed && NO_COMBO_KEYS_ARE_DOWN; 485 combo_t *combo = &key_combos[idx];
486 is_combo_key |= process_single_combo(combo, keycode, record, idx);
487 no_combo_keys_pressed = no_combo_keys_pressed && (NO_COMBO_KEYS_ARE_DOWN || COMBO_ACTIVE(combo) || COMBO_DISABLED(combo));
155 } 488 }
156 489
157 if (drop_buffer) { 490 if (record->event.pressed && is_combo_key) {
158 /* buffer is only dropped when we complete a combo, so we refresh the timer 491#ifndef COMBO_NO_TIMER
159 * here */ 492# ifdef COMBO_STRICT_TIMER
160 timer = timer_read(); 493 if (!timer) {
161 dump_key_buffer(false); 494 // timer is set only on the first key
162 } else if (!is_combo_key) { 495 timer = timer_read();
163 /* if no combos claim the key we need to emit the keybuffer */ 496 }
164 dump_key_buffer(true); 497# else
165
166 // reset state if there are no combo keys pressed at all
167 if (no_combo_keys_pressed) {
168 timer = 0;
169 is_active = true;
170 }
171 } else if (record->event.pressed && is_active) {
172 /* otherwise the key is consumed and placed in the buffer */
173 timer = timer_read(); 498 timer = timer_read();
499# endif
500#endif
174 501
175 if (buffer_size < MAX_COMBO_LENGTH) { 502 if (key_buffer_size < COMBO_KEY_BUFFER_LENGTH) {
176#ifdef COMBO_ALLOW_ACTION_KEYS 503 key_buffer[key_buffer_size++] = (queued_record_t){
177 key_buffer[buffer_size++] = *record; 504 .record = *record,
178#else 505 .keycode = keycode,
179 key_buffer[buffer_size++] = keycode; 506 .combo_index = -1, // this will be set when applying combos
507 };
508 }
509 } else {
510 if (combo_buffer_read != combo_buffer_write) {
511 // some combo is prepared
512 apply_combos();
513 } else {
514 // reset state if there are no combo keys pressed at all
515 dump_key_buffer();
516#ifndef COMBO_NO_TIMER
517 timer = 0;
180#endif 518#endif
519 clear_combos();
181 } 520 }
182 } 521 }
183
184 return !is_combo_key; 522 return !is_combo_key;
185} 523}
186 524
187void combo_task(void) { 525void combo_task(void) {
188 if (b_combo_enable && is_active && timer && timer_elapsed(timer) > COMBO_TERM) { 526 if (!b_combo_enable) {
189 /* This disables the combo, meaning key events for this 527 return;
190 * combo will be handled by the next processors in the chain
191 */
192 is_active = false;
193 dump_key_buffer(true);
194 } 528 }
529
530#ifndef COMBO_NO_TIMER
531 if (timer && timer_elapsed(timer) > longest_term) {
532 if (combo_buffer_read != combo_buffer_write) {
533 apply_combos();
534 longest_term = 0;
535 timer = 0;
536 } else {
537 dump_key_buffer();
538 timer = 0;
539 clear_combos();
540 }
541 }
542#endif
195} 543}
196 544
197void combo_enable(void) { b_combo_enable = true; } 545void combo_enable(void) { b_combo_enable = true; }
198 546
199void combo_disable(void) { 547void combo_disable(void) {
200 b_combo_enable = is_active = false; 548#ifndef COMBO_NO_TIMER
201 timer = 0; 549 timer = 0;
202 dump_key_buffer(true); 550#endif
551 b_combo_enable = false;
552 combo_buffer_read = combo_buffer_write;
203} 553}
204 554
205void combo_toggle(void) { 555void combo_toggle(void) {
diff --git a/quantum/process_keycode/process_combo.h b/quantum/process_keycode/process_combo.h
index 9af97588b..43c36d79e 100644
--- a/quantum/process_keycode/process_combo.h
+++ b/quantum/process_keycode/process_combo.h
@@ -20,23 +20,38 @@
20#include "quantum.h" 20#include "quantum.h"
21#include <stdint.h> 21#include <stdint.h>
22 22
23#ifdef EXTRA_EXTRA_LONG_COMBOS 23#ifdef EXTRA_SHORT_COMBOS
24# define MAX_COMBO_LENGTH 6
25#elif defined(EXTRA_EXTRA_LONG_COMBOS)
24# define MAX_COMBO_LENGTH 32 26# define MAX_COMBO_LENGTH 32
25#elif EXTRA_LONG_COMBOS 27#elif defined(EXTRA_LONG_COMBOS)
26# define MAX_COMBO_LENGTH 16 28# define MAX_COMBO_LENGTH 16
27#else 29#else
28# define MAX_COMBO_LENGTH 8 30# define MAX_COMBO_LENGTH 8
29#endif 31#endif
30 32
33#ifndef COMBO_KEY_BUFFER_LENGTH
34# define COMBO_KEY_BUFFER_LENGTH MAX_COMBO_LENGTH
35#endif
36#ifndef COMBO_BUFFER_LENGTH
37# define COMBO_BUFFER_LENGTH 4
38#endif
39
31typedef struct { 40typedef struct {
32 const uint16_t *keys; 41 const uint16_t *keys;
33 uint16_t keycode; 42 uint16_t keycode;
34#ifdef EXTRA_EXTRA_LONG_COMBOS 43#ifdef EXTRA_SHORT_COMBOS
44 uint8_t state;
45#else
46 bool disabled;
47 bool active;
48# if defined(EXTRA_EXTRA_LONG_COMBOS)
35 uint32_t state; 49 uint32_t state;
36#elif EXTRA_LONG_COMBOS 50# elif defined(EXTRA_LONG_COMBOS)
37 uint16_t state; 51 uint16_t state;
38#else 52# else
39 uint8_t state; 53 uint8_t state;
54# endif
40#endif 55#endif
41} combo_t; 56} combo_t;
42 57
@@ -46,12 +61,15 @@ typedef struct {
46 { .keys = &(ck)[0] } 61 { .keys = &(ck)[0] }
47 62
48#define COMBO_END 0 63#define COMBO_END 0
49#ifndef COMBO_COUNT
50# define COMBO_COUNT 0
51#endif
52#ifndef COMBO_TERM 64#ifndef COMBO_TERM
53# define COMBO_TERM TAPPING_TERM 65# define COMBO_TERM 50
54#endif 66#endif
67#ifndef COMBO_HOLD_TERM
68# define COMBO_HOLD_TERM TAPPING_TERM
69#endif
70
71/* check if keycode is only modifiers */
72#define KEYCODE_IS_MOD(code) (IS_MOD(code) || (code >= QK_MODS && code <= QK_MODS_MAX && !(code & QK_BASIC_MAX)))
55 73
56bool process_combo(uint16_t keycode, keyrecord_t *record); 74bool process_combo(uint16_t keycode, keyrecord_t *record);
57void combo_task(void); 75void combo_task(void);