205 lines
6.7 KiB
C
205 lines
6.7 KiB
C
// Copyright 2025 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
/**
|
|
* @file tap_flow.c
|
|
* @brief Tap Flow module implementation
|
|
*
|
|
* For full documentation, see
|
|
* <https://getreuer.info/posts/keyboards/tap-flow>
|
|
*/
|
|
|
|
#include "tap_flow.h"
|
|
|
|
ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0);
|
|
|
|
// Either the Combos feature or Repeat Key (or both) need to be enabled. Either
|
|
// of these features enables the .keycode field in keyrecord_t, which the
|
|
// tap_flow implementation relies on.
|
|
#if !defined(COMBO_ENABLE) && !defined(REPEAT_KEY_ENABLE)
|
|
#error "tap_flow: Please enable Combos (COMBO_ENABLE = true) or Repeat Key (REPEAT_KEY_ENABLE = yes), or both, in rules.mk."
|
|
#else
|
|
|
|
uint16_t g_tap_flow_term = TAP_FLOW_TERM;
|
|
|
|
static uint16_t prev_keycode = KC_NO;
|
|
// Events bypass tap_flow when there are unsettled LT keys in action_tapping's
|
|
// waiting_queue. Particularly, supposing an LT settles as held, the layer state
|
|
// will change and buffered events following the LT will be reconsidered as keys
|
|
// on that layer. That may change whether tap_flow is enabled or the timeout
|
|
// to use on those keys. We don't know in advance how the LT will settle.
|
|
static uint16_t settle_timer = 0;
|
|
static uint8_t is_tapped[(MATRIX_ROWS * MATRIX_COLS + 7) / 8] = {0};
|
|
|
|
static 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);
|
|
#ifndef NO_ACTION_LAYER
|
|
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
|
|
return QK_LAYER_TAP_GET_TAP_KEYCODE(keycode);
|
|
#endif // NO_ACTION_LAYER
|
|
}
|
|
return keycode;
|
|
}
|
|
|
|
#ifdef COMBO_ENABLE
|
|
#include "keymap_introspection.h"
|
|
|
|
static bool is_combo_key(uint16_t keycode) {
|
|
for (uint16_t i = 0; i < combo_count(); ++i) {
|
|
const uint16_t* keys = combo_get(i)->keys;
|
|
uint16_t key;
|
|
do {
|
|
key = pgm_read_word(keys++);
|
|
if (key == keycode) { return true; }
|
|
} while (key != COMBO_END);
|
|
}
|
|
return false;
|
|
}
|
|
#else
|
|
#define is_combo_key(keycode) false
|
|
#endif // COMBO_ENABLE
|
|
|
|
void housekeeping_task_tap_flow(void) {
|
|
if (settle_timer && timer_expired(timer_read(), settle_timer)) {
|
|
#ifdef TAP_FLOW_DEBUG
|
|
dprintf("tap_flow: settled.\n");
|
|
#endif // TAP_FLOW_DEBUG
|
|
settle_timer = 0;
|
|
}
|
|
}
|
|
|
|
bool pre_process_record_tap_flow(uint16_t keycode, keyrecord_t* record) {
|
|
const keypos_t pos = record->event.key;
|
|
|
|
if (IS_KEYEVENT(record->event) && pos.row < MATRIX_ROWS
|
|
&& pos.col < MATRIX_COLS &&
|
|
(IS_QK_MOD_TAP(keycode) || IS_QK_LAYER_TAP(keycode))) {
|
|
// The event is on an MT or LT with a valid matrix position.
|
|
const uint16_t tap_keycode = get_tap_keycode(keycode);
|
|
|
|
// Determine the key's index in the bit arrays.
|
|
const uint16_t index = pos.row * MATRIX_COLS + pos.col;
|
|
const uint16_t array_index = index / 8;
|
|
const uint8_t bit_mask = UINT8_C(1) << (index % 8);
|
|
|
|
if (record->event.pressed) { // On press.
|
|
const uint32_t idle_time = last_input_activity_elapsed();
|
|
uint16_t tap_flow_term = get_tap_flow(keycode, record, prev_keycode);
|
|
if (tap_flow_term > 500) {
|
|
tap_flow_term = 500;
|
|
}
|
|
|
|
if (!settle_timer && !is_combo_key(keycode) &&
|
|
idle_time < 500 && idle_time < tap_flow_term) {
|
|
#ifdef TAP_FLOW_DEBUG
|
|
dprintf("tap_flow: %02x%02xd within term (%u < %u) converted to tap.\n",
|
|
pos.row, pos.col, (uint16_t)idle_time, tap_flow_term);
|
|
#endif // TAP_FLOW_DEBUG
|
|
|
|
// Rewrite the event as a press of the tap keycode. This way, it
|
|
// bypasses the usual action_tapping logic.
|
|
record->keycode = tap_keycode;
|
|
// Record this key as tapped.
|
|
is_tapped[array_index] |= bit_mask;
|
|
} else {
|
|
// Otherwise if this is an LT key, track when it will settle according
|
|
// to its tapping term.
|
|
// NOTE: To be precise, the key could settle before the tapping term.
|
|
// This is an approximation.
|
|
#ifdef TAP_FLOW_DEBUG
|
|
if (settle_timer) {
|
|
dprintf("tap_flow: %02x%02xd unchanged (unsettled state).\n",
|
|
pos.row, pos.col);
|
|
} else if (is_combo_key(keycode)) {
|
|
dprintf("tap_flow: %02x%02xd unchanged (combo key).\n",
|
|
pos.row, pos.col);
|
|
} else {
|
|
dprintf("tap_flow: %02x%02xd unchanged (outside time).\n",
|
|
pos.row, pos.col);
|
|
}
|
|
#endif // TAP_FLOW_DEBUG
|
|
|
|
if (IS_QK_LAYER_TAP(keycode)) {
|
|
const uint16_t term = GET_TAPPING_TERM(keycode, record);
|
|
const uint16_t now = timer_read();
|
|
if (!settle_timer || term > TIMER_DIFF_16(settle_timer, now)) {
|
|
settle_timer = (now + term) | 1;
|
|
}
|
|
}
|
|
}
|
|
} else if ((is_tapped[array_index] & bit_mask) != 0) { // On tap release.
|
|
#ifdef TAP_FLOW_DEBUG
|
|
dprintf("tap_flow: %02x%02xu tap release.\n", pos.row, pos.col);
|
|
#endif // TAP_FLOW_DEBUG
|
|
|
|
// Rewrite the event as a release of the tap keycode.
|
|
record->keycode = tap_keycode;
|
|
// Record the key as released.
|
|
is_tapped[array_index] &= ~bit_mask;
|
|
}
|
|
}
|
|
|
|
if (record->event.pressed) { // Track the previous key press.
|
|
prev_keycode = keycode;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool process_record_tap_flow(uint16_t keycode, keyrecord_t* record) {
|
|
if (record->event.pressed) {
|
|
switch (keycode) {
|
|
case TAP_FLOW_PRINT:
|
|
send_string(get_u16_str(g_tap_flow_term, ' '));
|
|
return false;
|
|
case TAP_FLOW_UP:
|
|
g_tap_flow_term += 5;
|
|
return false;
|
|
case TAP_FLOW_DOWN:
|
|
g_tap_flow_term -= 5;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Keycode is a "typing" key: Space, A-Z, or main alphas area punctuation.
|
|
static bool is_typing(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;
|
|
}
|
|
|
|
// By default, enable filtering when both the tap-hold key and previous key are
|
|
// typing keys, and use the quick tap term.
|
|
__attribute__((weak)) uint16_t get_tap_flow(
|
|
uint16_t keycode, keyrecord_t* record, uint16_t prev_keycode) {
|
|
if (!is_typing(keycode) || !is_typing(prev_keycode)) {
|
|
return 0;
|
|
}
|
|
|
|
return g_tap_flow_term;
|
|
}
|
|
|
|
#endif // !defined(COMBO_ENABLE) && !defined(REPEAT_KEY_ENABLE)
|
|
|