Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] Add Chordal Hold, an "opposite hands rule" tap-hold option similar to Achordion, Bilateral Combinations. #24560

Open
wants to merge 40 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fa857db
Chordal Hold: restrict what chords settle as hold
getreuer Nov 2, 2024
e0f648d
Chordal Hold: docs and further improvements
getreuer Nov 3, 2024
352f4fa
Fix formatting.
getreuer Nov 3, 2024
ed53b7d
Doc rewording and minor edit.
getreuer Nov 4, 2024
3fdbb07
Support Chordal Hold of multiple tap-hold keys.
getreuer Nov 8, 2024
99d49ac
Fix formatting.
getreuer Nov 8, 2024
da8ccf0
Simplification and additional test.
getreuer Nov 8, 2024
1606d67
Fix formatting.
getreuer Nov 8, 2024
bd7e54a
Tighten tests.
getreuer Nov 11, 2024
6b55824
Add test two_mod_taps_same_hand_hold_til_timeout.
getreuer Nov 14, 2024
e924a0c
Revise handing of pairs of tap-hold keys.
getreuer Nov 16, 2024
fb6c2d8
Generate a default chordal_hold_layout.
getreuer Nov 17, 2024
8f86425
Merge branch 'develop' into chordal_hold
getreuer Nov 17, 2024
5b5ff41
Document chordal_hold_handedness().
getreuer Nov 20, 2024
60e8288
Add license notice to new and branched files in PR.
getreuer Nov 23, 2024
4e46c16
Merge branch 'develop' into chordal_hold
getreuer Nov 23, 2024
a525048
Add `tapping.chordal_hold` property for info.json.
getreuer Nov 23, 2024
4f3f5b3
Update docs/reference_info_json.md
getreuer Nov 28, 2024
234fb97
Merge branch 'develop' into chordal_hold
getreuer Nov 28, 2024
2014205
Revise "hand" jsonschema.
getreuer Nov 28, 2024
355f9f9
Chordal Hold: Improved layout handedness heuristic.
getreuer Dec 5, 2024
788f4aa
Use Optional instead of `| None`.
getreuer Dec 5, 2024
da32973
Refactor to avoid lambdas.
getreuer Dec 5, 2024
ae9fa23
Merge branch 'develop' into chordal_hold
getreuer Dec 5, 2024
c4d9180
Remove trailing comma in chordal_hold_layout.
getreuer Dec 7, 2024
503752a
Minor docs edits.
getreuer Dec 13, 2024
5af4ae7
Merge branch 'develop' into chordal_hold
getreuer Dec 13, 2024
2b1eff2
Revise to allow combining multiple same-hand mods.
getreuer Dec 24, 2024
bb7a3c3
Merge branch 'develop' into chordal_hold
getreuer Dec 24, 2024
4c5f603
Fix formatting.
getreuer Dec 24, 2024
a2bbbfa
Merge branch 'develop' into chordal_hold
getreuer Jan 3, 2025
c0cf45c
Add a couple tests with LT keys.
getreuer Jan 3, 2025
36fdeb3
Remove stale use of CHORDAL_HOLD_LAYOUT.
getreuer Jan 8, 2025
2aacc99
Fix misspelling lastest -> latest
getreuer Jan 8, 2025
11cc997
Merge branch 'develop' into chordal_hold
getreuer Jan 8, 2025
e6ecff4
Merge branch 'develop' into chordal_hold
getreuer Jan 9, 2025
ae88194
Handling tweak for LTs and tests.
getreuer Jan 15, 2025
d23fd44
Fix formatting.
getreuer Jan 15, 2025
f649bc4
More tests with LT keys.
getreuer Jan 16, 2025
3cefbaf
Fix formatting.
getreuer Jan 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions docs/tap_hold.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,167 @@ 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`.
:::

## 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.

* Chordal Hold has no effect when the other key is also a tap-hold key. This is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chordal Hold has no effect when the other key is also a tap-hold key.

Is this a limitation of the implementation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Great comment. I was originally thinking multiple simultaneous tap-hold keys is out of scope of what Chordal Hold needs to consider, since in an input sequence like "A↓, B↓" where A and B are tap-hold keys, A and B can't be settled yet.

But rethinking it, these keys will eventually settle, of course, depending on subsequent events. I've revised so that Chordal Hold considers input sequences involving multiple tap-hold keys and I'm happy how this is working out.

Details:

  1. Consider an input sequence "A↓, B↓, C↓, ..." of all presses of tap-hold keys. If held until the tapping-term, then regardless of handedness, these keys should be settled as held as usual. It is important to preserve this behavior for home row mods so that it is possible to chord multiple mods, e.g. Ctrl + Shift, in one hand.

  2. Another case: consider if within the tapping term a key is released: "A↓, B↓, C↓, C↑". Provided either Permissive Hold or Hold on Other Key Press is enabled, the tap-hold keys A, B preceding C would then be settled as held. With Chordal Hold, perhaps depending on handedness some or all of the keys should be tapped instead.

    I've implemented a general heuristic (waiting_buffer_find_chordal_hold_tap() in the code) to decide this, based on evaluating get_chordal_hold() between successive pairs of keys. Some specific practical cases described for 3-key sequences, though the rule works for any number of keys:

    • If A, B, C are all on the same hand (get_chordal_hold() evaluates false between AB and BC), they are all considered tapped. This is effectively "typing streak" suppression of the hold function. This is cool!

    • If A and B are on one hand and C on the other, then both A and B are held.

    • If A is on one hand and B and C on the other, then just A is held.

  3. Suppose an input sequence of tap-hold presses followed by a press of a regular key Z, like "A↓, B↓, C↓, ..., Z↓". Then, all the same logic as in case 2 applies.

The implementation is more invasive than where this PR started, which makes me nervous. The "state machine" is not easy to reason about or modify, though I'm starting to get a feel for it.

I think you understand this area of the code a lot better than I do. Please let me know whether what I describe here needs further explanation, or seems like a flawed design and/or could be implemented better. I've made an effort to make it clean, been testing Chordal Hold in daily use (typing with it as we speak), and significantly beefed up the unit tests. But surely there's room for improvement. Anyway, I'm excited how this is progressing! 🔥

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading and changing the action_tapping.c file after a long hiatus is non-trivial (to say the least) and I'm trying to get up to speed again to evaluate this PR.

My user space implementation is similar to ZMK's "positional hold-tap" when the following are pressed sequentially within tapping term on the right side of a QWERTY layout:

  1. Press RALT_T(KC_L)
  2. Press RGUI_T(KC_K)
  3. Continue holding both keys down

The host received the following:

KEY-DOWN - QMK: KC_L    Event key: l           Code: KeyL          KeyCode: 76
KEY-DOWN - QMK: KC_RGUI Event key: Meta        Code: MetaRight     KeyCode: 93

But Chordal implementation does not override the first key and will transmit both modifiers:

KEY-DOWN - QMK: KC_RALT Event key: Alt         Code: AltRight      KeyCode: 18
KEY-DOWN - QMK: KC_RGUI Event key: Meta        Code: MetaRight     KeyCode: 93

Will review the changes in more detail to figure this out.

I haven't considered the scenario of more than 2 simultaneous key presses, and it seems that specific use cases will have nuances. This PR might benefit from more end user tests to ensure robustness because support of this feature will land on the Discord server when merged.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the comparison! Yes, that is intended that both mods be held. I just added a unit test to verify this case. This behavior is useful so that one hand can hold Alt + GUI, or some other set of mods, while the other hand types a hotkey or uses the mouse.

OTOH, I can imagine there are other ways to handle those use cases. When using behavior as in your first output, what is a good solution for sending hotkeys like Ctrl+Shift+V? Do you find such multi-mod hotkeys are rare enough in practice that it's not a big deal? or maybe switch over to a Callum-style mod scheme for such things, or something else?

Copy link
Contributor

@filterpaper filterpaper Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your solution includes the following user function:

bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record,
                      uint16_t other_keycode, keyrecord_t* other_record)

In the event of "Ctrl+Shift+V" or any other (frequent) same hand modifier combination, users can use that function to enable specific same hand modifier activation. Layouts such as Colemak that places a lot of frequently used letters on home row and may benefit from a default same-hand tap as default.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gave it some try on typing training via https://www.keybr.com/ - rolls seem really fine and no strange delays.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonderful! That's great to hear. Thank you for the testing =)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried this diff today. This actually is much smoother that Achordion mode which was adding annoying delays on the home row mod. Cannot wait for it to land upstream!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did use it now the last days full time and so far found no issues, at least for my use case with the home row mods this state fully replaces Achordion, thanks a lot. Hope it can be merged without any issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alxzh @christoph-cullmann thank you both for the feedback! I agree, qualitatively this resembles and improves upon Achordion.

so that multiple tap-hold keys may be held on the same hand, which is common
to do with home row mods.

* 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.
getreuer marked this conversation as resolved.
Show resolved Hide resolved

### 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 matrix dimensions. If this is
inaccurate, handedness may be specified in several ways.

The easiest way to specify handedness is by `chordal_hold_layout`. Define in
config.h:

```c
#define CHORDAL_HOLD_LAYOUT
```

Then in keymap.c, define `chordal_hold_layout` in the following form:

```c
const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM =
getreuer marked this conversation as resolved.
Show resolved Hide resolved
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, either `'L'` for left, `'R'` for right, or `'*'` to exempt keys
from the "opposite hands rule." When a key has `'*'` handedness, pressing it
with either hand results in a hold. This could be used perhaps on thumb keys or
other places where you want to allow same-hand chords.

Alternatively, handedness may be defined functionally with
`chordal_hold_handedness_user()`. For example, in keymap.c define:

```c
char chordal_hold_handedness_user(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.
Similarly at the keyboard level, `chordal_hold_handedness_kb()` may be defined
to specify handedness.

getreuer marked this conversation as resolved.
Show resolved Hide resolved
::: 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.
:::


### 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_A):
if (other_keycode == KC_C || other_keycode == KC_V) {
return true;
}
break;

case RCTL_T(KC_SCLN):
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 above, the function may call `get_chordal_hold_default(tap_hold_record,
other_record)` to get the default tap vs. hold decision according to the
opposite hands rule.


## Retro Tapping

To enable `retro tapping`, add the following to your `config.h`:
Expand Down
70 changes: 62 additions & 8 deletions quantum/action_tapping.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,45 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re
}
# endif

# ifdef CHORDAL_HOLD
__attribute__((weak)) bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t *tap_hold_record, uint16_t other_keycode, keyrecord_t *other_record) {
return get_chordal_hold_default(tap_hold_record, other_record);
}

bool get_chordal_hold_default(keyrecord_t *tap_hold_record, keyrecord_t *other_record) {
if (tap_hold_record->event.type != KEY_EVENT || other_record->event.type != KEY_EVENT) {
return true; // Return true on combos or other non-key events.
}

char tap_hold_hand = chordal_hold_handedness_user(tap_hold_record->event.key);
if (tap_hold_hand == '*') {
return true;
}
char other_hand = chordal_hold_handedness_user(other_record->event.key);
return other_hand == '*' || tap_hold_hand != other_hand;
}

__attribute__((weak)) char chordal_hold_handedness_kb(keypos_t key) {
# if defined(SPLIT_KEYBOARD) || ((MATRIX_ROWS) > (MATRIX_COLS))
// If the keyboard is split or if MATRIX_ROWS > MATRIX_COLS, assume that the
// first half of the rows are left and the latter half are right.
return (key.row < (MATRIX_ROWS) / 2) ? /*left*/ 'L' : /*right*/ 'R';
# else
// Otherwise, assume the first half of the cols are left, others are right.
return (key.col < (MATRIX_COLS) / 2) ? /*left*/ 'L' : /*right*/ 'R';
# endif
}

__attribute__((weak)) char chordal_hold_handedness_user(keypos_t key) {
# if defined(CHORDAL_HOLD_LAYOUT)
// If given, read handedness from `chordal_hold_layout` array.
return (char)pgm_read_byte(&chordal_hold_layout[key.row][key.col]);
# else
return chordal_hold_handedness_kb(key);
# endif
}
# endif // CHORDAL_HOLD

# ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
__attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) {
return false;
Expand Down Expand Up @@ -188,7 +227,7 @@ bool process_tapping(keyrecord_t *keyp) {
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;
# endif

Expand Down Expand Up @@ -271,19 +310,34 @@ bool process_tapping(keyrecord_t *keyp) {
// set interrupted flag when other key pressed during tapping
if (event.pressed) {
tapping_key.tap.interrupted = true;
if (TAP_GET_HOLD_ON_OTHER_KEY_PRESS

# if defined(CHORDAL_HOLD)
if (!is_tap_record(keyp) && !get_chordal_hold(tapping_keycode, &tapping_key, get_record_keycode(keyp, true), keyp)) {
// Settle the tapping key as *tapped*, since it
// is not considered a held chord with keyp.
ac_dprintf("Tapping: End. Chord considered a tap\n");
tapping_key.tap.count = 1;
// 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;
process_record(&tapping_key);
debug_tapping_key();
} else
# endif
if (TAP_GET_HOLD_ON_OTHER_KEY_PRESS
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
// Auto Shift cannot evaluate this early
// Retro Shift uses the hold action for all nested taps even without HOLD_ON_OTHER_KEY_PRESS, so this is fine to skip
&& !(MAYBE_RETRO_SHIFTING(event, keyp) && get_auto_shifted_key(get_record_keycode(keyp, false), keyp))
// Auto Shift cannot evaluate this early
// Retro Shift uses the hold action for all nested taps even without HOLD_ON_OTHER_KEY_PRESS, so this is fine to skip
&& !(MAYBE_RETRO_SHIFTING(event, keyp) && get_auto_shifted_key(get_record_keycode(keyp, false), keyp))
# 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");
process_record(&tapping_key);
tapping_key = (keyrecord_t){0};
debug_tapping_key();
// enqueue
return false;
}
}
// enqueue
Expand Down
75 changes: 75 additions & 0 deletions quantum/action_tapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,81 @@ bool get_permissive_hold(uint16_t keycode, keyrecord_t *record);
bool get_retro_tapping(uint16_t keycode, keyrecord_t *record);
bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record);

#ifdef 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.
*
* "Handedness" is determined as follows, in order of decending precedence:
* 1. `chordal_hold_handedness_user()`, if defined.
* 2. `chordal_hold_layout`, if CHORDAL_HOLD_LAYOUT is defined.
* 3. `chordal_hold_handedness_kb()`, if defined.
* 4. fallback assumption based on keyboard matrix dimensions.
*
* @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);

/**
* Keyboard-level callback to determine handedness of a key.
*
* This function should return:
* '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_kb(keypos_t key);
/** User callback to determine handedness of a key. */
char chordal_hold_handedness_user(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
extern uint16_t g_tapping_term;
#endif
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 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/>.

# --------------------------------------------------------------------------------
# Keep this file, even if it is empty, as a marker that this folder contains tests
# --------------------------------------------------------------------------------
Loading