summaryrefslogtreecommitdiff
path: root/quantum
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
parent07553b41f0a03ca6549c09cecf9cd3dec7332346 (diff)
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')
-rw-r--r--quantum/keymap_common.c3
-rw-r--r--quantum/process_keycode/process_combo.c548
-rw-r--r--quantum/process_keycode/process_combo.h36
-rw-r--r--quantum/quantum.c23
4 files changed, 498 insertions, 112 deletions
diff --git a/quantum/keymap_common.c b/quantum/keymap_common.c
index e0fd6d4793..780c71ab9b 100644
--- a/quantum/keymap_common.c
+++ b/quantum/keymap_common.c
@@ -40,7 +40,10 @@ extern keymap_config_t keymap_config;
action_t action_for_key(uint8_t layer, keypos_t key) {
// 16bit keycodes - important
uint16_t keycode = keymap_key_to_keycode(layer, key);
+ return action_for_keycode(keycode);
+};
+action_t action_for_keycode(uint16_t keycode) {
// keycode remapping
keycode = keycode_config(keycode);
diff --git a/quantum/process_keycode/process_combo.c b/quantum/process_keycode/process_combo.c
index 9e16292486..b4e698decd 100644
--- a/quantum/process_keycode/process_combo.c
+++ b/quantum/process_keycode/process_combo.c
@@ -16,114 +16,445 @@
#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
-static uint8_t buffer_size = 0;
-#ifdef COMBO_ALLOW_ACTION_KEYS
-static keyrecord_t key_buffer[MAX_COMBO_LENGTH];
+#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
+
+#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;
+}
+
+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(bool emit) {
- if (buffer_size == 0) {
+static inline void dump_key_buffer(void) {
+ 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 = 0; key_buffer_i < key_buffer_size; key_buffer_i++) {
+
+ 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_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);
+
+ if (record->event.pressed && !COMBO_DISABLED(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) {
@@ -144,62 +475,81 @@ bool process_combo(uint16_t keycode, keyrecord_t *record) {
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 combo_task(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);
+ 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;
}
void combo_toggle(void) {
diff --git a/quantum/process_keycode/process_combo.h b/quantum/process_keycode/process_combo.h
index 9af97588b2..43c36d79e6 100644
--- a/quantum/process_keycode/process_combo.h
+++ b/quantum/process_keycode/process_combo.h
@@ -20,23 +20,38 @@
#include "quantum.h"
#include <stdint.h>
-#ifdef EXTRA_EXTRA_LONG_COMBOS
+#ifdef EXTRA_SHORT_COMBOS
+# define MAX_COMBO_LENGTH 6
+#elif defined(EXTRA_EXTRA_LONG_COMBOS)
# define MAX_COMBO_LENGTH 32
-#elif EXTRA_LONG_COMBOS
+#elif defined(EXTRA_LONG_COMBOS)
# define MAX_COMBO_LENGTH 16
#else
# define MAX_COMBO_LENGTH 8
#endif
+#ifndef COMBO_KEY_BUFFER_LENGTH
+# define COMBO_KEY_BUFFER_LENGTH MAX_COMBO_LENGTH
+#endif
+#ifndef COMBO_BUFFER_LENGTH
+# define COMBO_BUFFER_LENGTH 4
+#endif
+
typedef struct {
const uint16_t *keys;
uint16_t keycode;
-#ifdef EXTRA_EXTRA_LONG_COMBOS
+#ifdef EXTRA_SHORT_COMBOS
+ uint8_t state;
+#else
+ bool disabled;
+ bool active;
+# if defined(EXTRA_EXTRA_LONG_COMBOS)
uint32_t state;
-#elif EXTRA_LONG_COMBOS
+# elif defined(EXTRA_LONG_COMBOS)
uint16_t state;
-#else
+# else
uint8_t state;
+# endif
#endif
} combo_t;
@@ -46,12 +61,15 @@ typedef struct {
{ .keys = &(ck)[0] }
#define COMBO_END 0
-#ifndef COMBO_COUNT
-# define COMBO_COUNT 0
-#endif
#ifndef COMBO_TERM
-# define COMBO_TERM TAPPING_TERM
+# define COMBO_TERM 50
#endif
+#ifndef COMBO_HOLD_TERM
+# define COMBO_HOLD_TERM TAPPING_TERM
+#endif
+
+/* check if keycode is only modifiers */
+#define KEYCODE_IS_MOD(code) (IS_MOD(code) || (code >= QK_MODS && code <= QK_MODS_MAX && !(code & QK_BASIC_MAX)))
bool process_combo(uint16_t keycode, keyrecord_t *record);
void combo_task(void);
diff --git a/quantum/quantum.c b/quantum/quantum.c
index 3329c1146b..d173388714 100644
--- a/quantum/quantum.c
+++ b/quantum/quantum.c
@@ -143,7 +143,13 @@ void reset_keyboard(void) {
}
/* Convert record into usable keycode via the contained event. */
-uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache) { return get_event_keycode(record->event, update_layer_cache); }
+uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache) {
+#ifdef COMBO_ENABLE
+ if (record->keycode) { return record->keycode; }
+#endif
+ return get_event_keycode(record->event, update_layer_cache);
+}
+
/* Convert event into usable keycode. Checks the layer cache to ensure that it
* retains the correct keycode after a layer change, if the key is still pressed.
@@ -169,6 +175,18 @@ uint16_t get_event_keycode(keyevent_t event, bool update_layer_cache) {
return keymap_key_to_keycode(layer_switch_get_layer(event.key), event.key);
}
+/* Get keycode, and then process pre tapping functionality */
+bool pre_process_record_quantum(keyrecord_t *record) {
+ if (!(
+#ifdef COMBO_ENABLE
+ process_combo(get_record_keycode(record, true), record) &&
+#endif
+ true)) {
+ return false;
+ }
+ return true; // continue processing
+}
+
/* Get keycode, and then call keyboard function */
void post_process_record_quantum(keyrecord_t *record) {
uint16_t keycode = get_record_keycode(record, false);
@@ -254,9 +272,6 @@ bool process_record_quantum(keyrecord_t *record) {
#ifdef LEADER_ENABLE
process_leader(keycode, record) &&
#endif
-#ifdef COMBO_ENABLE
- process_combo(keycode, record) &&
-#endif
#ifdef PRINTING_ENABLE
process_printer(keycode, record) &&
#endif