[Core] Flow Tap tap-hold option to disable HRMs during fast typing (#25125)
aka Global Quick Tap, Require Prior Idle
This commit is contained in:
		
							parent
							
								
									a7bf8e64a5
								
							
						
					
					
						commit
						8d8dcb089e
					
				
					 11 changed files with 648 additions and 36 deletions
				
			
		| 
						 | 
				
			
			@ -1183,6 +1183,23 @@ bool is_tap_action(action_t action) {
 | 
			
		|||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t get_tap_keycode(uint16_t keycode) {
 | 
			
		||||
    switch (keycode) {
 | 
			
		||||
        case QK_MOD_TAP ... QK_MOD_TAP_MAX:
 | 
			
		||||
            return QK_MOD_TAP_GET_TAP_KEYCODE(keycode);
 | 
			
		||||
        case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
 | 
			
		||||
            return QK_LAYER_TAP_GET_TAP_KEYCODE(keycode);
 | 
			
		||||
        case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX:
 | 
			
		||||
            // IS_SWAP_HANDS_KEYCODE() tests for the special action keycodes
 | 
			
		||||
            // like SH_TOGG, SH_TT, ..., which overlap the SH_T(kc) range.
 | 
			
		||||
            if (!IS_SWAP_HANDS_KEYCODE(keycode)) {
 | 
			
		||||
                return QK_SWAP_HANDS_GET_TAP_KEYCODE(keycode);
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
    return keycode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** \brief Debug print (FIXME: Needs better description)
 | 
			
		||||
 *
 | 
			
		||||
 * FIXME: Needs documentation.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -128,6 +128,12 @@ void layer_switch(uint8_t new_layer);
 | 
			
		|||
bool is_tap_record(keyrecord_t *record);
 | 
			
		||||
bool is_tap_action(action_t action);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Given an MT or LT keycode, returns the tap keycode. Otherwise returns the
 | 
			
		||||
 * original keycode unchanged.
 | 
			
		||||
 */
 | 
			
		||||
uint16_t get_tap_keycode(uint16_t keycode);
 | 
			
		||||
 | 
			
		||||
#ifndef NO_ACTION_TAPPING
 | 
			
		||||
void process_record_tap_hint(keyrecord_t *record);
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
#include "action.h"
 | 
			
		||||
#include "action_layer.h"
 | 
			
		||||
#include "action_tapping.h"
 | 
			
		||||
#include "action_util.h"
 | 
			
		||||
#include "keycode.h"
 | 
			
		||||
#include "timer.h"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -49,9 +50,7 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re
 | 
			
		|||
}
 | 
			
		||||
#    endif
 | 
			
		||||
 | 
			
		||||
#    if defined(CHORDAL_HOLD)
 | 
			
		||||
extern const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM;
 | 
			
		||||
 | 
			
		||||
#    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.
 | 
			
		||||
static keypos_t registered_taps[REGISTERED_TAPS_SIZE] = {};
 | 
			
		||||
| 
						 | 
				
			
			@ -66,6 +65,14 @@ static void registered_taps_del_index(uint8_t i);
 | 
			
		|||
/** Logs the registered_taps array for debugging. */
 | 
			
		||||
static void debug_registered_taps(void);
 | 
			
		||||
 | 
			
		||||
static bool is_mt_or_lt(uint16_t keycode) {
 | 
			
		||||
    return IS_QK_MOD_TAP(keycode) || IS_QK_LAYER_TAP(keycode);
 | 
			
		||||
}
 | 
			
		||||
#    endif // defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
 | 
			
		||||
 | 
			
		||||
#    if defined(CHORDAL_HOLD)
 | 
			
		||||
extern const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM;
 | 
			
		||||
 | 
			
		||||
/** \brief Finds which queued events should be held according to Chordal Hold.
 | 
			
		||||
 *
 | 
			
		||||
 * In a situation with multiple unsettled tap-hold key presses, scan the queue
 | 
			
		||||
| 
						 | 
				
			
			@ -82,10 +89,6 @@ static void waiting_buffer_chordal_hold_taps_until(keypos_t key);
 | 
			
		|||
 | 
			
		||||
/** \brief Processes and pops buffered events until the first tap-hold event. */
 | 
			
		||||
static void waiting_buffer_process_regular(void);
 | 
			
		||||
 | 
			
		||||
static bool is_mt_or_lt(uint16_t keycode) {
 | 
			
		||||
    return IS_QK_MOD_TAP(keycode) || IS_QK_LAYER_TAP(keycode);
 | 
			
		||||
}
 | 
			
		||||
#    endif // CHORDAL_HOLD
 | 
			
		||||
 | 
			
		||||
#    ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
 | 
			
		||||
| 
						 | 
				
			
			@ -98,6 +101,13 @@ __attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyreco
 | 
			
		|||
#        include "process_auto_shift.h"
 | 
			
		||||
#    endif
 | 
			
		||||
 | 
			
		||||
#    if defined(FLOW_TAP_TERM)
 | 
			
		||||
static uint32_t last_input   = 0;
 | 
			
		||||
static uint16_t prev_keycode = KC_NO;
 | 
			
		||||
 | 
			
		||||
uint16_t get_flow_tap_term(uint16_t keycode, keyrecord_t *record, uint16_t prev_keycode);
 | 
			
		||||
#    endif // defined(FLOW_TAP_TERM)
 | 
			
		||||
 | 
			
		||||
static keyrecord_t tapping_key                         = {};
 | 
			
		||||
static keyrecord_t waiting_buffer[WAITING_BUFFER_SIZE] = {};
 | 
			
		||||
static uint8_t     waiting_buffer_head                 = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -147,6 +157,19 @@ void action_tapping_process(keyrecord_t record) {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (IS_EVENT(record.event)) {
 | 
			
		||||
#    if defined(FLOW_TAP_TERM)
 | 
			
		||||
        const uint16_t keycode = get_record_keycode(&record, false);
 | 
			
		||||
        // Track the previous key press.
 | 
			
		||||
        if (record.event.pressed) {
 | 
			
		||||
            prev_keycode = keycode;
 | 
			
		||||
        }
 | 
			
		||||
        // If there is no unsettled tap-hold key, update last input time. Ignore
 | 
			
		||||
        // mod keys in this update to allow for chording multiple mods for
 | 
			
		||||
        // hotkeys like "Ctrl+Shift+arrow".
 | 
			
		||||
        if (IS_NOEVENT(tapping_key.event) && !IS_MODIFIER_KEYCODE(keycode)) {
 | 
			
		||||
            last_input = timer_read32();
 | 
			
		||||
        }
 | 
			
		||||
#    endif // defined(FLOW_TAP_TERM)
 | 
			
		||||
        ac_dprintf("\n");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -205,7 +228,7 @@ void action_tapping_process(keyrecord_t record) {
 | 
			
		|||
bool process_tapping(keyrecord_t *keyp) {
 | 
			
		||||
    const keyevent_t event = keyp->event;
 | 
			
		||||
 | 
			
		||||
#    if defined(CHORDAL_HOLD)
 | 
			
		||||
#    if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
 | 
			
		||||
    if (!event.pressed) {
 | 
			
		||||
        const int8_t i = registered_tap_find(event.key);
 | 
			
		||||
        if (i != -1) {
 | 
			
		||||
| 
						 | 
				
			
			@ -217,7 +240,7 @@ bool process_tapping(keyrecord_t *keyp) {
 | 
			
		|||
            debug_registered_taps();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
#    endif // CHORDAL_HOLD
 | 
			
		||||
#    endif // defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
 | 
			
		||||
 | 
			
		||||
    // state machine is in the "reset" state, no tapping key is to be
 | 
			
		||||
    // processed
 | 
			
		||||
| 
						 | 
				
			
			@ -227,6 +250,27 @@ bool process_tapping(keyrecord_t *keyp) {
 | 
			
		|||
        } else if (event.pressed && is_tap_record(keyp)) {
 | 
			
		||||
            // the currently pressed key is a tapping key, therefore transition
 | 
			
		||||
            // into the "pressed" tapping key state
 | 
			
		||||
 | 
			
		||||
#    if defined(FLOW_TAP_TERM)
 | 
			
		||||
            const uint16_t keycode = get_record_keycode(keyp, false);
 | 
			
		||||
            if (is_mt_or_lt(keycode)) {
 | 
			
		||||
                const uint32_t idle_time = timer_elapsed32(last_input);
 | 
			
		||||
                uint16_t       term      = get_flow_tap_term(keycode, keyp, prev_keycode);
 | 
			
		||||
                if (term > 500) {
 | 
			
		||||
                    term = 500;
 | 
			
		||||
                }
 | 
			
		||||
                if (idle_time < 500 && idle_time < term) {
 | 
			
		||||
                    debug_event(keyp->event);
 | 
			
		||||
                    ac_dprintf(" within flow tap term (%u < %u) considered a tap\n", (int16_t)idle_time, term);
 | 
			
		||||
                    keyp->tap.count = 1;
 | 
			
		||||
                    registered_taps_add(keyp->event.key);
 | 
			
		||||
                    debug_registered_taps();
 | 
			
		||||
                    process_record(keyp);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
#    endif // defined(FLOW_TAP_TERM)
 | 
			
		||||
 | 
			
		||||
            ac_dprintf("Tapping: Start(Press tap key).\n");
 | 
			
		||||
            tapping_key = *keyp;
 | 
			
		||||
            process_record_tap_hint(&tapping_key);
 | 
			
		||||
| 
						 | 
				
			
			@ -655,28 +699,7 @@ void waiting_buffer_scan_tap(void) {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#    ifdef CHORDAL_HOLD
 | 
			
		||||
__attribute__((weak)) bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t *tap_hold_record, uint16_t other_keycode, keyrecord_t *other_record) {
 | 
			
		||||
    return get_chordal_hold_default(tap_hold_record, other_record);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool get_chordal_hold_default(keyrecord_t *tap_hold_record, keyrecord_t *other_record) {
 | 
			
		||||
    if (tap_hold_record->event.type != KEY_EVENT || other_record->event.type != KEY_EVENT) {
 | 
			
		||||
        return true; // Return true on combos or other non-key events.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    char tap_hold_hand = chordal_hold_handedness(tap_hold_record->event.key);
 | 
			
		||||
    if (tap_hold_hand == '*') {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    char other_hand = chordal_hold_handedness(other_record->event.key);
 | 
			
		||||
    return other_hand == '*' || tap_hold_hand != other_hand;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__attribute__((weak)) char chordal_hold_handedness(keypos_t key) {
 | 
			
		||||
    return (char)pgm_read_byte(&chordal_hold_layout[key.row][key.col]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#    if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
 | 
			
		||||
static void registered_taps_add(keypos_t key) {
 | 
			
		||||
    if (num_registered_taps >= REGISTERED_TAPS_SIZE) {
 | 
			
		||||
        ac_dprintf("TAPS OVERFLOW: CLEAR ALL STATES\n");
 | 
			
		||||
| 
						 | 
				
			
			@ -714,6 +737,30 @@ static void debug_registered_taps(void) {
 | 
			
		|||
    ac_dprintf("}\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#    endif // defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
 | 
			
		||||
 | 
			
		||||
#    ifdef CHORDAL_HOLD
 | 
			
		||||
__attribute__((weak)) bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t *tap_hold_record, uint16_t other_keycode, keyrecord_t *other_record) {
 | 
			
		||||
    return get_chordal_hold_default(tap_hold_record, other_record);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool get_chordal_hold_default(keyrecord_t *tap_hold_record, keyrecord_t *other_record) {
 | 
			
		||||
    if (tap_hold_record->event.type != KEY_EVENT || other_record->event.type != KEY_EVENT) {
 | 
			
		||||
        return true; // Return true on combos or other non-key events.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    char tap_hold_hand = chordal_hold_handedness(tap_hold_record->event.key);
 | 
			
		||||
    if (tap_hold_hand == '*') {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    char other_hand = chordal_hold_handedness(other_record->event.key);
 | 
			
		||||
    return other_hand == '*' || tap_hold_hand != other_hand;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__attribute__((weak)) char chordal_hold_handedness(keypos_t key) {
 | 
			
		||||
    return (char)pgm_read_byte(&chordal_hold_layout[key.row][key.col]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint8_t waiting_buffer_find_chordal_hold_tap(void) {
 | 
			
		||||
    keyrecord_t *prev         = &tapping_key;
 | 
			
		||||
    uint16_t     prev_keycode = get_record_keycode(&tapping_key, false);
 | 
			
		||||
| 
						 | 
				
			
			@ -761,6 +808,35 @@ static void waiting_buffer_process_regular(void) {
 | 
			
		|||
}
 | 
			
		||||
#    endif // CHORDAL_HOLD
 | 
			
		||||
 | 
			
		||||
#    ifdef FLOW_TAP_TERM
 | 
			
		||||
// By default, enable Flow Tap for the keys in the main alphas area and Space.
 | 
			
		||||
// This should work reasonably even if the layout is remapped on the host to an
 | 
			
		||||
// alt layout or international layout (e.g. Dvorak or AZERTY), where these same
 | 
			
		||||
// key positions are mostly used for typing letters.
 | 
			
		||||
__attribute__((weak)) bool is_flow_tap_key(uint16_t keycode) {
 | 
			
		||||
    if ((get_mods() & (MOD_MASK_CG | MOD_BIT_LALT)) != 0) {
 | 
			
		||||
        return false; // Disable Flow Tap on hotkeys.
 | 
			
		||||
    }
 | 
			
		||||
    switch (get_tap_keycode(keycode)) {
 | 
			
		||||
        case KC_SPC:
 | 
			
		||||
        case KC_A ... KC_Z:
 | 
			
		||||
        case KC_DOT:
 | 
			
		||||
        case KC_COMM:
 | 
			
		||||
        case KC_SCLN:
 | 
			
		||||
        case KC_SLSH:
 | 
			
		||||
            return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__attribute__((weak)) uint16_t get_flow_tap_term(uint16_t keycode, keyrecord_t *record, uint16_t prev_keycode) {
 | 
			
		||||
    if (is_flow_tap_key(keycode) && is_flow_tap_key(prev_keycode)) {
 | 
			
		||||
        return FLOW_TAP_TERM;
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
#    endif // FLOW_TAP_TERM
 | 
			
		||||
 | 
			
		||||
/** \brief Logs tapping key if ACTION_DEBUG is enabled. */
 | 
			
		||||
static void debug_tapping_key(void) {
 | 
			
		||||
    ac_dprintf("TAPPING_KEY=");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -111,6 +111,63 @@ char chordal_hold_handedness(keypos_t key);
 | 
			
		|||
extern const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef FLOW_TAP_TERM
 | 
			
		||||
/**
 | 
			
		||||
 * Callback to specify the keys where Flow Tap is enabled.
 | 
			
		||||
 *
 | 
			
		||||
 * Flow Tap is constrained to certain keys by the following rule: this callback
 | 
			
		||||
 * is called for both the tap-hold key *and* the key press immediately preceding
 | 
			
		||||
 * it. If the callback returns true for both keycodes, Flow Tap is enabled.
 | 
			
		||||
 *
 | 
			
		||||
 * The default implementation of this callback corresponds to
 | 
			
		||||
 *
 | 
			
		||||
 *     bool is_flow_tap_key(uint16_t keycode) {
 | 
			
		||||
 *       switch (get_tap_keycode(keycode)) {
 | 
			
		||||
 *         case KC_SPC:
 | 
			
		||||
 *         case KC_A ... KC_Z:
 | 
			
		||||
 *         case KC_DOT:
 | 
			
		||||
 *         case KC_COMM:
 | 
			
		||||
 *         case KC_SCLN:
 | 
			
		||||
 *         case KC_SLSH:
 | 
			
		||||
 *           return true;
 | 
			
		||||
 *       }
 | 
			
		||||
 *       return false;
 | 
			
		||||
 *     }
 | 
			
		||||
 *
 | 
			
		||||
 * @param keycode Keycode of the key.
 | 
			
		||||
 * @return Whether to enable Flow Tap for this key.
 | 
			
		||||
 */
 | 
			
		||||
bool is_flow_tap_key(uint16_t keycode);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Callback to customize Flow Tap filtering.
 | 
			
		||||
 *
 | 
			
		||||
 * Flow Tap acts only when key events are closer together than this time.
 | 
			
		||||
 *
 | 
			
		||||
 * Return a time of 0 to disable filtering. In this way, Flow Tap may be
 | 
			
		||||
 * disabled for certain tap-hold keys, or when following certain previous keys.
 | 
			
		||||
 *
 | 
			
		||||
 * The default implementation of this callback is
 | 
			
		||||
 *
 | 
			
		||||
 *     uint16_t get_flow_tap_term(uint16_t keycode, keyrecord_t* record,
 | 
			
		||||
 *                                uint16_t prev_keycode) {
 | 
			
		||||
 *       if (is_flow_tap_key(keycode) && is_flow_tap_key(prev_keycode)) {
 | 
			
		||||
 *         return g_flow_tap_term;
 | 
			
		||||
 *       }
 | 
			
		||||
 *       return 0;
 | 
			
		||||
 *     }
 | 
			
		||||
 *
 | 
			
		||||
 * NOTE: If both `is_flow_tap_key()` and `get_flow_tap_term()` are defined, then
 | 
			
		||||
 * `get_flow_tap_term()` takes precedence.
 | 
			
		||||
 *
 | 
			
		||||
 * @param keycode Keycode of the tap-hold key.
 | 
			
		||||
 * @param record keyrecord_t of the tap-hold event.
 | 
			
		||||
 * @param prev_keycode Keycode of the previously pressed key.
 | 
			
		||||
 * @return Time in milliseconds.
 | 
			
		||||
 */
 | 
			
		||||
uint16_t get_flow_tap_term(uint16_t keycode, keyrecord_t *record, uint16_t prev_keycode);
 | 
			
		||||
#endif // FLOW_TAP_TERM
 | 
			
		||||
 | 
			
		||||
#ifdef DYNAMIC_TAPPING_TERM_ENABLE
 | 
			
		||||
extern uint16_t g_tapping_term;
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,11 +22,7 @@ bool process_leader(uint16_t keycode, keyrecord_t *record) {
 | 
			
		|||
    if (record->event.pressed) {
 | 
			
		||||
        if (leader_sequence_active() && !leader_sequence_timed_out()) {
 | 
			
		||||
#ifndef LEADER_KEY_STRICT_KEY_PROCESSING
 | 
			
		||||
            if (IS_QK_MOD_TAP(keycode)) {
 | 
			
		||||
                keycode = QK_MOD_TAP_GET_TAP_KEYCODE(keycode);
 | 
			
		||||
            } else if (IS_QK_LAYER_TAP(keycode)) {
 | 
			
		||||
                keycode = QK_LAYER_TAP_GET_TAP_KEYCODE(keycode);
 | 
			
		||||
            }
 | 
			
		||||
            keycode = get_tap_keycode(keycode);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
            if (!leader_sequence_add(keycode)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue