|
@@ -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) {
|