diff options
Diffstat (limited to 'quantum/process_keycode/process_combo.c')
| -rw-r--r-- | quantum/process_keycode/process_combo.c | 558 | 
1 files changed, 455 insertions, 103 deletions
| diff --git a/quantum/process_keycode/process_combo.c b/quantum/process_keycode/process_combo.c index f38d7d47a0..e8661839c7 100644 --- a/quantum/process_keycode/process_combo.c +++ b/quantum/process_keycode/process_combo.c @@ -16,114 +16,449 @@  #include "print.h"  #include "process_combo.h" +#include "action_tapping.h" -#ifndef COMBO_VARIABLE_LEN -__attribute__((weak)) combo_t key_combos[COMBO_COUNT] = {}; + +#ifdef COMBO_COUNT +__attribute__((weak)) combo_t  key_combos[COMBO_COUNT]; +uint16_t COMBO_LEN = COMBO_COUNT;  #else  extern combo_t  key_combos[]; -extern int      COMBO_LEN; +extern uint16_t COMBO_LEN;  #endif  __attribute__((weak)) void process_combo_event(uint16_t combo_index, bool pressed) {} -static uint16_t timer               = 0; -static uint16_t current_combo_index = 0; -static bool     drop_buffer         = false; -static bool     is_active           = false; -static bool     b_combo_enable      = true;  // defaults to enabled +#ifdef COMBO_MUST_HOLD_PER_COMBO +__attribute__((weak)) bool get_combo_must_hold(uint16_t index, combo_t *combo) { return false; } +#endif + +#ifdef COMBO_MUST_TAP_PER_COMBO +__attribute__((weak)) bool get_combo_must_tap(uint16_t index, combo_t *combo) { return false; } +#endif + +#ifdef COMBO_TERM_PER_COMBO +__attribute__((weak)) uint16_t get_combo_term(uint16_t index, combo_t *combo) { return COMBO_TERM; } +#endif + +#ifdef COMBO_PROCESS_KEY_RELEASE +__attribute__((weak)) bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) { return false; } +#endif -static uint8_t buffer_size = 0; -#ifdef COMBO_ALLOW_ACTION_KEYS -static keyrecord_t key_buffer[MAX_COMBO_LENGTH]; +#ifndef COMBO_NO_TIMER +static uint16_t timer                 = 0; +#endif +static bool     b_combo_enable        = true;  // defaults to enabled +static uint16_t longest_term          = 0; + +typedef struct { +    keyrecord_t record; +    uint16_t combo_index; +    uint16_t keycode; +} queued_record_t; +static uint8_t key_buffer_size = 0; +static queued_record_t key_buffer[COMBO_KEY_BUFFER_LENGTH]; + +typedef struct { +    uint16_t combo_index; +} queued_combo_t; +static uint8_t combo_buffer_write= 0; +static uint8_t combo_buffer_read = 0; +static queued_combo_t combo_buffer[COMBO_BUFFER_LENGTH]; + +#define INCREMENT_MOD(i) i = (i + 1) % COMBO_BUFFER_LENGTH + +#define COMBO_KEY_POS ((keypos_t){.col=254, .row=254}) + + +#ifndef EXTRA_SHORT_COMBOS +/* flags are their own elements in combo_t struct. */ +#    define COMBO_ACTIVE(combo)   (combo->active) +#    define COMBO_DISABLED(combo) (combo->disabled) +#    define COMBO_STATE(combo)    (combo->state) + +#    define ACTIVATE_COMBO(combo)    do {combo->active = true;}while(0) +#    define DEACTIVATE_COMBO(combo)  do {combo->active = false;}while(0) +#    define DISABLE_COMBO(combo)     do {combo->disabled = true;}while(0) +#    define RESET_COMBO_STATE(combo) do { \ +    combo->disabled = false; \ +    combo->state = 0; \ +}while(0)  #else -static uint16_t key_buffer[MAX_COMBO_LENGTH]; +/* flags are at the two high bits of state. */ +#    define COMBO_ACTIVE(combo)   (combo->state & 0x80) +#    define COMBO_DISABLED(combo) (combo->state & 0x40) +#    define COMBO_STATE(combo)    (combo->state & 0x3F) + +#    define ACTIVATE_COMBO(combo)    do {combo->state |= 0x80;}while(0) +#    define DEACTIVATE_COMBO(combo)  do {combo->state &= ~0x80;}while(0) +#    define DISABLE_COMBO(combo)     do {combo->state |= 0x40;}while(0) +#    define RESET_COMBO_STATE(combo) do {combo->state &= ~0x7F;}while(0)  #endif -static inline void send_combo(uint16_t action, bool pressed) { -    if (action) { -        if (pressed) { -            register_code16(action); -        } else { -            unregister_code16(action); -        } +static inline void release_combo(uint16_t combo_index, combo_t *combo) { +    if (combo->keycode) { +        keyrecord_t record = { +            .event = { +                .key = COMBO_KEY_POS, +                .time = timer_read()|1, +                .pressed = false, +            }, +            .keycode = combo->keycode, +        }; +#ifndef NO_ACTION_TAPPING +        action_tapping_process(record); +#else +        process_record(&record); +#endif      } else { -        process_combo_event(current_combo_index, pressed); +        process_combo_event(combo_index, false);      } +    DEACTIVATE_COMBO(combo); +} + +static inline bool _get_combo_must_hold(uint16_t combo_index, combo_t *combo) { +#ifdef COMBO_NO_TIMER +    return false; +#elif defined(COMBO_MUST_HOLD_PER_COMBO) +    return get_combo_must_hold(combo_index, combo); +#elif defined(COMBO_MUST_HOLD_MODS) +    return (KEYCODE_IS_MOD(combo->keycode) || +            (combo->keycode >= QK_MOMENTARY && combo->keycode <= QK_MOMENTARY_MAX)); +#endif +    return false; +} + +static inline uint16_t _get_wait_time(uint16_t combo_index, combo_t *combo ) { +    if (_get_combo_must_hold(combo_index, combo) +#ifdef COMBO_MUST_TAP_PER_COMBO +            || get_combo_must_tap(combo_index, combo) +#endif +       ) { +        if (longest_term < COMBO_HOLD_TERM) { +            return COMBO_HOLD_TERM; +        } +    } + +    return longest_term; +} + +static inline uint16_t _get_combo_term(uint16_t combo_index, combo_t *combo) { + +#if defined(COMBO_TERM_PER_COMBO) +        return get_combo_term(combo_index, combo); +#endif + +    return COMBO_TERM;  } -static inline void dump_key_buffer(bool emit) { -    if (buffer_size == 0) { +void clear_combos(void) { +    uint16_t index = 0; +    longest_term = 0; +    for (index = 0; index < COMBO_LEN; ++index) { +        combo_t *combo = &key_combos[index]; +        if (!COMBO_ACTIVE(combo)) { +            RESET_COMBO_STATE(combo); +        } +    } +} + +static inline void dump_key_buffer(void) { +    /* First call start from 0 index; recursive calls need to start from i+1 index */ +    static uint8_t key_buffer_next = 0; + +    if (key_buffer_size == 0) {          return;      } -    if (emit) { -        for (uint8_t i = 0; i < buffer_size; i++) { -#ifdef COMBO_ALLOW_ACTION_KEYS -            const action_t action = store_or_get_action(key_buffer[i].event.pressed, key_buffer[i].event.key); -            process_action(&(key_buffer[i]), action); +    for (uint8_t key_buffer_i = key_buffer_next; key_buffer_i < key_buffer_size; key_buffer_i++) { +        key_buffer_next = key_buffer_i + 1; + +        queued_record_t *qrecord = &key_buffer[key_buffer_i]; +        keyrecord_t *record = &qrecord->record; + +        if (IS_NOEVENT(record->event)) { +            continue; +        } + +        if (!record->keycode && qrecord->combo_index != (uint16_t)-1) { +            process_combo_event(qrecord->combo_index, true); +        } else { +#ifndef NO_ACTION_TAPPING +        action_tapping_process(*record);  #else -            register_code16(key_buffer[i]); -            send_keyboard_report(); +        process_record(record);  #endif          } +        record->event.time = 0;      } -    buffer_size = 0; +    key_buffer_next = key_buffer_size = 0;  } -#define ALL_COMBO_KEYS_ARE_DOWN (((1 << count) - 1) == combo->state) -#define KEY_STATE_DOWN(key)         \ -    do {                            \ -        combo->state |= (1 << key); \ +#define NO_COMBO_KEYS_ARE_DOWN (0 == COMBO_STATE(combo)) +#define ALL_COMBO_KEYS_ARE_DOWN(state, key_count) (((1 << key_count) - 1) == state) +#define ONLY_ONE_KEY_IS_DOWN(state) !(state & (state - 1)) +#define KEY_NOT_YET_RELEASED(state, key_index) ((1 << key_index) & state) +#define KEY_STATE_DOWN(state, key_index) \ +    do {                                 \ +        state |= (1 << key_index);       \      } while (0) -#define KEY_STATE_UP(key)            \ -    do {                             \ -        combo->state &= ~(1 << key); \ +#define KEY_STATE_UP(state, key_index) \ +    do {                               \ +        state &= ~(1 << key_index);    \      } while (0) -static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record) { -    uint8_t  count = 0; -    uint16_t index = -1; -    /* Find index of keycode and number of combo keys */ -    for (const uint16_t *keys = combo->keys;; ++count) { -        uint16_t key = pgm_read_word(&keys[count]); -        if (keycode == key) index = count; +static inline void _find_key_index_and_count(const uint16_t *keys, uint16_t keycode, uint16_t *key_index, uint8_t *key_count) { +    while (true) { +        uint16_t key = pgm_read_word(&keys[*key_count]); +        if (keycode == key) *key_index = *key_count;          if (COMBO_END == key) break; +        (*key_count)++; +    } +} + +void drop_combo_from_buffer(uint16_t combo_index) { +    /* Mark a combo as processed from the buffer. If the buffer is in the +     * beginning of the buffer, drop it.  */ +    uint8_t i = combo_buffer_read; +    while (i != combo_buffer_write) { +        queued_combo_t *qcombo = &combo_buffer[i]; + +        if (qcombo->combo_index == combo_index) { +            combo_t *combo = &key_combos[combo_index]; +            DISABLE_COMBO(combo); + +            if (i == combo_buffer_read) { +                INCREMENT_MOD(combo_buffer_read); +            } +            break; +        } +        INCREMENT_MOD(i);      } +} + +void apply_combo(uint16_t combo_index, combo_t *combo) { +    /* Apply combo's result keycode to the last chord key of the combo and +     * disable the other keys. */ -    /* Continue processing if not a combo key */ -    if (-1 == (int8_t)index) return false; +    if (COMBO_DISABLED(combo)) { return; } -    bool is_combo_active = is_active; +    // state to check against so we find the last key of the combo from the buffer +#if defined(EXTRA_EXTRA_LONG_COMBOS) +    uint32_t state = 0; +#elif defined(EXTRA_LONG_COMBOS) +    uint16_t state = 0; +#else +    uint8_t state = 0; +#endif + +    for (uint8_t key_buffer_i = 0; key_buffer_i < key_buffer_size; key_buffer_i++) { + +        queued_record_t *qrecord = &key_buffer[key_buffer_i]; +        keyrecord_t *record = &qrecord->record; +        uint16_t keycode = qrecord->keycode; + +        uint8_t key_count = 0; +        uint16_t key_index = -1; +        _find_key_index_and_count(combo->keys, keycode, &key_index, &key_count); + +        if (-1 == (int16_t)key_index) { +            // key not part of this combo +            continue; +        } -    if (record->event.pressed) { -        KEY_STATE_DOWN(index); +        KEY_STATE_DOWN(state, key_index); +        if (ALL_COMBO_KEYS_ARE_DOWN(state, key_count)) { +            // this in the end executes the combo when the key_buffer is dumped. +            record->keycode = combo->keycode; +            record->event.key = COMBO_KEY_POS; -        if (is_combo_active) { -            if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was pressed */ -                send_combo(combo->keycode, true); -                drop_buffer = true; +            qrecord->combo_index = combo_index; +            ACTIVATE_COMBO(combo); + +            break; +        } else { +            // key was part of the combo but not the last one, "disable" it +            // by making it a TICK event. +            record->event.time = 0; +        } + +    } +    drop_combo_from_buffer(combo_index); +} + +static inline void apply_combos(void) { +    // Apply all buffered normal combos. +    for (uint8_t i = combo_buffer_read; +            i != combo_buffer_write; +            INCREMENT_MOD(i)) { + +        queued_combo_t *buffered_combo = &combo_buffer[i]; +        combo_t *combo = &key_combos[buffered_combo->combo_index]; + +#ifdef COMBO_MUST_TAP_PER_COMBO +        if (get_combo_must_tap(buffered_combo->combo_index, combo)) { +            // Tap-only combos are applied on key release only, so let's drop 'em here. +            drop_combo_from_buffer(buffered_combo->combo_index); +            continue; +        } +#endif +        apply_combo(buffered_combo->combo_index, combo); +    } +    dump_key_buffer(); +    clear_combos(); +} + +combo_t* overlaps(combo_t *combo1, combo_t *combo2) { +    /* Checks if the combos overlap and returns the combo that should be +     * dropped from the combo buffer. +     * The combo that has less keys will be dropped. If they have the same +     * amount of keys, drop combo1. */ + +    uint8_t idx1 = 0, idx2 = 0; +    uint16_t key1, key2; +    bool overlaps = false; + +    while ((key1 = pgm_read_word(&combo1->keys[idx1])) != COMBO_END) { +        idx2 = 0; +        while ((key2 = pgm_read_word(&combo2->keys[idx2])) != COMBO_END) { +            if (key1 == key2) overlaps = true; +            idx2 += 1; +        } +        idx1 += 1; +    } + +    if (!overlaps) return NULL; +    if (idx2 < idx1) return combo2; +    return combo1; +} + +static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record, uint16_t combo_index) { +    uint8_t key_count = 0; +    uint16_t key_index = -1; +    _find_key_index_and_count(combo->keys, keycode, &key_index, &key_count); + +    /* Continue processing if key isn't part of current combo. */ +    if (-1 == (int16_t)key_index) { +        return false; +    } + +    bool key_is_part_of_combo = !COMBO_DISABLED(combo) && is_combo_enabled(); + +    if (record->event.pressed && key_is_part_of_combo) { +        uint16_t time = _get_combo_term(combo_index, combo); +        if (!COMBO_ACTIVE(combo)) { +            KEY_STATE_DOWN(combo->state, key_index); +            if (longest_term < time) { +                longest_term = time;              }          } +        if (ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) { +            /* Combo was fully pressed */ +            /* Buffer the combo so we can fire it after COMBO_TERM */ + +#ifndef COMBO_NO_TIMER +            /* Don't buffer this combo if its combo term has passed. */ +            if (timer && timer_elapsed(timer) > time) { +                DISABLE_COMBO(combo); +                return true; +            } else +#endif +            { + +                // disable readied combos that overlap with this combo +                combo_t *drop = NULL; +                for (uint8_t combo_buffer_i = combo_buffer_read; +                        combo_buffer_i != combo_buffer_write; +                        INCREMENT_MOD(combo_buffer_i)) { + +                    queued_combo_t *qcombo = &combo_buffer[combo_buffer_i]; +                    combo_t *buffered_combo = &key_combos[qcombo->combo_index]; + +                    if ((drop = overlaps(buffered_combo, combo))) { +                        DISABLE_COMBO(drop); +                        if (drop == combo) { +                            // stop checking for overlaps if dropped combo was current combo. +                            break; +                        } else if (combo_buffer_i == combo_buffer_read && drop == buffered_combo) { +                            /* Drop the disabled buffered combo from the buffer if +                             * it is in the beginning of the buffer. */ +                            INCREMENT_MOD(combo_buffer_read); +                        } +                    } + +                } + +                if (drop != combo) { +                    // save this combo to buffer +                    combo_buffer[combo_buffer_write] = (queued_combo_t){ +                        .combo_index=combo_index, +                    }; +                    INCREMENT_MOD(combo_buffer_write); + +                    // get possible longer waiting time for tap-/hold-only combos. +                    longest_term = _get_wait_time(combo_index, combo); +                } +            } // if timer elapsed end + +        }      } else { -        if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was released */ -            send_combo(combo->keycode, false); +        // chord releases +        if (!COMBO_ACTIVE(combo) && ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) { +            /* First key quickly released */ +            if (COMBO_DISABLED(combo) || _get_combo_must_hold(combo_index, combo)) { +                // combo wasn't tappable, disable it and drop it from buffer. +                drop_combo_from_buffer(combo_index); +                key_is_part_of_combo = false; +            } +#ifdef COMBO_MUST_TAP_PER_COMBO +            else if (get_combo_must_tap(combo_index, combo)) { +                // immediately apply tap-only combo +                apply_combo(combo_index, combo); +                apply_combos(); // also apply other prepared combos and dump key buffer +#    ifdef COMBO_PROCESS_KEY_RELEASE +                if (process_combo_key_release(combo_index, combo, key_index, keycode)) { +                    release_combo(combo_index, combo); +                } +#    endif +            } +#endif +        } else if (COMBO_ACTIVE(combo) +                && ONLY_ONE_KEY_IS_DOWN(COMBO_STATE(combo)) +                && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index) +                ) { +            /* last key released */ +            release_combo(combo_index, combo); +            key_is_part_of_combo = true; + +#ifdef COMBO_PROCESS_KEY_RELEASE +            process_combo_key_release(combo_index, combo, key_index, keycode); +#endif +        } else if (COMBO_ACTIVE(combo) +                && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index) +                ) { +            /* first or middle key released */ +            key_is_part_of_combo = true; + +#ifdef COMBO_PROCESS_KEY_RELEASE +            if (process_combo_key_release(combo_index, combo, key_index, keycode)) { +                release_combo(combo_index, combo); +            } +#endif          } else { -            /* continue processing without immediately returning */ -            is_combo_active = false; +            /* The released key was part of an incomplete combo */ +            key_is_part_of_combo = false;          } -        KEY_STATE_UP(index); +        KEY_STATE_UP(combo->state, key_index);      } -    return is_combo_active; +    return key_is_part_of_combo;  } -#define NO_COMBO_KEYS_ARE_DOWN (0 == combo->state) -  bool process_combo(uint16_t keycode, keyrecord_t *record) {      bool is_combo_key          = false; -    drop_buffer                = false;      bool no_combo_keys_pressed = true;      if (keycode == CMB_ON && record->event.pressed) { @@ -141,65 +476,82 @@ bool process_combo(uint16_t keycode, keyrecord_t *record) {          return true;      } -    if (!is_combo_enabled()) { -        return true; -    } -#ifndef COMBO_VARIABLE_LEN -    for (current_combo_index = 0; current_combo_index < COMBO_COUNT; ++current_combo_index) { -#else -    for (current_combo_index = 0; current_combo_index < COMBO_LEN; ++current_combo_index) { +#ifdef COMBO_ONLY_FROM_LAYER +    /* Only check keycodes from one layer. */ +    keycode = keymap_key_to_keycode(COMBO_ONLY_FROM_LAYER, record->event.key);  #endif -        combo_t *combo = &key_combos[current_combo_index]; -        is_combo_key |= process_single_combo(combo, keycode, record); -        no_combo_keys_pressed = no_combo_keys_pressed && NO_COMBO_KEYS_ARE_DOWN; + +    for (uint16_t idx = 0; idx < COMBO_LEN; ++idx) { +        combo_t *combo = &key_combos[idx]; +        is_combo_key |= process_single_combo(combo, keycode, record, idx); +        no_combo_keys_pressed = no_combo_keys_pressed && (NO_COMBO_KEYS_ARE_DOWN || COMBO_ACTIVE(combo) || COMBO_DISABLED(combo));      } -    if (drop_buffer) { -        /* buffer is only dropped when we complete a combo, so we refresh the timer -         * here */ -        timer = timer_read(); -        dump_key_buffer(false); -    } else if (!is_combo_key) { -        /* if no combos claim the key we need to emit the keybuffer */ -        dump_key_buffer(true); - -        // reset state if there are no combo keys pressed at all -        if (no_combo_keys_pressed) { -            timer     = 0; -            is_active = true; -        } -    } else if (record->event.pressed && is_active) { -        /* otherwise the key is consumed and placed in the buffer */ +    if (record->event.pressed && is_combo_key) { +#ifndef COMBO_NO_TIMER +#   ifdef COMBO_STRICT_TIMER +        if (!timer) { +            // timer is set only on the first key +            timer = timer_read(); +        } +#   else          timer = timer_read(); +#   endif +#endif -        if (buffer_size < MAX_COMBO_LENGTH) { -#ifdef COMBO_ALLOW_ACTION_KEYS -            key_buffer[buffer_size++] = *record; -#else -            key_buffer[buffer_size++] = keycode; +        if (key_buffer_size < COMBO_KEY_BUFFER_LENGTH) { +            key_buffer[key_buffer_size++] = (queued_record_t){ +                .record = *record, +                .keycode = keycode, +                .combo_index = -1, // this will be set when applying combos +            }; +        } +    } else { +        if (combo_buffer_read != combo_buffer_write) { +            // some combo is prepared +            apply_combos(); +        } else { +            // reset state if there are no combo keys pressed at all +            dump_key_buffer(); +#ifndef COMBO_NO_TIMER +            timer = 0;  #endif +            clear_combos();          }      } -      return !is_combo_key;  } -void matrix_scan_combo(void) { -    if (b_combo_enable && is_active && timer && timer_elapsed(timer) > COMBO_TERM) { -        /* This disables the combo, meaning key events for this -         * combo will be handled by the next processors in the chain -         */ -        is_active = false; -        dump_key_buffer(true); +void combo_task(void) { +    if (!b_combo_enable) { +        return;      } + +#ifndef COMBO_NO_TIMER +    if (timer && timer_elapsed(timer) > longest_term) { +        if (combo_buffer_read != combo_buffer_write) { +            apply_combos(); +            longest_term = 0; +            timer = 0; +        } else { +            dump_key_buffer(); +            timer = 0; +            clear_combos(); +        } +    } +#endif  }  void combo_enable(void) { b_combo_enable = true; }  void combo_disable(void) { -    b_combo_enable = is_active = false; +#ifndef COMBO_NO_TIMER      timer                      = 0; -    dump_key_buffer(true); +#endif +    b_combo_enable = false; +    combo_buffer_read = combo_buffer_write; +    clear_combos(); +    dump_key_buffer();  }  void combo_toggle(void) { | 
