aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/config_options.md14
-rw-r--r--docs/feature_combo.md292
-rw-r--r--keyboards/gboards/g/keymap_combo.h6
-rw-r--r--quantum/keymap_common.c3
-rw-r--r--quantum/process_keycode/process_combo.c548
-rw-r--r--quantum/process_keycode/process_combo.h36
-rw-r--r--quantum/quantum.c23
-rw-r--r--tmk_core/common/action.c37
-rw-r--r--tmk_core/common/action.h5
-rw-r--r--tmk_core/common/action_tapping.c41
-rw-r--r--tmk_core/common/action_tapping.h1
11 files changed, 841 insertions, 165 deletions
diff --git a/docs/config_options.md b/docs/config_options.md
index 0c98b3101..78c1f70fd 100644
--- a/docs/config_options.md
+++ b/docs/config_options.md
@@ -188,9 +188,21 @@ If you define these options you will enable the associated feature, which may in
188 few ms of delay from this. But if you're doing chording on something with 3-4ms 188 few ms of delay from this. But if you're doing chording on something with 3-4ms
189 scan times? You probably want this. 189 scan times? You probably want this.
190* `#define COMBO_COUNT 2` 190* `#define COMBO_COUNT 2`
191 * Set this to the number of combos that you're using in the [Combo](feature_combo.md) feature. 191 * Set this to the number of combos that you're using in the [Combo](feature_combo.md) feature. Or leave it undefined and programmatically set the count.
192* `#define COMBO_TERM 200` 192* `#define COMBO_TERM 200`
193 * how long for the Combo keys to be detected. Defaults to `TAPPING_TERM` if not defined. 193 * how long for the Combo keys to be detected. Defaults to `TAPPING_TERM` if not defined.
194* `#define COMBO_MUST_HOLD_MODS`
195 * Flag for enabling extending timeout on Combos containing modifers
196* `#define COMBO_MOD_TERM 200`
197 * Allows for extending COMBO_TERM for mod keys while mid-combo.
198* `#define COMBO_MUST_HOLD_PER_COMBO`
199 * Flag to enable per-combo COMBO_TERM extension and `get_combo_must_hold()` function
200* `#define COMBO_TERM_PER_COMBO`
201 * Flag to enable per-combo COMBO_TERM extension and `get_combo_term()` function
202* `#define COMBO_STRICT_TIMER`
203 * Only start the combo timer on the first key press instead of on all key presses.
204* `#define COMBO_NO_TIMER`
205 * Disable the combo timer completely for relaxed combos.
194* `#define TAP_CODE_DELAY 100` 206* `#define TAP_CODE_DELAY 100`
195 * Sets the delay between `register_code` and `unregister_code`, if you're having issues with it registering properly (common on VUSB boards). The value is in milliseconds. 207 * Sets the delay between `register_code` and `unregister_code`, if you're having issues with it registering properly (common on VUSB boards). The value is in milliseconds.
196* `#define TAP_HOLD_CAPS_DELAY 80` 208* `#define TAP_HOLD_CAPS_DELAY 80`
diff --git a/docs/feature_combo.md b/docs/feature_combo.md
index d831328f6..d98e6f2ac 100644
--- a/docs/feature_combo.md
+++ b/docs/feature_combo.md
@@ -1,24 +1,39 @@
1# Combos 1# Combos
2 2
3The Combo feature is a chording type solution for adding custom actions. It lets you hit multiple keys at once and produce a different effect. For instance, hitting `A` and `S` within the tapping term would hit `ESC` instead, or have it perform even more complex tasks. 3The Combo feature is a chording type solution for adding custom actions. It lets you hit multiple keys at once and produce a different effect. For instance, hitting `A` and `S` within the combo term would hit `ESC` instead, or have it perform even more complex tasks.
4 4
5To enable this feature, you need to add `COMBO_ENABLE = yes` to your `rules.mk`. 5To enable this feature, you need to add `COMBO_ENABLE = yes` to your `rules.mk`.
6 6
7Additionally, in your `config.h`, you'll need to specify the number of combos that you'll be using, by adding `#define COMBO_COUNT 1` (replacing 1 with the number that you're using). 7Additionally, in your `config.h`, you'll need to specify the number of combos that you'll be using, by adding `#define COMBO_COUNT 1` (replacing 1 with the number that you're using). It is also possible to not define this and instead set the variable `COMBO_LEN` yourself. There's a trick where we don't need to think about this variable at all. More on this later.
8<!-- At this time, this is necessary -->
9 8
10Also, by default, the tapping term for the Combos is set to the same value as `TAPPING_TERM` (200 by default on most boards). But you can specify a different value by defining it in your `config.h`. For instance: `#define COMBO_TERM 300` would set the time out period for combos to 300ms.
11 9
12Then, your `keymap.c` file, you'll need to define a sequence of keys, terminated with `COMBO_END`, and a structure to list the combination of keys, and it's resulting action. 10Then, in your `keymap.c` file, you'll need to define a sequence of keys, terminated with `COMBO_END`, and a structure to list the combination of keys, and its resulting action.
13 11
14```c 12```c
15const uint16_t PROGMEM test_combo[] = {KC_A, KC_B, COMBO_END}; 13const uint16_t PROGMEM test_combo1[] = {KC_A, KC_B, COMBO_END};
16combo_t key_combos[COMBO_COUNT] = {COMBO(test_combo, KC_ESC)}; 14const uint16_t PROGMEM test_combo2[] = {KC_C, KC_D, COMBO_END};
15combo_t key_combos[COMBO_COUNT] = {
16 COMBO(test_combo1, KC_ESC),
17 COMBO(test_combo2, LCTL(KC_Z)), // keycodes with modifiers are possible too!
18};
17``` 19```
18 20
19This will send "Escape" if you hit the A and B keys. 21This will send "Escape" if you hit the A and B keys, and Ctrl+Z when you hit the C and D keys.
22
23As of [PR#8591](https://github.com/qmk/qmk_firmware/pull/8591/), it is possible to fire combos from ModTap keys and LayerTap keys. So in the above example you could have keys `LSFT_T(KC_A)` and `LT(_LAYER, KC_B)` and it would work. So Home Row Mods and Home Row Combos at same time is now a thing!
20 24
21!> This method only supports [basic keycodes](keycodes_basic.md). See the examples for more control. 25It is also now possible to overlap combos. Before, with the example below both combos would activate when all three keys were pressed. Now only the three key combo will activate.
26
27```c
28const uint16_t PROGMEM test_combo1[] = {LSFT_T(KC_A), LT(_LAYER, KC_B), COMBO_END};
29const uint16_t PROGMEM test_combo2[] = {LSFT_T(KC_A), LT(_LAYER, KC_B), KC_C, COMBO_END};
30combo_t key_combos[COMBO_COUNT] = {
31 COMBO(test_combo1, KC_ESC)
32 COMBO(test_combo2, KC_TAB)
33};
34```
35
36Executing more complex keycodes like ModTaps and LayerTaps is now also possible.
22 37
23## Examples 38## Examples
24 39
@@ -27,63 +42,68 @@ If you want to add a list, then you'd use something like this:
27```c 42```c
28enum combos { 43enum combos {
29 AB_ESC, 44 AB_ESC,
30 JK_TAB 45 JK_TAB,
46 QW_SFT,
47 SD_LAYER,
31}; 48};
32 49
33const uint16_t PROGMEM ab_combo[] = {KC_A, KC_B, COMBO_END}; 50const uint16_t PROGMEM ab_combo[] = {KC_A, KC_B, COMBO_END};
34const uint16_t PROGMEM jk_combo[] = {KC_J, KC_K, COMBO_END}; 51const uint16_t PROGMEM jk_combo[] = {KC_J, KC_K, COMBO_END};
52const uint16_t PROGMEM qw_combo[] = {KC_Q, KC_W, COMBO_END};
53const uint16_t PROGMEM sd_combo[] = {KC_S, KC_D, COMBO_END};
35 54
36combo_t key_combos[COMBO_COUNT] = { 55combo_t key_combos[COMBO_COUNT] = {
37 [AB_ESC] = COMBO(ab_combo, KC_ESC), 56 [AB_ESC] = COMBO(ab_combo, KC_ESC),
38 [JK_TAB] = COMBO(jk_combo, KC_TAB) 57 [JK_TAB] = COMBO(jk_combo, KC_TAB),
58 [QW_SFT] = COMBO(qw_combo, KC_LSFT)
59 [SD_LAYER] = COMBO(layer_combo, MO(_LAYER)),
39}; 60};
40``` 61```
41 62
42For a more complicated implementation, you can use the `process_combo_event` function to add custom handling. 63For a more complicated implementation, you can use the `process_combo_event` function to add custom handling.
64Additionally, this example shows how you can leave `COMBO_COUNT` undefined.
43 65
44```c 66```c
45enum combo_events { 67enum combo_events {
46 ZC_COPY, 68 EM_EMAIL,
47 XV_PASTE 69 BSPC_LSFT_CLEAR,
70 COMBO_LENGTH
48}; 71};
72uint16_t COMBO_LEN = COMBO_LENGTH; // remove the COMBO_COUNT define and use this instead!
49 73
50const uint16_t PROGMEM copy_combo[] = {KC_Z, KC_C, COMBO_END}; 74const uint16_t PROGMEM email_combo[] = {KC_E, KC_M, COMBO_END};
51const uint16_t PROGMEM paste_combo[] = {KC_X, KC_V, COMBO_END}; 75const uint16_t PROGMEM clear_line_combo[] = {KC_BSPC, KC_LSFT, COMBO_END};
52 76
53combo_t key_combos[COMBO_COUNT] = { 77combo_t key_combos[] = {
54 [ZC_COPY] = COMBO_ACTION(copy_combo), 78 [EM_EMAIL] = COMBO_ACTION(email_combo),
55 [XV_PASTE] = COMBO_ACTION(paste_combo), 79 [BSPC_LSFT_CLEAR] = COMBO_ACTION(clear_line_combo),
56}; 80};
81/* COMBO_ACTION(x) is same as COMBO(x, KC_NO) */
57 82
58void process_combo_event(uint16_t combo_index, bool pressed) { 83void process_combo_event(uint16_t combo_index, bool pressed) {
59 switch(combo_index) { 84 switch(combo_index) {
60 case ZC_COPY: 85 case EM_EMAIL:
61 if (pressed) { 86 if (pressed) {
62 tap_code16(LCTL(KC_C)); 87 SEND_STRING("john.doe@example.com");
63 } 88 }
64 break; 89 break;
65 case XV_PASTE: 90 case BSPC_LSFT_CLEAR:
66 if (pressed) { 91 if (pressed) {
67 tap_code16(LCTL(KC_V)); 92 tap_code16(KC_END);
93 tap_code16(S(KC_HOME));
94 tap_code16(KC_BSPC);
68 } 95 }
69 break; 96 break;
70 } 97 }
71} 98}
72``` 99```
73 100
74This will send Ctrl+C if you hit Z and C, and Ctrl+V if you hit X and V. But you could change this to do stuff like change layers, play sounds, or change settings. 101This will send "john.doe@example.com" if you chord E and M together, and clear the current line with Backspace and Left-Shift. You could change this to do stuff like play sounds or change settings.
75
76## Additional Configuration
77 102
78If you're using long combos, or even longer combos, you may run into issues with this, as the structure may not be large enough to accommodate what you're doing. 103It is worth noting that `COMBO_ACTION`s are not needed anymore. As of [PR#8591](https://github.com/qmk/qmk_firmware/pull/8591/), it is possible to run your own custom keycodes from combos. Just define the custom keycode, program its functionality in `process_record_user`, and define a combo with `COMBO(<key_array>, <your_custom_keycode>)`.
79 104
80In this case, you can add either `#define EXTRA_LONG_COMBOS` or `#define EXTRA_EXTRA_LONG_COMBOS` in your `config.h` file. 105## Keycodes
81 106You can enable, disable and toggle the Combo feature on the fly. This is useful if you need to disable them temporarily, such as for a game. The following keycodes are available for use in your `keymap.c`
82You may also be able to enable action keys by defining `COMBO_ALLOW_ACTION_KEYS`.
83
84## Keycodes
85
86You can enable, disable and toggle the Combo feature on the fly. This is useful if you need to disable them temporarily, such as for a game.
87 107
88|Keycode |Description | 108|Keycode |Description |
89|----------|---------------------------------| 109|----------|---------------------------------|
@@ -91,6 +111,187 @@ You can enable, disable and toggle the Combo feature on the fly. This is useful
91|`CMB_OFF` |Turns off Combo feature | 111|`CMB_OFF` |Turns off Combo feature |
92|`CMB_TOG` |Toggles Combo feature on and off | 112|`CMB_TOG` |Toggles Combo feature on and off |
93 113
114# Advanced Configuration
115These configuration settings can be set in your `config.h` file.
116
117## Combo Term
118By default, the timeout for the Combos to be recognized is set to 50ms. This can be changed if accidental combo misfires are happening or if you're having difficulties pressing keys at the same time. For instance, `#define COMBO_TERM 40` would set the timeout period for combos to 40ms.
119
120## Buffer and state sizes
121If you're using long combos, or you have a lot of overlapping combos, you may run into issues with this, as the buffers may not be large enough to accommodate what you're doing. In this case, you can configure the sizes of the buffers used. Be aware, larger combo sizes and larger buffers will increase memory usage!
122
123To configure the amount of keys a combo can be composed of, change the following:
124
125| Keys | Define to be set |
126|------|-----------------------------------|
127| 6 | `#define EXTRA_SHORT_COMBOS` |
128| 8 | QMK Default |
129| 16 | `#define EXTRA_LONG_COMBOS` |
130| 32 | `#define EXTRA_EXTRA_LONG_COMBOS` |
131
132Defining `EXTRA_SHORT_COMBOS` combines a combo's internal state into just one byte. This can, in some cases, save some memory. If it doesn't, no point using it. If you do, you also have to make sure you don't define combos with more than 6 keys.
133
134Processing combos has two buffers, one for the key presses, another for the combos being activated. Use the following options to configure the sizes of these buffers:
135
136| Define | Default |
137|-------------------------------------|------------------------------------------------------|
138| `#define COMBO_KEY_BUFFER_LENGTH 8` | 8 (the key amount `(EXTRA_)EXTRA_LONG_COMBOS` gives) |
139| `#define COMBO_BUFFER_LENGTH 4` | 4 |
140
141## Modifier Combos
142If a combo resolves to a Modifier, the window for processing the combo can be extended independently from normal combos. By default, this is disabled but can be enabled with `#define COMBO_MUST_HOLD_MODS`, and the time window can be configured with `#define COMBO_HOLD_TERM 150` (default: `TAPPING_TERM`). With `COMBO_MUST_HOLD_MODS`, you cannot tap the combo any more which makes the combo less prone to misfires.
143
144## Per Combo Timing, Holding and Tapping
145For each combo, it is possible to configure the time window it has to pressed in, if it needs to be held down, or if it needs to be tapped.
146
147For example, tap-only combos are useful if any (or all) of the underlying keys is a Mod-Tap or a Layer-Tap key. When you tap the combo, you get the combo result. When you press the combo and hold it down, the combo doesn't actually activate. Instead the keys are processed separately as if the combo wasn't even there.
148
149In order to use these features, the following configuration options and functions need to be defined. Coming up with useful timings and configuration is left as an exercise for the reader.
150
151| Config Flag | Function | Description |
152|-----------------------------|-----------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
153| `COMBO_TERM_PER_COMBO` | uint16_t get_combo_term(uint16_t index, combo_t \*combo) | Optional per-combo timeout window. (default: `COMBO_TERM`) |
154| `COMBO_MUST_HOLD_PER_COMBO` | bool get_combo_must_hold(uint16_t index, combo_t \*combo) | Controls if a given combo should fire immediately on tap or if it needs to be held. (default: `false`) |
155| `COMBO_MUST_TAP_PER_COMBO` | bool get_combo_must_tap(uint16_t index, combo_t \*combo) | Controls if a given combo should fire only if tapped within `COMBO_HOLD_TERM`. (default: `false`) |
156
157Examples:
158```c
159uint16_t get_combo_term(uint16_t index, combo_t *combo) {
160 // decide by combo->keycode
161 switch (combo->keycode) {
162 case KC_X:
163 return 50;
164 }
165
166 // or with combo index, i.e. its name from enum.
167 switch (index) {
168 case COMBO_NAME_HERE:
169 return 9001;
170 }
171
172 // And if you're feeling adventurous, you can even decide by the keys in the chord,
173 // i.e. the exact array of keys you defined for the combo.
174 // This can be useful if your combos have a common key and you want to apply the
175 // same combo term for all of them.
176 if (combo->keys[0] == KC_ENTER) { // if first key in the array is KC_ENTER
177 return 150;
178 }
179
180 return COMBO_TERM;
181}
182
183bool get_combo_must_hold(uint16_t index, combo_t *combo) {
184 // Same as above, decide by keycode, the combo index, or by the keys in the chord.
185
186 if (KEYCODE_IS_MOD(combo->keycode) ||
187 (combo->keycode >= QK_MOMENTARY && combo->keycode <= QK_MOMENTARY_MAX) // MO(kc) keycodes
188 ) {
189 return true;
190 }
191
192 switch (index) {
193 case COMBO_NAME_HERE:
194 return true;
195 }
196
197 return false;
198}
199
200bool get_combo_must_tap(uint16_t index, combo_t *combo) {
201 // If you want all combos to be tap-only, just uncomment the next line
202 // return true
203
204 // If you want *all* combos, that have Mod-Tap/Layer-Tap/Momentary keys in its chord, to be tap-only, this is for you:
205 uint16_t key;
206 uint8_t idx = 0;
207 while ((key = pgm_read_word(&combo->keys[idx])) != COMBO_END) {
208 switch (key) {
209 case QK_MOD_TAP...QK_MOD_TAP_MAX:
210 case QK_LAYER_TAP...QK_LAYER_TAP_MAX:
211 case QK_MOMENTARY...QK_MOMENTARY_MAX:
212 return true;
213 }
214 idx += 1;
215 }
216 return false;
217
218}
219```
220
221## Variable Length Combos
222If you leave `COMBO_COUNT` undefined in `config.h`, it allows you to programmatically declare the size of the Combo data structure and avoid updating `COMBO_COUNT`. Instead a variable called `COMBO_LEN` has to be set. It can be set with something similar to the following in `keymap.c`: `uint16_t COMBO_LEN = sizeof(key_combos) / sizeof(key_combos[0]);` or by adding `COMBO_LENGTH` as the *last* entry in the combo enum and then `uint16_t COMBO_LEN = COMBO_LENGTH;` as such:
223```c
224enum myCombos {
225 ...,
226 COMBO_LENGTH
227};
228uint16_t COMBO_LEN = COMBO_LENGTH;
229```
230Regardless of the method used to declare `COMBO_LEN`, this also requires to convert the `combo_t key_combos[COMBO_COUNT] = {...};` line to `combo_t key_combos[] = {...};`.
231
232
233## Combo timer
234
235Normally, the timer is started on the first key press and then reset on every subsequent key press within the `COMBO_TERM`.
236Inputting combos is relaxed like this, but also slightly more prone to accidental misfires.
237
238The next two options alter the behaviour of the timer.
239
240### `#define COMBO_STRICT_TIMER`
241
242With `COMBO_STRICT_TIMER`, the timer is started only on the first key press.
243Inputting combos is now less relaxed; you need to make sure the full chord is pressed within the `COMBO_TERM`.
244Misfires are less common but if you type multiple combos fast, there is a
245chance that the latter ones might not activate properly.
246
247### `#define COMBO_NO_TIMER`
248
249By defining `COMBO_NO_TIMER`, the timer is disabled completely and combos are activated on the first key release.
250This also disables the "must hold" functionalities as they just wouldn't work at all.
251
252## Customizable key releases
253
254By defining `COMBO_PROCESS_KEY_RELEASE` and implementing the function `bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode)`, you can run your custom code on each key release after a combo was activated. For example you could change the RGB colors, activate haptics, or alter the modifiers.
255
256You can also release a combo early by returning `true` from the function.
257
258Here's an example where a combo resolves to two modifiers, and on key releases the modifiers are unregistered one by one, depending on which key was released.
259
260```c
261enum combos {
262 AB_MODS,
263 COMBO_LENGTH
264};
265uint16_t COMBO_LEN = COMBO_LENGTH;
266
267const uint16_t PROGMEM ab_combo[] = {KC_A, KC_B, COMBO_END};
268
269combo_t key_combos[] = {
270 [AB_MODS] = COMBO(ab_combo, LCTL(KC_LSFT)),
271};
272
273bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) {
274 switch (combo_index) {
275 case AB_MODS:
276 switch(keycode) {
277 case KC_A:
278 unregister_mods(MOD_MASK_CTRL);
279 break;
280 case KC_B:
281 unregister_mods(MOD_MASK_SHIFT);
282 break;
283 }
284 return false; // do not release combo
285 }
286 return false;
287}
288```
289## Layer independent combos
290
291If you, for example, use multiple base layers for different key layouts, one for QWERTY, and another one for Colemak, you might want your combos to work from the same key positions on all layers. Defining the same combos again for another layout is redundant and takes more memory. The solution is to just check the keycodes from one layer.
292
293With `#define COMBO_ONLY_FROM_LAYER _LAYER_A` the combos' keys are always checked from layer `_LAYER_A` even though the active layer would be `_LAYER_B`.
294
94## User callbacks 295## User callbacks
95 296
96In addition to the keycodes, there are a few functions that you can use to set the status, or check it: 297In addition to the keycodes, there are a few functions that you can use to set the status, or check it:
@@ -101,3 +302,28 @@ In addition to the keycodes, there are a few functions that you can use to set t
101| `combo_disable()` | Disables the combo feature, and clears the combo buffer | 302| `combo_disable()` | Disables the combo feature, and clears the combo buffer |
102| `combo_toggle()` | Toggles the state of the combo feature | 303| `combo_toggle()` | Toggles the state of the combo feature |
103| `is_combo_enabled()` | Returns the status of the combo feature state (true or false) | 304| `is_combo_enabled()` | Returns the status of the combo feature state (true or false) |
305
306
307# Dictionary Management
308
309Having 3 places to update when adding new combos or altering old ones does become cumbersome when you have a lot of combos. We can alleviate this with some magic! ... If you consider C macros magic.
310First, you need to add `VPATH += keyboards/gboards` to your `rules.mk`. Next, include the file `g/keymap_combo.h` in your `keymap.c`.
311
312!> This functionality uses the same `process_combo_event` function as `COMBO_ACTION` macros do, so you cannot use the function yourself in your keymap. Instead, you have to define the `case`s of the `switch` statement by themselves within `inject.h`, which `g/keymap_combo.h` will then include into the function.
313
314Then, write your combos in `combos.def` file in the following manner:
315
316```c
317// name result chord keys
318COMB(AB_ESC, KC_ESC, KC_A, KC_B)
319COMB(JK_TAB, KC_TAB, KC_J, KC_K)
320COMB(JKL_SPC, KC_SPC, KC_J, KC_K, KC_L)
321COMB(BSSL_CLR, KC_NO, KC_BSPC, KC_LSFT) // using KC_NO as the resulting keycode is the same as COMBO_ACTION before.
322COMB(QW_UNDO, C(KC_Z), KC_Q, KC_W)
323SUBS(TH_THE, "the", KC_T, KC_H) // SUBS uses SEND_STRING to output the given string.
324...
325```
326
327Now, you can update only one place to add or alter combos. You don't even need to remember to update the `COMBO_COUNT` or the `COMBO_LEN` variables at all. Everything is taken care of. Magic!
328
329For small to huge ready made dictionaries of combos, you can check out http://combos.gboards.ca/.
diff --git a/keyboards/gboards/g/keymap_combo.h b/keyboards/gboards/g/keymap_combo.h
index 58e99863e..b92b6a4bc 100644
--- a/keyboards/gboards/g/keymap_combo.h
+++ b/keyboards/gboards/g/keymap_combo.h
@@ -28,7 +28,10 @@
28#define TOGG A_ENUM 28#define TOGG A_ENUM
29enum combos { 29enum combos {
30#include "combos.def" 30#include "combos.def"
31 COMBO_LENGTH
31}; 32};
33// Export length to combo module
34uint16_t COMBO_LEN = COMBO_LENGTH;
32 35
33// Bake combos into mem 36// Bake combos into mem
34#undef COMB 37#undef COMB
@@ -53,9 +56,6 @@ combo_t key_combos[] = {
53#undef SUBS 56#undef SUBS
54#undef TOGG 57#undef TOGG
55 58
56// Export length to combo module
57int COMBO_LEN = sizeof(key_combos) / sizeof(key_combos[0]);
58
59// Fill QMK hook 59// Fill QMK hook
60#define COMB BLANK 60#define COMB BLANK
61#define SUBS A_ACTI 61#define SUBS A_ACTI
diff --git a/quantum/keymap_common.c b/quantum/keymap_common.c
index e0fd6d479..780c71ab9 100644
--- a/quantum/keymap_common.c
+++ b/quantum/keymap_common.c
@@ -40,7 +40,10 @@ extern keymap_config_t keymap_config;
40action_t action_for_key(uint8_t layer, keypos_t key) { 40action_t action_for_key(uint8_t layer, keypos_t key) {
41 // 16bit keycodes - important 41 // 16bit keycodes - important
42 uint16_t keycode = keymap_key_to_keycode(layer, key); 42 uint16_t keycode = keymap_key_to_keycode(layer, key);
43 return action_for_keycode(keycode);
44};
43 45
46action_t action_for_keycode(uint16_t keycode) {
44 // keycode remapping 47 // keycode remapping
45 keycode = keycode_config(keycode); 48 keycode = keycode_config(keycode);
46 49
diff --git a/quantum/process_keycode/process_combo.c b/quantum/process_keycode/process_combo.c
index 9e1629248..b4e698dec 100644
--- a/quantum/process_keycode/process_combo.c
+++ b/quantum/process_keycode/process_combo.c
@@ -16,114 +16,445 @@
16 16
17#include "print.h" 17#include "print.h"
18#include "process_combo.h" 18#include "process_combo.h"
19#include "action_tapping.h"
19 20
20#ifndef COMBO_VARIABLE_LEN 21
21__attribute__((weak)) combo_t key_combos[COMBO_COUNT] = {}; 22#ifdef COMBO_COUNT
23__attribute__((weak)) combo_t key_combos[COMBO_COUNT];
24uint16_t COMBO_LEN = COMBO_COUNT;
22#else 25#else
23extern combo_t key_combos[]; 26extern combo_t key_combos[];
24extern int COMBO_LEN; 27extern uint16_t COMBO_LEN;
25#endif 28#endif
26 29
27__attribute__((weak)) void process_combo_event(uint16_t combo_index, bool pressed) {} 30__attribute__((weak)) void process_combo_event(uint16_t combo_index, bool pressed) {}
28 31
29static uint16_t timer = 0; 32#ifdef COMBO_MUST_HOLD_PER_COMBO
30static uint16_t current_combo_index = 0; 33__attribute__((weak)) bool get_combo_must_hold(uint16_t index, combo_t *combo) { return false; }
31static bool drop_buffer = false; 34#endif
32static bool is_active = false; 35
33static bool b_combo_enable = true; // defaults to enabled 36#ifdef COMBO_MUST_TAP_PER_COMBO
37__attribute__((weak)) bool get_combo_must_tap(uint16_t index, combo_t *combo) { return false; }
38#endif
34 39
35static uint8_t buffer_size = 0; 40#ifdef COMBO_TERM_PER_COMBO
36#ifdef COMBO_ALLOW_ACTION_KEYS 41__attribute__((weak)) uint16_t get_combo_term(uint16_t index, combo_t *combo) { return COMBO_TERM; }
37static keyrecord_t key_buffer[MAX_COMBO_LENGTH]; 42#endif
43
44#ifdef COMBO_PROCESS_KEY_RELEASE
45__attribute__((weak)) bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) { return false; }
46#endif
47
48#ifndef COMBO_NO_TIMER
49static uint16_t timer = 0;
50#endif
51static bool b_combo_enable = true; // defaults to enabled
52static uint16_t longest_term = 0;
53
54typedef struct {
55 keyrecord_t record;
56 uint16_t combo_index;
57 uint16_t keycode;
58} queued_record_t;
59static uint8_t key_buffer_size = 0;
60static queued_record_t key_buffer[COMBO_KEY_BUFFER_LENGTH];
61
62typedef struct {
63 uint16_t combo_index;
64} queued_combo_t;
65static uint8_t combo_buffer_write= 0;
66static uint8_t combo_buffer_read = 0;
67static queued_combo_t combo_buffer[COMBO_BUFFER_LENGTH];
68
69#define INCREMENT_MOD(i) i = (i + 1) % COMBO_BUFFER_LENGTH
70
71#define COMBO_KEY_POS ((keypos_t){.col=254, .row=254})
72
73
74#ifndef EXTRA_SHORT_COMBOS
75/* flags are their own elements in combo_t struct. */
76# define COMBO_ACTIVE(combo) (combo->active)
77# define COMBO_DISABLED(combo) (combo->disabled)
78# define COMBO_STATE(combo) (combo->state)
79
80# define ACTIVATE_COMBO(combo) do {combo->active = true;}while(0)
81# define DEACTIVATE_COMBO(combo) do {combo->active = false;}while(0)
82# define DISABLE_COMBO(combo) do {combo->disabled = true;}while(0)
83# define RESET_COMBO_STATE(combo) do { \
84 combo->disabled = false; \
85 combo->state = 0; \
86}while(0)
38#else 87#else
39static uint16_t key_buffer[MAX_COMBO_LENGTH]; 88/* flags are at the two high bits of state. */
89# define COMBO_ACTIVE(combo) (combo->state & 0x80)
90# define COMBO_DISABLED(combo) (combo->state & 0x40)
91# define COMBO_STATE(combo) (combo->state & 0x3F)
92
93# define ACTIVATE_COMBO(combo) do {combo->state |= 0x80;}while(0)
94# define DEACTIVATE_COMBO(combo) do {combo->state &= ~0x80;}while(0)
95# define DISABLE_COMBO(combo) do {combo->state |= 0x40;}while(0)
96# define RESET_COMBO_STATE(combo) do {combo->state &= ~0x7F;}while(0)
40#endif 97#endif
41 98
42static inline void send_combo(uint16_t action, bool pressed) { 99static inline void release_combo(uint16_t combo_index, combo_t *combo) {
43 if (action) { 100 if (combo->keycode) {
44 if (pressed) { 101 keyrecord_t record = {
45 register_code16(action); 102 .event = {
46 } else { 103 .key = COMBO_KEY_POS,
47 unregister_code16(action); 104 .time = timer_read()|1,
48 } 105 .pressed = false,
106 },
107 .keycode = combo->keycode,
108 };
109#ifndef NO_ACTION_TAPPING
110 action_tapping_process(record);
111#else
112 process_record(&record);
113#endif
49 } else { 114 } else {
50 process_combo_event(current_combo_index, pressed); 115 process_combo_event(combo_index, false);
116 }
117 DEACTIVATE_COMBO(combo);
118}
119
120static inline bool _get_combo_must_hold(uint16_t combo_index, combo_t *combo) {
121#ifdef COMBO_NO_TIMER
122 return false;
123#elif defined(COMBO_MUST_HOLD_PER_COMBO)
124 return get_combo_must_hold(combo_index, combo);
125#elif defined(COMBO_MUST_HOLD_MODS)
126 return (KEYCODE_IS_MOD(combo->keycode) ||
127 (combo->keycode >= QK_MOMENTARY && combo->keycode <= QK_MOMENTARY_MAX));
128#endif
129 return false;
130}
131
132static inline uint16_t _get_wait_time(uint16_t combo_index, combo_t *combo ) {
133 if (_get_combo_must_hold(combo_index, combo)
134#ifdef COMBO_MUST_TAP_PER_COMBO
135 || get_combo_must_tap(combo_index, combo)
136#endif
137 ) {
138 if (longest_term < COMBO_HOLD_TERM) {
139 return COMBO_HOLD_TERM;
140 }
141 }
142
143 return longest_term;
144}
145
146static inline uint16_t _get_combo_term(uint16_t combo_index, combo_t *combo) {
147
148#if defined(COMBO_TERM_PER_COMBO)
149 return get_combo_term(combo_index, combo);
150#endif
151
152 return COMBO_TERM;
153}
154
155void clear_combos(void) {
156 uint16_t index = 0;
157 longest_term = 0;
158 for (index = 0; index < COMBO_LEN; ++index) {
159 combo_t *combo = &key_combos[index];
160 if (!COMBO_ACTIVE(combo)) {
161 RESET_COMBO_STATE(combo);
162 }
51 } 163 }
52} 164}
53 165
54static inline void dump_key_buffer(bool emit) { 166static inline void dump_key_buffer(void) {
55 if (buffer_size == 0) { 167 if (key_buffer_size == 0) {
56 return; 168 return;
57 } 169 }
58 170
59 if (emit) { 171 for (uint8_t key_buffer_i = 0; key_buffer_i < key_buffer_size; key_buffer_i++) {
60 for (uint8_t i = 0; i < buffer_size; i++) { 172
61#ifdef COMBO_ALLOW_ACTION_KEYS 173 queued_record_t *qrecord = &key_buffer[key_buffer_i];
62 const action_t action = store_or_get_action(key_buffer[i].event.pressed, key_buffer[i].event.key); 174 keyrecord_t *record = &qrecord->record;
63 process_action(&(key_buffer[i]), action); 175
176 if (IS_NOEVENT(record->event)) {
177 continue;
178 }
179
180 if (!record->keycode && qrecord->combo_index != (uint16_t)-1) {
181 process_combo_event(qrecord->combo_index, true);
182 } else {
183#ifndef NO_ACTION_TAPPING
184 action_tapping_process(*record);
64#else 185#else
65 register_code16(key_buffer[i]); 186 process_record(record);
66 send_keyboard_report();
67#endif 187#endif
68 } 188 }
189 record->event.time = 0;
69 } 190 }
70 191
71 buffer_size = 0; 192 key_buffer_size = 0;
72} 193}
73 194
74#define ALL_COMBO_KEYS_ARE_DOWN (((1 << count) - 1) == combo->state) 195#define NO_COMBO_KEYS_ARE_DOWN (0 == COMBO_STATE(combo))
75#define KEY_STATE_DOWN(key) \ 196#define ALL_COMBO_KEYS_ARE_DOWN(state, key_count) (((1 << key_count) - 1) == state)
76 do { \ 197#define ONLY_ONE_KEY_IS_DOWN(state) !(state & (state - 1))
77 combo->state |= (1 << key); \ 198#define KEY_NOT_YET_RELEASED(state, key_index) ((1 << key_index) & state)
199#define KEY_STATE_DOWN(state, key_index) \
200 do { \
201 state |= (1 << key_index); \
78 } while (0) 202 } while (0)
79#define KEY_STATE_UP(key) \ 203#define KEY_STATE_UP(state, key_index) \
80 do { \ 204 do { \
81 combo->state &= ~(1 << key); \ 205 state &= ~(1 << key_index); \
82 } while (0) 206 } while (0)
83 207
84static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record) { 208static inline void _find_key_index_and_count(const uint16_t *keys, uint16_t keycode, uint16_t *key_index, uint8_t *key_count) {
85 uint8_t count = 0; 209 while (true) {
86 uint16_t index = -1; 210 uint16_t key = pgm_read_word(&keys[*key_count]);
87 /* Find index of keycode and number of combo keys */ 211 if (keycode == key) *key_index = *key_count;
88 for (const uint16_t *keys = combo->keys;; ++count) {
89 uint16_t key = pgm_read_word(&keys[count]);
90 if (keycode == key) index = count;
91 if (COMBO_END == key) break; 212 if (COMBO_END == key) break;
213 (*key_count)++;
214 }
215}
216
217void drop_combo_from_buffer(uint16_t combo_index) {
218 /* Mark a combo as processed from the buffer. If the buffer is in the
219 * beginning of the buffer, drop it. */
220 uint8_t i = combo_buffer_read;
221 while (i != combo_buffer_write) {
222 queued_combo_t *qcombo = &combo_buffer[i];
223
224 if (qcombo->combo_index == combo_index) {
225 combo_t *combo = &key_combos[combo_index];
226 DISABLE_COMBO(combo);
227
228 if (i == combo_buffer_read) {
229 INCREMENT_MOD(combo_buffer_read);
230 }
231 break;
232 }
233 INCREMENT_MOD(i);
92 } 234 }
235}
236
237void apply_combo(uint16_t combo_index, combo_t *combo) {
238 /* Apply combo's result keycode to the last chord key of the combo and
239 * disable the other keys. */
93 240
94 /* Continue processing if not a combo key */ 241 if (COMBO_DISABLED(combo)) { return; }
95 if (-1 == (int8_t)index) return false;
96 242
97 bool is_combo_active = is_active; 243 // state to check against so we find the last key of the combo from the buffer
244#if defined(EXTRA_EXTRA_LONG_COMBOS)
245 uint32_t state = 0;
246#elif defined(EXTRA_LONG_COMBOS)
247 uint16_t state = 0;
248#else
249 uint8_t state = 0;
250#endif
251
252 for (uint8_t key_buffer_i = 0; key_buffer_i < key_buffer_size; key_buffer_i++) {
253
254 queued_record_t *qrecord = &key_buffer[key_buffer_i];
255 keyrecord_t *record = &qrecord->record;
256 uint16_t keycode = qrecord->keycode;
257
258 uint8_t key_count = 0;
259 uint16_t key_index = -1;
260 _find_key_index_and_count(combo->keys, keycode, &key_index, &key_count);
261
262 if (-1 == (int16_t)key_index) {
263 // key not part of this combo
264 continue;
265 }
98 266
99 if (record->event.pressed) { 267 KEY_STATE_DOWN(state, key_index);
100 KEY_STATE_DOWN(index); 268 if (ALL_COMBO_KEYS_ARE_DOWN(state, key_count)) {
269 // this in the end executes the combo when the key_buffer is dumped.
270 record->keycode = combo->keycode;
271 record->event.key = COMBO_KEY_POS;
101 272
102 if (is_combo_active) { 273 qrecord->combo_index = combo_index;
103 if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was pressed */ 274 ACTIVATE_COMBO(combo);
104 send_combo(combo->keycode, true); 275
105 drop_buffer = true; 276 break;
277 } else {
278 // key was part of the combo but not the last one, "disable" it
279 // by making it a TICK event.
280 record->event.time = 0;
281 }
282
283 }
284 drop_combo_from_buffer(combo_index);
285}
286
287static inline void apply_combos(void) {
288 // Apply all buffered normal combos.
289 for (uint8_t i = combo_buffer_read;
290 i != combo_buffer_write;
291 INCREMENT_MOD(i)) {
292
293 queued_combo_t *buffered_combo = &combo_buffer[i];
294 combo_t *combo = &key_combos[buffered_combo->combo_index];
295
296#ifdef COMBO_MUST_TAP_PER_COMBO
297 if (get_combo_must_tap(buffered_combo->combo_index, combo)) {
298 // Tap-only combos are applied on key release only, so let's drop 'em here.
299 drop_combo_from_buffer(buffered_combo->combo_index);
300 continue;
301 }
302#endif
303 apply_combo(buffered_combo->combo_index, combo);
304 }
305 dump_key_buffer();
306 clear_combos();
307}
308
309combo_t* overlaps(combo_t *combo1, combo_t *combo2) {
310 /* Checks if the combos overlap and returns the combo that should be
311 * dropped from the combo buffer.
312 * The combo that has less keys will be dropped. If they have the same
313 * amount of keys, drop combo1. */
314
315 uint8_t idx1 = 0, idx2 = 0;
316 uint16_t key1, key2;
317 bool overlaps = false;
318
319 while ((key1 = pgm_read_word(&combo1->keys[idx1])) != COMBO_END) {
320 idx2 = 0;
321 while ((key2 = pgm_read_word(&combo2->keys[idx2])) != COMBO_END) {
322 if (key1 == key2) overlaps = true;
323 idx2 += 1;
324 }
325 idx1 += 1;
326 }
327
328 if (!overlaps) return NULL;
329 if (idx2 < idx1) return combo2;
330 return combo1;
331}
332
333static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record, uint16_t combo_index) {
334 uint8_t key_count = 0;
335 uint16_t key_index = -1;
336 _find_key_index_and_count(combo->keys, keycode, &key_index, &key_count);
337
338 /* Continue processing if key isn't part of current combo. */
339 if (-1 == (int16_t)key_index) {
340 return false;
341 }
342
343 bool key_is_part_of_combo = !COMBO_DISABLED(combo);
344
345 if (record->event.pressed && !COMBO_DISABLED(combo)) {
346 uint16_t time = _get_combo_term(combo_index, combo);
347 if (!COMBO_ACTIVE(combo)) {
348 KEY_STATE_DOWN(combo->state, key_index);
349 if (longest_term < time) {
350 longest_term = time;
106 } 351 }
107 } 352 }
353 if (ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) {
354 /* Combo was fully pressed */
355 /* Buffer the combo so we can fire it after COMBO_TERM */
356
357#ifndef COMBO_NO_TIMER
358 /* Don't buffer this combo if its combo term has passed. */
359 if (timer && timer_elapsed(timer) > time) {
360 DISABLE_COMBO(combo);
361 return true;
362 } else
363#endif
364 {
365
366 // disable readied combos that overlap with this combo
367 combo_t *drop = NULL;
368 for (uint8_t combo_buffer_i = combo_buffer_read;
369 combo_buffer_i != combo_buffer_write;
370 INCREMENT_MOD(combo_buffer_i)) {
371
372 queued_combo_t *qcombo = &combo_buffer[combo_buffer_i];
373 combo_t *buffered_combo = &key_combos[qcombo->combo_index];
374
375 if ((drop = overlaps(buffered_combo, combo))) {
376 DISABLE_COMBO(drop);
377 if (drop == combo) {
378 // stop checking for overlaps if dropped combo was current combo.
379 break;
380 } else if (combo_buffer_i == combo_buffer_read && drop == buffered_combo) {
381 /* Drop the disabled buffered combo from the buffer if
382 * it is in the beginning of the buffer. */
383 INCREMENT_MOD(combo_buffer_read);
384 }
385 }
386
387 }
388
389 if (drop != combo) {
390 // save this combo to buffer
391 combo_buffer[combo_buffer_write] = (queued_combo_t){
392 .combo_index=combo_index,
393 };
394 INCREMENT_MOD(combo_buffer_write);
395
396 // get possible longer waiting time for tap-/hold-only combos.
397 longest_term = _get_wait_time(combo_index, combo);
398 }
399 } // if timer elapsed end
400
401 }
108 } else { 402 } else {
109 if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was released */ 403 // chord releases
110 send_combo(combo->keycode, false); 404 if (!COMBO_ACTIVE(combo) && ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) {
405 /* First key quickly released */
406 if (COMBO_DISABLED(combo) || _get_combo_must_hold(combo_index, combo)) {
407 // combo wasn't tappable, disable it and drop it from buffer.
408 drop_combo_from_buffer(combo_index);
409 key_is_part_of_combo = false;
410 }
411#ifdef COMBO_MUST_TAP_PER_COMBO
412 else if (get_combo_must_tap(combo_index, combo)) {
413 // immediately apply tap-only combo
414 apply_combo(combo_index, combo);
415 apply_combos(); // also apply other prepared combos and dump key buffer
416# ifdef COMBO_PROCESS_KEY_RELEASE
417 if (process_combo_key_release(combo_index, combo, key_index, keycode)) {
418 release_combo(combo_index, combo);
419 }
420# endif
421 }
422#endif
423 } else if (COMBO_ACTIVE(combo)
424 && ONLY_ONE_KEY_IS_DOWN(COMBO_STATE(combo))
425 && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index)
426 ) {
427 /* last key released */
428 release_combo(combo_index, combo);
429 key_is_part_of_combo = true;
430
431#ifdef COMBO_PROCESS_KEY_RELEASE
432 process_combo_key_release(combo_index, combo, key_index, keycode);
433#endif
434 } else if (COMBO_ACTIVE(combo)
435 && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index)
436 ) {
437 /* first or middle key released */
438 key_is_part_of_combo = true;
439
440#ifdef COMBO_PROCESS_KEY_RELEASE
441 if (process_combo_key_release(combo_index, combo, key_index, keycode)) {
442 release_combo(combo_index, combo);
443 }
444#endif
111 } else { 445 } else {
112 /* continue processing without immediately returning */ 446 /* The released key was part of an incomplete combo */
113 is_combo_active = false; 447 key_is_part_of_combo = false;
114 } 448 }
115 449
116 KEY_STATE_UP(index); 450 KEY_STATE_UP(combo->state, key_index);
117 } 451 }
118 452
119 return is_combo_active; 453 return key_is_part_of_combo;
120} 454}
121 455
122#define NO_COMBO_KEYS_ARE_DOWN (0 == combo->state)
123
124bool process_combo(uint16_t keycode, keyrecord_t *record) { 456bool process_combo(uint16_t keycode, keyrecord_t *record) {
125 bool is_combo_key = false; 457 bool is_combo_key = false;
126 drop_buffer = false;
127 bool no_combo_keys_pressed = true; 458 bool no_combo_keys_pressed = true;
128 459
129 if (keycode == CMB_ON && record->event.pressed) { 460 if (keycode == CMB_ON && record->event.pressed) {
@@ -144,62 +475,81 @@ bool process_combo(uint16_t keycode, keyrecord_t *record) {
144 if (!is_combo_enabled()) { 475 if (!is_combo_enabled()) {
145 return true; 476 return true;
146 } 477 }
147#ifndef COMBO_VARIABLE_LEN 478
148 for (current_combo_index = 0; current_combo_index < COMBO_COUNT; ++current_combo_index) { 479#ifdef COMBO_ONLY_FROM_LAYER
149#else 480 /* Only check keycodes from one layer. */
150 for (current_combo_index = 0; current_combo_index < COMBO_LEN; ++current_combo_index) { 481 keycode = keymap_key_to_keycode(COMBO_ONLY_FROM_LAYER, record->event.key);
151#endif 482#endif
152 combo_t *combo = &key_combos[current_combo_index]; 483
153 is_combo_key |= process_single_combo(combo, keycode, record); 484 for (uint16_t idx = 0; idx < COMBO_LEN; ++idx) {
154 no_combo_keys_pressed = no_combo_keys_pressed && NO_COMBO_KEYS_ARE_DOWN; 485 combo_t *combo = &key_combos[idx];
486 is_combo_key |= process_single_combo(combo, keycode, record, idx);
487 no_combo_keys_pressed = no_combo_keys_pressed && (NO_COMBO_KEYS_ARE_DOWN || COMBO_ACTIVE(combo) || COMBO_DISABLED(combo));
155 } 488 }
156 489
157 if (drop_buffer) { 490 if (record->event.pressed && is_combo_key) {
158 /* buffer is only dropped when we complete a combo, so we refresh the timer 491#ifndef COMBO_NO_TIMER
159 * here */ 492# ifdef COMBO_STRICT_TIMER
160 timer = timer_read(); 493 if (!timer) {
161 dump_key_buffer(false); 494 // timer is set only on the first key
162 } else if (!is_combo_key) { 495 timer = timer_read();
163 /* if no combos claim the key we need to emit the keybuffer */ 496 }
164 dump_key_buffer(true); 497# else
165
166 // reset state if there are no combo keys pressed at all
167 if (no_combo_keys_pressed) {
168 timer = 0;
169 is_active = true;
170 }
171 } else if (record->event.pressed && is_active) {
172 /* otherwise the key is consumed and placed in the buffer */
173 timer = timer_read(); 498 timer = timer_read();
499# endif
500#endif
174 501
175 if (buffer_size < MAX_COMBO_LENGTH) { 502 if (key_buffer_size < COMBO_KEY_BUFFER_LENGTH) {
176#ifdef COMBO_ALLOW_ACTION_KEYS 503 key_buffer[key_buffer_size++] = (queued_record_t){
177 key_buffer[buffer_size++] = *record; 504 .record = *record,
178#else 505 .keycode = keycode,
179 key_buffer[buffer_size++] = keycode; 506 .combo_index = -1, // this will be set when applying combos
507 };
508 }
509 } else {
510 if (combo_buffer_read != combo_buffer_write) {
511 // some combo is prepared
512 apply_combos();
513 } else {
514 // reset state if there are no combo keys pressed at all
515 dump_key_buffer();
516#ifndef COMBO_NO_TIMER
517 timer = 0;
180#endif 518#endif
519 clear_combos();
181 } 520 }
182 } 521 }
183
184 return !is_combo_key; 522 return !is_combo_key;
185} 523}
186 524
187void combo_task(void) { 525void combo_task(void) {
188 if (b_combo_enable && is_active && timer && timer_elapsed(timer) > COMBO_TERM) { 526 if (!b_combo_enable) {
189 /* This disables the combo, meaning key events for this 527 return;
190 * combo will be handled by the next processors in the chain
191 */
192 is_active = false;
193 dump_key_buffer(true);
194 } 528 }
529
530#ifndef COMBO_NO_TIMER
531 if (timer && timer_elapsed(timer) > longest_term) {
532 if (combo_buffer_read != combo_buffer_write) {
533 apply_combos();
534 longest_term = 0;
535 timer = 0;
536 } else {
537 dump_key_buffer();
538 timer = 0;
539 clear_combos();
540 }
541 }
542#endif
195} 543}
196 544
197void combo_enable(void) { b_combo_enable = true; } 545void combo_enable(void) { b_combo_enable = true; }
198 546
199void combo_disable(void) { 547void combo_disable(void) {
200 b_combo_enable = is_active = false; 548#ifndef COMBO_NO_TIMER
201 timer = 0; 549 timer = 0;
202 dump_key_buffer(true); 550#endif
551 b_combo_enable = false;
552 combo_buffer_read = combo_buffer_write;
203} 553}
204 554
205void combo_toggle(void) { 555void combo_toggle(void) {
diff --git a/quantum/process_keycode/process_combo.h b/quantum/process_keycode/process_combo.h
index 9af97588b..43c36d79e 100644
--- a/quantum/process_keycode/process_combo.h
+++ b/quantum/process_keycode/process_combo.h
@@ -20,23 +20,38 @@
20#include "quantum.h" 20#include "quantum.h"
21#include <stdint.h> 21#include <stdint.h>
22 22
23#ifdef EXTRA_EXTRA_LONG_COMBOS 23#ifdef EXTRA_SHORT_COMBOS
24# define MAX_COMBO_LENGTH 6
25#elif defined(EXTRA_EXTRA_LONG_COMBOS)
24# define MAX_COMBO_LENGTH 32 26# define MAX_COMBO_LENGTH 32
25#elif EXTRA_LONG_COMBOS 27#elif defined(EXTRA_LONG_COMBOS)
26# define MAX_COMBO_LENGTH 16 28# define MAX_COMBO_LENGTH 16
27#else 29#else
28# define MAX_COMBO_LENGTH 8 30# define MAX_COMBO_LENGTH 8
29#endif 31#endif
30 32
33#ifndef COMBO_KEY_BUFFER_LENGTH
34# define COMBO_KEY_BUFFER_LENGTH MAX_COMBO_LENGTH
35#endif
36#ifndef COMBO_BUFFER_LENGTH
37# define COMBO_BUFFER_LENGTH 4
38#endif
39
31typedef struct { 40typedef struct {
32 const uint16_t *keys; 41 const uint16_t *keys;
33 uint16_t keycode; 42 uint16_t keycode;
34#ifdef EXTRA_EXTRA_LONG_COMBOS 43#ifdef EXTRA_SHORT_COMBOS
44 uint8_t state;
45#else
46 bool disabled;
47 bool active;
48# if defined(EXTRA_EXTRA_LONG_COMBOS)
35 uint32_t state; 49 uint32_t state;
36#elif EXTRA_LONG_COMBOS 50# elif defined(EXTRA_LONG_COMBOS)
37 uint16_t state; 51 uint16_t state;
38#else 52# else
39 uint8_t state; 53 uint8_t state;
54# endif
40#endif 55#endif
41} combo_t; 56} combo_t;
42 57
@@ -46,12 +61,15 @@ typedef struct {
46 { .keys = &(ck)[0] } 61 { .keys = &(ck)[0] }
47 62
48#define COMBO_END 0 63#define COMBO_END 0
49#ifndef COMBO_COUNT
50# define COMBO_COUNT 0
51#endif
52#ifndef COMBO_TERM 64#ifndef COMBO_TERM
53# define COMBO_TERM TAPPING_TERM 65# define COMBO_TERM 50
54#endif 66#endif
67#ifndef COMBO_HOLD_TERM
68# define COMBO_HOLD_TERM TAPPING_TERM
69#endif
70
71/* check if keycode is only modifiers */
72#define KEYCODE_IS_MOD(code) (IS_MOD(code) || (code >= QK_MODS && code <= QK_MODS_MAX && !(code & QK_BASIC_MAX)))
55 73
56bool process_combo(uint16_t keycode, keyrecord_t *record); 74bool process_combo(uint16_t keycode, keyrecord_t *record);
57void combo_task(void); 75void combo_task(void);
diff --git a/quantum/quantum.c b/quantum/quantum.c
index 3329c1146..d17338871 100644
--- a/quantum/quantum.c
+++ b/quantum/quantum.c
@@ -143,7 +143,13 @@ void reset_keyboard(void) {
143} 143}
144 144
145/* Convert record into usable keycode via the contained event. */ 145/* Convert record into usable keycode via the contained event. */
146uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache) { return get_event_keycode(record->event, update_layer_cache); } 146uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache) {
147#ifdef COMBO_ENABLE
148 if (record->keycode) { return record->keycode; }
149#endif
150 return get_event_keycode(record->event, update_layer_cache);
151}
152
147 153
148/* Convert event into usable keycode. Checks the layer cache to ensure that it 154/* Convert event into usable keycode. Checks the layer cache to ensure that it
149 * retains the correct keycode after a layer change, if the key is still pressed. 155 * retains the correct keycode after a layer change, if the key is still pressed.
@@ -169,6 +175,18 @@ uint16_t get_event_keycode(keyevent_t event, bool update_layer_cache) {
169 return keymap_key_to_keycode(layer_switch_get_layer(event.key), event.key); 175 return keymap_key_to_keycode(layer_switch_get_layer(event.key), event.key);
170} 176}
171 177
178/* Get keycode, and then process pre tapping functionality */
179bool pre_process_record_quantum(keyrecord_t *record) {
180 if (!(
181#ifdef COMBO_ENABLE
182 process_combo(get_record_keycode(record, true), record) &&
183#endif
184 true)) {
185 return false;
186 }
187 return true; // continue processing
188}
189
172/* Get keycode, and then call keyboard function */ 190/* Get keycode, and then call keyboard function */
173void post_process_record_quantum(keyrecord_t *record) { 191void post_process_record_quantum(keyrecord_t *record) {
174 uint16_t keycode = get_record_keycode(record, false); 192 uint16_t keycode = get_record_keycode(record, false);
@@ -254,9 +272,6 @@ bool process_record_quantum(keyrecord_t *record) {
254#ifdef LEADER_ENABLE 272#ifdef LEADER_ENABLE
255 process_leader(keycode, record) && 273 process_leader(keycode, record) &&
256#endif 274#endif
257#ifdef COMBO_ENABLE
258 process_combo(keycode, record) &&
259#endif
260#ifdef PRINTING_ENABLE 275#ifdef PRINTING_ENABLE
261 process_printer(keycode, record) && 276 process_printer(keycode, record) &&
262#endif 277#endif
diff --git a/tmk_core/common/action.c b/tmk_core/common/action.c
index bd41d28b6..d19fd2a04 100644
--- a/tmk_core/common/action.c
+++ b/tmk_core/common/action.c
@@ -55,6 +55,8 @@ __attribute__((weak)) bool get_ignore_mod_tap_interrupt(uint16_t keycode, keyrec
55__attribute__((weak)) bool get_retro_tapping(uint16_t keycode, keyrecord_t *record) { return false; } 55__attribute__((weak)) bool get_retro_tapping(uint16_t keycode, keyrecord_t *record) { return false; }
56#endif 56#endif
57 57
58__attribute__((weak)) bool pre_process_record_quantum(keyrecord_t *record) { return true; }
59
58#ifndef TAP_CODE_DELAY 60#ifndef TAP_CODE_DELAY
59# define TAP_CODE_DELAY 0 61# define TAP_CODE_DELAY 0
60#endif 62#endif
@@ -106,9 +108,13 @@ void action_exec(keyevent_t event) {
106#endif 108#endif
107 109
108#ifndef NO_ACTION_TAPPING 110#ifndef NO_ACTION_TAPPING
109 action_tapping_process(record); 111 if (IS_NOEVENT(record.event) || pre_process_record_quantum(&record)) {
112 action_tapping_process(record);
113 }
110#else 114#else
111 process_record(&record); 115 if (IS_NOEVENT(record.event) || pre_process_record_quantum(&record)) {
116 process_record(&record);
117 }
112 if (!IS_NOEVENT(record.event)) { 118 if (!IS_NOEVENT(record.event)) {
113 dprint("processed: "); 119 dprint("processed: ");
114 debug_record(record); 120 debug_record(record);
@@ -206,7 +212,16 @@ void process_record(keyrecord_t *record) {
206} 212}
207 213
208void process_record_handler(keyrecord_t *record) { 214void process_record_handler(keyrecord_t *record) {
215#ifdef COMBO_ENABLE
216 action_t action;
217 if (record->keycode) {
218 action = action_for_keycode(record->keycode);
219 } else {
220 action = store_or_get_action(record->event.pressed, record->event.key);
221 }
222#else
209 action_t action = store_or_get_action(record->event.pressed, record->event.key); 223 action_t action = store_or_get_action(record->event.pressed, record->event.key);
224#endif
210 dprint("ACTION: "); 225 dprint("ACTION: ");
211 debug_action(action); 226 debug_action(action);
212#ifndef NO_ACTION_LAYER 227#ifndef NO_ACTION_LAYER
@@ -994,6 +1009,24 @@ bool is_tap_key(keypos_t key) {
994 * 1009 *
995 * FIXME: Needs documentation. 1010 * FIXME: Needs documentation.
996 */ 1011 */
1012bool is_tap_record(keyrecord_t *record) {
1013#ifdef COMBO_ENABLE
1014 action_t action;
1015 if (record->keycode) {
1016 action = action_for_keycode(record->keycode);
1017 } else {
1018 action = layer_switch_get_action(record->event.key);
1019 }
1020#else
1021 action_t action = layer_switch_get_action(record->event.key);
1022#endif
1023 return is_tap_action(action);
1024}
1025
1026/** \brief Utilities for actions. (FIXME: Needs better description)
1027 *
1028 * FIXME: Needs documentation.
1029 */
997bool is_tap_action(action_t action) { 1030bool is_tap_action(action_t action) {
998 switch (action.kind.id) { 1031 switch (action.kind.id) {
999 case ACT_LMODS_TAP: 1032 case ACT_LMODS_TAP:
diff --git a/tmk_core/common/action.h b/tmk_core/common/action.h
index 8cb4722c6..3d357b33b 100644
--- a/tmk_core/common/action.h
+++ b/tmk_core/common/action.h
@@ -53,6 +53,9 @@ typedef struct {
53#ifndef NO_ACTION_TAPPING 53#ifndef NO_ACTION_TAPPING
54 tap_t tap; 54 tap_t tap;
55#endif 55#endif
56#ifdef COMBO_ENABLE
57 uint16_t keycode;
58#endif
56} keyrecord_t; 59} keyrecord_t;
57 60
58/* Execute action per keyevent */ 61/* Execute action per keyevent */
@@ -60,6 +63,7 @@ void action_exec(keyevent_t event);
60 63
61/* action for key */ 64/* action for key */
62action_t action_for_key(uint8_t layer, keypos_t key); 65action_t action_for_key(uint8_t layer, keypos_t key);
66action_t action_for_keycode(uint16_t keycode);
63 67
64/* macro */ 68/* macro */
65const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt); 69const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt);
@@ -111,6 +115,7 @@ void clear_keyboard_but_mods(void);
111void clear_keyboard_but_mods_and_keys(void); 115void clear_keyboard_but_mods_and_keys(void);
112void layer_switch(uint8_t new_layer); 116void layer_switch(uint8_t new_layer);
113bool is_tap_key(keypos_t key); 117bool is_tap_key(keypos_t key);
118bool is_tap_record(keyrecord_t *record);
114bool is_tap_action(action_t action); 119bool is_tap_action(action_t action);
115 120
116#ifndef NO_ACTION_TAPPING 121#ifndef NO_ACTION_TAPPING
diff --git a/tmk_core/common/action_tapping.c b/tmk_core/common/action_tapping.c
index 56044e096..1701ae471 100644
--- a/tmk_core/common/action_tapping.c
+++ b/tmk_core/common/action_tapping.c
@@ -18,11 +18,16 @@
18# define IS_TAPPING_PRESSED() (IS_TAPPING() && tapping_key.event.pressed) 18# define IS_TAPPING_PRESSED() (IS_TAPPING() && tapping_key.event.pressed)
19# define IS_TAPPING_RELEASED() (IS_TAPPING() && !tapping_key.event.pressed) 19# define IS_TAPPING_RELEASED() (IS_TAPPING() && !tapping_key.event.pressed)
20# define IS_TAPPING_KEY(k) (IS_TAPPING() && KEYEQ(tapping_key.event.key, (k))) 20# define IS_TAPPING_KEY(k) (IS_TAPPING() && KEYEQ(tapping_key.event.key, (k)))
21#ifndef COMBO_ENABLE
22# define IS_TAPPING_RECORD(r) (IS_TAPPING() && KEYEQ(tapping_key.event.key, (r->event.key)))
23#else
24# define IS_TAPPING_RECORD(r) (IS_TAPPING() && KEYEQ(tapping_key.event.key, (r->event.key)) && tapping_key.keycode == r->keycode)
25#endif
21 26
22__attribute__((weak)) uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { return TAPPING_TERM; } 27__attribute__((weak)) uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { return TAPPING_TERM; }
23 28
24# ifdef TAPPING_TERM_PER_KEY 29# ifdef TAPPING_TERM_PER_KEY
25# define WITHIN_TAPPING_TERM(e) (TIMER_DIFF_16(e.time, tapping_key.event.time) < get_tapping_term(get_event_keycode(tapping_key.event, false), &tapping_key)) 30# define WITHIN_TAPPING_TERM(e) (TIMER_DIFF_16(e.time, tapping_key.event.time) < get_tapping_term(get_record_keycode(&tapping_key, false), &tapping_key))
26# else 31# else
27# define WITHIN_TAPPING_TERM(e) (TIMER_DIFF_16(e.time, tapping_key.event.time) < TAPPING_TERM) 32# define WITHIN_TAPPING_TERM(e) (TIMER_DIFF_16(e.time, tapping_key.event.time) < TAPPING_TERM)
28# endif 33# endif
@@ -103,7 +108,7 @@ bool process_tapping(keyrecord_t *keyp) {
103 if (IS_TAPPING_PRESSED()) { 108 if (IS_TAPPING_PRESSED()) {
104 if (WITHIN_TAPPING_TERM(event)) { 109 if (WITHIN_TAPPING_TERM(event)) {
105 if (tapping_key.tap.count == 0) { 110 if (tapping_key.tap.count == 0) {
106 if (IS_TAPPING_KEY(event.key) && !event.pressed) { 111 if (IS_TAPPING_RECORD(keyp) && !event.pressed) {
107 // first tap! 112 // first tap!
108 debug("Tapping: First tap(0->1).\n"); 113 debug("Tapping: First tap(0->1).\n");
109 tapping_key.tap.count = 1; 114 tapping_key.tap.count = 1;
@@ -122,14 +127,14 @@ bool process_tapping(keyrecord_t *keyp) {
122# if defined(TAPPING_TERM_PER_KEY) || (TAPPING_TERM >= 500) || defined(PERMISSIVE_HOLD) || defined(PERMISSIVE_HOLD_PER_KEY) 127# if defined(TAPPING_TERM_PER_KEY) || (TAPPING_TERM >= 500) || defined(PERMISSIVE_HOLD) || defined(PERMISSIVE_HOLD_PER_KEY)
123 else if ((( 128 else if (((
124# ifdef TAPPING_TERM_PER_KEY 129# ifdef TAPPING_TERM_PER_KEY
125 get_tapping_term(get_event_keycode(tapping_key.event, false), keyp) 130 get_tapping_term(get_record_keycode(&tapping_key, false), keyp)
126# else 131# else
127 TAPPING_TERM 132 TAPPING_TERM
128# endif 133# endif
129 >= 500) 134 >= 500)
130 135
131# ifdef PERMISSIVE_HOLD_PER_KEY 136# ifdef PERMISSIVE_HOLD_PER_KEY
132 || get_permissive_hold(get_event_keycode(tapping_key.event, false), keyp) 137 || get_permissive_hold(get_record_keycode(&tapping_key, false), keyp)
133# elif defined(PERMISSIVE_HOLD) 138# elif defined(PERMISSIVE_HOLD)
134 || true 139 || true
135# endif 140# endif
@@ -177,7 +182,7 @@ bool process_tapping(keyrecord_t *keyp) {
177 } 182 }
178 // tap_count > 0 183 // tap_count > 0
179 else { 184 else {
180 if (IS_TAPPING_KEY(event.key) && !event.pressed) { 185 if (IS_TAPPING_RECORD(keyp) && !event.pressed) {
181 debug("Tapping: Tap release("); 186 debug("Tapping: Tap release(");
182 debug_dec(tapping_key.tap.count); 187 debug_dec(tapping_key.tap.count);
183 debug(")\n"); 188 debug(")\n");
@@ -186,11 +191,15 @@ bool process_tapping(keyrecord_t *keyp) {
186 tapping_key = *keyp; 191 tapping_key = *keyp;
187 debug_tapping_key(); 192 debug_tapping_key();
188 return true; 193 return true;
189 } else if (is_tap_key(event.key) && event.pressed) { 194 } else if (is_tap_record(keyp) && event.pressed) {
190 if (tapping_key.tap.count > 1) { 195 if (tapping_key.tap.count > 1) {
191 debug("Tapping: Start new tap with releasing last tap(>1).\n"); 196 debug("Tapping: Start new tap with releasing last tap(>1).\n");
192 // unregister key 197 // unregister key
193 process_record(&(keyrecord_t){.tap = tapping_key.tap, .event.key = tapping_key.event.key, .event.time = event.time, .event.pressed = false}); 198 process_record(&(keyrecord_t){.tap = tapping_key.tap, .event.key = tapping_key.event.key, .event.time = event.time, .event.pressed = false,
199#ifdef COMBO_ENABLE
200 .keycode = tapping_key.keycode,
201#endif
202 });
194 } else { 203 } else {
195 debug("Tapping: Start while last tap(1).\n"); 204 debug("Tapping: Start while last tap(1).\n");
196 } 205 }
@@ -218,17 +227,21 @@ bool process_tapping(keyrecord_t *keyp) {
218 debug_tapping_key(); 227 debug_tapping_key();
219 return false; 228 return false;
220 } else { 229 } else {
221 if (IS_TAPPING_KEY(event.key) && !event.pressed) { 230 if (IS_TAPPING_RECORD(keyp) && !event.pressed) {
222 debug("Tapping: End. last timeout tap release(>0)."); 231 debug("Tapping: End. last timeout tap release(>0).");
223 keyp->tap = tapping_key.tap; 232 keyp->tap = tapping_key.tap;
224 process_record(keyp); 233 process_record(keyp);
225 tapping_key = (keyrecord_t){}; 234 tapping_key = (keyrecord_t){};
226 return true; 235 return true;
227 } else if (is_tap_key(event.key) && event.pressed) { 236 } else if (is_tap_record(keyp) && event.pressed) {
228 if (tapping_key.tap.count > 1) { 237 if (tapping_key.tap.count > 1) {
229 debug("Tapping: Start new tap with releasing last timeout tap(>1).\n"); 238 debug("Tapping: Start new tap with releasing last timeout tap(>1).\n");
230 // unregister key 239 // unregister key
231 process_record(&(keyrecord_t){.tap = tapping_key.tap, .event.key = tapping_key.event.key, .event.time = event.time, .event.pressed = false}); 240 process_record(&(keyrecord_t){.tap = tapping_key.tap, .event.key = tapping_key.event.key, .event.time = event.time, .event.pressed = false,
241#ifdef COMBO_ENABLE
242 .keycode = tapping_key.keycode,
243#endif
244 });
232 } else { 245 } else {
233 debug("Tapping: Start while last timeout tap(1).\n"); 246 debug("Tapping: Start while last timeout tap(1).\n");
234 } 247 }
@@ -248,12 +261,12 @@ bool process_tapping(keyrecord_t *keyp) {
248 } else if (IS_TAPPING_RELEASED()) { 261 } else if (IS_TAPPING_RELEASED()) {
249 if (WITHIN_TAPPING_TERM(event)) { 262 if (WITHIN_TAPPING_TERM(event)) {
250 if (event.pressed) { 263 if (event.pressed) {
251 if (IS_TAPPING_KEY(event.key)) { 264 if (IS_TAPPING_RECORD(keyp)) {
252//# ifndef TAPPING_FORCE_HOLD 265//# ifndef TAPPING_FORCE_HOLD
253# if !defined(TAPPING_FORCE_HOLD) || defined(TAPPING_FORCE_HOLD_PER_KEY) 266# if !defined(TAPPING_FORCE_HOLD) || defined(TAPPING_FORCE_HOLD_PER_KEY)
254 if ( 267 if (
255# ifdef TAPPING_FORCE_HOLD_PER_KEY 268# ifdef TAPPING_FORCE_HOLD_PER_KEY
256 !get_tapping_force_hold(get_event_keycode(tapping_key.event, false), keyp) && 269 !get_tapping_force_hold(get_record_keycode(&tapping_key, false), keyp) &&
257# endif 270# endif
258 !tapping_key.tap.interrupted && tapping_key.tap.count > 0) { 271 !tapping_key.tap.interrupted && tapping_key.tap.count > 0) {
259 // sequential tap. 272 // sequential tap.
@@ -271,7 +284,7 @@ bool process_tapping(keyrecord_t *keyp) {
271 // FIX: start new tap again 284 // FIX: start new tap again
272 tapping_key = *keyp; 285 tapping_key = *keyp;
273 return true; 286 return true;
274 } else if (is_tap_key(event.key)) { 287 } else if (is_tap_record(keyp)) {
275 // Sequential tap can be interfered with other tap key. 288 // Sequential tap can be interfered with other tap key.
276 debug("Tapping: Start with interfering other tap.\n"); 289 debug("Tapping: Start with interfering other tap.\n");
277 tapping_key = *keyp; 290 tapping_key = *keyp;
@@ -303,7 +316,7 @@ bool process_tapping(keyrecord_t *keyp) {
303 } 316 }
304 // not tapping state 317 // not tapping state
305 else { 318 else {
306 if (event.pressed && is_tap_key(event.key)) { 319 if (event.pressed && is_tap_record(keyp)) {
307 debug("Tapping: Start(Press tap key).\n"); 320 debug("Tapping: Start(Press tap key).\n");
308 tapping_key = *keyp; 321 tapping_key = *keyp;
309 process_record_tap_hint(&tapping_key); 322 process_record_tap_hint(&tapping_key);
diff --git a/tmk_core/common/action_tapping.h b/tmk_core/common/action_tapping.h
index 893ccb1ce..7de8049c7 100644
--- a/tmk_core/common/action_tapping.h
+++ b/tmk_core/common/action_tapping.h
@@ -30,6 +30,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
30#define WAITING_BUFFER_SIZE 8 30#define WAITING_BUFFER_SIZE 8
31 31
32#ifndef NO_ACTION_TAPPING 32#ifndef NO_ACTION_TAPPING
33uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache);
33uint16_t get_event_keycode(keyevent_t event, bool update_layer_cache); 34uint16_t get_event_keycode(keyevent_t event, bool update_layer_cache);
34void action_tapping_process(keyrecord_t record); 35void action_tapping_process(keyrecord_t record);
35 36