summaryrefslogtreecommitdiff
path: root/quantum/process_keycode
diff options
context:
space:
mode:
Diffstat (limited to 'quantum/process_keycode')
-rw-r--r--quantum/process_keycode/process_combo.c550
-rw-r--r--quantum/process_keycode/process_combo.h38
-rw-r--r--quantum/process_keycode/process_haptic.c147
-rw-r--r--quantum/process_keycode/process_haptic.h21
-rw-r--r--quantum/process_keycode/process_key_override.c518
-rw-r--r--quantum/process_keycode/process_key_override.h153
-rw-r--r--quantum/process_keycode/process_music.c2
-rw-r--r--quantum/process_keycode/process_music.h2
-rw-r--r--quantum/process_keycode/process_rgb.c1
-rw-r--r--quantum/process_keycode/process_tap_dance.c2
-rw-r--r--quantum/process_keycode/process_tap_dance.h2
11 files changed, 1321 insertions, 115 deletions
diff --git a/quantum/process_keycode/process_combo.c b/quantum/process_keycode/process_combo.c
index f38d7d47a0..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 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;
}
void combo_toggle(void) {
diff --git a/quantum/process_keycode/process_combo.h b/quantum/process_keycode/process_combo.h
index e51a2f1f4e..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,15 +61,18 @@ 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 matrix_scan_combo(void);
+void combo_task(void);
void process_combo_event(uint16_t combo_index, bool pressed);
void combo_enable(void);
diff --git a/quantum/process_keycode/process_haptic.c b/quantum/process_keycode/process_haptic.c
new file mode 100644
index 0000000000..29a4ffd10a
--- /dev/null
+++ b/quantum/process_keycode/process_haptic.c
@@ -0,0 +1,147 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "haptic.h"
+#include "process_haptic.h"
+#include "quantum_keycodes.h"
+
+__attribute__((weak)) bool get_haptic_enabled_key(uint16_t keycode, keyrecord_t *record) {
+ switch (keycode) {
+#ifdef NO_HAPTIC_MOD
+ case QK_MOD_TAP ... QK_MOD_TAP_MAX:
+ if (record->tap.count == 0) return false;
+ break;
+ case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
+ if (record->tap.count != TAPPING_TOGGLE) return false;
+ break;
+ case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
+ if (record->tap.count == 0) return false;
+ break;
+ case KC_LCTRL ... KC_RGUI:
+ case QK_MOMENTARY ... QK_MOMENTARY_MAX:
+#endif
+#ifdef NO_HAPTIC_FN
+ case KC_FN0 ... KC_FN31:
+#endif
+#ifdef NO_HAPTIC_ALPHA
+ case KC_A ... KC_Z:
+#endif
+#ifdef NO_HAPTIC_PUNCTUATION
+ case KC_ENTER:
+ case KC_ESCAPE:
+ case KC_BSPACE:
+ case KC_SPACE:
+ case KC_MINUS:
+ case KC_EQUAL:
+ case KC_LBRACKET:
+ case KC_RBRACKET:
+ case KC_BSLASH:
+ case KC_NONUS_HASH:
+ case KC_SCOLON:
+ case KC_QUOTE:
+ case KC_GRAVE:
+ case KC_COMMA:
+ case KC_SLASH:
+ case KC_DOT:
+ case KC_NONUS_BSLASH:
+#endif
+#ifdef NO_HAPTIC_LOCKKEYS
+ case KC_CAPSLOCK:
+ case KC_SCROLLLOCK:
+ case KC_NUMLOCK:
+#endif
+#ifdef NO_HAPTIC_NAV
+ case KC_PSCREEN:
+ case KC_PAUSE:
+ case KC_INSERT:
+ case KC_DELETE:
+ case KC_PGDOWN:
+ case KC_PGUP:
+ case KC_LEFT:
+ case KC_UP:
+ case KC_RIGHT:
+ case KC_DOWN:
+ case KC_END:
+ case KC_HOME:
+#endif
+#ifdef NO_HAPTIC_NUMERIC
+ case KC_1 ... KC_0:
+#endif
+ return false;
+ }
+ return true;
+}
+
+bool process_haptic(uint16_t keycode, keyrecord_t *record) {
+ if (record->event.pressed) {
+ switch (keycode) {
+ case HPT_ON:
+ haptic_enable();
+ break;
+ case HPT_OFF:
+ haptic_disable();
+ break;
+ case HPT_TOG:
+ haptic_toggle();
+ break;
+ case HPT_RST:
+ haptic_reset();
+ break;
+ case HPT_FBK:
+ haptic_feedback_toggle();
+ break;
+ case HPT_BUZ:
+ haptic_buzz_toggle();
+ break;
+ case HPT_MODI:
+ haptic_mode_increase();
+ break;
+ case HPT_MODD:
+ haptic_mode_decrease();
+ break;
+ case HPT_DWLI:
+ haptic_dwell_increase();
+ break;
+ case HPT_DWLD:
+ haptic_dwell_decrease();
+ break;
+ case HPT_CONT:
+ haptic_toggle_continuous();
+ break;
+ case HPT_CONI:
+ haptic_cont_increase();
+ break;
+ case HPT_COND:
+ haptic_cont_decrease();
+ break;
+ }
+ }
+
+ if (haptic_get_enable()) {
+ if (record->event.pressed) {
+ // keypress
+ if (haptic_get_feedback() < 2 && get_haptic_enabled_key(keycode, record)) {
+ haptic_play();
+ }
+ } else {
+ // keyrelease
+ if (haptic_get_feedback() > 0 && get_haptic_enabled_key(keycode, record)) {
+ haptic_play();
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/quantum/process_keycode/process_haptic.h b/quantum/process_keycode/process_haptic.h
new file mode 100644
index 0000000000..6dbb0f014d
--- /dev/null
+++ b/quantum/process_keycode/process_haptic.h
@@ -0,0 +1,21 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "action.h"
+
+bool process_haptic(uint16_t keycode, keyrecord_t *record);
diff --git a/quantum/process_keycode/process_key_override.c b/quantum/process_keycode/process_key_override.c
new file mode 100644
index 0000000000..09b2725079
--- /dev/null
+++ b/quantum/process_keycode/process_key_override.c
@@ -0,0 +1,518 @@
+/*
+ * Copyright 2021 Jonas Gessner
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "quantum.h"
+#include "report.h"
+#include "timer.h"
+#include "process_key_override.h"
+
+#include <debug.h>
+
+#ifndef KEY_OVERRIDE_REPEAT_DELAY
+# define KEY_OVERRIDE_REPEAT_DELAY 500
+#endif
+
+// For benchmarking the time it takes to call process_key_override on every key press (needs keyboard debugging enabled as well)
+// #define BENCH_KEY_OVERRIDE
+
+// For debug output (needs keyboard debugging enabled as well)
+// #define DEBUG_KEY_OVERRIDE
+
+#ifdef DEBUG_KEY_OVERRIDE
+# define key_override_printf dprintf
+#else
+# define key_override_printf(str, ...) \
+ {}
+#endif
+
+// Helpers
+
+// Private functions implemented elsewhere in qmk/tmk
+extern uint8_t extract_mod_bits(uint16_t code);
+extern void set_weak_override_mods(uint8_t mods);
+extern void clear_weak_override_mods(void);
+extern void set_suppressed_override_mods(uint8_t mods);
+extern void clear_suppressed_override_mods(void);
+
+static uint16_t clear_mods_from(uint16_t keycode) {
+ switch (keycode) {
+ case QK_MODS ... QK_MODS_MAX:
+ break;
+ default:
+ return keycode;
+ }
+
+ static const uint16_t all_mods = QK_LCTL | QK_LSFT | QK_LALT | QK_LGUI | QK_RCTL | QK_RSFT | QK_RALT | QK_RGUI;
+
+ return (keycode & ~(all_mods));
+}
+
+// Internal variables
+static const key_override_t *active_override = NULL;
+static bool active_override_trigger_is_down = false;
+
+// Used to keep track of what non-modifier key was last pressed down. We never want to activate an override for a trigger key that is not the last non-mod key that was pressed down. OSes internally completely unregister a key that is held when a different key is held down after. We want to respect this here.
+static uint16_t last_key_down = 0;
+// When was the last key pressed down?
+static uint32_t last_key_down_time = 0;
+
+// What timestamp are we comparing to when waiting to register a deferred key?
+static uint32_t defer_reference_time = 0;
+// What delay should pass until deferred key is registered?
+static uint32_t defer_delay = 0;
+
+// Holds the keycode that should be registered at a later time, in order to not get false key presses
+static uint16_t deferred_register = 0;
+
+// TODO: in future maybe save in EEPROM?
+static bool enabled = true;
+
+// Public variables
+__attribute__((weak)) const key_override_t **key_overrides = NULL;
+
+// Forward decls
+static const key_override_t *clear_active_override(const bool allow_reregister);
+
+void key_override_on(void) {
+ enabled = true;
+ key_override_printf("Key override ON\n");
+}
+
+void key_override_off(void) {
+ enabled = false;
+ clear_active_override(false);
+ key_override_printf("Key override OFF\n");
+}
+
+void key_override_toggle(void) {
+ if (key_override_is_enabled()) {
+ key_override_off();
+ } else {
+ key_override_on();
+ }
+}
+
+bool key_override_is_enabled(void) { return enabled; }
+
+// Returns whether the modifiers that are pressed are such that the override should activate
+static bool key_override_matches_active_modifiers(const key_override_t *override, const uint8_t mods) {
+ // Check that negative keys pass
+ if ((override->negative_mod_mask & mods) != 0) {
+ return false;
+ }
+
+ // Immediately return true if the override requires no mods down
+ if (override->trigger_mods == 0) {
+ return true;
+ }
+
+ if ((override->options & ko_option_one_mod) != 0) {
+ // At least one of the trigger modifiers must be down
+ return (override->trigger_mods & mods) != 0;
+ } else {
+ // All trigger modifiers must be down, but each mod can be active on either side (if both sides are specified).
+
+ // Which mods, regardless of side, are required?
+ uint8_t one_sided_required_mods = (override->trigger_mods & 0b1111) | (override->trigger_mods >> 4);
+
+ // Which of the required modifiers are active?
+ uint8_t active_required_mods = override->trigger_mods & mods;
+
+ // Move the active requird mods to one side
+ uint8_t one_sided_active_required_mods = (active_required_mods & 0b1111) | (active_required_mods >> 4);
+
+ // Check that there is a full match between the required one-sided mods and active required one sided mods
+ return one_sided_active_required_mods == one_sided_required_mods;
+ }
+
+ return false;
+}
+
+static void schedule_deferred_register(const uint16_t keycode) {
+ if (timer_elapsed32(last_key_down_time) < KEY_OVERRIDE_REPEAT_DELAY) {
+ // Defer until KEY_OVERRIDE_REPEAT_DELAY has passed since the trigger key was pressed down. This emulates the behavior as holding down a key x, then holding down shift shortly after. Usually the shifted key X is not immediately produced, but rather a 'key repeat delay' passes before any repeated character is output.
+ defer_reference_time = last_key_down_time;
+ defer_delay = KEY_OVERRIDE_REPEAT_DELAY;
+ } else {
+ // Wait a very short time when a modifier event triggers the override to avoid false activations when e.g. a modifier is pressed just before a key is released (with the intention of pairing the modifier with a different key), or a modifier is lifted shortly before the trigger key is lifted. Operating systems by default reject modifier-events that happen very close to a non-modifier event.
+ defer_reference_time = timer_read32();
+ defer_delay = 50; // 50ms
+ }
+ deferred_register = keycode;
+}
+
+const key_override_t *clear_active_override(const bool allow_reregister) {
+ if (active_override == NULL) {
+ return NULL;
+ }
+
+ key_override_printf("Deactivating override\n");
+
+ deferred_register = 0;
+
+ // Clear the suppressed mods
+ clear_suppressed_override_mods();
+
+ // Unregister the replacement. First remove the weak override mods
+ clear_weak_override_mods();
+
+ const key_override_t *const old = active_override;
+
+ const uint8_t mod_free_replacement = clear_mods_from(active_override->replacement);
+
+ bool unregister_replacement = mod_free_replacement != KC_NO && // KC_NO is never registered
+ mod_free_replacement < SAFE_RANGE; // Custom keycodes are never registered
+
+ // Try firing the custom handler
+ if (active_override->custom_action != NULL) {
+ unregister_replacement &= active_override->custom_action(false, active_override->context);
+ }
+
+ // Then unregister the mod-free replacement key if desired
+ if (unregister_replacement) {
+ if (IS_KEY(mod_free_replacement)) {
+ del_key(mod_free_replacement);
+ } else {
+ key_override_printf("NOT KEY 1\n");
+ send_keyboard_report();
+ unregister_code(mod_free_replacement);
+ }
+ }
+
+ const uint16_t trigger = active_override->trigger;
+
+ const bool reregister_trigger = allow_reregister && // Check if allowed from caller
+ (active_override->options & ko_option_no_reregister_trigger) == 0 && // Check if override allows
+ active_override_trigger_is_down && // Check if trigger is even down
+ trigger != KC_NO && // KC_NO is never registered
+ trigger < SAFE_RANGE; // A custom keycode should not be registered
+
+ // Optionally re-register the trigger if it is still down
+ if (reregister_trigger) {
+ key_override_printf("Re-registering trigger deferred: %u\n", trigger);
+
+ // This will always be a modifier event, so defer always
+ schedule_deferred_register(trigger);
+ }
+
+ send_keyboard_report();
+
+ active_override = NULL;
+ active_override_trigger_is_down = false;
+
+ return old;
+}
+
+/** Checks if the key event is an allowed activation event for the provided override. Does not check things like whether the correct mods or correct trigger key is down. */
+static bool check_activation_event(const key_override_t *override, const bool key_down, const bool is_mod) {
+ ko_option_t options = override->options;
+
+ if ((options & ko_options_all_activations) == 0) {
+ // No activation option provided at all. This is wrong, but let's assume the default activations (ko_options_all_activations) were meant...
+ options = ko_options_all_activations;
+ }
+
+ if (is_mod) {
+ if (key_down) {
+ return (options & ko_option_activation_required_mod_down) != 0;
+ } else {
+ return (options & ko_option_activation_negative_mod_up) != 0;
+ }
+ } else {
+ if (key_down) {
+ return (options & ko_option_activation_trigger_down) != 0;
+ } else {
+ return false;
+ }
+ }
+}
+
+/** Iterates through the list of key overrides and tries activating each, until it finds one that activates or reaches the end of overrides. Returns true if the key action for `keycode` should be sent */
+static bool try_activating_override(const uint16_t keycode, const uint8_t layer, const bool key_down, const bool is_mod, const uint8_t active_mods, bool *activated) {
+ if (key_overrides == NULL) {
+ return true;
+ }
+
+ for (uint8_t i = 0;; i++) {
+ const key_override_t *const override = key_overrides[i];
+
+ // End of array
+ if (override == NULL) {
+ break;
+ }
+
+ // Fast, but not full mods check. Most key presses will not have any mods down, and most overrides will require mods. Hence here we filter overrides that require mods to be down while no mods are down
+ if (active_mods == 0 && override->trigger_mods != 0) {
+ key_override_printf("Not activating override: Modifiers don't match\n");
+ continue;
+ }
+
+ // Check layer
+ if ((override->layers & (1 << layer)) == 0) {
+ key_override_printf("Not activating override: Not set to activate on pressed layer\n");
+ continue;
+ }
+
+ // Check allowed activation events
+ if (!check_activation_event(override, key_down, is_mod)) {
+ key_override_printf("Not activating override: Activation event not allowed\n");
+ continue;
+ }
+
+ const bool is_trigger = override->trigger == keycode;
+
+ // Check if trigger lifted. This is a small optimization in order to skip the remaining checks
+ if (is_trigger && !key_down) {
+ key_override_printf("Not activating override: Trigger lifted\n");
+ continue;
+ }
+
+ // If the trigger is KC_NO it means 'no key', so only the required modifiers need to be down.
+ const bool no_trigger = override->trigger == KC_NO;
+
+ // Check if aleady active
+ if (override == active_override) {
+ key_override_printf("Not activating override: Alerady actived\n");
+ continue;
+ }
+
+ // Check if enabled
+ if (override->enabled != NULL && !((*(override->enabled) & 1))) {
+ key_override_printf("Not activating override: Not enabled\n");
+ continue;
+ }
+
+ // Check mods precisely
+ if (!key_override_matches_active_modifiers(override, active_mods)) {
+ key_override_printf("Not activating override: Modifiers don't match\n");
+ continue;
+ }
+
+ // Check if trigger key is down.
+ const bool trigger_down = is_trigger && key_down;
+
+ // At this point, all requirements for activation are checked, except whether the trigger key is pressed. Now we check if the required trigger is down
+ // If no trigger key is required, yes.
+ // If the trigger was just pressed, yes.
+ // If the last non-mod key that was pressed down is the trigger key, yes.
+ bool should_activate = no_trigger || trigger_down || last_key_down == override->trigger;
+
+ if (!should_activate) {
+ key_override_printf("Not activating override. Trigger not down\n");
+ continue;
+ }
+
+ key_override_printf("Activating override\n");
+
+ clear_active_override(false);
+
+ active_override = override;
+ active_override_trigger_is_down = true;
+
+ set_suppressed_override_mods(override->suppressed_mods);
+
+ if (!trigger_down && !no_trigger) {
+ // When activating a key override the trigger is is always unregistered. In the case where the key that newly pressed is not the trigger key, we have to explicitly remove the trigger key from the keyboard report. If the trigger was just pressed down we simply suppress the event which also has the effect of the trigger key not being registered in the keyboard report.
+ if (IS_KEY(override->trigger)) {
+ del_key(override->trigger);
+ } else {
+ unregister_code(override->trigger);
+ }
+ }
+
+ const uint16_t mod_free_replacement = clear_mods_from(override->replacement);
+
+ bool register_replacement = mod_free_replacement != KC_NO && // KC_NO is never registered
+ mod_free_replacement < SAFE_RANGE; // Custom keycodes are never registered
+
+ // Try firing the custom handler
+ if (override->custom_action != NULL) {
+ register_replacement &= override->custom_action(true, override->context);
+ }
+
+ if (register_replacement) {
+ const uint8_t override_mods = extract_mod_bits(override->replacement);
+ set_weak_override_mods(override_mods);
+
+ // If this is a modifier event that activates the key override we _always_ defer the actual full activation of the override
+ if (is_mod) {
+ key_override_printf("Deferring register replacement key\n");
+ schedule_deferred_register(mod_free_replacement);
+ send_keyboard_report();
+ } else {
+ if (IS_KEY(mod_free_replacement)) {
+ add_key(mod_free_replacement);
+ } else {
+ key_override_printf("NOT KEY 2\n");
+ send_keyboard_report();
+ // On macOS there seems to be a race condition when it comes to the keyboard report and consumer keycodes. It seems the OS may recognize a consumer keycode before an updated keyboard report, even if the keyboard report is actually sent before the consumer key. I assume it is some sort of race condition because it happens infrequently and very irregularly. Waiting for about at least 10ms between sending the keyboard report and sending the consumer code has shown to fix this.
+ wait_ms(10);
+ register_code(mod_free_replacement);
+ }
+ }
+ } else {
+ // If not registering the replacement key send keyboard report to update the unregistered keys.
+ send_keyboard_report();
+ }
+
+ *activated = true;
+
+ // If the trigger is down, suppress the event so that it does not get added to the keyboard report.
+ return !trigger_down;
+ }
+
+ *activated = false;
+
+ return true;
+}
+
+void key_override_task(void) {
+ if (deferred_register == 0) {
+ return;
+ }
+
+ if (timer_elapsed32(defer_reference_time) >= defer_delay) {
+ key_override_printf("Registering deferred key\n");
+ register_code16(deferred_register);
+ deferred_register = 0;
+ defer_reference_time = 0;
+ defer_delay = 0;
+ }
+}
+
+bool process_key_override(const uint16_t keycode, const keyrecord_t *const record) {
+#ifdef BENCH_KEY_OVERRIDE
+ uint16_t start = timer_read();
+#endif
+
+ const bool key_down = record->event.pressed;
+ const bool is_mod = IS_MOD(keycode);
+
+ if (key_down) {
+ switch (keycode) {
+ case KEY_OVERRIDE_TOGGLE:
+ key_override_toggle();
+ return false;
+
+ case KEY_OVERRIDE_ON:
+ key_override_on();
+ return false;
+
+ case KEY_OVERRIDE_OFF:
+ key_override_off();
+ return false;
+
+ default:
+ break;
+ }
+ }
+
+ if (!enabled) {
+ return true;
+ }
+
+ uint8_t effective_mods = get_mods();
+
+#ifdef KEY_OVERRIDE_INCLUDE_WEAK_MODS
+ effective_mods |= get_weak_mods();
+#endif
+
+#ifndef NO_ACTION_ONESHOT
+ // Locked one shot mods are added to get_mods(), I think (why??) while oneshot mods are in get_oneshot_mods(). Still OR with get_locked_oneshot_mods because that's where those mods _should_ be saved.
+ effective_mods |= get_oneshot_locked_mods() | get_oneshot_mods();
+#endif
+
+ if (is_mod) {
+ // The mods returned from get_mods() will be updated with this new event _after_ this code runs. Hence we manually update the effective mods here to really know the effective mods.
+ if (key_down) {
+ effective_mods |= MOD_BIT(keycode);
+ } else {
+ effective_mods &= ~MOD_BIT(keycode);
+ }
+ } else {
+ if (key_down) {
+ last_key_down = keycode;
+ last_key_down_time = timer_read32();
+ deferred_register = 0;
+ }
+
+ // The last key that was pressed was just released. No more keys are therefore sending input
+ if (!key_down && keycode == last_key_down) {
+ last_key_down = 0;
+ last_key_down_time = 0;
+ // We also cancel any deferred registers because, again, no keys are sending any input. Only the last key that is pressed creates an input – this key was just lifted.
+ deferred_register = 0;
+ }
+ }
+
+ key_override_printf("key down: %u keycode: %u is mod: %u effective mods: %u\n", key_down, keycode, is_mod, effective_mods);
+
+ bool send_key_action = true;
+ bool activated = false;
+
+ // Non-mod key up events never activate a key override
+ if (is_mod || key_down) {
+ // Get the exact layer that was hit. It will be cached at this point
+ const uint8_t layer = read_source_layers_cache(record->event.key);
+
+ // Use blocked to ensure the same override is not activated again immediately after it is deactivated
+ send_key_action = try_activating_override(keycode, layer, key_down, is_mod, effective_mods, &activated);
+
+ if (!send_key_action) {
+ send_keyboard_report();
+ }
+ }
+
+ if (!activated && active_override != NULL) {
+ if (is_mod) {
+ // Check if necessary modifier of current override goes up or a negative mod goes down
+ if (!key_override_matches_active_modifiers(active_override, effective_mods)) {
+ key_override_printf("Deactivating override because necessary modifier lifted or negative mod pressed\n");
+ clear_active_override(true);
+ }
+ } else {
+ // Check if trigger of current override goes up or if override does not allow additional keys to be down and another key goes down
+ const bool is_trigger = keycode == active_override->trigger;
+ bool should_deactivate = false;
+
+ // Check if trigger key lifted
+ if (is_trigger && !key_down) {
+ should_deactivate = true;
+ active_override_trigger_is_down = false;
+ key_override_printf("Deactivating override because trigger key up\n");
+ }
+
+ // Check if another key was pressed
+ if (key_down && (active_override->options & ko_option_no_unregister_on_other_key_down) == 0) {
+ should_deactivate = true;
+ key_override_printf("Deactivating override because another key was pressed\n");
+ }
+
+ if (should_deactivate) {
+ clear_active_override(false);
+ }
+ }
+ }
+
+#ifdef BENCH_KEY_OVERRIDE
+ uint16_t elapsed = timer_elapsed(start);
+
+ dprintf("Processing key overrides took: %u ms\n", elapsed);
+#endif
+
+ return send_key_action;
+}
diff --git a/quantum/process_keycode/process_key_override.h b/quantum/process_keycode/process_key_override.h
new file mode 100644
index 0000000000..fd76f297a8
--- /dev/null
+++ b/quantum/process_keycode/process_key_override.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2021 Jonas Gessner
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "action_layer.h"
+
+/**
+ * Key overrides allow you to send a different key-modifier combination or perform a custom action when a certain modifier-key combination is pressed.
+ *
+ * For example, you may configure a key override to send the delete key when shift + backspace are pressed together, or that your volume keys become screen brightness keys when holding ctrl. The possibilities are quite vast and the documentation contains a few examples for inspiration.
+ *
+ * See the documentation and examples here: https://docs.qmk.fm/#/feature_key_overrides
+ */
+
+/** Bitfield with various options controlling the behavior of a key override. */
+typedef enum {
+ /** Allow activating when the trigger key is pressed down. */
+ ko_option_activation_trigger_down = (1 << 0),
+ /** Allow activating when a necessary modifier is pressed down. */
+ ko_option_activation_required_mod_down = (1 << 1),
+ /** Allow activating when a negative modifier is released. */
+ ko_option_activation_negative_mod_up = (1 << 2),
+
+ ko_options_all_activations = ko_option_activation_negative_mod_up | ko_option_activation_required_mod_down | ko_option_activation_trigger_down,
+
+ /** If set, any of the modifiers in trigger_mods will be enough to activate the override (logical OR of modifiers). If not set, all the modifiers in trigger_mods have to be pressed (logical AND of modifiers). */
+ ko_option_one_mod = (1 << 3),
+
+ /** If set, the trigger key will never be registered again after the override is deactivated. */
+ ko_option_no_reregister_trigger = (1 << 4),
+
+ /** If set, the override will not deactivate when another key is pressed down. Use only if you really know you need this. */
+ ko_option_no_unregister_on_other_key_down = (1 << 5),
+
+ /** The default options used by the ko_make_xxx functions. */
+ ko_options_default = ko_options_all_activations,
+} ko_option_t;
+
+/** Defines a single key override */
+typedef struct {
+ // The non-modifier keycode that triggers the override. This keycode, and the necessary modifiers (trigger_mods) must be pressed to activate this override. Set this to the keycode of the key that should activate the override. Set to KC_NO to require only the necessary modifiers to be pressed and no non-modifier.
+ uint16_t trigger;
+
+ // Which mods need to be down for activation. If both sides of a modifier are set (e.g. left ctrl and right ctrl) then only one is required to be pressed (e.g. left ctrl suffices). Use the MOD_MASK_XXX and MOD_BIT() macros for this.
+ uint8_t trigger_mods;
+
+ // This is a BITMASK (!), defining which layers this override applies to. To use this override on layer i set the ith bit (1 << i).
+ layer_state_t layers;
+
+ // Which modifiers cannot be down. It must hold that (active_mods & negative_mod_mask) == 0, otherwise the key override will not be activated. An active override will be deactivated once this is no longer true.
+ uint8_t negative_mod_mask;
+
+ // Modifiers to 'suppress' while the override is active. To suppress a modifier means that even though the modifier key is held down, the host OS sees the modifier as not pressed. Can be used to suppress the trigger modifiers, as a trivial example.
+ uint8_t suppressed_mods;
+
+ // The complex keycode to send as replacement when this override is triggered. This can be a simple keycode, a key-modifier combination (e.g. C(KC_A)), or KC_NO (to register no replacement keycode). Use in combination with suppressed_mods to get the correct modifiers to be sent.
+ uint16_t replacement;
+
+ // Options controlling the behavior of the override, such as what actions are allowed to activate the override.
+ ko_option_t options;
+
+ // If not NULL, this function will be called right before the replacement key is registered, along with the provided context and a flag indicating whether the override was activated or deactivated. This function allows you to run some custom actions for specific key overrides. If you return `false`, the replacement key is not registered/unregistered as it would normally. Return `true` to register and unregister the override normally.
+ bool (*custom_action)(bool activated, void *context);
+
+ // A context that will be passed to the custom action function.
+ void *context;
+
+ // If this points to false this override will not be used. Set to NULL to always have this override enabled.
+ bool *enabled;
+} key_override_t;
+
+/** Define this as a null-terminated array of pointers to key overrides. These key overrides will be used by qmk. */
+extern const key_override_t **key_overrides;
+
+/** Turns key overrides on */
+void key_override_on(void);
+
+/** Turns key overrides off */
+void key_override_off(void);
+
+/** Toggles key overrides on */
+void key_override_toggle(void);
+
+/** Returns whether key overrides are enabled */
+bool key_override_is_enabled(void);
+
+/** Handling of key overrides and its implemented keycodes */
+bool process_key_override(const uint16_t keycode, const keyrecord_t *const record);
+
+/** Perform any deferred keys */
+void key_override_task(void);
+
+/**
+ * Preferrably use these macros to create key overrides. They fix many of the options to a standard setting that should satisfy most basic use-cases. Only directly create a key_override_t struct when you really need to.
+ */
+
+// clang-format off
+
+/**
+ * Convenience initializer to create a basic key override. Activates the override on all layers.
+ */
+#define ko_make_basic(trigger_mods, trigger_key, replacement_key) \
+ ko_make_with_layers(trigger_mods, trigger_key, replacement_key, ~0)
+
+/**
+ * Convenience initializer to create a basic key override. Provide a bitmap (of type layer_state_t) with the bits set for each layer on which the override should activate.
+ */
+#define ko_make_with_layers(trigger_mods, trigger_key, replacement_key, layers) \
+ ko_make_with_layers_and_negmods(trigger_mods, trigger_key, replacement_key, layers, 0)
+
+/**
+ * Convenience initializer to create a basic key override. Provide a bitmap with the bits set for each layer on which the override should activate. Also provide a negative modifier mask, that is used to define which modifiers may not be pressed.
+ */
+#define ko_make_with_layers_and_negmods(trigger_mods, trigger_key, replacement_key, layers, negative_mask) \
+ ko_make_with_layers_negmods_and_options(trigger_mods, trigger_key, replacement_key, layers, negative_mask, ko_options_default)
+
+ /**
+ * Convenience initializer to create a basic key override. Provide a bitmap with the bits set for each layer on which the override should activate. Also provide a negative modifier mask, that is used to define which modifiers may not be pressed. Provide options for additional control of the behavior of the override.
+ */
+#define ko_make_with_layers_negmods_and_options(trigger_mods_, trigger_key, replacement_key, layer_mask, negative_mask, options_) \
+ ((const key_override_t){ \
+ .trigger_mods = (trigger_mods_), \
+ .layers = (layer_mask), \
+ .suppressed_mods = (trigger_mods_), \
+ .options = (options_), \
+ .negative_mod_mask = (negative_mask), \
+ .custom_action = NULL, \
+ .context = NULL, \
+ .trigger = (trigger_key), \
+ .replacement = (replacement_key), \
+ .enabled = NULL \
+ })
+
+// clang-format on
diff --git a/quantum/process_keycode/process_music.c b/quantum/process_keycode/process_music.c
index eb06be96c2..2beccbd8f9 100644
--- a/quantum/process_keycode/process_music.c
+++ b/quantum/process_keycode/process_music.c
@@ -296,7 +296,7 @@ void music_mode_cycle(void) {
# endif
}
-void matrix_scan_music(void) {
+void music_task(void) {
if (music_sequence_playing) {
if ((music_sequence_timer == 0) || (timer_elapsed(music_sequence_timer) > music_sequence_interval)) {
music_sequence_timer = timer_read();
diff --git a/quantum/process_keycode/process_music.h b/quantum/process_keycode/process_music.h
index 01014aa6c2..e275cd9d26 100644
--- a/quantum/process_keycode/process_music.h
+++ b/quantum/process_keycode/process_music.h
@@ -44,7 +44,7 @@ void music_scale_user(void);
void music_all_notes_off(void);
void music_mode_cycle(void);
-void matrix_scan_music(void);
+void music_task(void);
bool music_mask(uint16_t keycode);
bool music_mask_kb(uint16_t keycode);
diff --git a/quantum/process_keycode/process_rgb.c b/quantum/process_keycode/process_rgb.c
index 167c0c03c9..b9fee1ca59 100644
--- a/quantum/process_keycode/process_rgb.c
+++ b/quantum/process_keycode/process_rgb.c
@@ -14,7 +14,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "process_rgb.h"
-#include "rgb.h"
typedef void (*rgb_func_pointer)(void);
diff --git a/quantum/process_keycode/process_tap_dance.c b/quantum/process_keycode/process_tap_dance.c
index 17dc540a64..c8712d919f 100644
--- a/quantum/process_keycode/process_tap_dance.c
+++ b/quantum/process_keycode/process_tap_dance.c
@@ -161,7 +161,7 @@ bool process_tap_dance(uint16_t keycode, keyrecord_t *record) {
return true;
}
-void matrix_scan_tap_dance() {
+void tap_dance_task() {
if (highest_td == -1) return;
uint16_t tap_user_defined;
diff --git a/quantum/process_keycode/process_tap_dance.h b/quantum/process_keycode/process_tap_dance.h
index a013c5cabf..d9ffb1e73d 100644
--- a/quantum/process_keycode/process_tap_dance.h
+++ b/quantum/process_keycode/process_tap_dance.h
@@ -85,7 +85,7 @@ extern qk_tap_dance_action_t tap_dance_actions[];
void preprocess_tap_dance(uint16_t keycode, keyrecord_t *record);
bool process_tap_dance(uint16_t keycode, keyrecord_t *record);
-void matrix_scan_tap_dance(void);
+void tap_dance_task(void);
void reset_tap_dance(qk_tap_dance_state_t *state);
void qk_tap_dance_pair_on_each_tap(qk_tap_dance_state_t *state, void *user_data);