From eac8fa799909817bfc7cb4043448f85551154c6b Mon Sep 17 00:00:00 2001 From: Ofer Plesser Date: Sat, 10 Dec 2016 00:49:11 +0200 Subject: [PATCH 1/4] Implemented basic key combination feature --- build_keyboard.mk | 5 ++ quantum/process_keycode/process_combo.c | 66 +++++++++++++++++++++++++ quantum/process_keycode/process_combo.h | 25 ++++++++++ quantum/quantum.c | 3 ++ quantum/quantum.h | 4 ++ 5 files changed, 103 insertions(+) create mode 100644 quantum/process_keycode/process_combo.c create mode 100644 quantum/process_keycode/process_combo.h diff --git a/build_keyboard.mk b/build_keyboard.mk index ce505de126..14f4f36bc9 100644 --- a/build_keyboard.mk +++ b/build_keyboard.mk @@ -144,6 +144,11 @@ ifeq ($(strip $(MIDI_ENABLE)), yes) SRC += $(QUANTUM_DIR)/process_keycode/process_midi.c endif +ifeq ($(strip $(COMBO_ENABLE)), yes) + OPT_DEFS += -DCOMBO_ENABLE + SRC += $(QUANTUM_DIR)/process_keycode/process_combo.c +endif + ifeq ($(strip $(VIRTSER_ENABLE)), yes) OPT_DEFS += -DVIRTSER_ENABLE endif diff --git a/quantum/process_keycode/process_combo.c b/quantum/process_keycode/process_combo.c new file mode 100644 index 0000000000..a6cfed11aa --- /dev/null +++ b/quantum/process_keycode/process_combo.c @@ -0,0 +1,66 @@ +#include "process_combo.h" +#include "print.h" + +// __attribute__ ((weak)) +// combo_t key_combos[] = { + +// }; + +#define SEND_KEY(key) \ +do { \ + register_code16(key); \ + send_keyboard_report(); \ + unregister_code16(key); \ +} while(0) + + +#define ALL_COMBO_KEYS_ARE_DOWN (((1<state) +static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record) +{ + uint8_t count = 0; + bool is_combo_key = false; + // bool combo_key_released = false; + + // Count the number of combo keys + for (const uint16_t *key = combo->keys; COMBO_END != pgm_read_word(key); ++key, ++count); + + for (uint8_t i = 0; i < count; ++i) { + uint16_t key = pgm_read_word(&combo->keys[i]); + + if (key == keycode) { + is_combo_key = true; + + if (record->event.pressed) { + combo->state |= (1<state) { + // The combo was sent, no need to send released key + return true; + } + + combo->state &= ~(1<action); + combo->state = 0; + } + + return is_combo_key; +} + + +bool process_combo(uint16_t keycode, keyrecord_t *record) +{ + bool is_combo_key = false; + + for (int i = 0; i < NUM_ELEMS(key_combos); ++i) { + combo_t *combo = &key_combos[i]; + is_combo_key |= process_single_combo(combo, keycode, record); + } + + return !is_combo_key; +} \ No newline at end of file diff --git a/quantum/process_keycode/process_combo.h b/quantum/process_keycode/process_combo.h new file mode 100644 index 0000000000..68786c0f19 --- /dev/null +++ b/quantum/process_keycode/process_combo.h @@ -0,0 +1,25 @@ +#ifndef PROCESS_COMBO_H +#define PROCESS_COMBO_H + +#include +#include "progmem.h" +#include "quantum.h" + + +typedef struct +{ + const uint16_t *keys; + uint16_t action; + uint32_t state; +} combo_t; + + +#define COMBO_END 0 +#define NUM_ELEMS(a) (sizeof(a)/sizeof 0[a]) + + +extern combo_t key_combos[1]; + +bool process_combo(uint16_t keycode, keyrecord_t *record); + +#endif \ No newline at end of file diff --git a/quantum/quantum.c b/quantum/quantum.c index f653564a67..eabeacff87 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -113,6 +113,9 @@ bool process_record_quantum(keyrecord_t *record) { if (!( process_record_kb(keycode, record) && + #ifdef COMBO_ENABLE + process_combo(keycode, record) && + #endif #ifdef MIDI_ENABLE process_midi(keycode, record) && #endif diff --git a/quantum/quantum.h b/quantum/quantum.h index e6adf974ab..8614c053ab 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -63,6 +63,10 @@ extern uint32_t default_layer_state; #include "process_printer.h" #endif +#ifdef COMBO_ENABLE + #include "process_combo.h" +#endif + #define SEND_STRING(str) send_string(PSTR(str)) void send_string(const char *str); From b6bf4e0dce062a535685c4e772f613252d401ed3 Mon Sep 17 00:00:00 2001 From: Ofer Plesser Date: Sat, 10 Dec 2016 16:11:59 +0200 Subject: [PATCH 2/4] Added support for timing out combos if a key as been pressed for longer than COMBO_TERM --- quantum/process_keycode/process_combo.c | 111 +++++++++++++++++------- quantum/process_keycode/process_combo.h | 20 ++++- quantum/quantum.c | 5 ++ 3 files changed, 103 insertions(+), 33 deletions(-) diff --git a/quantum/process_keycode/process_combo.c b/quantum/process_keycode/process_combo.c index a6cfed11aa..ff7e8aba55 100644 --- a/quantum/process_keycode/process_combo.c +++ b/quantum/process_keycode/process_combo.c @@ -1,11 +1,6 @@ #include "process_combo.h" #include "print.h" -// __attribute__ ((weak)) -// combo_t key_combos[] = { - -// }; - #define SEND_KEY(key) \ do { \ register_code16(key); \ @@ -13,54 +8,110 @@ do { \ unregister_code16(key); \ } while(0) +#define COMBO_TIMER_ELAPSED -1 + +#if COMBO_TERM +#define IS_COMBO_KEY_HELD(combo) (COMBO_TIMER_ELAPSED == combo->timer ? false : true) +#define RESET_COMBO_TIMER_AND_KEY(combo) combo->timer = 0; combo->key = 0 +#else +#define IS_COMBO_KEY_HELD(combo) (true) +#define RESET_COMBO_TIMER_AND_KEY(combo) do {} while (0) +#endif + + +__attribute__ ((weak)) +combo_t key_combos[COMBO_COUNT] = { + +}; + +static inline void reset_combo(combo_t *combo) +{ + combo->state = 0; + RESET_COMBO_TIMER_AND_KEY(combo); +} #define ALL_COMBO_KEYS_ARE_DOWN (((1<state) +#define NO_COMBO_KEYS_ARE_DOWN (0 == combo->state) +#define KEY_STATE_DOWN(key) do{ combo->state |= (1<state &= ~(1<keys; ;++count) { + uint16_t key = pgm_read_word(&keys[count]); + if (keycode == key) index = count; + if (COMBO_END == key) break; + } - // Count the number of combo keys - for (const uint16_t *key = combo->keys; COMBO_END != pgm_read_word(key); ++key, ++count); + /* Return if not a combo key */ + if (-1 == index) return false; - for (uint8_t i = 0; i < count; ++i) { - uint16_t key = pgm_read_word(&combo->keys[i]); + bool is_combo_active = IS_COMBO_KEY_HELD(combo); - if (key == keycode) { - is_combo_key = true; - - if (record->event.pressed) { - combo->state |= (1<state) { - // The combo was sent, no need to send released key - return true; - } - - combo->state &= ~(1<event.pressed) { + KEY_STATE_DOWN(index); + +#if COMBO_TERM + if (is_combo_active) { + combo->timer = timer_read(); + combo->key = keycode; } +#endif + + } else { + if (is_combo_active && combo->state) { /* Combo key was tapped */ + RESET_COMBO_TIMER_AND_KEY(combo); + SEND_KEY(keycode); + } + +#if COMBO_TERM + if (!is_combo_active && keycode == combo->key) { /* Held combo key was released */ + unregister_code16(combo->key); + } +#endif + + KEY_STATE_UP(index); } - if (ALL_COMBO_KEYS_ARE_DOWN) { + if (ALL_COMBO_KEYS_ARE_DOWN && is_combo_active) { SEND_KEY(combo->action); - combo->state = 0; + reset_combo(combo); + } + + if(NO_COMBO_KEYS_ARE_DOWN && !is_combo_active) { + reset_combo(combo); } - return is_combo_key; + return is_combo_active; } - bool process_combo(uint16_t keycode, keyrecord_t *record) { bool is_combo_key = false; - for (int i = 0; i < NUM_ELEMS(key_combos); ++i) { + for (int i = 0; i < COMBO_COUNT; ++i) { combo_t *combo = &key_combos[i]; is_combo_key |= process_single_combo(combo, keycode, record); } return !is_combo_key; +} + +void matrix_scan_combo(void) +{ +#if COMBO_TERM + for (int i = 0; i < COMBO_COUNT; ++i) { + combo_t *combo = &key_combos[i]; + if (combo->timer && + combo->timer != COMBO_TIMER_ELAPSED && + timer_elapsed(combo->timer) > COMBO_TERM) { + + combo->timer = COMBO_TIMER_ELAPSED; + unregister_code16(combo->key); + register_code16(combo->key); + } + } +#endif } \ No newline at end of file diff --git a/quantum/process_keycode/process_combo.h b/quantum/process_keycode/process_combo.h index 68786c0f19..c475acd332 100644 --- a/quantum/process_keycode/process_combo.h +++ b/quantum/process_keycode/process_combo.h @@ -5,21 +5,35 @@ #include "progmem.h" #include "quantum.h" +#ifndef COMBO_TERM +#define COMBO_TERM TAPPING_TERM +#endif typedef struct { const uint16_t *keys; uint16_t action; uint32_t state; +#if COMBO_TERM + uint16_t timer; + uint16_t key; +#endif } combo_t; +#if COMBO_TERM +#define COMBO(ck, ca) {.keys = &(ck)[0], .action = (ca), .state = 0, .timer = 0, .key = 0} +#else +#define COMBO(ck, ca) {.keys = &(ck)[0], .action = (ca), .state = 0 } +#endif #define COMBO_END 0 -#define NUM_ELEMS(a) (sizeof(a)/sizeof 0[a]) +#ifndef COMBO_COUNT +#define COMBO_COUNT 0 +#endif - -extern combo_t key_combos[1]; +extern combo_t key_combos[COMBO_COUNT]; bool process_combo(uint16_t keycode, keyrecord_t *record); +void matrix_scan_combo(void); #endif \ No newline at end of file diff --git a/quantum/quantum.c b/quantum/quantum.c index eabeacff87..7767b63016 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -509,6 +509,11 @@ void matrix_scan_quantum() { #ifdef TAP_DANCE_ENABLE matrix_scan_tap_dance(); #endif + + #ifdef COMBO_ENABLE + matrix_scan_combo(); + #endif + matrix_scan_kb(); } From 6e7cfa83b9424061914793b02757fa4ec75b356b Mon Sep 17 00:00:00 2001 From: Ofer Plesser Date: Fri, 16 Dec 2016 21:50:28 +0200 Subject: [PATCH 3/4] Refactored as well as added support for action keys in combos --- quantum/process_keycode/process_combo.c | 125 ++++++++++++++---------- quantum/process_keycode/process_combo.h | 34 ++++--- 2 files changed, 90 insertions(+), 69 deletions(-) diff --git a/quantum/process_keycode/process_combo.c b/quantum/process_keycode/process_combo.c index ff7e8aba55..e2189ad98b 100644 --- a/quantum/process_keycode/process_combo.c +++ b/quantum/process_keycode/process_combo.c @@ -1,39 +1,39 @@ #include "process_combo.h" #include "print.h" -#define SEND_KEY(key) \ -do { \ - register_code16(key); \ - send_keyboard_report(); \ - unregister_code16(key); \ -} while(0) #define COMBO_TIMER_ELAPSED -1 -#if COMBO_TERM -#define IS_COMBO_KEY_HELD(combo) (COMBO_TIMER_ELAPSED == combo->timer ? false : true) -#define RESET_COMBO_TIMER_AND_KEY(combo) combo->timer = 0; combo->key = 0 -#else -#define IS_COMBO_KEY_HELD(combo) (true) -#define RESET_COMBO_TIMER_AND_KEY(combo) do {} while (0) -#endif - __attribute__ ((weak)) -combo_t key_combos[COMBO_COUNT] = { +combo_t key_combos[] = { }; -static inline void reset_combo(combo_t *combo) -{ - combo->state = 0; - RESET_COMBO_TIMER_AND_KEY(combo); +__attribute__ ((weak)) +void process_combo_event(uint8_t combo_index, bool pressed) { + } -#define ALL_COMBO_KEYS_ARE_DOWN (((1<state) -#define NO_COMBO_KEYS_ARE_DOWN (0 == combo->state) -#define KEY_STATE_DOWN(key) do{ combo->state |= (1<state &= ~(1<state) +#define NO_COMBO_KEYS_ARE_DOWN (0 == combo->state) +#define KEY_STATE_DOWN(key) do{ combo->state |= (1<state &= ~(1<timer ? false : true; if (record->event.pressed) { KEY_STATE_DOWN(index); - -#if COMBO_TERM + if (is_combo_active) { - combo->timer = timer_read(); - combo->key = keycode; - } + if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was pressed */ + send_combo(combo->keycode, true); + combo->timer = COMBO_TIMER_ELAPSED; + } else { /* Combo key was pressed */ + combo->timer = timer_read(); +#ifdef COMBO_ALLOW_ACTION_KEYS + combo->prev_record = *record; +#else + combo->prev_key = keycode; #endif - + } + } } else { - if (is_combo_active && combo->state) { /* Combo key was tapped */ - RESET_COMBO_TIMER_AND_KEY(combo); - SEND_KEY(keycode); + if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was released */ + send_combo(combo->keycode, false); } -#if COMBO_TERM - if (!is_combo_active && keycode == combo->key) { /* Held combo key was released */ - unregister_code16(combo->key); - } + if (is_combo_active) { /* Combo key was tapped */ +#ifdef COMBO_ALLOW_ACTION_KEYS + record->event.pressed = true; + process_action(record, store_or_get_action(record->event.pressed, record->event.key)); + record->event.pressed = false; + process_action(record, store_or_get_action(record->event.pressed, record->event.key)); +#else + register_code16(keycode); + send_keyboard_report(); + unregister_code16(keycode); #endif + combo->timer = 0; + } - KEY_STATE_UP(index); + KEY_STATE_UP(index); } - if (ALL_COMBO_KEYS_ARE_DOWN && is_combo_active) { - SEND_KEY(combo->action); - reset_combo(combo); - } - - if(NO_COMBO_KEYS_ARE_DOWN && !is_combo_active) { - reset_combo(combo); + if (NO_COMBO_KEYS_ARE_DOWN) { + combo->timer = 0; } return is_combo_active; @@ -91,8 +100,8 @@ bool process_combo(uint16_t keycode, keyrecord_t *record) { bool is_combo_key = false; - for (int i = 0; i < COMBO_COUNT; ++i) { - combo_t *combo = &key_combos[i]; + for (current_combo_index = 0; current_combo_index < COMBO_COUNT; ++current_combo_index) { + combo_t *combo = &key_combos[current_combo_index]; is_combo_key |= process_single_combo(combo, keycode, record); } @@ -101,17 +110,25 @@ bool process_combo(uint16_t keycode, keyrecord_t *record) void matrix_scan_combo(void) { -#if COMBO_TERM for (int i = 0; i < COMBO_COUNT; ++i) { combo_t *combo = &key_combos[i]; if (combo->timer && combo->timer != COMBO_TIMER_ELAPSED && timer_elapsed(combo->timer) > COMBO_TERM) { - + + /* This disables the combo, meaning key events for this + * combo will be handled by the next processors in the chain + */ combo->timer = COMBO_TIMER_ELAPSED; - unregister_code16(combo->key); - register_code16(combo->key); + +#ifdef COMBO_ALLOW_ACTION_KEYS + process_action(&combo->prev_record, + store_or_get_action(combo->prev_record.event.pressed, + combo->prev_record.event.key)); +#else + unregister_code16(combo->prev_key); + register_code16(combo->prev_key); +#endif } } -#endif -} \ No newline at end of file +} diff --git a/quantum/process_keycode/process_combo.h b/quantum/process_keycode/process_combo.h index c475acd332..847f2b7376 100644 --- a/quantum/process_keycode/process_combo.h +++ b/quantum/process_keycode/process_combo.h @@ -5,35 +5,39 @@ #include "progmem.h" #include "quantum.h" -#ifndef COMBO_TERM -#define COMBO_TERM TAPPING_TERM -#endif - typedef struct { const uint16_t *keys; - uint16_t action; + uint16_t keycode; +#ifdef EXTRA_EXTRA_LONG_COMBOS uint32_t state; -#if COMBO_TERM +#elif EXTRA_LONG_COMBOS + uint16_t state; +#else + uint8_t state; +#endif uint16_t timer; - uint16_t key; +#ifdef COMBO_ALLOW_ACTION_KEYS + keyrecord_t prev_record; +#else + uint16_t prev_key; #endif } combo_t; -#if COMBO_TERM -#define COMBO(ck, ca) {.keys = &(ck)[0], .action = (ca), .state = 0, .timer = 0, .key = 0} -#else -#define COMBO(ck, ca) {.keys = &(ck)[0], .action = (ca), .state = 0 } -#endif +#define COMBO(ck, ca) {.keys = &(ck)[0], .keycode = (ca)} +#define COMBO_ACTION(ck) {.keys = &(ck)[0]} + #define COMBO_END 0 #ifndef COMBO_COUNT #define COMBO_COUNT 0 #endif - -extern combo_t key_combos[COMBO_COUNT]; +#ifndef COMBO_TERM +#define COMBO_TERM TAPPING_TERM +#endif bool process_combo(uint16_t keycode, keyrecord_t *record); void matrix_scan_combo(void); +void process_combo_event(uint8_t combo_index, bool pressed); -#endif \ No newline at end of file +#endif From 40abf8bc9ce22cab472f79e3a97c413ac5648986 Mon Sep 17 00:00:00 2001 From: Ofer Plesser Date: Fri, 16 Dec 2016 22:00:29 +0200 Subject: [PATCH 4/4] Moved combo processing lower down in process logic --- quantum/quantum.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quantum/quantum.c b/quantum/quantum.c index 7767b63016..e5385bc21c 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -113,9 +113,6 @@ bool process_record_quantum(keyrecord_t *record) { if (!( process_record_kb(keycode, record) && - #ifdef COMBO_ENABLE - process_combo(keycode, record) && - #endif #ifdef MIDI_ENABLE process_midi(keycode, record) && #endif @@ -131,6 +128,9 @@ bool process_record_quantum(keyrecord_t *record) { #ifndef DISABLE_CHORDING process_chording(keycode, record) && #endif + #ifdef COMBO_ENABLE + process_combo(keycode, record) && + #endif #ifdef UNICODE_ENABLE process_unicode(keycode, record) && #endif