2017-03-28 22:20:36 +00:00
|
|
|
/* Copyright 2016 Jack Humbert
|
|
|
|
*
|
|
|
|
* 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/>.
|
|
|
|
*/
|
|
|
|
|
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.
2019-04-08 21:07:15 +00:00
|
|
|
#include "process_combo.h"
|
2023-07-11 07:07:24 +00:00
|
|
|
#include <stddef.h>
|
|
|
|
#include "process_auto_shift.h"
|
|
|
|
#include "caps_word.h"
|
|
|
|
#include "timer.h"
|
2023-07-12 08:38:59 +00:00
|
|
|
#include "wait.h"
|
2023-07-11 07:07:24 +00:00
|
|
|
#include "keyboard.h"
|
|
|
|
#include "keymap_common.h"
|
|
|
|
#include "action_layer.h"
|
2021-08-05 23:44:57 +00:00
|
|
|
#include "action_tapping.h"
|
2023-07-11 07:07:24 +00:00
|
|
|
#include "action_util.h"
|
2023-05-15 12:27:37 +00:00
|
|
|
#include "keymap_introspection.h"
|
2016-12-10 14:11:59 +00:00
|
|
|
|
2020-07-16 12:39:01 +00:00
|
|
|
__attribute__((weak)) void process_combo_event(uint16_t combo_index, bool pressed) {}
|
2016-12-16 19:50:28 +00:00
|
|
|
|
2023-02-12 16:31:04 +00:00
|
|
|
#ifndef COMBO_ONLY_FROM_LAYER
|
|
|
|
__attribute__((weak)) uint8_t combo_ref_from_layer(uint8_t layer) {
|
|
|
|
return layer;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
#ifdef COMBO_MUST_HOLD_PER_COMBO
|
2024-10-11 23:13:31 +00:00
|
|
|
__attribute__((weak)) bool get_combo_must_hold(uint16_t combo_index, combo_t *combo) {
|
2022-02-12 18:29:31 +00:00
|
|
|
return false;
|
|
|
|
}
|
2021-08-05 23:44:57 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef COMBO_MUST_TAP_PER_COMBO
|
2024-10-11 23:13:31 +00:00
|
|
|
__attribute__((weak)) bool get_combo_must_tap(uint16_t combo_index, combo_t *combo) {
|
2022-02-12 18:29:31 +00:00
|
|
|
return false;
|
|
|
|
}
|
2021-08-05 23:44:57 +00:00
|
|
|
#endif
|
2016-12-16 19:50:28 +00:00
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
#ifdef COMBO_TERM_PER_COMBO
|
2024-10-11 23:13:31 +00:00
|
|
|
__attribute__((weak)) uint16_t get_combo_term(uint16_t combo_index, combo_t *combo) {
|
2022-02-12 18:29:31 +00:00
|
|
|
return COMBO_TERM;
|
|
|
|
}
|
2021-08-05 23:44:57 +00:00
|
|
|
#endif
|
|
|
|
|
2022-01-09 20:02:25 +00:00
|
|
|
#ifdef COMBO_MUST_PRESS_IN_ORDER_PER_COMBO
|
2022-02-12 18:29:31 +00:00
|
|
|
__attribute__((weak)) bool get_combo_must_press_in_order(uint16_t combo_index, combo_t *combo) {
|
|
|
|
return true;
|
|
|
|
}
|
2022-01-09 20:02:25 +00:00
|
|
|
#endif
|
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
#ifdef COMBO_PROCESS_KEY_RELEASE
|
2022-02-12 18:29:31 +00:00
|
|
|
__attribute__((weak)) bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) {
|
|
|
|
return false;
|
|
|
|
}
|
2021-08-05 23:44:57 +00:00
|
|
|
#endif
|
|
|
|
|
2024-09-06 06:27:20 +00:00
|
|
|
#ifdef COMBO_PROCESS_KEY_REPRESS
|
|
|
|
__attribute__((weak)) bool process_combo_key_repress(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2022-01-09 20:02:25 +00:00
|
|
|
#ifdef COMBO_SHOULD_TRIGGER
|
2022-02-12 18:29:31 +00:00
|
|
|
__attribute__((weak)) bool combo_should_trigger(uint16_t combo_index, combo_t *combo, uint16_t keycode, keyrecord_t *record) {
|
|
|
|
return true;
|
|
|
|
}
|
2022-01-09 20:02:25 +00:00
|
|
|
#endif
|
|
|
|
|
2024-09-06 06:27:20 +00:00
|
|
|
typedef enum { COMBO_KEY_NOT_PRESSED, COMBO_KEY_PRESSED, COMBO_KEY_REPRESSED } combo_key_action_t;
|
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
#ifndef COMBO_NO_TIMER
|
2021-11-01 19:18:33 +00:00
|
|
|
static uint16_t timer = 0;
|
2021-08-05 23:44:57 +00:00
|
|
|
#endif
|
2022-02-12 18:29:31 +00:00
|
|
|
static bool b_combo_enable = true; // defaults to enabled
|
2021-11-01 19:18:33 +00:00
|
|
|
static uint16_t longest_term = 0;
|
2021-08-05 23:44:57 +00:00
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
keyrecord_t record;
|
2021-11-01 19:18:33 +00:00
|
|
|
uint16_t combo_index;
|
|
|
|
uint16_t keycode;
|
2021-08-05 23:44:57 +00:00
|
|
|
} queued_record_t;
|
2021-11-01 19:18:33 +00:00
|
|
|
static uint8_t key_buffer_size = 0;
|
2021-08-05 23:44:57 +00:00
|
|
|
static queued_record_t key_buffer[COMBO_KEY_BUFFER_LENGTH];
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
uint16_t combo_index;
|
|
|
|
} queued_combo_t;
|
2021-11-01 19:18:33 +00:00
|
|
|
static uint8_t combo_buffer_write = 0;
|
|
|
|
static uint8_t combo_buffer_read = 0;
|
2021-08-05 23:44:57 +00:00
|
|
|
static queued_combo_t combo_buffer[COMBO_BUFFER_LENGTH];
|
|
|
|
|
|
|
|
#define INCREMENT_MOD(i) i = (i + 1) % COMBO_BUFFER_LENGTH
|
|
|
|
|
|
|
|
#ifndef EXTRA_SHORT_COMBOS
|
|
|
|
/* flags are their own elements in combo_t struct. */
|
2021-11-01 19:18:33 +00:00
|
|
|
# define COMBO_ACTIVE(combo) (combo->active)
|
2021-08-05 23:44:57 +00:00
|
|
|
# define COMBO_DISABLED(combo) (combo->disabled)
|
2021-11-01 19:18:33 +00:00
|
|
|
# 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)
|
2016-12-16 19:50:28 +00:00
|
|
|
#else
|
2021-08-05 23:44:57 +00:00
|
|
|
/* flags are at the two high bits of state. */
|
2021-11-01 19:18:33 +00:00
|
|
|
# define COMBO_ACTIVE(combo) (combo->state & 0x80)
|
2021-08-05 23:44:57 +00:00
|
|
|
# define COMBO_DISABLED(combo) (combo->state & 0x40)
|
2021-11-01 19:18:33 +00:00
|
|
|
# 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)
|
2016-12-10 14:11:59 +00:00
|
|
|
#endif
|
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.
2019-04-08 21:07:15 +00:00
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
static inline void release_combo(uint16_t combo_index, combo_t *combo) {
|
|
|
|
if (combo->keycode) {
|
|
|
|
keyrecord_t record = {
|
2023-04-03 08:33:45 +00:00
|
|
|
.event = MAKE_COMBOEVENT(false),
|
2021-08-05 23:44:57 +00:00
|
|
|
.keycode = combo->keycode,
|
|
|
|
};
|
|
|
|
#ifndef NO_ACTION_TAPPING
|
|
|
|
action_tapping_process(record);
|
|
|
|
#else
|
|
|
|
process_record(&record);
|
|
|
|
#endif
|
2016-12-10 14:11:59 +00:00
|
|
|
} else {
|
2021-08-05 23:44:57 +00:00
|
|
|
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)
|
2021-11-01 19:18:33 +00:00
|
|
|
return (KEYCODE_IS_MOD(combo->keycode) || (combo->keycode >= QK_MOMENTARY && combo->keycode <= QK_MOMENTARY_MAX));
|
2021-08-05 23:44:57 +00:00
|
|
|
#endif
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-11-01 19:18:33 +00:00
|
|
|
static inline uint16_t _get_wait_time(uint16_t combo_index, combo_t *combo) {
|
2021-08-05 23:44:57 +00:00
|
|
|
if (_get_combo_must_hold(combo_index, combo)
|
|
|
|
#ifdef COMBO_MUST_TAP_PER_COMBO
|
2021-11-01 19:18:33 +00:00
|
|
|
|| get_combo_must_tap(combo_index, combo)
|
2021-08-05 23:44:57 +00:00
|
|
|
#endif
|
2021-11-01 19:18:33 +00:00
|
|
|
) {
|
2021-08-05 23:44:57 +00:00
|
|
|
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)
|
2021-11-01 19:18:33 +00:00
|
|
|
return get_combo_term(combo_index, combo);
|
2021-08-05 23:44:57 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return COMBO_TERM;
|
|
|
|
}
|
|
|
|
|
|
|
|
void clear_combos(void) {
|
|
|
|
uint16_t index = 0;
|
2021-11-01 19:18:33 +00:00
|
|
|
longest_term = 0;
|
2023-05-15 12:27:37 +00:00
|
|
|
for (index = 0; index < combo_count(); ++index) {
|
|
|
|
combo_t *combo = combo_get(index);
|
2021-08-05 23:44:57 +00:00
|
|
|
if (!COMBO_ACTIVE(combo)) {
|
|
|
|
RESET_COMBO_STATE(combo);
|
|
|
|
}
|
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.
2019-04-08 21:07:15 +00:00
|
|
|
}
|
|
|
|
}
|
2016-12-09 22:49:11 +00:00
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
static inline void dump_key_buffer(void) {
|
2021-08-14 04:45:52 +00:00
|
|
|
/* First call start from 0 index; recursive calls need to start from i+1 index */
|
|
|
|
static uint8_t key_buffer_next = 0;
|
2022-01-29 07:19:36 +00:00
|
|
|
#if TAP_CODE_DELAY > 0
|
|
|
|
bool delay_done = false;
|
|
|
|
#endif
|
2021-08-14 04:45:52 +00:00
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
if (key_buffer_size == 0) {
|
2019-08-30 18:19:03 +00:00
|
|
|
return;
|
|
|
|
}
|
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.
2019-04-08 21:07:15 +00:00
|
|
|
|
2021-08-14 04:45:52 +00:00
|
|
|
for (uint8_t key_buffer_i = key_buffer_next; key_buffer_i < key_buffer_size; key_buffer_i++) {
|
|
|
|
key_buffer_next = key_buffer_i + 1;
|
2021-08-05 23:44:57 +00:00
|
|
|
|
|
|
|
queued_record_t *qrecord = &key_buffer[key_buffer_i];
|
2021-11-01 19:18:33 +00:00
|
|
|
keyrecord_t * record = &qrecord->record;
|
2021-08-05 23:44:57 +00:00
|
|
|
|
|
|
|
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
|
2021-11-01 19:18:33 +00:00
|
|
|
action_tapping_process(*record);
|
2016-12-16 19:50:28 +00:00
|
|
|
#else
|
2021-11-01 19:18:33 +00:00
|
|
|
process_record(record);
|
2016-12-10 14:11:59 +00:00
|
|
|
#endif
|
2019-08-30 18:19:03 +00:00
|
|
|
}
|
2023-04-03 08:33:45 +00:00
|
|
|
record->event.type = TICK_EVENT;
|
2022-08-13 13:48:51 +00:00
|
|
|
|
|
|
|
#if defined(CAPS_WORD_ENABLE) && defined(AUTO_SHIFT_ENABLE)
|
|
|
|
// Edge case: preserve the weak Left Shift mod if both Caps Word and
|
|
|
|
// Auto Shift are on. Caps Word capitalizes by setting the weak Left
|
|
|
|
// Shift mod during the press event, but Auto Shift doesn't send the
|
|
|
|
// key until it receives the release event.
|
|
|
|
del_weak_mods((is_caps_word_on() && get_autoshift_state()) ? ~MOD_BIT(KC_LSFT) : 0xff);
|
|
|
|
#else
|
2022-01-29 07:19:36 +00:00
|
|
|
clear_weak_mods();
|
2022-08-13 13:48:51 +00:00
|
|
|
#endif // defined(CAPS_WORD_ENABLE) && defined(AUTO_SHIFT_ENABLE)
|
2022-01-29 07:19:36 +00:00
|
|
|
|
|
|
|
#if TAP_CODE_DELAY > 0
|
|
|
|
// only delay once and for a non-tapping key
|
|
|
|
if (!delay_done && !is_tap_record(record)) {
|
|
|
|
delay_done = true;
|
|
|
|
wait_ms(TAP_CODE_DELAY);
|
|
|
|
}
|
|
|
|
#endif
|
2016-12-09 22:49:11 +00:00
|
|
|
}
|
|
|
|
|
2021-08-14 04:45:52 +00:00
|
|
|
key_buffer_next = key_buffer_size = 0;
|
2016-12-09 22:49:11 +00:00
|
|
|
}
|
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
#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); \
|
2019-08-30 18:19:03 +00:00
|
|
|
} while (0)
|
2021-08-05 23:44:57 +00:00
|
|
|
#define KEY_STATE_UP(state, key_index) \
|
|
|
|
do { \
|
|
|
|
state &= ~(1 << key_index); \
|
2019-08-30 18:19:03 +00:00
|
|
|
} while (0)
|
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
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;
|
2019-08-30 18:19:03 +00:00
|
|
|
if (COMBO_END == key) break;
|
2021-08-05 23:44:57 +00:00
|
|
|
(*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) {
|
2023-05-15 12:27:37 +00:00
|
|
|
combo_t *combo = combo_get(combo_index);
|
2021-08-05 23:44:57 +00:00
|
|
|
DISABLE_COMBO(combo);
|
|
|
|
|
|
|
|
if (i == combo_buffer_read) {
|
|
|
|
INCREMENT_MOD(combo_buffer_read);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
INCREMENT_MOD(i);
|
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.
2019-04-08 21:07:15 +00:00
|
|
|
}
|
2021-08-05 23:44:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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. */
|
2019-08-30 18:19:03 +00:00
|
|
|
|
2021-11-01 19:18:33 +00:00
|
|
|
if (COMBO_DISABLED(combo)) {
|
|
|
|
return;
|
|
|
|
}
|
2019-08-30 18:19:03 +00:00
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
// 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)
|
2023-02-12 16:31:04 +00:00
|
|
|
uint16_t state = 0;
|
2021-08-05 23:44:57 +00:00
|
|
|
#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];
|
2021-11-01 19:18:33 +00:00
|
|
|
keyrecord_t * record = &qrecord->record;
|
|
|
|
uint16_t keycode = qrecord->keycode;
|
2021-08-05 23:44:57 +00:00
|
|
|
|
2021-11-01 19:18:33 +00:00
|
|
|
uint8_t key_count = 0;
|
2021-08-05 23:44:57 +00:00
|
|
|
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;
|
|
|
|
}
|
2019-08-30 18:19:03 +00:00
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
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.
|
2023-04-03 08:33:45 +00:00
|
|
|
record->keycode = combo->keycode;
|
|
|
|
record->event.type = COMBO_EVENT;
|
2023-05-10 15:59:52 +00:00
|
|
|
record->event.key = MAKE_KEYPOS(0, 0);
|
2019-08-30 18:19:03 +00:00
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
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.
|
2023-04-03 08:33:45 +00:00
|
|
|
record->event.type = TICK_EVENT;
|
2021-08-05 23:44:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
drop_combo_from_buffer(combo_index);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void apply_combos(void) {
|
|
|
|
// Apply all buffered normal combos.
|
2021-11-01 19:18:33 +00:00
|
|
|
for (uint8_t i = combo_buffer_read; i != combo_buffer_write; INCREMENT_MOD(i)) {
|
2021-08-05 23:44:57 +00:00
|
|
|
queued_combo_t *buffered_combo = &combo_buffer[i];
|
2023-05-15 12:27:37 +00:00
|
|
|
combo_t * combo = combo_get(buffered_combo->combo_index);
|
2021-08-05 23:44:57 +00:00
|
|
|
|
|
|
|
#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();
|
|
|
|
}
|
|
|
|
|
2021-11-01 19:18:33 +00:00
|
|
|
combo_t *overlaps(combo_t *combo1, combo_t *combo2) {
|
2021-08-05 23:44:57 +00:00
|
|
|
/* 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. */
|
|
|
|
|
2021-11-01 19:18:33 +00:00
|
|
|
uint8_t idx1 = 0, idx2 = 0;
|
2021-08-05 23:44:57 +00:00
|
|
|
uint16_t key1, key2;
|
2021-11-01 19:18:33 +00:00
|
|
|
bool overlaps = false;
|
2021-08-05 23:44:57 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-01-09 20:02:25 +00:00
|
|
|
#if defined(COMBO_MUST_PRESS_IN_ORDER) || defined(COMBO_MUST_PRESS_IN_ORDER_PER_COMBO)
|
|
|
|
static bool keys_pressed_in_order(uint16_t combo_index, combo_t *combo, uint16_t key_index, uint16_t keycode, keyrecord_t *record) {
|
|
|
|
# ifdef COMBO_MUST_PRESS_IN_ORDER_PER_COMBO
|
|
|
|
if (!get_combo_must_press_in_order(combo_index, combo)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
# endif
|
|
|
|
if (
|
|
|
|
// The `state` bit for the key being pressed.
|
|
|
|
(1 << key_index) ==
|
|
|
|
// The *next* combo key's bit.
|
|
|
|
(COMBO_STATE(combo) + 1)
|
|
|
|
// E.g. two keys already pressed: `state == 11`.
|
|
|
|
// Next possible `state` is `111`.
|
|
|
|
// So the needed bit is `100` which we get with `11 + 1`.
|
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2024-09-06 06:27:20 +00:00
|
|
|
static combo_key_action_t process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record, uint16_t combo_index) {
|
2021-11-01 19:18:33 +00:00
|
|
|
uint8_t key_count = 0;
|
2021-08-05 23:44:57 +00:00
|
|
|
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) {
|
2024-09-06 06:27:20 +00:00
|
|
|
return COMBO_KEY_NOT_PRESSED;
|
2021-08-05 23:44:57 +00:00
|
|
|
}
|
|
|
|
|
2022-01-09 20:02:25 +00:00
|
|
|
bool key_is_part_of_combo = (!COMBO_DISABLED(combo) && is_combo_enabled()
|
|
|
|
#if defined(COMBO_MUST_PRESS_IN_ORDER) || defined(COMBO_MUST_PRESS_IN_ORDER_PER_COMBO)
|
|
|
|
&& keys_pressed_in_order(combo_index, combo, key_index, keycode, record)
|
|
|
|
#endif
|
|
|
|
#ifdef COMBO_SHOULD_TRIGGER
|
|
|
|
&& combo_should_trigger(combo_index, combo, keycode, record)
|
|
|
|
#endif
|
|
|
|
);
|
2021-08-05 23:44:57 +00:00
|
|
|
|
2021-08-14 04:45:52 +00:00
|
|
|
if (record->event.pressed && key_is_part_of_combo) {
|
2021-08-05 23:44:57 +00:00
|
|
|
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;
|
2019-08-30 18:19:03 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-05 23:44:57 +00:00
|
|
|
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);
|
2024-09-06 06:27:20 +00:00
|
|
|
return COMBO_KEY_PRESSED;
|
2021-08-05 23:44:57 +00:00
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
|
|
|
|
// disable readied combos that overlap with this combo
|
|
|
|
combo_t *drop = NULL;
|
2021-11-01 19:18:33 +00:00
|
|
|
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];
|
2023-05-15 12:27:37 +00:00
|
|
|
combo_t * buffered_combo = combo_get(qcombo->combo_index);
|
2021-08-05 23:44:57 +00:00
|
|
|
|
|
|
|
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){
|
2021-11-01 19:18:33 +00:00
|
|
|
.combo_index = combo_index,
|
2021-08-05 23:44:57 +00:00
|
|
|
};
|
|
|
|
INCREMENT_MOD(combo_buffer_write);
|
|
|
|
|
|
|
|
// get possible longer waiting time for tap-/hold-only combos.
|
|
|
|
longest_term = _get_wait_time(combo_index, combo);
|
|
|
|
}
|
2022-02-12 18:29:31 +00:00
|
|
|
} // if timer elapsed end
|
2021-08-05 23:44:57 +00:00
|
|
|
}
|
2024-09-06 06:27:20 +00:00
|
|
|
#ifdef COMBO_PROCESS_KEY_REPRESS
|
|
|
|
} else if (record->event.pressed) {
|
|
|
|
if (COMBO_ACTIVE(combo)) {
|
|
|
|
if (process_combo_key_repress(combo_index, combo, key_index, keycode)) {
|
|
|
|
KEY_STATE_DOWN(combo->state, key_index);
|
|
|
|
return COMBO_KEY_REPRESSED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
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.
2019-04-08 21:07:15 +00:00
|
|
|
} else {
|
2021-08-05 23:44:57 +00:00
|
|
|
// 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);
|
2022-02-12 18:29:31 +00:00
|
|
|
apply_combos(); // also apply other prepared combos and dump key buffer
|
2021-08-05 23:44:57 +00:00
|
|
|
# ifdef COMBO_PROCESS_KEY_RELEASE
|
|
|
|
if (process_combo_key_release(combo_index, combo, key_index, keycode)) {
|
|
|
|
release_combo(combo_index, combo);
|
|
|
|
}
|
|
|
|
# endif
|
|
|
|
}
|
|
|
|
#endif
|
2021-11-01 19:18:33 +00:00
|
|
|
} else if (COMBO_ACTIVE(combo) && ONLY_ONE_KEY_IS_DOWN(COMBO_STATE(combo)) && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index)) {
|
2021-08-05 23:44:57 +00:00
|
|
|
/* 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
|
2021-11-01 19:18:33 +00:00
|
|
|
} else if (COMBO_ACTIVE(combo) && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index)) {
|
2021-08-05 23:44:57 +00:00
|
|
|
/* 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
|
2019-08-30 18:19:03 +00:00
|
|
|
} else {
|
2021-08-05 23:44:57 +00:00
|
|
|
/* The released key was part of an incomplete combo */
|
|
|
|
key_is_part_of_combo = false;
|
2019-08-30 18:19:03 +00:00
|
|
|
}
|
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
KEY_STATE_UP(combo->state, key_index);
|
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.
2019-04-08 21:07:15 +00:00
|
|
|
}
|
2016-12-09 22:49:11 +00:00
|
|
|
|
2024-09-06 06:27:20 +00:00
|
|
|
return key_is_part_of_combo ? COMBO_KEY_PRESSED : COMBO_KEY_NOT_PRESSED;
|
2016-12-10 14:11:59 +00:00
|
|
|
}
|
|
|
|
|
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.
2019-04-08 21:07:15 +00:00
|
|
|
bool process_combo(uint16_t keycode, keyrecord_t *record) {
|
2024-09-06 06:27:20 +00:00
|
|
|
uint8_t is_combo_key = COMBO_KEY_NOT_PRESSED;
|
|
|
|
bool no_combo_keys_pressed = true;
|
2019-08-30 18:19:03 +00:00
|
|
|
|
2022-10-27 12:59:58 +00:00
|
|
|
if (keycode == QK_COMBO_ON && record->event.pressed) {
|
2019-08-30 18:19:03 +00:00
|
|
|
combo_enable();
|
|
|
|
return true;
|
|
|
|
}
|
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.
2019-04-08 21:07:15 +00:00
|
|
|
|
2022-10-27 12:59:58 +00:00
|
|
|
if (keycode == QK_COMBO_OFF && record->event.pressed) {
|
2019-08-30 18:19:03 +00:00
|
|
|
combo_disable();
|
|
|
|
return true;
|
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.
2019-04-08 21:07:15 +00:00
|
|
|
}
|
2016-12-16 19:50:28 +00:00
|
|
|
|
2022-10-27 12:59:58 +00:00
|
|
|
if (keycode == QK_COMBO_TOGGLE && record->event.pressed) {
|
2019-08-30 18:19:03 +00:00
|
|
|
combo_toggle();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
#ifdef COMBO_ONLY_FROM_LAYER
|
|
|
|
/* Only check keycodes from one layer. */
|
|
|
|
keycode = keymap_key_to_keycode(COMBO_ONLY_FROM_LAYER, record->event.key);
|
2023-02-12 16:31:04 +00:00
|
|
|
#else
|
2023-09-25 02:32:20 +00:00
|
|
|
uint8_t highest_layer = get_highest_layer(layer_state | default_layer_state);
|
2023-02-12 16:31:04 +00:00
|
|
|
uint8_t ref_layer = combo_ref_from_layer(highest_layer);
|
|
|
|
if (ref_layer != highest_layer) {
|
|
|
|
keycode = keymap_key_to_keycode(ref_layer, record->event.key);
|
|
|
|
}
|
2020-03-22 13:17:26 +00:00
|
|
|
#endif
|
2021-08-05 23:44:57 +00:00
|
|
|
|
2023-05-15 12:27:37 +00:00
|
|
|
for (uint16_t idx = 0; idx < combo_count(); ++idx) {
|
|
|
|
combo_t *combo = combo_get(idx);
|
2021-08-05 23:44:57 +00:00
|
|
|
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));
|
2019-08-30 18:19:03 +00:00
|
|
|
}
|
|
|
|
|
2021-08-05 23:44:57 +00:00
|
|
|
if (record->event.pressed && is_combo_key) {
|
|
|
|
#ifndef COMBO_NO_TIMER
|
2021-11-01 19:18:33 +00:00
|
|
|
# ifdef COMBO_STRICT_TIMER
|
2021-08-05 23:44:57 +00:00
|
|
|
if (!timer) {
|
|
|
|
// timer is set only on the first key
|
|
|
|
timer = timer_read();
|
|
|
|
}
|
2021-11-01 19:18:33 +00:00
|
|
|
# else
|
2019-08-30 18:19:03 +00:00
|
|
|
timer = timer_read();
|
2021-11-01 19:18:33 +00:00
|
|
|
# endif
|
2021-08-05 23:44:57 +00:00
|
|
|
#endif
|
2019-08-30 18:19:03 +00:00
|
|
|
|
2024-09-06 06:27:20 +00:00
|
|
|
#ifdef COMBO_PROCESS_KEY_REPRESS
|
|
|
|
if (is_combo_key == COMBO_KEY_PRESSED)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
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
|
|
|
|
};
|
|
|
|
}
|
2021-08-05 23:44:57 +00:00
|
|
|
}
|
|
|
|
} 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;
|
2016-12-16 19:50:28 +00:00
|
|
|
#endif
|
2021-08-05 23:44:57 +00:00
|
|
|
clear_combos();
|
2019-08-30 18:19:03 +00:00
|
|
|
}
|
2016-12-10 14:11:59 +00:00
|
|
|
}
|
2019-08-30 18:19:03 +00:00
|
|
|
return !is_combo_key;
|
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.
2019-04-08 21:07:15 +00:00
|
|
|
}
|
|
|
|
|
2021-07-28 11:01:23 +00:00
|
|
|
void combo_task(void) {
|
2021-08-05 23:44:57 +00:00
|
|
|
if (!b_combo_enable) {
|
|
|
|
return;
|
2019-08-30 18:19:03 +00:00
|
|
|
}
|
2021-08-05 23:44:57 +00:00
|
|
|
|
|
|
|
#ifndef COMBO_NO_TIMER
|
|
|
|
if (timer && timer_elapsed(timer) > longest_term) {
|
|
|
|
if (combo_buffer_read != combo_buffer_write) {
|
|
|
|
apply_combos();
|
|
|
|
longest_term = 0;
|
2021-11-01 19:18:33 +00:00
|
|
|
timer = 0;
|
2021-08-05 23:44:57 +00:00
|
|
|
} else {
|
|
|
|
dump_key_buffer();
|
|
|
|
timer = 0;
|
|
|
|
clear_combos();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2016-12-16 19:50:28 +00:00
|
|
|
}
|
2019-07-16 08:37:19 +00:00
|
|
|
|
2022-02-12 18:29:31 +00:00
|
|
|
void combo_enable(void) {
|
|
|
|
b_combo_enable = true;
|
|
|
|
}
|
2019-07-16 08:37:19 +00:00
|
|
|
|
|
|
|
void combo_disable(void) {
|
2021-08-05 23:44:57 +00:00
|
|
|
#ifndef COMBO_NO_TIMER
|
2021-11-01 19:18:33 +00:00
|
|
|
timer = 0;
|
2021-08-05 23:44:57 +00:00
|
|
|
#endif
|
2021-11-01 19:18:33 +00:00
|
|
|
b_combo_enable = false;
|
2021-08-05 23:44:57 +00:00
|
|
|
combo_buffer_read = combo_buffer_write;
|
2021-08-14 04:45:52 +00:00
|
|
|
clear_combos();
|
|
|
|
dump_key_buffer();
|
2019-07-16 08:37:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void combo_toggle(void) {
|
|
|
|
if (b_combo_enable) {
|
|
|
|
combo_disable();
|
|
|
|
} else {
|
|
|
|
combo_enable();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-12 18:29:31 +00:00
|
|
|
bool is_combo_enabled(void) {
|
|
|
|
return b_combo_enable;
|
|
|
|
}
|