This commit is contained in:
Pascal Getreuer 2024-11-20 23:06:05 -08:00 committed by GitHub
commit 1ac2af4ff1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 2578 additions and 28 deletions

View File

@ -422,7 +422,11 @@
"h": {"$ref": "qmk.definitions.v1#/key_unit"}, "h": {"$ref": "qmk.definitions.v1#/key_unit"},
"w": {"$ref": "qmk.definitions.v1#/key_unit"}, "w": {"$ref": "qmk.definitions.v1#/key_unit"},
"x": {"$ref": "qmk.definitions.v1#/key_unit"}, "x": {"$ref": "qmk.definitions.v1#/key_unit"},
"y": {"$ref": "qmk.definitions.v1#/key_unit"} "y": {"$ref": "qmk.definitions.v1#/key_unit"},
"hand": {
"type": "string",
"pattern": "[LR*]",
}
} }
} }
} }

View File

@ -425,6 +425,248 @@ uint16_t get_quick_tap_term(uint16_t keycode, keyrecord_t *record) {
If `QUICK_TAP_TERM` is set higher than `TAPPING_TERM`, it will default to `TAPPING_TERM`. If `QUICK_TAP_TERM` is set higher than `TAPPING_TERM`, it will default to `TAPPING_TERM`.
::: :::
## Chordal Hold
Chordal Hold is intended to be used together with either Permissive Hold or Hold
On Other Key Press. Chordal Hold is enabled by adding to your `config.h`:
```c
#define CHORDAL_HOLD
```
Chordal Hold implements, by default, an "opposite hands" rule. Suppose a
tap-hold key is pressed and then, before the tapping term, another key is
pressed. With Chordal Hold, the tap-hold key is settled as tapped if the two
keys are on the same hand.
Otherwise, if the keys are on opposite hands, Chordal Hold introduces no new
behavior. Hold On Other Key Press or Permissive Hold may be used together with
Chordal Hold to configure the behavior in the opposite hands case. With Hold On
Other Key Press, an opposite hands chord is settled immediately as held. Or with
Permissive Hold, an opposite hands chord is settled as held provided the other
key is pressed and released (nested press) before releasing the tap-hold key.
Chordal Hold may be useful to avoid accidental modifier activation with
mod-taps, particularly in rolled keypresses when using home row mods.
Notes:
* Chordal Hold has no effect after the tapping term.
* Combos are exempt from the opposite hands rule, since "handedness" is
ill-defined in this case. Even so, Chordal Hold's behavior involving combos
may be customized through the `get_chordal_hold()` callback.
An example of a sequence that is affected by “chordal hold”:
- `SFT_T(KC_A)` Down
- `KC_C` Down
- `KC_C` Up
- `SFT_T(KC_A)` Up
```
TAPPING_TERM
+---------------------------|--------+
| +----------------------+ | |
| | SFT_T(KC_A) | | |
| +----------------------+ | |
| +--------------+ | |
| | KC_C | | |
| +--------------+ | |
+---------------------------|--------+
```
If the two keys are on the same hand, then this will produce `ac` with
`SFT_T(KC_A)` settled as tapped the moment that `KC_C` is pressed.
If the two keys are on opposite hands and the `HOLD_ON_OTHER_KEY_PRESS`
option enabled, this will produce `C` with `SFT_T(KC_A)` settled as held the
moment that `KC_C` is pressed.
Or if the two keys are on opposite hands and the `PERMISSIVE_HOLD` option is
enabled, this will produce `C` with `SFT_T(KC_A)` settled as held the
moment that `KC_C` is released.
### Chordal Hold Handedness
Determining whether keys are on the same or opposite hands involves defining the
"handedness" of each key position. By default, if nothing is specified,
handedness is guessed based on keyboard geometry.
Handedness may be specified with `chordal_hold_layout`. In keymap.c, define
`chordal_hold_layout` in the following form:
```c
const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM =
LAYOUT(
'L', 'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R', 'R',
'L', 'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R', 'R',
'L', 'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R', 'R',
'L', 'L', 'L', 'R', 'R', 'R'
);
```
Use the same `LAYOUT` macro as used to define your keymap layers. Each entry is
a character indicating the handedness of one key, either `'L'` for left, `'R'`
for right, or `'*'` to exempt keys from the "opposite hands rule." A key with
`'*'` handedness may settle as held in chords with any other key. This could be
used perhaps on thumb keys or other places where you want to allow same-hand
chords.
Keyboard makers may specify handedness in keyboard.json. Under `"layouts"`,
specify the handedness of a key by adding a `"hand"` field with a value of
either `"L"`, `"R"`, or `"*"`. Note that if `"layouts"` contains multiple
layouts, only the first one is read. For example:
```json
{"matrix": [5, 6], "x": 0, "y": 5.5, "w": 1.25, "hand", "*"},
```
Alternatively, handedness may be defined functionally with
`chordal_hold_handedness()`. For example, in keymap.c define:
```c
char chordal_hold_handedness(keypos_t key) {
if (key.col == 0 || key.col == MATRIX_COLS - 1) {
return '*'; // Exempt the outer columns.
}
// On split keyboards, typically, the first half of the rows are on the
// left, and the other half are on the right.
return key.row < MATRIX_ROWS / 2 ? 'L' : 'R';
}
```
Given the matrix position of a key, the function should return `'L'`, `'R'`, or
`'*'`. Adapt the logic in this function according to the keyboard's matrix.
::: warning
Note the matrix may have irregularities around larger keys, around the edges of
the board, and around thumb clusters. You may find it helpful to use [this
debugging example](faq_debug#which-matrix-position-is-this-keypress) to
correspond physical keys to matrix positions.
:::
::: tip If you define both `chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS]` and
`chordal_hold_handedness(keypos_t key)` for handedness, the latter takes
precedence.
### Per-chord customization
Beyond the per-key configuration possible through handedness, Chordal Hold may
be configured at a *per-chord* granularity for detailed tuning. In keymap.c,
define `get_chordal_hold()`. Returning true settles the chord as held, while
returning false settles as tapped.
For example:
```c
bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record,
uint16_t other_keycode, keyrecord_t* other_record) {
// Exceptionally allow some one-handed chords for hotkeys.
switch (tap_hold_keycode) {
case LCTL_T(KC_Z):
if (other_keycode == KC_C || other_keycode == KC_V) {
return true;
}
break;
case RCTL_T(KC_SLSH):
if (other_keycode == KC_N) {
return true;
}
break;
}
// Otherwise defer to the opposite hands rule.
return get_chordal_hold_default(tap_hold_record, other_record);
}
```
As shown in the last line above, you may use
`get_chordal_hold_default(tap_hold_record, other_record)` to get the default tap
vs. hold decision according to the opposite hands rule.
If you use home row mods, you may want to produce a hotkey like Ctrl+Shift+V by
holding Ctrl and Shift mod-taps on one hand while tapping `KC_V` with the other
hand, say:
- `RCTL_T(KC_K)` Down
- `RSFT_T(KC_L)` Down (on the same hand as `RCTL_T(KC_K)`)
- `KC_V` Down
- `KC_V` Up
- `RCTL_T(KC_K)` Up
- `RSFT_T(KC_L)` Up
However, supposing `RCTL_T(KC_K)` and `RSFT_T(KC_L)` are on the same hand,
Chordal Hold by default considers `RCTL_T(KC_K)` tapped, producing "`kV`"
instead of the desired Ctrl+Shift+V.
To address this, `get_chordal_hold()` may be defined to allow chords between any
pair of mod-tap keys with
```c
bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record,
uint16_t other_keycode, keyrecord_t* other_record) {
// Allow hold between any pair of mod-tap keys.
if (IS_QK_MOD_TAP(tap_hold_keycode) && IS_QK_MOD_TAP(other_keycode)) {
return true;
}
// Otherwise defer to the opposite hands rule.
return get_chordal_hold_default(tap_hold_record, other_record);
}
```
Or to allow one-handed chords of specific mod-taps but not others, use:
```c
bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record,
uint16_t other_keycode, keyrecord_t* other_record) {
switch (tap_hold_keycode) {
case RCTL_T(KC_K):
if (other_keycode == RSFT_T(KC_L)) {
// Allow hold in "RCTL_T(KC_K) down, RSFT_T(KC_L) down".
return true;
}
break;
case RSFT_T(KC_L):
if (other_keycode == RCTL_T(KC_K)) {
// Allow hold in "RSFT_T(KC_L) down, RCTL_T(KC_K) down".
return true;
}
break;
}
// Otherwise defer to the opposite hands rule.
return get_chordal_hold_default(tap_hold_record, other_record);
}
```
Above, two exceptions are defined, one where `RCTL_T(KC_K)` is pressed first and
another where `RSFT_T(KC_L)` is held first, such that Ctrl+Shift+V could be done
by holding the mod-taps in either order. For yet finer control, you could choose
to define an exception for one order but not the other:
```c
bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record,
uint16_t other_keycode, keyrecord_t* other_record) {
switch (tap_hold_keycode) {
case RCTL_T(KC_K):
if (other_keycode == RSFT_T(KC_L)) {
// Allow hold in "RCTL_T(KC_K) down, RSFT_T(KC_L), down".
return true;
}
break;
// ... but RSFT_T(KC_L) is considered tapped in
// "RSFT_T(KC_L) down, RCTL_T(KC_K) down".
}
// Otherwise defer to the opposite hands rule.
return get_chordal_hold_default(tap_hold_record, other_record);
}
```
## Retro Tapping ## Retro Tapping
To enable `retro tapping`, add the following to your `config.h`: To enable `retro tapping`, add the following to your `config.h`:

View File

@ -1,5 +1,7 @@
"""Used by the make system to generate keyboard.c from info.json. """Used by the make system to generate keyboard.c from info.json.
""" """
import statistics
from milc import cli from milc import cli
from qmk.info import info_json from qmk.info import info_json
@ -87,6 +89,7 @@ def _gen_matrix_mask(info_data):
lines.append(f' 0b{"".join(reversed(mask[i]))},') lines.append(f' 0b{"".join(reversed(mask[i]))},')
lines.append('};') lines.append('};')
lines.append('#endif') lines.append('#endif')
lines.append('')
return lines return lines
@ -122,6 +125,57 @@ def _gen_joystick_axes(info_data):
lines.append('};') lines.append('};')
lines.append('#endif') lines.append('#endif')
lines.append('')
return lines
def _gen_chordal_hold_layout(info_data):
"""Convert info.json content to chordal_hold_layout
"""
keys_x = []
keys_hand = []
# Get x-coordinate for the center of each key.
# NOTE: If there are multiple layouts, only the first is read.
for layout_name, layout_data in info_data['layouts'].items():
for key_data in layout_data['layout']:
keys_x.append(key_data['x'] + key_data.get('w', 1.0) / 2)
keys_hand.append(key_data.get('hand', ''))
break
x_midline = statistics.median(keys_x)
x_prev = None
layout_handedness = [[]]
for x, hand in zip(keys_x, keys_hand):
if x_prev is not None and x < x_prev:
layout_handedness.append([])
if not hand:
# Where unspecified, assume handedness based on the key's location
# relative to the midline.
if abs(x - x_midline) > 0.25:
hand = 'L' if x < x_midline else 'R'
else:
hand = '*'
layout_handedness[-1].append(hand)
x_prev = x
lines = []
lines.append('#ifdef CHORDAL_HOLD')
lines.append('__attribute__((weak)) const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = ' + layout_name + '(')
for i, row in enumerate(layout_handedness):
line = ' ' + ', '.join(f"'{c}'" for c in row)
if i < len(layout_handedness) - 1:
line += ','
lines.append(line)
lines.append(');')
lines.append('#endif')
return lines return lines
@ -136,11 +190,12 @@ def generate_keyboard_c(cli):
kb_info_json = info_json(cli.args.keyboard) kb_info_json = info_json(cli.args.keyboard)
# Build the layouts.h file. # Build the layouts.h file.
keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', ''] keyboard_c_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', '']
keyboard_h_lines.extend(_gen_led_configs(kb_info_json)) keyboard_c_lines.extend(_gen_led_configs(kb_info_json))
keyboard_h_lines.extend(_gen_matrix_mask(kb_info_json)) keyboard_c_lines.extend(_gen_matrix_mask(kb_info_json))
keyboard_h_lines.extend(_gen_joystick_axes(kb_info_json)) keyboard_c_lines.extend(_gen_joystick_axes(kb_info_json))
keyboard_c_lines.extend(_gen_chordal_hold_layout(kb_info_json))
# Show the results # Show the results
dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet) dump_lines(cli.args.output, keyboard_c_lines, cli.args.quiet)

View File

@ -49,6 +49,30 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re
} }
# endif # endif
# ifdef CHORDAL_HOLD
extern const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM;
# 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] = {};
static uint8_t num_registered_taps = 0;
/** Adds `key` to the registered_taps array. */
static void registered_taps_add(keypos_t key);
/** Returns the index of `key` in registered_taps, or -1 if not found. */
static int8_t registered_tap_find(keypos_t key);
/** Removes index `i` from the registered_taps array. */
static void registered_taps_del_index(uint8_t i);
/** Logs the registered_taps array for debugging. */
static void debug_registered_taps(void);
/** Processes and pops buffered events until the first tap-hold event. */
static void waiting_buffer_process_regular(void);
static bool is_one_shot(uint16_t keycode) {
return IS_QK_ONE_SHOT_MOD(keycode) || IS_QK_ONE_SHOT_LAYER(keycode);
}
# endif // CHORDAL_HOLD
# ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY # ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
__attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) { __attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) {
return false; return false;
@ -166,6 +190,20 @@ void action_tapping_process(keyrecord_t record) {
bool process_tapping(keyrecord_t *keyp) { bool process_tapping(keyrecord_t *keyp) {
const keyevent_t event = keyp->event; const keyevent_t event = keyp->event;
# ifdef CHORDAL_HOLD
if (!event.pressed) {
const int8_t i = registered_tap_find(event.key);
if (i != -1) {
// If a tap-hold key was previously settled as tapped, set its
// tap.count correspondingly on release.
keyp->tap.count = 1;
registered_taps_del_index(i);
ac_dprintf("CHORDAL_HOLD: Found tap release for [%d]\n", i);
debug_registered_taps();
}
}
# endif // CHORDAL_HOLD
// state machine is in the "reset" state, no tapping key is to be // state machine is in the "reset" state, no tapping key is to be
// processed // processed
if (IS_NOEVENT(tapping_key.event)) { if (IS_NOEVENT(tapping_key.event)) {
@ -188,7 +226,7 @@ bool process_tapping(keyrecord_t *keyp) {
return true; return true;
} }
# if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY) # if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(CHORDAL_HOLD) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
TAP_DEFINE_KEYCODE; TAP_DEFINE_KEYCODE;
# endif # endif
@ -199,6 +237,7 @@ bool process_tapping(keyrecord_t *keyp) {
// early return for tick events // early return for tick events
return true; return true;
} }
if (tapping_key.tap.count == 0) { if (tapping_key.tap.count == 0) {
if (IS_TAPPING_RECORD(keyp) && !event.pressed) { if (IS_TAPPING_RECORD(keyp) && !event.pressed) {
// first tap! // first tap!
@ -237,6 +276,19 @@ bool process_tapping(keyrecord_t *keyp) {
/* Process release event of a key pressed before tapping starts /* Process release event of a key pressed before tapping starts
* Without this unexpected repeating will occur with having fast repeating setting * Without this unexpected repeating will occur with having fast repeating setting
* https://github.com/tmk/tmk_keyboard/issues/60 * https://github.com/tmk/tmk_keyboard/issues/60
*
* NOTE: This workaround causes events to process out of order,
* e.g. in a rolled press of three tap-hold keys like
*
* "A down, B down, C down, A up, B up, C up"
*
* events are processed as
*
* "A down, B down, A up, B up, C down, C up"
*
* It seems incorrect to process keyp before the tapping key.
* This workaround is old, from 2013. This might no longer
* be needed for the original problem it was meant to address.
*/ */
else if (!event.pressed && !waiting_buffer_typed(event)) { else if (!event.pressed && !waiting_buffer_typed(event)) {
// Modifier/Layer should be retained till end of this tapping. // Modifier/Layer should be retained till end of this tapping.
@ -271,6 +323,25 @@ bool process_tapping(keyrecord_t *keyp) {
// set interrupted flag when other key pressed during tapping // set interrupted flag when other key pressed during tapping
if (event.pressed) { if (event.pressed) {
tapping_key.tap.interrupted = true; tapping_key.tap.interrupted = true;
# if defined(CHORDAL_HOLD)
if (!is_one_shot(tapping_keycode) && !get_chordal_hold(tapping_keycode, &tapping_key, get_record_keycode(keyp, false), keyp)) {
// In process_action(), HOLD_ON_OTHER_KEY_PRESS
// will revert interrupted events to holds, so
// this needs to be set false.
tapping_key.tap.interrupted = false;
ac_dprintf("Tapping: End. Chord considered a tap\n");
tapping_key.tap.count = 1;
registered_taps_add(tapping_key.event.key);
debug_registered_taps();
process_record(&tapping_key);
tapping_key = (keyrecord_t){0};
// Process regular keys in the waiting buffer.
waiting_buffer_process_regular();
} else
# endif
if (TAP_GET_HOLD_ON_OTHER_KEY_PRESS if (TAP_GET_HOLD_ON_OTHER_KEY_PRESS
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT) # if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
// Auto Shift cannot evaluate this early // Auto Shift cannot evaluate this early
@ -278,12 +349,22 @@ bool process_tapping(keyrecord_t *keyp) {
&& !(MAYBE_RETRO_SHIFTING(event, keyp) && get_auto_shifted_key(get_record_keycode(keyp, false), keyp)) && !(MAYBE_RETRO_SHIFTING(event, keyp) && get_auto_shifted_key(get_record_keycode(keyp, false), keyp))
# endif # endif
) { ) {
// Settle the tapping key as *held*, since
// HOLD_ON_OTHER_KEY_PRESS is enabled for this key.
ac_dprintf("Tapping: End. No tap. Interfered by pressed key\n"); ac_dprintf("Tapping: End. No tap. Interfered by pressed key\n");
process_record(&tapping_key); process_record(&tapping_key);
# ifdef CHORDAL_HOLD
if (waiting_buffer_tail != waiting_buffer_head && is_tap_record(&waiting_buffer[waiting_buffer_tail])) {
tapping_key = waiting_buffer[waiting_buffer_tail];
// Pop tail from the queue.
waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE;
} else
# endif
{
tapping_key = (keyrecord_t){0}; tapping_key = (keyrecord_t){0};
}
debug_tapping_key(); debug_tapping_key();
// enqueue
return false;
} }
} }
// enqueue // enqueue
@ -520,26 +601,91 @@ void waiting_buffer_scan_tap(void) {
} }
} }
/** \brief Tapping key debug print # 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) {
* FIXME: Needs docs 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 void registered_taps_add(keypos_t key) {
if (num_registered_taps >= REGISTERED_TAPS_SIZE) {
ac_dprintf("TAPS OVERFLOW: CLEAR ALL STATES\n");
clear_keyboard();
num_registered_taps = 0;
}
registered_taps[num_registered_taps] = key;
++num_registered_taps;
}
static int8_t registered_tap_find(keypos_t key) {
for (int8_t i = 0; i < num_registered_taps; ++i) {
if (KEYEQ(registered_taps[i], key)) {
return i;
}
}
return -1;
}
static void registered_taps_del_index(uint8_t i) {
if (i < num_registered_taps) {
--num_registered_taps;
if (i < num_registered_taps) {
registered_taps[i] = registered_taps[num_registered_taps];
}
}
}
static void debug_registered_taps(void) {
ac_dprintf("registered_taps = { ");
for (int8_t i = 0; i < num_registered_taps; ++i) {
ac_dprintf("%02X%02X ", registered_taps[i].row, registered_taps[i].col);
}
ac_dprintf("}\n");
}
static void waiting_buffer_process_regular(void) {
for (; waiting_buffer_tail != waiting_buffer_head; waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE) {
if (is_tap_record(&waiting_buffer[waiting_buffer_tail])) {
break; // Stop once a tap-hold key event is reached.
}
ac_dprintf("waiting_buffer_process_regular: processing [%u]\n", waiting_buffer_tail);
process_record(&waiting_buffer[waiting_buffer_tail]);
}
debug_waiting_buffer();
}
# endif // CHORDAL_HOLD
/** \brief Logs tapping key if ACTION_DEBUG is enabled. */
static void debug_tapping_key(void) { static void debug_tapping_key(void) {
ac_dprintf("TAPPING_KEY="); ac_dprintf("TAPPING_KEY=");
debug_record(tapping_key); debug_record(tapping_key);
ac_dprintf("\n"); ac_dprintf("\n");
} }
/** \brief Waiting buffer debug print /** \brief Logs waiting buffer if ACTION_DEBUG is enabled. */
*
* FIXME: Needs docs
*/
static void debug_waiting_buffer(void) { static void debug_waiting_buffer(void) {
ac_dprintf("{"); ac_dprintf("{");
for (uint8_t i = waiting_buffer_tail; i != waiting_buffer_head; i = (i + 1) % WAITING_BUFFER_SIZE) { for (uint8_t i = waiting_buffer_tail; i != waiting_buffer_head; i = (i + 1) % WAITING_BUFFER_SIZE) {
ac_dprintf(" [%u]=", i); ac_dprintf(" [%u]=", i);
debug_record(waiting_buffer[i]); debug_record(waiting_buffer[i]);
ac_dprintf(" ");
} }
ac_dprintf("}\n"); ac_dprintf("}\n");
} }

View File

@ -46,6 +46,73 @@ bool get_permissive_hold(uint16_t keycode, keyrecord_t *record);
bool get_retro_tapping(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); bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record);
#ifdef CHORDAL_HOLD
/**
* Callback to say when a key chord before the tapping term may be held.
*
* In keymap.c, define the callback
*
* bool get_chordal_hold(uint16_t tap_hold_keycode,
* keyrecord_t* tap_hold_record,
* uint16_t other_keycode,
* keyrecord_t* other_record) {
* // Conditions...
* }
*
* This callback is called when:
*
* 1. `tap_hold_keycode` is pressed.
* 2. `other_keycode` is pressed while `tap_hold_keycode` is still held,
* provided `other_keycode` is *not* also a tap-hold key and it is pressed
* before the tapping term.
*
* If false is returned, this has the effect of immediately settling the
* tap-hold key as tapped. If true is returned, the tap-hold key is still
* unsettled, and may be settled as held depending on configuration and
* subsequent events.
*
* @param tap_hold_keycode Keycode of the tap-hold key.
* @param tap_hold_record Record from the tap-hold press event.
* @param other_keycode Keycode of the other key.
* @param other_record Record from the other key's press event.
* @return True if the tap-hold key may be considered held; false if tapped.
*/
bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t *tap_hold_record, uint16_t other_keycode, keyrecord_t *other_record);
/**
* Default "opposite hands rule" for whether a key chord may settle as held.
*
* This function returns true when the tap-hold key and other key are on
* "opposite hands." In detail, handedness of the two keys are compared. If
* handedness values differ, or if either handedness is '*', the function
* returns true, indicating that it may be held. Otherwise, it returns false,
* in which case the tap-hold key is immediately settled at tapped.
*
* @param tap_hold_record Record of the active tap-hold key press.
* @param other_record Record of the other, interrupting key press.
* @return True if the tap-hold key may be considered held; false if tapped.
*/
bool get_chordal_hold_default(keyrecord_t *tap_hold_record, keyrecord_t *other_record);
/**
* Gets the handedness of a key.
*
* This function returns:
* 'L' for keys pressed by the left hand,
* 'R' for keys on the right hand,
* '*' for keys exempt from the "opposite hands rule." This could be used
* perhaps on thumb keys or keys that might be pressed by either hand.
*
* @param key A key matrix position.
* @return Handedness value.
*/
char chordal_hold_handedness(keypos_t key);
# ifdef CHORDAL_HOLD_LAYOUT
extern const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM;
# endif
#endif
#ifdef DYNAMIC_TAPPING_TERM_ENABLE #ifdef DYNAMIC_TAPPING_TERM_ENABLE
extern uint16_t g_tapping_term; extern uint16_t g_tapping_term;
#endif #endif

View File

@ -0,0 +1,21 @@
/* Copyright 2022 Vladislav Kucheriavykh
*
* 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/>.
*/
#pragma once
#include "test_common.h"
#define CHORDAL_HOLD
#define HOLD_ON_OTHER_KEY_PRESS

View File

@ -0,0 +1,16 @@
# Copyright 2022 Vladislav Kucheriavykh
#
# 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/>.
INTROSPECTION_KEYMAP_C = test_keymap.c

View File

@ -0,0 +1,8 @@
#include "quantum.h"
const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = {
{'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
{'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
{'*', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
{'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
};

View File

@ -0,0 +1,581 @@
// Copyright 2024 Google LLC
//
// 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/>.
#include "keyboard_report_util.hpp"
#include "keycode.h"
#include "test_common.hpp"
#include "action_tapping.h"
#include "test_fixture.hpp"
#include "test_keymap_key.hpp"
using testing::_;
using testing::InSequence;
class ChordalHoldHoldOnOtherKeyPress : public TestFixture {};
TEST_F(ChordalHoldHoldOnOtherKeyPress, chord_with_mod_tap_settled_as_hold) {
TestDriver driver;
InSequence s;
// Mod-tap key on the left hand.
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
// Regular key on the right hand.
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_key, regular_key});
// Press mod-tap-hold key.
EXPECT_NO_REPORT(driver);
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press regular key.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A));
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release regular key.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldHoldOnOtherKeyPress, chord_nested_press_settled_as_hold) {
TestDriver driver;
InSequence s;
// Mod-tap key on the left hand.
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
// Regular key on the right hand.
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_key, regular_key});
// Press mod-tap-hold key.
EXPECT_NO_REPORT(driver);
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Tap regular key.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldHoldOnOtherKeyPress, chord_rolled_press_settled_as_hold) {
TestDriver driver;
InSequence s;
// Mod-tap key on the left hand.
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
// Regular key on the right hand.
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_key, regular_key});
// Press mod-tap key.
EXPECT_NO_REPORT(driver);
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press regular key.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A));
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap key.
EXPECT_REPORT(driver, (KC_A));
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release regular key.
EXPECT_EMPTY_REPORT(driver);
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldHoldOnOtherKeyPress, non_chord_with_mod_tap_settled_as_tap) {
TestDriver driver;
InSequence s;
// Mod-tap key and regular key both on the left hand.
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
auto regular_key = KeymapKey(0, 2, 0, KC_A);
set_keymap({mod_tap_key, regular_key});
// Press mod-tap-hold key.
EXPECT_NO_REPORT(driver);
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press regular key.
EXPECT_REPORT(driver, (KC_P));
EXPECT_REPORT(driver, (KC_P, KC_A));
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release regular key.
EXPECT_REPORT(driver, (KC_P));
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldHoldOnOtherKeyPress, tap_mod_tap_key) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
set_keymap({mod_tap_key});
EXPECT_NO_REPORT(driver);
mod_tap_key.press();
idle_for(TAPPING_TERM - 1);
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldHoldOnOtherKeyPress, hold_mod_tap_key) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
set_keymap({mod_tap_key});
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
mod_tap_key.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldHoldOnOtherKeyPress, two_mod_taps_same_hand_hold_til_timeout) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, MATRIX_COLS - 2, 0, RCTL_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B));
set_keymap({mod_tap_key1, mod_tap_key2});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Continue holding til the tapping term.
EXPECT_REPORT(driver, (KC_A, KC_RIGHT_SHIFT));
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release mod-tap keys.
EXPECT_REPORT(driver, (KC_RIGHT_SHIFT));
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldHoldOnOtherKeyPress, two_mod_taps_nested_press_opposite_hands) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B));
set_keymap({mod_tap_key1, mod_tap_key2});
// Press first mod-tap key.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press second mod-tap key.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release second mod-tap key.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release first mod-tap key.
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldHoldOnOtherKeyPress, two_mod_taps_nested_press_same_hand) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 2, 0, RSFT_T(KC_B));
set_keymap({mod_tap_key1, mod_tap_key2});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap keys.
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key2.release();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_same_hand_streak_roll) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B));
auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C));
set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A, KC_B));
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release keys 1, 2, 3.
//
// NOTE: The correct order of events should be
// EXPECT_REPORT(driver, (KC_A));
// EXPECT_REPORT(driver, (KC_A, KC_B));
// EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
// EXPECT_REPORT(driver, (KC_B, KC_C));
// EXPECT_REPORT(driver, (KC_C));
// EXPECT_EMPTY_REPORT(driver);
//
// However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60,
// the events are processed out of order, with the first two keys released
// before pressing KC_C.
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_C));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
mod_tap_key3.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_same_hand_streak_orders) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B));
auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C));
set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
EXPECT_REPORT(driver, (KC_A));
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
// Press mod-tap keys.
mod_tap_key1.press();
run_one_scan_loop();
mod_tap_key2.press();
run_one_scan_loop();
mod_tap_key3.press();
run_one_scan_loop();
// Release keys 3, 2, 1.
mod_tap_key3.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
idle_for(TAPPING_TERM);
// Press mod-tap keys.
mod_tap_key1.press();
run_one_scan_loop();
mod_tap_key2.press();
run_one_scan_loop();
mod_tap_key3.press();
run_one_scan_loop();
// Release keys 3, 1, 2.
mod_tap_key3.release();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// NOTE: The correct order of events should be
// EXPECT_REPORT(driver, (KC_A));
// EXPECT_REPORT(driver, (KC_A, KC_B));
// EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
// EXPECT_REPORT(driver, (KC_A, KC_C));
// EXPECT_REPORT(driver, (KC_A));
// EXPECT_EMPTY_REPORT(driver);
//
// However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60,
// the events are processed out of order, with the first two keys released
// before pressing KC_C.
EXPECT_REPORT(driver, (KC_A));
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_A));
EXPECT_REPORT(driver, (KC_A, KC_C));
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
idle_for(TAPPING_TERM);
// Press mod-tap keys.
mod_tap_key1.press();
run_one_scan_loop();
mod_tap_key2.press();
run_one_scan_loop();
mod_tap_key3.press();
run_one_scan_loop();
// Release keys 2, 3, 1.
mod_tap_key2.release();
run_one_scan_loop();
mod_tap_key3.release();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_two_left_one_right) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B));
auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C));
set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL));
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release key 3.
EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL, KC_C));
EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL));
mod_tap_key3.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release key 2, then key 1.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key2.release();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL));
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release key 3.
EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL, KC_C));
EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL));
mod_tap_key3.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release key 1, then key 2.
EXPECT_REPORT(driver, (KC_LEFT_CTRL));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_one_held_two_tapped) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 2, 0, CTL_T(KC_B));
auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C));
set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release keys 3, 2, 1.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key3.press();
run_one_scan_loop();
mod_tap_key3.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM);
mod_tap_key1.press();
run_one_scan_loop();
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release keys 3, 1, 2.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key3.press();
run_one_scan_loop();
mod_tap_key3.release();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}

View File

@ -0,0 +1,21 @@
/* Copyright 2022 Vladislav Kucheriavykh
*
* 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/>.
*/
#pragma once
#include "test_common.h"
#define CHORDAL_HOLD
#define PERMISSIVE_HOLD

View File

@ -0,0 +1,16 @@
# Copyright 2022 Vladislav Kucheriavykh
#
# 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/>.
INTROSPECTION_KEYMAP_C = test_keymap.c

View File

@ -0,0 +1,8 @@
#include "quantum.h"
const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = {
{'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
{'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
{'*', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
{'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
};

View File

@ -0,0 +1,148 @@
/* Copyright 2021 Stefan Kerkmann
*
* 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/>.
*/
#include "action_util.h"
#include "keyboard_report_util.hpp"
#include "test_common.hpp"
using testing::_;
using testing::InSequence;
class OneShot : public TestFixture {};
class OneShotParametrizedTestFixture : public ::testing::WithParamInterface<std::pair<KeymapKey, KeymapKey>>, public OneShot {};
TEST_P(OneShotParametrizedTestFixture, OSMAsRegularModifierWithAdditionalKeypress) {
TestDriver driver;
KeymapKey osm_key = GetParam().first;
KeymapKey regular_key = GetParam().second;
set_keymap({osm_key, regular_key});
// Press OSM.
EXPECT_NO_REPORT(driver);
osm_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press regular key.
EXPECT_NO_REPORT(driver);
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release regular key.
EXPECT_REPORT(driver, (osm_key.report_code)).Times(2);
EXPECT_REPORT(driver, (regular_key.report_code, osm_key.report_code));
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release OSM.
EXPECT_EMPTY_REPORT(driver);
osm_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
// clang-format off
INSTANTIATE_TEST_CASE_P(
OneShotModifierTests,
OneShotParametrizedTestFixture,
::testing::Values(
// First is osm key, second is regular key.
std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LSFT), KC_LSFT}, KeymapKey{0, 1, 1, KC_A}),
std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LCTL), KC_LCTL}, KeymapKey{0, 1, 1, KC_A}),
std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LALT), KC_LALT}, KeymapKey{0, 1, 1, KC_A}),
std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LGUI), KC_LGUI}, KeymapKey{0, 1, 1, KC_A}),
std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RCTL), KC_RCTL}, KeymapKey{0, 1, 1, KC_A}),
std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RSFT), KC_RSFT}, KeymapKey{0, 1, 1, KC_A}),
std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RALT), KC_RALT}, KeymapKey{0, 1, 1, KC_A}),
std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RGUI), KC_RGUI}, KeymapKey{0, 1, 1, KC_A})
));
// clang-format on
TEST_F(OneShot, OSLWithAdditionalKeypress) {
TestDriver driver;
InSequence s;
KeymapKey osl_key = KeymapKey{0, 0, 0, OSL(1)};
KeymapKey osl_key1 = KeymapKey{1, 0, 0, KC_X};
KeymapKey regular_key0 = KeymapKey{0, 1, 0, KC_Y};
KeymapKey regular_key1 = KeymapKey{1, 1, 0, KC_A};
set_keymap({osl_key, osl_key1, regular_key0, regular_key1});
// Press OSL key.
EXPECT_NO_REPORT(driver);
osl_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release OSL key.
EXPECT_NO_REPORT(driver);
osl_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press regular key.
EXPECT_REPORT(driver, (regular_key1.report_code));
EXPECT_EMPTY_REPORT(driver);
regular_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release regular key.
EXPECT_NO_REPORT(driver);
regular_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(OneShot, OSLWithOsmAndAdditionalKeypress) {
TestDriver driver;
InSequence s;
KeymapKey osl_key = KeymapKey{0, 0, 0, OSL(1)};
KeymapKey osm_key = KeymapKey{1, 1, 0, OSM(MOD_LSFT), KC_LSFT};
KeymapKey regular_key = KeymapKey{1, 1, 1, KC_A};
KeymapKey blank_key = KeymapKey{1, 0, 0, KC_NO};
set_keymap({osl_key, osm_key, regular_key, blank_key});
// Press OSL key.
EXPECT_NO_REPORT(driver);
osl_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release OSL key.
EXPECT_NO_REPORT(driver);
osl_key.release();
run_one_scan_loop();
EXPECT_TRUE(layer_state_is(1));
VERIFY_AND_CLEAR(driver);
// Press and release OSM.
EXPECT_NO_REPORT(driver);
tap_key(osm_key);
EXPECT_TRUE(layer_state_is(1));
VERIFY_AND_CLEAR(driver);
// Tap regular key.
EXPECT_REPORT(driver, (osm_key.report_code, regular_key.report_code));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
}

View File

@ -0,0 +1,746 @@
// Copyright 2024 Google LLC
//
// 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/>.
#include "keyboard_report_util.hpp"
#include "keycode.h"
#include "test_common.hpp"
#include "action_tapping.h"
#include "test_fixture.hpp"
#include "test_keymap_key.hpp"
using testing::_;
using testing::InSequence;
class ChordalHoldPermissiveHold : public TestFixture {};
TEST_F(ChordalHoldPermissiveHold, chordal_hold_handedness) {
EXPECT_EQ(chordal_hold_handedness({.col = 0, .row = 0}), 'L');
EXPECT_EQ(chordal_hold_handedness({.col = MATRIX_COLS - 1, .row = 0}), 'R');
EXPECT_EQ(chordal_hold_handedness({.col = 0, .row = 2}), '*');
}
TEST_F(ChordalHoldPermissiveHold, get_chordal_hold_default) {
auto make_record = [](uint8_t row, uint8_t col, keyevent_type_t type = KEY_EVENT) {
return keyrecord_t{
.event =
{
.key = {.col = col, .row = row},
.type = type,
.pressed = true,
},
};
};
// Create two records on the left hand.
keyrecord_t record_l0 = make_record(0, 0);
keyrecord_t record_l1 = make_record(1, 0);
// Create a record on the right hand.
keyrecord_t record_r = make_record(0, MATRIX_COLS - 1);
// Function should return true when records are on opposite hands.
EXPECT_TRUE(get_chordal_hold_default(&record_l0, &record_r));
EXPECT_TRUE(get_chordal_hold_default(&record_r, &record_l0));
// ... and false when on the same hand.
EXPECT_FALSE(get_chordal_hold_default(&record_l0, &record_l1));
EXPECT_FALSE(get_chordal_hold_default(&record_l1, &record_l0));
// But (2, 0) has handedness '*', for which true is returned for chords
// with either hand.
keyrecord_t record_l2 = make_record(2, 0);
EXPECT_TRUE(get_chordal_hold_default(&record_l2, &record_l0));
EXPECT_TRUE(get_chordal_hold_default(&record_l2, &record_r));
// Create a record resulting from a combo.
keyrecord_t record_combo = make_record(0, 0, COMBO_EVENT);
// Function returns true in all cases.
EXPECT_TRUE(get_chordal_hold_default(&record_l0, &record_combo));
EXPECT_TRUE(get_chordal_hold_default(&record_r, &record_combo));
EXPECT_TRUE(get_chordal_hold_default(&record_combo, &record_l0));
EXPECT_TRUE(get_chordal_hold_default(&record_combo, &record_r));
}
TEST_F(ChordalHoldPermissiveHold, chord_nested_press_settled_as_hold) {
TestDriver driver;
InSequence s;
// Mod-tap key on the left hand.
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
// Regular key on the right hand.
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_key, regular_key});
// Press mod-tap key.
EXPECT_NO_REPORT(driver);
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Tap regular key.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
// Release mod-tap key.
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, chord_rolled_press_settled_as_tap) {
TestDriver driver;
InSequence s;
// Mod-tap key on the left hand.
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
// Regular key on the right hand.
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_key, regular_key});
// Press mod-tap key and regular key.
EXPECT_NO_REPORT(driver);
mod_tap_key.press();
run_one_scan_loop();
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap key.
EXPECT_REPORT(driver, (KC_P));
EXPECT_REPORT(driver, (KC_P, KC_A));
EXPECT_REPORT(driver, (KC_A));
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release regular key.
EXPECT_EMPTY_REPORT(driver);
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, non_chord_with_mod_tap_settled_as_tap) {
TestDriver driver;
InSequence s;
// Mod-tap key and regular key both on the left hand.
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
auto regular_key = KeymapKey(0, 2, 0, KC_A);
set_keymap({mod_tap_key, regular_key});
// Press mod-tap-hold key.
EXPECT_NO_REPORT(driver);
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press regular key.
EXPECT_REPORT(driver, (KC_P));
EXPECT_REPORT(driver, (KC_P, KC_A));
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release regular key.
EXPECT_REPORT(driver, (KC_P));
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, tap_mod_tap_key) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
set_keymap({mod_tap_key});
EXPECT_NO_REPORT(driver);
mod_tap_key.press();
idle_for(TAPPING_TERM - 1);
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, hold_mod_tap_key) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
set_keymap({mod_tap_key});
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
mod_tap_key.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, two_mod_taps_same_hand_hold_til_timeout) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, MATRIX_COLS - 2, 0, RCTL_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B));
set_keymap({mod_tap_key1, mod_tap_key2});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Continue holding til the tapping term.
EXPECT_REPORT(driver, (KC_A, KC_RIGHT_SHIFT));
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release mod-tap keys.
EXPECT_REPORT(driver, (KC_RIGHT_SHIFT));
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, two_mod_taps_nested_press_opposite_hands) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B));
set_keymap({mod_tap_key1, mod_tap_key2});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap keys.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, two_mod_taps_nested_press_same_hand) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 2, 0, RSFT_T(KC_B));
set_keymap({mod_tap_key1, mod_tap_key2});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap keys.
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.release();
run_one_scan_loop();
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, three_mod_taps_same_hand_streak_roll) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B));
auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C));
set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A, KC_B));
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release keys 1, 2, 3.
//
// NOTE: The correct order of events should be
// EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
// EXPECT_REPORT(driver, (KC_B, KC_C));
// EXPECT_REPORT(driver, (KC_C));
// EXPECT_EMPTY_REPORT(driver);
//
// However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60,
// the events are processed out of order, with the first two keys released
// before pressing KC_C.
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_C));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
mod_tap_key3.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, three_mod_taps_same_hand_streak_orders) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B));
auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C));
set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A, KC_B));
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release keys 3, 2, 1.
EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key3.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A, KC_B));
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release keys 3, 1, 2.
EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key3.release();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A, KC_B));
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release keys 2, 3, 1.
//
// NOTE: The correct order of events should be
// EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
// EXPECT_REPORT(driver, (KC_A, KC_C));
// EXPECT_REPORT(driver, (KC_A));
// EXPECT_EMPTY_REPORT(driver);
//
// However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60,
// the events are processed out of order.
EXPECT_REPORT(driver, (KC_A));
EXPECT_REPORT(driver, (KC_A, KC_C));
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key2.release();
run_one_scan_loop();
mod_tap_key3.release();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, three_mod_taps_opposite_hands_roll) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B));
auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C));
set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release keys 1, 2, 3.
//
// NOTE: The correct order of events should be
// EXPECT_REPORT(driver, (KC_A, KC_B));
// EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
// EXPECT_REPORT(driver, (KC_B, KC_C));
// EXPECT_REPORT(driver, (KC_C));
// EXPECT_EMPTY_REPORT(driver);
//
// However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60,
// the events are processed out of order, with the first two keys released
// before pressing KC_C.
// Release keys 1, 2, 3.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_C));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
mod_tap_key3.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, three_mod_taps_two_left_one_right) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B));
auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C));
set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release key 3.
EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL));
EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL, KC_C));
EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL));
mod_tap_key3.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release key 2, then key 1.
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key2.press();
run_one_scan_loop();
EXPECT_NO_REPORT(driver);
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release key 3.
EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL));
EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL, KC_C));
EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL));
mod_tap_key3.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release key 1, then key 2.
EXPECT_REPORT(driver, (KC_LEFT_CTRL));
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, three_mod_taps_one_held_two_tapped) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 2, 0, CTL_T(KC_B));
auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C));
set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
mod_tap_key2.press();
run_one_scan_loop();
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release keys 3, 2, 1.
//
// NOTE: The correct order of events should be
// EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
// EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
// EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C));
// EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
// EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
// EXPECT_EMPTY_REPORT(driver);
//
// However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60,
// the events are processed out of order.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_C));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key3.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM);
mod_tap_key1.press();
run_one_scan_loop();
mod_tap_key2.press();
run_one_scan_loop();
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release keys 3, 1, 2.
//
// NOTE: The correct order of events should be
// EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
// EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
// EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C));
// EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
// EXPECT_REPORT(driver, (KC_B));
// EXPECT_EMPTY_REPORT(driver);
//
// However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60,
// the events are processed out of order.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
EXPECT_REPORT(driver, (KC_B));
EXPECT_REPORT(driver, (KC_B, KC_C));
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key3.release();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHold, two_mod_taps_one_regular_key) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 2, 0, CTL_T(KC_B));
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_C);
set_keymap({mod_tap_key1, mod_tap_key2, regular_key});
// Press keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
mod_tap_key2.press();
run_one_scan_loop();
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_C));
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM);
mod_tap_key1.press();
run_one_scan_loop();
mod_tap_key2.press();
run_one_scan_loop();
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_B));
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}

View File

@ -0,0 +1,27 @@
/* Copyright 2022 Isaac Elenbaas
*
* 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/>.
*/
#pragma once
#include "test_common.h"
#define CHORDAL_HOLD
#define PERMISSIVE_HOLD
#define RETRO_SHIFT 2 * TAPPING_TERM
// releases between AUTO_SHIFT_TIMEOUT and TAPPING_TERM are not tested
#define AUTO_SHIFT_TIMEOUT TAPPING_TERM
#define AUTO_SHIFT_MODIFIERS

View File

@ -0,0 +1,17 @@
# Copyright 2022 Isaac Elenbaas
#
# 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/>.
AUTO_SHIFT_ENABLE = yes
INTROSPECTION_KEYMAP_C = test_keymap.c

View File

@ -0,0 +1,8 @@
#include "quantum.h"
const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = {
{'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
{'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
{'*', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
{'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
};

View File

@ -0,0 +1,419 @@
/* Copyright 2022 Isaac Elenbaas
*
* 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/>.
*/
#include "keyboard_report_util.hpp"
#include "keycode.h"
#include "test_common.hpp"
#include "action_tapping.h"
#include "test_fixture.hpp"
#include "test_keymap_key.hpp"
bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
return true;
}
using testing::_;
using testing::AnyNumber;
using testing::AnyOf;
using testing::InSequence;
class RetroShiftPermissiveHold : public TestFixture {};
TEST_F(RetroShiftPermissiveHold, tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_hold_key, regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Press regular key. */
EXPECT_NO_REPORT(driver);
regular_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release regular key. */
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LCTL, KC_A));
EXPECT_REPORT(driver, (KC_LCTL));
regular_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-hold key. */
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}
TEST_F(RetroShiftPermissiveHold, tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A));
set_keymap({mod_tap_hold_key, mod_tap_regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Press mod-tap-regular key. */
EXPECT_NO_REPORT(driver);
mod_tap_regular_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-regular key. */
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LCTL, KC_A));
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_regular_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-hold key. */
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}
TEST_F(RetroShiftPermissiveHold, tap_regular_key_while_mod_tap_key_is_held_over_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_hold_key, regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Press regular key. */
EXPECT_NO_REPORT(driver);
regular_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release regular key. */
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LCTL, KC_A));
EXPECT_REPORT(driver, (KC_LCTL));
regular_key.release();
run_one_scan_loop();
idle_for(TAPPING_TERM);
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-hold key. */
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}
TEST_F(RetroShiftPermissiveHold, tap_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A));
set_keymap({mod_tap_hold_key, mod_tap_regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Press mod-tap-regular key. */
EXPECT_NO_REPORT(driver);
mod_tap_regular_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-regular key. */
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LCTL, KC_A));
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_regular_key.release();
run_one_scan_loop();
idle_for(TAPPING_TERM);
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-hold key. */
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}
TEST_F(RetroShiftPermissiveHold, hold_regular_key_while_mod_tap_key_is_held_over_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_hold_key, regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Press regular key. */
EXPECT_NO_REPORT(driver);
regular_key.press();
run_one_scan_loop();
idle_for(AUTO_SHIFT_TIMEOUT);
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release regular key. */
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(KC_LCTL, KC_LSFT),
KeyboardReport(KC_LSFT),
KeyboardReport(KC_LCTL))))
.Times(AnyNumber());
// clang-format on
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(KC_LCTL, KC_LSFT),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
EXPECT_REPORT(driver, (KC_LCTL));
regular_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-hold key. */
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}
TEST_F(RetroShiftPermissiveHold, hold_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A));
set_keymap({mod_tap_hold_key, mod_tap_regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Press mod-tap-regular key. */
EXPECT_NO_REPORT(driver);
mod_tap_regular_key.press();
run_one_scan_loop();
idle_for(AUTO_SHIFT_TIMEOUT);
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-regular key. */
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(KC_LCTL, KC_LSFT),
KeyboardReport(KC_LSFT),
KeyboardReport(KC_LCTL))))
.Times(AnyNumber());
// clang-format on
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(KC_LCTL, KC_LSFT),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_regular_key.release();
run_one_scan_loop();
idle_for(TAPPING_TERM);
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-hold key. */
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}
TEST_F(RetroShiftPermissiveHold, roll_tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_hold_key, regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Press regular key. */
EXPECT_NO_REPORT(driver);
regular_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-hold key. */
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release regular key. */
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
regular_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}
TEST_F(RetroShiftPermissiveHold, roll_tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A));
set_keymap({mod_tap_hold_key, mod_tap_regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Press mod-tap-regular key. */
EXPECT_NO_REPORT(driver);
mod_tap_regular_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-hold key. */
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-regular key. */
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
mod_tap_regular_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}
TEST_F(RetroShiftPermissiveHold, roll_hold_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_hold_key, regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Press regular key. */
EXPECT_NO_REPORT(driver);
regular_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-hold key. */
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
idle_for(AUTO_SHIFT_TIMEOUT);
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release regular key. */
EXPECT_NO_REPORT(driver);
regular_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}
TEST_F(RetroShiftPermissiveHold, roll_hold_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A));
set_keymap({mod_tap_hold_key, mod_tap_regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Press mod-tap-regular key. */
EXPECT_NO_REPORT(driver);
mod_tap_regular_key.press();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-hold key. */
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
/* Release mod-tap-regular key. */
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
EXPECT_EMPTY_REPORT(driver);
idle_for(AUTO_SHIFT_TIMEOUT);
mod_tap_regular_key.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}