diff options
Diffstat (limited to 'quantum')
| -rw-r--r-- | quantum/action.c | 5 | ||||
| -rw-r--r-- | quantum/action.h | 2 | ||||
| -rw-r--r-- | quantum/action_tapping.c | 171 | ||||
| -rw-r--r-- | quantum/action_tapping.h | 30 | ||||
| -rw-r--r-- | quantum/action_util.c | 5 |
5 files changed, 212 insertions, 1 deletions
diff --git a/quantum/action.c b/quantum/action.c index dd82c9ec99..aacafbe2ff 100644 --- a/quantum/action.c +++ b/quantum/action.c @@ -281,6 +281,11 @@ void process_record(keyrecord_t *record) { if (IS_NOEVENT(record->event)) { return; } +#ifdef SPECULATIVE_HOLD + if (record->event.pressed) { + speculative_key_settled(record); + } +#endif // SPECULATIVE_HOLD #ifdef FLOW_TAP_TERM flow_tap_update_last_event(record); #endif // FLOW_TAP_TERM diff --git a/quantum/action.h b/quantum/action.h index 7616486c6d..a459c438c1 100644 --- a/quantum/action.h +++ b/quantum/action.h @@ -38,7 +38,7 @@ extern "C" { /* tapping count and state */ typedef struct { bool interrupted : 1; - bool reserved2 : 1; + bool speculated : 1; bool reserved1 : 1; bool reserved0 : 1; uint8_t count : 4; diff --git a/quantum/action_tapping.c b/quantum/action_tapping.c index c2f45ea178..5d43dd99ea 100644 --- a/quantum/action_tapping.c +++ b/quantum/action_tapping.c @@ -6,8 +6,10 @@ #include "action_tapping.h" #include "action_util.h" #include "keycode.h" +#include "keycode_config.h" #include "quantum_keycodes.h" #include "timer.h" +#include "wait.h" #ifndef NO_ACTION_TAPPING @@ -51,6 +53,21 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re } # endif +# ifdef SPECULATIVE_HOLD +typedef struct { + keypos_t key; + uint8_t mods; +} speculative_key_t; +# define SPECULATIVE_KEYS_SIZE 8 +static speculative_key_t speculative_keys[SPECULATIVE_KEYS_SIZE] = {}; +static uint8_t num_speculative_keys = 0; +static uint8_t prev_speculative_mods = 0; +static uint8_t speculative_mods = 0; + +/** Handler to be called on incoming press events. */ +static void speculative_key_press(keyrecord_t *record); +# endif // SPECULATIVE_HOLD + # if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM) # define REGISTERED_TAPS_SIZE 8 // Array of tap-hold keys that have been settled as tapped but not yet released. @@ -129,6 +146,13 @@ static void debug_waiting_buffer(void); * FIXME: Needs doc */ void action_tapping_process(keyrecord_t record) { +# ifdef SPECULATIVE_HOLD + prev_speculative_mods = speculative_mods; + if (record.event.pressed) { + speculative_key_press(&record); + } +# endif // SPECULATIVE_HOLD + if (process_tapping(&record)) { if (IS_EVENT(record.event)) { ac_dprintf("processed: "); @@ -145,6 +169,12 @@ void action_tapping_process(keyrecord_t record) { } } +# ifdef SPECULATIVE_HOLD + if (speculative_mods != prev_speculative_mods) { + send_keyboard_report(); + } +# endif // SPECULATIVE_HOLD + // process waiting_buffer if (IS_EVENT(record.event) && waiting_buffer_head != waiting_buffer_tail) { ac_dprintf("---- action_exec: process waiting_buffer -----\n"); @@ -708,6 +738,147 @@ void waiting_buffer_scan_tap(void) { } } +# ifdef SPECULATIVE_HOLD +static void debug_speculative_keys(void) { + ac_dprintf("mods = { "); + for (int8_t i = 0; i < num_speculative_keys; ++i) { + ac_dprintf("%02X ", speculative_keys[i].mods); + } + ac_dprintf("}, keys = { "); + for (int8_t i = 0; i < num_speculative_keys; ++i) { + ac_dprintf("%02X%02X ", speculative_keys[i].key.row, speculative_keys[i].key.col); + } + ac_dprintf("}\n"); +} + +// Find key in speculative_keys. Returns num_speculative_keys if not found. +static int8_t speculative_keys_find(keypos_t key) { + uint8_t i; + for (i = 0; i < num_speculative_keys; ++i) { + if (KEYEQ(speculative_keys[i].key, key)) { + break; + } + } + return i; +} + +static void speculative_key_press(keyrecord_t *record) { + if (num_speculative_keys >= SPECULATIVE_KEYS_SIZE) { // Overflow! + ac_dprintf("SPECULATIVE KEYS OVERFLOW: IGNORING EVENT\n"); + return; // Don't trigger: speculative_keys is full. + } + if (speculative_keys_find(record->event.key) < num_speculative_keys) { + return; // Don't trigger: key is already in speculative_keys. + } + + const uint16_t keycode = get_record_keycode(record, false); + if (!IS_QK_MOD_TAP(keycode)) { + return; // Don't trigger: not a mod-tap key. + } + + uint8_t mods = mod_config(QK_MOD_TAP_GET_MODS(keycode)); + if ((mods & 0x10) != 0) { // Unpack 5-bit mods to 8-bit representation. + mods <<= 4; + } + if ((~(get_mods() | speculative_mods) & mods) == 0) { + return; // Don't trigger: mods are already active. + } + + // Don't do Speculative Hold when there are non-speculated buffered events, + // since that could result in sending keys out of order. + for (uint8_t i = waiting_buffer_tail; i != waiting_buffer_head; i = (i + 1) % WAITING_BUFFER_SIZE) { + if (!waiting_buffer[i].tap.speculated) { + return; + } + } + + if (get_speculative_hold(keycode, record)) { + record->tap.speculated = true; + speculative_mods |= mods; + // Remember the keypos and mods associated with this key. + speculative_keys[num_speculative_keys] = (speculative_key_t){ + .key = record->event.key, + .mods = mods, + }; + ++num_speculative_keys; + + ac_dprintf("Speculative Hold: "); + debug_speculative_keys(); + } +} + +uint8_t get_speculative_mods(void) { + return speculative_mods; +} + +__attribute__((weak)) bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) { + const uint8_t mods = mod_config(QK_MOD_TAP_GET_MODS(keycode)); + return (mods & (MOD_LCTL | MOD_LSFT)) == mods; +} + +void speculative_key_settled(keyrecord_t *record) { + if (num_speculative_keys == 0) { + return; // Early return when there are no active speculative keys. + } + + uint8_t i = speculative_keys_find(record->event.key); + + const uint16_t keycode = get_record_keycode(record, false); + if (IS_QK_MOD_TAP(keycode) && record->tap.count == 0) { // MT hold press. + if (i < num_speculative_keys) { + --num_speculative_keys; + const uint8_t cleared_mods = speculative_keys[i].mods; + + if (num_speculative_keys) { + speculative_mods &= ~cleared_mods; + // Don't call send_keyboard_report() here; allow default + // handling to reapply the mod before the next report. + + // Remove the ith entry from speculative_keys. + for (uint8_t j = i; j < num_speculative_keys; ++j) { + speculative_keys[j] = speculative_keys[j + 1]; + } + } else { + speculative_mods = 0; + } + + ac_dprintf("Speculative Hold: settled %02x, ", cleared_mods); + debug_speculative_keys(); + } + } else { // Tap press event; cancel speculatively-held mod. + if (i >= num_speculative_keys) { + i = 0; + } + + // Clear mods for the ith key and all keys that follow. + uint8_t cleared_mods = 0; + for (uint8_t j = i; j < num_speculative_keys; ++j) { + cleared_mods |= speculative_keys[j].mods; + } + + num_speculative_keys = i; // Remove ith and following entries. + + if ((prev_speculative_mods & cleared_mods) != 0) { +# ifdef DUMMY_MOD_NEUTRALIZER_KEYCODE + neutralize_flashing_modifiers(get_mods() | prev_speculative_mods); +# endif // DUMMY_MOD_NEUTRALIZER_KEYCODE + } + + if (num_speculative_keys) { + speculative_mods &= ~cleared_mods; + } else { + speculative_mods = 0; + } + + send_keyboard_report(); + wait_ms(TAP_CODE_DELAY); + + ac_dprintf("Speculative Hold: canceled %02x, ", cleared_mods); + debug_speculative_keys(); + } +} +# endif // SPECULATIVE_HOLD + # if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM) static void registered_taps_add(keypos_t key) { if (num_registered_taps >= REGISTERED_TAPS_SIZE) { diff --git a/quantum/action_tapping.h b/quantum/action_tapping.h index 8220952706..227e3330e1 100644 --- a/quantum/action_tapping.h +++ b/quantum/action_tapping.h @@ -46,6 +46,36 @@ bool get_permissive_hold(uint16_t keycode, keyrecord_t *record); bool get_retro_tapping(uint16_t keycode, keyrecord_t *record); bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record); +#ifdef SPECULATIVE_HOLD +/** Gets the currently active speculative mods. */ +uint8_t get_speculative_mods(void); + +/** + * Callback to say if a mod-tap key may be speculatively held. + * + * By default, speculative hold is enabled for mod-tap keys where the mod is + * Ctrl, Shift, and Ctrl+Shift for either hand. + * + * @param keycode Keycode of the mod-tap key. + * @param record Record associated with the mod-tap press event. + * @return True if the mod-tap key may be speculatively held. + */ +bool get_speculative_hold(uint16_t keycode, keyrecord_t *record); + +/** + * Handler to be called on press events after tap-holds are settled. + * + * This function is to be called in process_record() in action.c, that is, just + * after tap-hold events are settled as either tapped or held. When `record` + * corresponds to a speculatively-held key, the speculative mod is cleared. + * + * @param record Record associated with the mod-tap press event. + */ +void speculative_key_settled(keyrecord_t *record); +#else +# define get_speculative_mods() 0 +#endif // SPECULATIVE_HOLD + #ifdef CHORDAL_HOLD /** * Callback to say when a key chord before the tapping term may be held. diff --git a/quantum/action_util.c b/quantum/action_util.c index 0996ff908e..00cec24e3f 100644 --- a/quantum/action_util.c +++ b/quantum/action_util.c @@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #include "debug.h" #include "action_util.h" #include "action_layer.h" +#include "action_tapping.h" #include "timer.h" #include "keycode_config.h" #include <string.h> @@ -284,6 +285,10 @@ static uint8_t get_mods_for_report(void) { } #endif +#ifdef SPECULATIVE_HOLD + mods |= get_speculative_mods(); +#endif + #ifdef KEY_OVERRIDE_ENABLE // These need to be last to be able to properly control key overrides mods &= ~suppressed_mods; |