aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBob <rsheldiii@gmail.com>2019-04-08 17:07:15 -0400
committerDrashna Jaelre <drashna@live.com>2019-04-08 14:07:15 -0700
commitbc536b9b6d98e5428a28f6e6ba69675bd77b79cc (patch)
tree1a22225476402e824957f77bd801d371a2622ed5
parentf8d365a47847f8e49fde5096aa065dbee08cf728 (diff)
downloadqmk_firmware-bc536b9b6d98e5428a28f6e6ba69675bd77b79cc.tar.gz
qmk_firmware-bc536b9b6d98e5428a28f6e6ba69675bd77b79cc.zip
Switch process_combo to using global register and timer (#2561)
Since combos keep local state about what keys have been previously pressed, when combos are layered, multiple keypresses will register for any key with multiple combos assigned to it. In order to fix this, I switched process_combo to use a global keycode / keyrecord register and timer. When a keypress is consumed by a combo, it gets stored in the register and the timer is updated; when the next keypress takes too long or a key is pressed that isn't part of any combo, the buffer is emitted and the timer reset. This has a few side effects. For instance, I couldn't _not_ fix combo keys printing out of order while also fixing this bug, so combo keys print in order correctly when a combo fails. since combos no longer have local timers, the logic around when combos time out has changed. now that there is a single timer pressing any combo key (including one in a different combo) will reset the timer for all combos, making combo entry a little more lenient. Since combos no longer have local keycode / keyrecord state, there is an edge case where incomplete combo keys can be consumed. if you have a combo for a+s = tab and a combo for b+n = space, if you press a+b+n, only a space will be emitted. This is because when b+n completes successfully, it drops the register.
-rw-r--r--quantum/process_keycode/process_combo.c239
-rw-r--r--quantum/process_keycode/process_combo.h33
2 files changed, 148 insertions, 124 deletions
diff --git a/quantum/process_keycode/process_combo.c b/quantum/process_keycode/process_combo.c
index 13f8bbb33..a157ed48b 100644
--- a/quantum/process_keycode/process_combo.c
+++ b/quantum/process_keycode/process_combo.c
@@ -14,141 +14,164 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */ 15 */
16 16
17#include "process_combo.h"
18#include "print.h" 17#include "print.h"
18#include "process_combo.h"
19 19
20 20__attribute__((weak)) combo_t key_combos[COMBO_COUNT] = {
21__attribute__ ((weak))
22combo_t key_combos[COMBO_COUNT] = {
23 21
24}; 22};
25 23
26__attribute__ ((weak)) 24__attribute__((weak)) void process_combo_event(uint8_t combo_index,
27void process_combo_event(uint8_t combo_index, bool pressed) { 25 bool pressed) {}
28
29}
30 26
27static uint16_t timer = 0;
31static uint8_t current_combo_index = 0; 28static uint8_t current_combo_index = 0;
29static bool drop_buffer = false;
30static bool is_active = false;
32 31
33static inline void send_combo(uint16_t action, bool pressed) 32static uint8_t buffer_size = 0;
34{
35 if (action) {
36 if (pressed) {
37 register_code16(action);
38 } else {
39 unregister_code16(action);
40 }
41 } else {
42 process_combo_event(current_combo_index, pressed);
43 }
44}
45
46#define ALL_COMBO_KEYS_ARE_DOWN (((1<<count)-1) == combo->state)
47#define NO_COMBO_KEYS_ARE_DOWN (0 == combo->state)
48#define KEY_STATE_DOWN(key) do{ combo->state |= (1<<key); } while(0)
49#define KEY_STATE_UP(key) do{ combo->state &= ~(1<<key); } while(0)
50static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record)
51{
52 uint8_t count = 0;
53 uint8_t index = -1;
54 /* Find index of keycode and number of combo keys */
55 for (const uint16_t *keys = combo->keys; ;++count) {
56 uint16_t key = pgm_read_word(&keys[count]);
57 if (keycode == key) index = count;
58 if (COMBO_END == key) break;
59 }
60
61 /* Return if not a combo key */
62 if (-1 == (int8_t)index) return false;
63
64 /* The combos timer is used to signal whether the combo is active */
65 bool is_combo_active = combo->is_active;
66
67 if (record->event.pressed) {
68 KEY_STATE_DOWN(index);
69
70 if (is_combo_active) {
71 if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was pressed */
72 send_combo(combo->keycode, true);
73 combo->is_active = false;
74 } else { /* Combo key was pressed */
75 combo->timer = timer_read();
76 combo->is_active = true;
77#ifdef COMBO_ALLOW_ACTION_KEYS 33#ifdef COMBO_ALLOW_ACTION_KEYS
78 combo->prev_record = *record; 34static keyrecord_t key_buffer[MAX_COMBO_LENGTH];
79#else 35#else
80 combo->prev_key = keycode; 36static uint16_t key_buffer[MAX_COMBO_LENGTH];
81#endif 37#endif
82 } 38
83 } 39static inline void send_combo(uint16_t action, bool pressed) {
40 if (action) {
41 if (pressed) {
42 register_code16(action);
84 } else { 43 } else {
85 if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was released */ 44 unregister_code16(action);
86 send_combo(combo->keycode, false); 45 }
87 } 46 } else {
47 process_combo_event(current_combo_index, pressed);
48 }
49}
88 50
89 if (is_combo_active) { /* Combo key was tapped */ 51static inline void dump_key_buffer(bool emit) {
52 if (buffer_size == 0) {
53 return;
54 }
55
56 if (emit) {
57 for (uint8_t i = 0; i < buffer_size; i++) {
90#ifdef COMBO_ALLOW_ACTION_KEYS 58#ifdef COMBO_ALLOW_ACTION_KEYS
91 record->event.pressed = true; 59 const action_t action = store_or_get_action(key_buffer[i].event.pressed,
92 process_action(record, store_or_get_action(record->event.pressed, record->event.key)); 60 key_buffer[i].event.key);
93 record->event.pressed = false; 61 process_action(&(key_buffer[i]), action);
94 process_action(record, store_or_get_action(record->event.pressed, record->event.key));
95#else 62#else
96 register_code16(keycode); 63 register_code16(key_buffer[i]);
97 send_keyboard_report(); 64 send_keyboard_report();
98 unregister_code16(keycode);
99#endif 65#endif
100 combo->is_active = false;
101 combo->timer = 0;
102 }
103
104 KEY_STATE_UP(index);
105 } 66 }
67 }
106 68
107 if (NO_COMBO_KEYS_ARE_DOWN) { 69 buffer_size = 0;
108 combo->is_active = true;
109 combo->timer = 0;
110 }
111
112 return is_combo_active;
113} 70}
114 71
115bool process_combo(uint16_t keycode, keyrecord_t *record) 72#define ALL_COMBO_KEYS_ARE_DOWN (((1 << count) - 1) == combo->state)
116{ 73#define KEY_STATE_DOWN(key) \
117 bool is_combo_key = false; 74 do { \
75 combo->state |= (1 << key); \
76 } while (0)
77#define KEY_STATE_UP(key) \
78 do { \
79 combo->state &= ~(1 << key); \
80 } while (0)
81
82static bool process_single_combo(combo_t *combo, uint16_t keycode,
83 keyrecord_t *record) {
84 uint8_t count = 0;
85 uint8_t index = -1;
86 /* Find index of keycode and number of combo keys */
87 for (const uint16_t *keys = combo->keys;; ++count) {
88 uint16_t key = pgm_read_word(&keys[count]);
89 if (keycode == key)
90 index = count;
91 if (COMBO_END == key)
92 break;
93 }
94
95 /* Continue processing if not a combo key */
96 if (-1 == (int8_t)index)
97 return false;
98
99 bool is_combo_active = is_active;
100
101 if (record->event.pressed) {
102 KEY_STATE_DOWN(index);
103
104 if (is_combo_active) {
105 if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was pressed */
106 send_combo(combo->keycode, true);
107 drop_buffer = true;
108 }
109 }
110 } else {
111 if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was released */
112 send_combo(combo->keycode, false);
113 } else {
114 /* continue processing without immediately returning */
115 is_combo_active = false;
116 }
118 117
119 for (current_combo_index = 0; current_combo_index < COMBO_COUNT; ++current_combo_index) { 118 KEY_STATE_UP(index);
120 combo_t *combo = &key_combos[current_combo_index]; 119 }
121 is_combo_key |= process_single_combo(combo, keycode, record);
122 }
123 120
124 return !is_combo_key; 121 return is_combo_active;
125} 122}
126 123
127void matrix_scan_combo(void) 124#define NO_COMBO_KEYS_ARE_DOWN (0 == combo->state)
128{ 125
129 for (int i = 0; i < COMBO_COUNT; ++i) { 126bool process_combo(uint16_t keycode, keyrecord_t *record) {
130 // Do not treat the (weak) key_combos too strict. 127 bool is_combo_key = false;
131 #pragma GCC diagnostic push 128 drop_buffer = false;
132 #pragma GCC diagnostic ignored "-Warray-bounds" 129 bool no_combo_keys_pressed = false;
133 combo_t *combo = &key_combos[i]; 130
134 #pragma GCC diagnostic pop 131 for (current_combo_index = 0; current_combo_index < COMBO_COUNT;
135 if (combo->is_active && 132 ++current_combo_index) {
136 combo->timer && 133 combo_t *combo = &key_combos[current_combo_index];
137 timer_elapsed(combo->timer) > COMBO_TERM) { 134 is_combo_key |= process_single_combo(combo, keycode, record);
138 135 no_combo_keys_pressed |= NO_COMBO_KEYS_ARE_DOWN;
139 /* This disables the combo, meaning key events for this 136 }
140 * combo will be handled by the next processors in the chain 137
141 */ 138 if (drop_buffer) {
142 combo->is_active = false; 139 /* buffer is only dropped when we complete a combo, so we refresh the timer
140 * here */
141 timer = timer_read();
142 dump_key_buffer(false);
143 } else if (!is_combo_key) {
144 /* if no combos claim the key we need to emit the keybuffer */
145 dump_key_buffer(true);
146
147 // reset state if there are no combo keys pressed at all
148 if (no_combo_keys_pressed) {
149 timer = 0;
150 is_active = true;
151 }
152 } else if (record->event.pressed && is_active) {
153 /* otherwise the key is consumed and placed in the buffer */
154 timer = timer_read();
143 155
156 if (buffer_size < MAX_COMBO_LENGTH) {
144#ifdef COMBO_ALLOW_ACTION_KEYS 157#ifdef COMBO_ALLOW_ACTION_KEYS
145 process_action(&combo->prev_record, 158 key_buffer[buffer_size++] = *record;
146 store_or_get_action(combo->prev_record.event.pressed,
147 combo->prev_record.event.key));
148#else 159#else
149 unregister_code16(combo->prev_key); 160 key_buffer[buffer_size++] = keycode;
150 register_code16(combo->prev_key);
151#endif 161#endif
152 }
153 } 162 }
163 }
164
165 return !is_combo_key;
166}
167
168void matrix_scan_combo(void) {
169 if (is_active && timer && timer_elapsed(timer) > COMBO_TERM) {
170
171 /* This disables the combo, meaning key events for this
172 * combo will be handled by the next processors in the chain
173 */
174 is_active = false;
175 dump_key_buffer(true);
176 }
154} 177}
diff --git a/quantum/process_keycode/process_combo.h b/quantum/process_keycode/process_combo.h
index a5787c9ed..f06d2d345 100644
--- a/quantum/process_keycode/process_combo.h
+++ b/quantum/process_keycode/process_combo.h
@@ -17,33 +17,34 @@
17#ifndef PROCESS_COMBO_H 17#ifndef PROCESS_COMBO_H
18#define PROCESS_COMBO_H 18#define PROCESS_COMBO_H
19 19
20#include <stdint.h>
21#include "progmem.h" 20#include "progmem.h"
22#include "quantum.h" 21#include "quantum.h"
22#include <stdint.h>
23 23
24typedef struct
25{
26 const uint16_t *keys;
27 uint16_t keycode;
28#ifdef EXTRA_EXTRA_LONG_COMBOS 24#ifdef EXTRA_EXTRA_LONG_COMBOS
29 uint32_t state; 25#define MAX_COMBO_LENGTH 32
30#elif EXTRA_LONG_COMBOS 26#elif EXTRA_LONG_COMBOS
31 uint16_t state; 27#define MAX_COMBO_LENGTH 16
32#else 28#else
33 uint8_t state; 29#define MAX_COMBO_LENGTH 8
34#endif 30#endif
35 uint16_t timer; 31
36 bool is_active; 32typedef struct {
37#ifdef COMBO_ALLOW_ACTION_KEYS 33 const uint16_t *keys;
38 keyrecord_t prev_record; 34 uint16_t keycode;
35#ifdef EXTRA_EXTRA_LONG_COMBOS
36 uint32_t state;
37#elif EXTRA_LONG_COMBOS
38 uint16_t state;
39#else 39#else
40 uint16_t prev_key; 40 uint8_t state;
41#endif 41#endif
42} combo_t; 42} combo_t;
43 43
44 44#define COMBO(ck, ca) \
45#define COMBO(ck, ca) {.keys = &(ck)[0], .keycode = (ca)} 45 { .keys = &(ck)[0], .keycode = (ca) }
46#define COMBO_ACTION(ck) {.keys = &(ck)[0]} 46#define COMBO_ACTION(ck) \
47 { .keys = &(ck)[0] }
47 48
48#define COMBO_END 0 49#define COMBO_END 0
49#ifndef COMBO_COUNT 50#ifndef COMBO_COUNT