This commit is contained in:
parent
4dc5811f00
commit
ee0a3c3be0
61 changed files with 5743 additions and 0 deletions
57
modules/getreuer/tap_flow/README.md
Normal file
57
modules/getreuer/tap_flow/README.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Tap Flow
|
||||
|
||||
<table>
|
||||
<tr><td><b>Module</b></td><td><tt>getreuer/tap_flow</tt></td></tr>
|
||||
<tr><td><b>Version</b></td><td>2025-03-15</td></tr>
|
||||
<tr><td><b>Maintainer</b></td><td>Pascal Getreuer (@getreuer)</td></tr>
|
||||
<tr><td><b>License</b></td><td><a href="../LICENSE.txt">Apache 2.0</a></td></tr>
|
||||
<tr><td><b>Documentation</b></td><td>
|
||||
<a href="https://getreuer.info/posts/keyboards/tap-flow">https://getreuer.info/posts/keyboards/tap-flow</a>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
This module is an implementation of "global quick tap" (GQT), aka "require
|
||||
priori idle," for tap-hold keys. It is particularly useful for home row mods to
|
||||
avoid accidental mod triggers in fast typing.
|
||||
|
||||
To use this module, add the following to your `keymap.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"modules": ["getreuer/tap_flow"]
|
||||
}
|
||||
```
|
||||
|
||||
Tap Flow's term can be tuned on the fly with the following keycodes:
|
||||
|
||||
| Keycode | Alias | Description |
|
||||
|-------------------|-----------|-----------------------------------|
|
||||
| `TAP_FLOW_PRINT` | `TFLOW_P` | Type the current value. |
|
||||
| `TAP_FLOW_UP` | `TFLOW_U` | Increase by 5 ms. |
|
||||
| `TAP_FLOW_DOWN` | `TFLOW_D` | Decrease by 5 ms. |
|
||||
|
||||
Tap Flow's default behavior is:
|
||||
|
||||
* Filtering is done only when a tap-hold press is within `TAP_FLOW_TERM` of the
|
||||
previous key event, which defaults to 150 ms. Use `TFLOW_U` / `TFLOW_D`
|
||||
to tune, then define `TAP_FLOW_TERM` in your `config.h` to set the value
|
||||
printed by `TFLOW_P`.
|
||||
|
||||
* Filtering is done only when both the tap-hold key and the previous key are
|
||||
among <kbd>Space</kbd>, letters <kbd>A</kbd>–<kbd>Z</kbd>, and
|
||||
punctuations <kbd>,</kbd> <kbd>.</kbd> <kbd>;</kbd> <kbd>/</kbd>. Define the
|
||||
`get_tap_flow()` callback to customize this logic.
|
||||
|
||||
Tap Flow modifies the tap-hold decision such that when a tap-hold key is pressed
|
||||
within a short timeout of the preceding key, the tapping function is used. The
|
||||
assumption is that during fast typing, only the tap function of tap-hold keys is
|
||||
desired (though perhaps with an exception for Shift or AltGr, noted below),
|
||||
whereas the hold functions (mods and layers) are used in isolation, or at least
|
||||
with a brief pause preceding the tap-hold key press.
|
||||
|
||||
Optionally, the feature can be customized with the `get_tap_flow()` callback. In
|
||||
this way, exceptions may be made for Shift and AltGr (or whatever you wish) to
|
||||
use a shorter time or to disable filtering for those keys entirely.
|
||||
|
||||
For full documentation, see
|
||||
<https://getreuer.info/posts/keyboards/tap-flow>
|
17
modules/getreuer/tap_flow/introspection.h
Normal file
17
modules/getreuer/tap_flow/introspection.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
// 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.
|
||||
|
||||
#pragma once
|
||||
#include "tap_flow.h"
|
||||
|
24
modules/getreuer/tap_flow/qmk_module.json
Normal file
24
modules/getreuer/tap_flow/qmk_module.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"module_name": "Tap Flow",
|
||||
"maintainer": "getreuer",
|
||||
"keycodes": [
|
||||
{
|
||||
"key": "TAP_FLOW_PRINT",
|
||||
"aliases": [
|
||||
"TFLOW_P"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "TAP_FLOW_UP",
|
||||
"aliases": [
|
||||
"TFLOW_U"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "TAP_FLOW_DOWN",
|
||||
"aliases": [
|
||||
"TFLOW_D"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
205
modules/getreuer/tap_flow/tap_flow.c
Normal file
205
modules/getreuer/tap_flow/tap_flow.c
Normal file
|
@ -0,0 +1,205 @@
|
|||
// 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)
|
||||
|
72
modules/getreuer/tap_flow/tap_flow.h
Normal file
72
modules/getreuer/tap_flow/tap_flow.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
// 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.h
|
||||
* @brief Tap Flow module: disable HRMs during fast typing
|
||||
*
|
||||
* This module is an implementation of "global quick tap" (GQT), aka "require
|
||||
* priori idle," for tap-hold keys. It is particularly useful for home row mods
|
||||
* to avoid accidental mod triggers in fast typing.
|
||||
*
|
||||
* Tap Flow modifies the tap-hold decision such that when a tap-hold key is
|
||||
* pressed within a short timeout of the preceding key, the tapping function is
|
||||
* used. The assumption is that during fast typing, only the tap function of
|
||||
* tap-hold keys is desired (though perhaps with an exception for Shift or
|
||||
* AltGr, noted below), whereas the hold functions (mods and layers) are
|
||||
* used in isolation, or at least with a brief pause preceding the tap-hold key
|
||||
* press.
|
||||
*
|
||||
* Optionally, the feature can be customized with the `get_tap_flow()` callback.
|
||||
* In this way, exceptions may be made for Shift and AltGr (or whatever you
|
||||
* wish) to use a shorter time or to disable filtering for those keys entirely.
|
||||
*
|
||||
*
|
||||
* For full documentation, see
|
||||
* <https://getreuer.info/posts/keyboards/tap-flow>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "quantum.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef TAP_FLOW_TERM
|
||||
# define TAP_FLOW_TERM 150
|
||||
#endif // TAP_FLOW_TERM
|
||||
|
||||
/**
|
||||
* Optional callback to customize filtering.
|
||||
*
|
||||
* Tap Flow acts only when key events are closer together than this time.
|
||||
*
|
||||
* Return a time of 0 to disable filtering. In this way, Tap Flow may be
|
||||
* disabled for certain tap-hold keys, or when following certain previous keys.
|
||||
*
|
||||
* @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_tap_flow(uint16_t keycode, keyrecord_t* record,
|
||||
uint16_t prev_keycode);
|
||||
|
||||
extern uint16_t g_tap_flow_term;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue