Parent directory

process_combo.c

22098 bytes
  1/* Copyright 2016 Jack Humbert
  2 *
  3 * This program is free software: you can redistribute it and/or modify
  4 * it under the terms of the GNU General Public License as published by
  5 * the Free Software Foundation, either version 2 of the License, or
  6 * (at your option) any later version.
  7 *
  8 * This program is distributed in the hope that it will be useful,
  9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 11 * GNU General Public License for more details.
 12 *
 13 * You should have received a copy of the GNU General Public License
 14 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 15 */
 16
 17#include "process_combo.h"
 18#include <stddef.h>
 19#include "process_auto_shift.h"
 20#include "caps_word.h"
 21#include "timer.h"
 22#include "wait.h"
 23#include "keyboard.h"
 24#include "keymap_common.h"
 25#include "action_layer.h"
 26#include "action_tapping.h"
 27#include "action_util.h"
 28#include "keymap_introspection.h"
 29
 30__attribute__((weak)) void process_combo_event(uint16_t combo_index, bool pressed) {}
 31
 32#ifndef COMBO_ONLY_FROM_LAYER
 33__attribute__((weak)) uint8_t combo_ref_from_layer(uint8_t layer) {
 34    return layer;
 35}
 36#endif
 37
 38#ifdef COMBO_MUST_HOLD_PER_COMBO
 39__attribute__((weak)) bool get_combo_must_hold(uint16_t combo_index, combo_t *combo) {
 40    return false;
 41}
 42#endif
 43
 44#ifdef COMBO_MUST_TAP_PER_COMBO
 45__attribute__((weak)) bool get_combo_must_tap(uint16_t combo_index, combo_t *combo) {
 46    return false;
 47}
 48#endif
 49
 50#ifdef COMBO_TERM_PER_COMBO
 51__attribute__((weak)) uint16_t get_combo_term(uint16_t combo_index, combo_t *combo) {
 52    return COMBO_TERM;
 53}
 54#endif
 55
 56#ifdef COMBO_MUST_PRESS_IN_ORDER_PER_COMBO
 57__attribute__((weak)) bool get_combo_must_press_in_order(uint16_t combo_index, combo_t *combo) {
 58    return true;
 59}
 60#endif
 61
 62#ifdef COMBO_PROCESS_KEY_RELEASE
 63__attribute__((weak)) bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) {
 64    return false;
 65}
 66#endif
 67
 68#ifdef COMBO_PROCESS_KEY_REPRESS
 69__attribute__((weak)) bool process_combo_key_repress(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) {
 70    return false;
 71}
 72#endif
 73
 74#ifdef COMBO_SHOULD_TRIGGER
 75__attribute__((weak)) bool combo_should_trigger(uint16_t combo_index, combo_t *combo, uint16_t keycode, keyrecord_t *record) {
 76    return true;
 77}
 78#endif
 79
 80typedef enum { COMBO_KEY_NOT_PRESSED, COMBO_KEY_PRESSED, COMBO_KEY_REPRESSED } combo_key_action_t;
 81
 82#ifndef COMBO_NO_TIMER
 83static uint16_t timer = 0;
 84#endif
 85static bool     b_combo_enable = true; // defaults to enabled
 86static uint16_t longest_term   = 0;
 87
 88typedef struct {
 89    keyrecord_t record;
 90    uint16_t    combo_index;
 91    uint16_t    keycode;
 92} queued_record_t;
 93static uint8_t         key_buffer_size = 0;
 94static queued_record_t key_buffer[COMBO_KEY_BUFFER_LENGTH];
 95
 96typedef struct {
 97    uint16_t combo_index;
 98} queued_combo_t;
 99static uint8_t        combo_buffer_write = 0;
100static uint8_t        combo_buffer_read  = 0;
101static queued_combo_t combo_buffer[COMBO_BUFFER_LENGTH];
102
103#define INCREMENT_MOD(i) i = (i + 1) % COMBO_BUFFER_LENGTH
104
105#ifndef EXTRA_SHORT_COMBOS
106/* flags are their own elements in combo_t struct. */
107#    define COMBO_ACTIVE(combo) (combo->active)
108#    define COMBO_DISABLED(combo) (combo->disabled)
109#    define COMBO_STATE(combo) (combo->state)
110
111#    define ACTIVATE_COMBO(combo) \
112        do {                      \
113            combo->active = true; \
114        } while (0)
115#    define DEACTIVATE_COMBO(combo) \
116        do {                        \
117            combo->active = false;  \
118        } while (0)
119#    define DISABLE_COMBO(combo)    \
120        do {                        \
121            combo->disabled = true; \
122        } while (0)
123#    define RESET_COMBO_STATE(combo) \
124        do {                         \
125            combo->disabled = false; \
126            combo->state    = 0;     \
127        } while (0)
128#else
129/* flags are at the two high bits of state. */
130#    define COMBO_ACTIVE(combo) (combo->state & 0x80)
131#    define COMBO_DISABLED(combo) (combo->state & 0x40)
132#    define COMBO_STATE(combo) (combo->state & 0x3F)
133
134#    define ACTIVATE_COMBO(combo) \
135        do {                      \
136            combo->state |= 0x80; \
137        } while (0)
138#    define DEACTIVATE_COMBO(combo) \
139        do {                        \
140            combo->state &= ~0x80;  \
141        } while (0)
142#    define DISABLE_COMBO(combo)  \
143        do {                      \
144            combo->state |= 0x40; \
145        } while (0)
146#    define RESET_COMBO_STATE(combo) \
147        do {                         \
148            combo->state &= ~0x7F;   \
149        } while (0)
150#endif
151
152static inline void release_combo(uint16_t combo_index, combo_t *combo) {
153    if (combo->keycode) {
154        keyrecord_t record = {
155            .event   = MAKE_COMBOEVENT(false),
156            .keycode = combo->keycode,
157        };
158#ifndef NO_ACTION_TAPPING
159        action_tapping_process(record);
160#else
161        process_record(&record);
162#endif
163    } else {
164        process_combo_event(combo_index, false);
165    }
166    DEACTIVATE_COMBO(combo);
167}
168
169static inline bool _get_combo_must_hold(uint16_t combo_index, combo_t *combo) {
170#ifdef COMBO_NO_TIMER
171    return false;
172#elif defined(COMBO_MUST_HOLD_PER_COMBO)
173    return get_combo_must_hold(combo_index, combo);
174#elif defined(COMBO_MUST_HOLD_MODS)
175    return (KEYCODE_IS_MOD(combo->keycode) || (combo->keycode >= QK_MOMENTARY && combo->keycode <= QK_MOMENTARY_MAX));
176#endif
177    return false;
178}
179
180static inline uint16_t _get_wait_time(uint16_t combo_index, combo_t *combo) {
181    if (_get_combo_must_hold(combo_index, combo)
182#ifdef COMBO_MUST_TAP_PER_COMBO
183        || get_combo_must_tap(combo_index, combo)
184#endif
185    ) {
186        if (longest_term < COMBO_HOLD_TERM) {
187            return COMBO_HOLD_TERM;
188        }
189    }
190
191    return longest_term;
192}
193
194static inline uint16_t _get_combo_term(uint16_t combo_index, combo_t *combo) {
195#if defined(COMBO_TERM_PER_COMBO)
196    return get_combo_term(combo_index, combo);
197#endif
198
199    return COMBO_TERM;
200}
201
202void clear_combos(void) {
203    uint16_t index = 0;
204    longest_term   = 0;
205    for (index = 0; index < combo_count(); ++index) {
206        combo_t *combo = combo_get(index);
207        if (!COMBO_ACTIVE(combo)) {
208            RESET_COMBO_STATE(combo);
209        }
210    }
211}
212
213static inline void dump_key_buffer(void) {
214    /* First call start from 0 index; recursive calls need to start from i+1 index */
215    static uint8_t key_buffer_next = 0;
216#if TAP_CODE_DELAY > 0
217    bool delay_done = false;
218#endif
219
220    if (key_buffer_size == 0) {
221        return;
222    }
223
224    for (uint8_t key_buffer_i = key_buffer_next; key_buffer_i < key_buffer_size; key_buffer_i++) {
225        key_buffer_next = key_buffer_i + 1;
226
227        queued_record_t *qrecord = &key_buffer[key_buffer_i];
228        keyrecord_t *    record  = &qrecord->record;
229
230        if (IS_NOEVENT(record->event)) {
231            continue;
232        }
233
234        if (!record->keycode && qrecord->combo_index != (uint16_t)-1) {
235            process_combo_event(qrecord->combo_index, true);
236        } else {
237#ifndef NO_ACTION_TAPPING
238            action_tapping_process(*record);
239#else
240            process_record(record);
241#endif
242        }
243        record->event.type = TICK_EVENT;
244
245#if defined(CAPS_WORD_ENABLE) && defined(AUTO_SHIFT_ENABLE)
246        // Edge case: preserve the weak Left Shift mod if both Caps Word and
247        // Auto Shift are on. Caps Word capitalizes by setting the weak Left
248        // Shift mod during the press event, but Auto Shift doesn't send the
249        // key until it receives the release event.
250        del_weak_mods((is_caps_word_on() && get_autoshift_state()) ? ~MOD_BIT(KC_LSFT) : 0xff);
251#else
252        clear_weak_mods();
253#endif // defined(CAPS_WORD_ENABLE) && defined(AUTO_SHIFT_ENABLE)
254
255#if TAP_CODE_DELAY > 0
256        // only delay once and for a non-tapping key
257        if (!delay_done && !is_tap_record(record)) {
258            delay_done = true;
259            wait_ms(TAP_CODE_DELAY);
260        }
261#endif
262    }
263
264    key_buffer_next = key_buffer_size = 0;
265}
266
267#define NO_COMBO_KEYS_ARE_DOWN (0 == COMBO_STATE(combo))
268#define ALL_COMBO_KEYS_ARE_DOWN(state, key_count) (((1 << key_count) - 1) == state)
269#define ONLY_ONE_KEY_IS_DOWN(state) !(state & (state - 1))
270#define KEY_NOT_YET_RELEASED(state, key_index) ((1 << key_index) & state)
271#define KEY_STATE_DOWN(state, key_index) \
272    do {                                 \
273        state |= (1 << key_index);       \
274    } while (0)
275#define KEY_STATE_UP(state, key_index) \
276    do {                               \
277        state &= ~(1 << key_index);    \
278    } while (0)
279
280static inline void _find_key_index_and_count(const uint16_t *keys, uint16_t keycode, uint16_t *key_index, uint8_t *key_count) {
281    while (true) {
282        uint16_t key = pgm_read_word(&keys[*key_count]);
283        if (keycode == key) *key_index = *key_count;
284        if (COMBO_END == key) break;
285        (*key_count)++;
286    }
287}
288
289void drop_combo_from_buffer(uint16_t combo_index) {
290    /* Mark a combo as processed from the buffer. If the buffer is in the
291     * beginning of the buffer, drop it.  */
292    uint8_t i = combo_buffer_read;
293    while (i != combo_buffer_write) {
294        queued_combo_t *qcombo = &combo_buffer[i];
295
296        if (qcombo->combo_index == combo_index) {
297            combo_t *combo = combo_get(combo_index);
298            DISABLE_COMBO(combo);
299
300            if (i == combo_buffer_read) {
301                INCREMENT_MOD(combo_buffer_read);
302            }
303            break;
304        }
305        INCREMENT_MOD(i);
306    }
307}
308
309void apply_combo(uint16_t combo_index, combo_t *combo) {
310    /* Apply combo's result keycode to the last chord key of the combo and
311     * disable the other keys. */
312
313    if (COMBO_DISABLED(combo)) {
314        return;
315    }
316
317    // state to check against so we find the last key of the combo from the buffer
318#if defined(EXTRA_EXTRA_LONG_COMBOS)
319    uint32_t state = 0;
320#elif defined(EXTRA_LONG_COMBOS)
321    uint16_t state         = 0;
322#else
323    uint8_t state = 0;
324#endif
325
326    for (uint8_t key_buffer_i = 0; key_buffer_i < key_buffer_size; key_buffer_i++) {
327        queued_record_t *qrecord = &key_buffer[key_buffer_i];
328        keyrecord_t *    record  = &qrecord->record;
329        uint16_t         keycode = qrecord->keycode;
330
331        uint8_t  key_count = 0;
332        uint16_t key_index = -1;
333        _find_key_index_and_count(combo->keys, keycode, &key_index, &key_count);
334
335        if (-1 == (int16_t)key_index) {
336            // key not part of this combo
337            continue;
338        }
339
340        KEY_STATE_DOWN(state, key_index);
341        if (ALL_COMBO_KEYS_ARE_DOWN(state, key_count)) {
342            // this in the end executes the combo when the key_buffer is dumped.
343            record->keycode    = combo->keycode;
344            record->event.type = COMBO_EVENT;
345            record->event.key  = MAKE_KEYPOS(0, 0);
346
347            qrecord->combo_index = combo_index;
348            ACTIVATE_COMBO(combo);
349
350            break;
351        } else {
352            // key was part of the combo but not the last one, "disable" it
353            // by making it a TICK event.
354            record->event.type = TICK_EVENT;
355        }
356    }
357    drop_combo_from_buffer(combo_index);
358}
359
360static inline void apply_combos(void) {
361    // Apply all buffered normal combos.
362    for (uint8_t i = combo_buffer_read; i != combo_buffer_write; INCREMENT_MOD(i)) {
363        queued_combo_t *buffered_combo = &combo_buffer[i];
364        combo_t *       combo          = combo_get(buffered_combo->combo_index);
365
366#ifdef COMBO_MUST_TAP_PER_COMBO
367        if (get_combo_must_tap(buffered_combo->combo_index, combo)) {
368            // Tap-only combos are applied on key release only, so let's drop 'em here.
369            drop_combo_from_buffer(buffered_combo->combo_index);
370            continue;
371        }
372#endif
373        apply_combo(buffered_combo->combo_index, combo);
374    }
375    dump_key_buffer();
376    clear_combos();
377}
378
379combo_t *overlaps(combo_t *combo1, combo_t *combo2) {
380    /* Checks if the combos overlap and returns the combo that should be
381     * dropped from the combo buffer.
382     * The combo that has less keys will be dropped. If they have the same
383     * amount of keys, drop combo1. */
384
385    uint8_t  idx1 = 0, idx2 = 0;
386    uint16_t key1, key2;
387    bool     overlaps = false;
388
389    while ((key1 = pgm_read_word(&combo1->keys[idx1])) != COMBO_END) {
390        idx2 = 0;
391        while ((key2 = pgm_read_word(&combo2->keys[idx2])) != COMBO_END) {
392            if (key1 == key2) overlaps = true;
393            idx2 += 1;
394        }
395        idx1 += 1;
396    }
397
398    if (!overlaps) return NULL;
399    if (idx2 < idx1) return combo2;
400    return combo1;
401}
402
403#if defined(COMBO_MUST_PRESS_IN_ORDER) || defined(COMBO_MUST_PRESS_IN_ORDER_PER_COMBO)
404static bool keys_pressed_in_order(uint16_t combo_index, combo_t *combo, uint16_t key_index, uint16_t keycode, keyrecord_t *record) {
405#    ifdef COMBO_MUST_PRESS_IN_ORDER_PER_COMBO
406    if (!get_combo_must_press_in_order(combo_index, combo)) {
407        return true;
408    }
409#    endif
410    if (
411        // The `state` bit for the key being pressed.
412        (1 << key_index) ==
413        // The *next* combo key's bit.
414        (COMBO_STATE(combo) + 1)
415        // E.g. two keys already pressed: `state == 11`.
416        // Next possible `state` is `111`.
417        // So the needed bit is `100` which we get with `11 + 1`.
418    ) {
419        return true;
420    }
421    return false;
422}
423#endif
424
425static combo_key_action_t process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record, uint16_t combo_index) {
426    uint8_t  key_count = 0;
427    uint16_t key_index = -1;
428    _find_key_index_and_count(combo->keys, keycode, &key_index, &key_count);
429
430    /* Continue processing if key isn't part of current combo. */
431    if (-1 == (int16_t)key_index) {
432        return COMBO_KEY_NOT_PRESSED;
433    }
434
435    bool key_is_part_of_combo = (!COMBO_DISABLED(combo) && is_combo_enabled()
436#if defined(COMBO_MUST_PRESS_IN_ORDER) || defined(COMBO_MUST_PRESS_IN_ORDER_PER_COMBO)
437                                 && keys_pressed_in_order(combo_index, combo, key_index, keycode, record)
438#endif
439#ifdef COMBO_SHOULD_TRIGGER
440                                 && combo_should_trigger(combo_index, combo, keycode, record)
441#endif
442    );
443
444    if (record->event.pressed && key_is_part_of_combo) {
445        uint16_t time = _get_combo_term(combo_index, combo);
446        if (!COMBO_ACTIVE(combo)) {
447            KEY_STATE_DOWN(combo->state, key_index);
448            if (longest_term < time) {
449                longest_term = time;
450            }
451        }
452        if (ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) {
453            /* Combo was fully pressed */
454            /* Buffer the combo so we can fire it after COMBO_TERM */
455
456#ifndef COMBO_NO_TIMER
457            /* Don't buffer this combo if its combo term has passed. */
458            if (timer && timer_elapsed(timer) > time) {
459                DISABLE_COMBO(combo);
460                return COMBO_KEY_PRESSED;
461            } else
462#endif
463            {
464
465                // disable readied combos that overlap with this combo
466                combo_t *drop = NULL;
467                for (uint8_t combo_buffer_i = combo_buffer_read; combo_buffer_i != combo_buffer_write; INCREMENT_MOD(combo_buffer_i)) {
468                    queued_combo_t *qcombo         = &combo_buffer[combo_buffer_i];
469                    combo_t *       buffered_combo = combo_get(qcombo->combo_index);
470
471                    if ((drop = overlaps(buffered_combo, combo))) {
472                        DISABLE_COMBO(drop);
473                        if (drop == combo) {
474                            // stop checking for overlaps if dropped combo was current combo.
475                            break;
476                        } else if (combo_buffer_i == combo_buffer_read && drop == buffered_combo) {
477                            /* Drop the disabled buffered combo from the buffer if
478                             * it is in the beginning of the buffer. */
479                            INCREMENT_MOD(combo_buffer_read);
480                        }
481                    }
482                }
483
484                if (drop != combo) {
485                    // save this combo to buffer
486                    combo_buffer[combo_buffer_write] = (queued_combo_t){
487                        .combo_index = combo_index,
488                    };
489                    INCREMENT_MOD(combo_buffer_write);
490
491                    // get possible longer waiting time for tap-/hold-only combos.
492                    longest_term = _get_wait_time(combo_index, combo);
493                }
494            } // if timer elapsed end
495        }
496#ifdef COMBO_PROCESS_KEY_REPRESS
497    } else if (record->event.pressed) {
498        if (COMBO_ACTIVE(combo)) {
499            if (process_combo_key_repress(combo_index, combo, key_index, keycode)) {
500                KEY_STATE_DOWN(combo->state, key_index);
501                return COMBO_KEY_REPRESSED;
502            }
503        }
504#endif
505    } else {
506        // chord releases
507        if (!COMBO_ACTIVE(combo) && ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) {
508            /* First key quickly released */
509            if (COMBO_DISABLED(combo) || _get_combo_must_hold(combo_index, combo)) {
510                // combo wasn't tappable, disable it and drop it from buffer.
511                drop_combo_from_buffer(combo_index);
512                key_is_part_of_combo = false;
513            }
514#ifdef COMBO_MUST_TAP_PER_COMBO
515            else if (get_combo_must_tap(combo_index, combo)) {
516                // immediately apply tap-only combo
517                apply_combo(combo_index, combo);
518                apply_combos(); // also apply other prepared combos and dump key buffer
519#    ifdef COMBO_PROCESS_KEY_RELEASE
520                if (process_combo_key_release(combo_index, combo, key_index, keycode)) {
521                    release_combo(combo_index, combo);
522                }
523#    endif
524            }
525#endif
526        } else if (COMBO_ACTIVE(combo) && ONLY_ONE_KEY_IS_DOWN(COMBO_STATE(combo)) && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index)) {
527            /* last key released */
528            release_combo(combo_index, combo);
529            key_is_part_of_combo = true;
530
531#ifdef COMBO_PROCESS_KEY_RELEASE
532            process_combo_key_release(combo_index, combo, key_index, keycode);
533#endif
534        } else if (COMBO_ACTIVE(combo) && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index)) {
535            /* first or middle key released */
536            key_is_part_of_combo = true;
537
538#ifdef COMBO_PROCESS_KEY_RELEASE
539            if (process_combo_key_release(combo_index, combo, key_index, keycode)) {
540                release_combo(combo_index, combo);
541            }
542#endif
543        } else {
544            /* The released key was part of an incomplete combo */
545            key_is_part_of_combo = false;
546        }
547
548        KEY_STATE_UP(combo->state, key_index);
549    }
550
551    return key_is_part_of_combo ? COMBO_KEY_PRESSED : COMBO_KEY_NOT_PRESSED;
552}
553
554bool process_combo(uint16_t keycode, keyrecord_t *record) {
555    uint8_t is_combo_key          = COMBO_KEY_NOT_PRESSED;
556    bool    no_combo_keys_pressed = true;
557
558    if (keycode == QK_COMBO_ON && record->event.pressed) {
559        combo_enable();
560        return true;
561    }
562
563    if (keycode == QK_COMBO_OFF && record->event.pressed) {
564        combo_disable();
565        return true;
566    }
567
568    if (keycode == QK_COMBO_TOGGLE && record->event.pressed) {
569        combo_toggle();
570        return true;
571    }
572
573#ifdef COMBO_ONLY_FROM_LAYER
574    /* Only check keycodes from one layer. */
575    keycode = keymap_key_to_keycode(COMBO_ONLY_FROM_LAYER, record->event.key);
576#else
577    uint8_t  highest_layer = get_highest_layer(layer_state | default_layer_state);
578    uint8_t  ref_layer     = combo_ref_from_layer(highest_layer);
579    if (ref_layer != highest_layer) {
580        keycode = keymap_key_to_keycode(ref_layer, record->event.key);
581    }
582#endif
583
584    for (uint16_t idx = 0; idx < combo_count(); ++idx) {
585        combo_t *combo = combo_get(idx);
586        is_combo_key |= process_single_combo(combo, keycode, record, idx);
587        no_combo_keys_pressed = no_combo_keys_pressed && (NO_COMBO_KEYS_ARE_DOWN || COMBO_ACTIVE(combo) || COMBO_DISABLED(combo));
588    }
589
590    if (record->event.pressed && is_combo_key) {
591#ifndef COMBO_NO_TIMER
592#    ifdef COMBO_STRICT_TIMER
593        if (!timer) {
594            // timer is set only on the first key
595            timer = timer_read();
596        }
597#    else
598        timer = timer_read();
599#    endif
600#endif
601
602#ifdef COMBO_PROCESS_KEY_REPRESS
603        if (is_combo_key == COMBO_KEY_PRESSED)
604#endif
605        {
606            if (key_buffer_size < COMBO_KEY_BUFFER_LENGTH) {
607                key_buffer[key_buffer_size++] = (queued_record_t){
608                    .record      = *record,
609                    .keycode     = keycode,
610                    .combo_index = -1, // this will be set when applying combos
611                };
612            }
613        }
614    } else {
615        if (combo_buffer_read != combo_buffer_write) {
616            // some combo is prepared
617            apply_combos();
618        } else {
619            // reset state if there are no combo keys pressed at all
620            dump_key_buffer();
621#ifndef COMBO_NO_TIMER
622            timer = 0;
623#endif
624            clear_combos();
625        }
626    }
627    return !is_combo_key;
628}
629
630void combo_task(void) {
631    if (!b_combo_enable) {
632        return;
633    }
634
635#ifndef COMBO_NO_TIMER
636    if (timer && timer_elapsed(timer) > longest_term) {
637        if (combo_buffer_read != combo_buffer_write) {
638            apply_combos();
639            longest_term = 0;
640            timer        = 0;
641        } else {
642            dump_key_buffer();
643            timer = 0;
644            clear_combos();
645        }
646    }
647#endif
648}
649
650void combo_enable(void) {
651    b_combo_enable = true;
652}
653
654void combo_disable(void) {
655#ifndef COMBO_NO_TIMER
656    timer = 0;
657#endif
658    b_combo_enable    = false;
659    combo_buffer_read = combo_buffer_write;
660    clear_combos();
661    dump_key_buffer();
662}
663
664void combo_toggle(void) {
665    if (b_combo_enable) {
666        combo_disable();
667    } else {
668        combo_enable();
669    }
670}
671
672bool is_combo_enabled(void) {
673    return b_combo_enable;
674}