Compare commits

...

37 Commits

Author SHA1 Message Date
Pascal Getreuer
72465cb045 Merge c0cf45c2414dfa78c6e579d294b311e58a2f2ef9 into 5eb60827d5ba65c7f56307390a499bec75dfea8b 2025-01-04 03:18:29 -08:00
QMK Bot
5eb60827d5 Merge remote-tracking branch 'origin/master' into develop 2025-01-04 11:09:00 +00:00
Tocho Tochev
c9f9475243 Fix durgod k3x0 docs (#24774) 2025-01-04 11:08:26 +00:00
QMK Bot
a25df1ab7a Merge remote-tracking branch 'origin/master' into develop 2025-01-04 08:42:05 +00:00
takashicompany
9dba024d76 Add EE_HANDS for KLEC-02 (#24777)
Add #define EE_HANDS
2025-01-04 09:41:32 +01:00
Pascal Getreuer
c0cf45c241 Add a couple tests with LT keys. 2025-01-03 10:43:59 -08:00
Pascal Getreuer
a2bbbfa5b6 Merge branch 'develop' into chordal_hold 2025-01-02 22:07:42 -08:00
Pascal Getreuer
4c5f603dc4 Fix formatting. 2024-12-24 12:00:00 -08:00
Pascal Getreuer
bb7a3c3011 Merge branch 'develop' into chordal_hold 2024-12-24 11:47:46 -08:00
Pascal Getreuer
2b1eff29c4 Revise to allow combining multiple same-hand mods.
This commit revises Chordal Hold as described in discussion in
https://github.com/qmk/qmk_firmware/pull/24560#discussion_r1894655238

1. In "RCTL_T(KC_A)↓, RSFT_T(KC_C)↓, RCTL_T(KC_A)↑" before the tapping
   term, RCTL_T(KC_A) is settled as tapped.
2. In "RCTL_T(KC_A)↓, RSFT_T(KC_C)↓, RSFT_T(KC_C)↑", both RCTL_T(KC_A)
   and RSFT_T(KC_C) are settled as tapped.
3. In "RCTL_T(KC_A)↓, RSFT_T(KC_C)↓, KC_U↓" (all keys on the same side),
   both RCTL_T(KC_A) and RSFT_T(KC_C) are settled as tapped.
4. In "RCTL_T(KC_A)↓, RSFT_T(KC_C)↓, LSFT_T(KC_T)↓", with the third key
   on the other side, we allow Permissive Hold or Hold On Other Keypress
   to decide how/when to settle the keys.
5. In "RCTL_T(KC_A)↓, RSFT_T(KC_C)↓" held until the tapping term, the
   keys are settled as held.

1–3 provide same-hand roll protection. 4–5 are for combining multiple
same-hand modifiers.

I've updated the unit tests and have been running it on my keyboard, for
a few hours so far, and all seems good. I really like this scheme. It
allows combining same-side mods, yet it also has roll protection on
streaks. For me, this feels like Achordion, but clearly better streak
handling and improved responsiveness.
2024-12-24 11:40:00 -08:00
Pascal Getreuer
5af4ae7c34 Merge branch 'develop' into chordal_hold 2024-12-13 12:01:22 -08:00
Pascal Getreuer
503752a991 Minor docs edits. 2024-12-13 12:00:55 -08:00
Pascal Getreuer
c4d91801b9 Remove trailing comma in chordal_hold_layout. 2024-12-07 09:33:27 -08:00
Pascal Getreuer
ae9fa23395 Merge branch 'develop' into chordal_hold 2024-12-04 21:18:51 -08:00
Pascal Getreuer
da32973054 Refactor to avoid lambdas. 2024-12-04 21:17:01 -08:00
Pascal Getreuer
788f4aa72a Use Optional instead of | None. 2024-12-04 19:45:08 -08:00
Pascal Getreuer
355f9f910e Chordal Hold: Improved layout handedness heuristic.
This commit improves the heuristic used in generate-keyboard-c for
inferring key handedness from keyboard.json geometry data.

Heuristic summary:

1. If the layout is symmetric (e.g. most split keyboards), guess the
   handedness based on the sign of (x - layout_x_midpoint).

2. Otherwise, if the layout has a key of >=6u width, it is probably the
   spacebar. Form a dividing line through the spacebar, nearly vertical
   but with a slight angle to follow typical row stagger.

3. Otherwise, assume handedness based on the widest horizontal
   separation.

I have tested this strategy on a couple dozen keyboards and found it to
work reliably.
2024-12-04 19:34:56 -08:00
Pascal Getreuer
2014205e09 Revise "hand" jsonschema. 2024-11-27 21:30:53 -08:00
Pascal Getreuer
234fb97313 Merge branch 'develop' into chordal_hold 2024-11-27 21:24:24 -08:00
Pascal Getreuer
4f3f5b31ae Update docs/reference_info_json.md 2024-11-27 21:23:37 -08:00
Pascal Getreuer
a525048a5f Add tapping.chordal_hold property for info.json. 2024-11-22 22:45:34 -08:00
Pascal Getreuer
4e46c16be8 Merge branch 'develop' into chordal_hold 2024-11-22 22:33:09 -08:00
Pascal Getreuer
60e8288e58 Add license notice to new and branched files in PR. 2024-11-22 22:32:16 -08:00
Pascal Getreuer
5b5ff41d79 Document chordal_hold_handedness(). 2024-11-19 20:17:44 -08:00
Pascal Getreuer
8f86425ffc Merge branch 'develop' into chordal_hold 2024-11-17 00:14:25 -08:00
Pascal Getreuer
fb6c2d8c6a Generate a default chordal_hold_layout. 2024-11-16 23:56:17 -08:00
Pascal Getreuer
e924a0cd36 Revise handing of pairs of tap-hold keys. 2024-11-15 20:08:52 -08:00
Pascal Getreuer
6b558247db Add test two_mod_taps_same_hand_hold_til_timeout. 2024-11-13 22:47:28 -08:00
Pascal Getreuer
bd7e54a31d Tighten tests. 2024-11-10 21:01:06 -08:00
Pascal Getreuer
1606d67f51 Fix formatting. 2024-11-08 13:36:12 -08:00
Pascal Getreuer
da8ccf0d18 Simplification and additional test. 2024-11-08 13:32:16 -08:00
Pascal Getreuer
99d49aca58 Fix formatting. 2024-11-07 20:47:29 -08:00
Pascal Getreuer
3fdbb079cb Support Chordal Hold of multiple tap-hold keys. 2024-11-07 19:14:30 -08:00
Pascal Getreuer
ed53b7dadc Doc rewording and minor edit. 2024-11-03 20:02:46 -08:00
Pascal Getreuer
352f4fa095 Fix formatting. 2024-11-03 10:21:56 -08:00
Pascal Getreuer
e0f648d260 Chordal Hold: docs and further improvements 2024-11-02 22:58:34 -07:00
Pascal Getreuer
fa857db172 Chordal Hold: restrict what chords settle as hold 2024-11-02 00:00:28 -07:00
29 changed files with 3321 additions and 36 deletions

View File

@ -200,6 +200,7 @@
"SPLIT_WPM_ENABLE": {"info_key": "split.transport.sync.wpm", "value_type": "flag"},
// Tapping
"CHORDAL_HOLD": {"info_key": "tapping.chordal_hold", "value_type": "flag"},
"HOLD_ON_OTHER_KEY_PRESS": {"info_key": "tapping.hold_on_other_key_press", "value_type": "flag"},
"HOLD_ON_OTHER_KEY_PRESS_PER_KEY": {"info_key": "tapping.hold_on_other_key_press_per_key", "value_type": "flag"},
"PERMISSIVE_HOLD": {"info_key": "tapping.permissive_hold", "value_type": "flag"},

View File

@ -422,7 +422,11 @@
"h": {"$ref": "qmk.definitions.v1#/key_unit"},
"w": {"$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",
"enum": ["L", "R", "*"]
}
}
}
}
@ -915,6 +919,7 @@
"tapping": {
"type": "object",
"properties": {
"chordal_hold": {"type": "boolean"},
"force_hold": {"type": "boolean"},
"force_hold_per_key": {"type": "boolean"},
"ignore_mod_tap_interrupt": {"type": "boolean"},

View File

@ -74,6 +74,8 @@ You can create `info.json` files at every level under `qmk_firmware/keyboards/<k
* The delay between keydown and keyup for tap events in milliseconds.
* Default: `0` (no delay)
* `tapping`
* `chordal_hold` <Badge type="info">Boolean</Badge>
* Default: `false`
* `hold_on_other_key_press` <Badge type="info">Boolean</Badge>
* Default: `false`
* `hold_on_other_key_press_per_key` <Badge type="info">Boolean</Badge>
@ -328,6 +330,8 @@ The ISO enter key is represented by a 1.25u×2uh key. Renderers which utilize in
* `h` <Badge type="info">KeyUnit</Badge>
* The height of the key, in key units.
* Default: `1` (1u)
* `hand` <Badge type="info">String</Badge>
* The handedness of the key for Chordal Hold, either `"L"` (left hand), `"R"` (right hand), or `"*"` (either or exempted handedness).
* `label` <Badge type="info">String</Badge>
* What to name the key. This is *not* a key assignment as in the keymap, but should usually correspond to the keycode for the first layer of the default keymap.
* Example: `"Escape"`

View File

@ -425,6 +425,169 @@ 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.
* 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 when `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 when 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` allows the chord to be 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.
## Retro Tapping
To enable `retro tapping`, add the following to your `config.h`:

View File

@ -6,7 +6,7 @@ This is a standard off-the-shelf Durgod Taurus K310 full-sized 104/105-key
keyboard without backlight. This supports both the ANSI and ISO variants.
* Keyboard Maintainers: [dkjer](https://github.com/dkjer) and [tylert](https://github.com/tylert)
* Hardware Supported: [Durgod Taurus K310 board with STM32F070RBT6](https://www.durgod.com/page9?product_id=53&_l=en "Durgod.com Product Page")
* Hardware Supported: [Durgod Taurus K310 board with STM32F070RBT6](https://www.durgod.com/product/k310-space-gray/)
* Hardware Availability: [Amazon.com](https://www.amazon.com/Durgod-Taurus-K310-Mechanical-Keyboard/dp/B07TXB4XF3)
## Instructions

View File

@ -6,7 +6,7 @@ This is a standard off-the-shelf Durgod Taurus K320 TKL (87/88-key)
keyboard without backlight. This supports both the ANSI and ISO variants.
* Keyboard Maintainers: [dkjer](https://github.com/dkjer) and [tylert](https://github.com/tylert)
* Hardware Supported: [Durgod Taurus K320 board with STM32F070RBT6](https://www.durgod.com/page9?product_id=47&_l=en "Durgod.com Product Page")
* Hardware Supported: [Durgod Taurus K320 board with STM32F070RBT6](https://www.durgod.com/product/k320-space-gray/)
* Hardware Availability: [Amazon.com](https://www.amazon.com/Durgod-Taurus-Corona-Mechanical-Keyboard/dp/B078H3WPHM)
## Instructions
@ -15,11 +15,11 @@ keyboard without backlight. This supports both the ANSI and ISO variants.
Make command example for this keyboard (after setting up your build environment):
make durgod/k3x0/k320/base:default
make durgod/k320/base:default
Flashing example for this keyboard:
make durgod/k3x0/k320/base:default:flash
make durgod/k320/base:default:flash
See the [build environment setup](https://docs.qmk.fm/#/getting_started_build_tools) and the [make instructions](https://docs.qmk.fm/#/getting_started_make_guide) for more information. Brand new to QMK? Start with our [Complete Newbs Guide](https://docs.qmk.fm/#/newbs).

View File

@ -5,8 +5,8 @@ K310 full-sized 104/105-key and K320 TKL 87/88-key keyboards.
* Keyboard Maintainers: [dkjer](https://github.com/dkjer) and [tylert](https://github.com/tylert)
* Hardware Supported:
* [Durgod Taurus K310 with STM32F070RBT6](https://www.durgod.com/page9?product_id=53&_l=en "Taurus K310 Product Page | Durgod.com")
* [Durgod Taurus K320 with STM32F070RBT6](https://www.durgod.com/page9?product_id=47&_l=en "Taurus K320 Product Page | Durgod.com")
* [Durgod Taurus K310 with STM32F070RBT6](https://www.durgod.com/product/k310-space-gray/)
* [Durgod Taurus K320 with STM32F070RBT6](https://www.durgod.com/product/k320-space-gray/)
* Hardware Availability:
* [K310 on Amazon.com](https://www.amazon.com/Durgod-Taurus-K310-Mechanical-Keyboard/dp/B07TXB4XF3)
* [K320 on Amazon.com](https://www.amazon.com/Durgod-Taurus-Corona-Mechanical-Keyboard/dp/B078H3WPHM)
@ -16,8 +16,8 @@ K310 full-sized 104/105-key and K320 TKL 87/88-key keyboards.
### Build
Instructions for building the K310 and K320 firmware can be found here:
* [K310](k310/readme.md)
* [K320](k320/readme.md)
* [K310](../k310/readme.md)
* [K320](../k320/readme.md)
### Initial Flash
@ -67,10 +67,10 @@ dfu-util -a 0 -d 0483:DF11 -s 0x08000000 -U k3x0_original.bin
```bash
# k310
qmk flash -kb durgod/k3x0/k310 -km default
qmk flash -kb durgod/k310 -km default
# k320
qmk flash -kb durgod/k3x0/k320 -km default
qmk flash -kb durgod/k320 -km default
```
### Subsequent Flashing

View File

@ -0,0 +1,6 @@
// Copyright 2024 takashicompany (@takashicompany)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#define EE_HANDS

View File

@ -1,5 +1,9 @@
"""Used by the make system to generate keyboard.c from info.json.
"""
import bisect
import dataclasses
from typing import Optional
from milc import cli
from qmk.info import info_json
@ -87,6 +91,7 @@ def _gen_matrix_mask(info_data):
lines.append(f' 0b{"".join(reversed(mask[i]))},')
lines.append('};')
lines.append('#endif')
lines.append('')
return lines
@ -122,6 +127,128 @@ def _gen_joystick_axes(info_data):
lines.append('};')
lines.append('#endif')
lines.append('')
return lines
@dataclasses.dataclass
class LayoutKey:
"""Geometric info for one key in a layout."""
row: int
col: int
x: float
y: float
w: float = 1.0
h: float = 1.0
hand: Optional[str] = None
@staticmethod
def from_json(key_json):
row, col = key_json['matrix']
return LayoutKey(
row=row,
col=col,
x=key_json['x'],
y=key_json['y'],
w=key_json.get('w', 1.0),
h=key_json.get('h', 1.0),
hand=key_json.get('hand', None),
)
@property
def cx(self):
"""Center x coordinate of the key."""
return self.x + self.w / 2.0
@property
def cy(self):
"""Center y coordinate of the key."""
return self.y + self.h / 2.0
class Layout:
"""Geometric info of a layout."""
def __init__(self, layout_json):
self.keys = [LayoutKey.from_json(key_json) for key_json in layout_json['layout']]
self.x_min = min(key.cx for key in self.keys)
self.x_max = max(key.cx for key in self.keys)
self.x_mid = (self.x_min + self.x_max) / 2
# If there is one key with width >= 6u, it is probably the spacebar.
i = [i for i, key in enumerate(self.keys) if key.w >= 6.0]
self.spacebar = self.keys[i[0]] if len(i) == 1 else None
def is_symmetric(self, tol: float = 0.02):
"""Whether the key positions are symmetric about x_mid."""
x = sorted([key.cx for key in self.keys])
for i in range(len(x)):
x_i_mirrored = 2.0 * self.x_mid - x[i]
# Find leftmost x element greater than or equal to (x_i_mirrored - tol).
j = bisect.bisect_left(x, x_i_mirrored - tol)
if j == len(x) or abs(x[j] - x_i_mirrored) > tol:
return False
return True
def widest_horizontal_gap(self):
"""Finds the x midpoint of the widest horizontal gap between keys."""
x = sorted([key.cx for key in self.keys])
x_mid = self.x_mid
max_sep = 0
for i in range(len(x) - 1):
sep = x[i + 1] - x[i]
if sep > max_sep:
max_sep = sep
x_mid = (x[i + 1] + x[i]) / 2
return x_mid
def _gen_chordal_hold_layout(info_data):
"""Convert info.json content to chordal_hold_layout
"""
# NOTE: If there are multiple layouts, only the first is read.
for layout_name, layout_json in info_data['layouts'].items():
layout = Layout(layout_json)
break
if layout.is_symmetric():
# If the layout is symmetric (e.g. most split keyboards), guess the
# handedness based on the sign of (x - layout.x_mid).
hand_signs = [key.x - layout.x_mid for key in layout.keys]
elif layout.spacebar is not None:
# If the layout has a spacebar, form a dividing line through the spacebar,
# nearly vertical but with a slight angle to follow typical row stagger.
x0 = layout.spacebar.cx - 0.05
y0 = layout.spacebar.cy - 1.0
hand_signs = [(key.x - x0) - (key.y - y0) / 3.0 for key in layout.keys]
else:
# Fallback: assume handedness based on the widest horizontal separation.
x_mid = layout.widest_horizontal_gap()
hand_signs = [key.x - x_mid for key in layout.keys]
for key, hand_sign in zip(layout.keys, hand_signs):
if key.hand is None:
if key == layout.spacebar or abs(hand_sign) <= 0.02:
key.hand = '*'
else:
key.hand = 'L' if hand_sign < 0.0 else 'R'
lines = []
lines.append('#ifdef CHORDAL_HOLD')
line = ('__attribute__((weak)) const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = ' + layout_name + '(')
x_prev = None
for key in layout.keys:
if x_prev is None or key.x < x_prev:
lines.append(line)
line = ' '
line += f"'{key.hand}', "
x_prev = key.x
lines.append(line[:-2])
lines.append(');')
lines.append('#endif')
return lines
@ -136,11 +263,12 @@ def generate_keyboard_c(cli):
kb_info_json = info_json(cli.args.keyboard)
# 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_h_lines.extend(_gen_matrix_mask(kb_info_json))
keyboard_h_lines.extend(_gen_joystick_axes(kb_info_json))
keyboard_c_lines.extend(_gen_led_configs(kb_info_json))
keyboard_c_lines.extend(_gen_matrix_mask(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
dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet)
dump_lines(cli.args.output, keyboard_c_lines, cli.args.quiet)

File diff suppressed because it is too large Load Diff

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_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
extern uint16_t g_tapping_term;
#endif

View File

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

View File

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

View File

@ -0,0 +1,22 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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,168 @@
/* Copyright 2021 Stefan Kerkmann
* 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 "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, OSMWithAdditionalKeypress) {
TestDriver driver;
KeymapKey osm_key = GetParam().first;
KeymapKey regular_key = GetParam().second;
set_keymap({osm_key, regular_key});
// Press and release OSM.
EXPECT_NO_REPORT(driver);
tap_key(osm_key);
VERIFY_AND_CLEAR(driver);
// Press regular key.
EXPECT_REPORT(driver, (osm_key.report_code, regular_key.report_code));
regular_key.press();
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_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();
// Press regular key.
regular_key.press();
run_one_scan_loop();
// Release regular key.
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release OSM.
EXPECT_REPORT(driver, (regular_key.report_code, osm_key.report_code));
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,264 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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 ChordalHoldDefault : public TestFixture {};
TEST_F(ChordalHoldDefault, chord_nested_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.
EXPECT_NO_REPORT(driver);
mod_tap_key.press();
run_one_scan_loop();
// Tap regular key.
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
// Release mod-tap key.
EXPECT_REPORT(driver, (KC_P));
EXPECT_REPORT(driver, (KC_P, KC_A));
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldDefault, 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(ChordalHoldDefault, 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(ChordalHoldDefault, 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(ChordalHoldDefault, 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(ChordalHoldDefault, 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();
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Continue holding til the tapping term.
EXPECT_REPORT(driver, (KC_RIGHT_CTRL));
EXPECT_REPORT(driver, (KC_RIGHT_CTRL, 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(ChordalHoldDefault, 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();
mod_tap_key2.press();
run_one_scan_loop();
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_A));
EXPECT_REPORT(driver, (KC_A, KC_B));
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(ChordalHoldDefault, tap_regular_key_while_layer_tap_key_is_held) {
TestDriver driver;
InSequence s;
auto layer_tap_hold_key = KeymapKey(0, 1, 0, LT(1, KC_P));
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
auto layer_key = KeymapKey(1, MATRIX_COLS - 1, 0, KC_B);
set_keymap({layer_tap_hold_key, regular_key, layer_key});
EXPECT_NO_REPORT(driver);
layer_tap_hold_key.press(); // Press layer-tap-hold key.
run_one_scan_loop();
regular_key.press(); // Press regular key.
run_one_scan_loop();
regular_key.release(); // Release regular key.
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_REPORT(driver, (KC_P, KC_A));
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
layer_tap_hold_key.release(); // Release layer-tap-hold key.
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}

View File

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

View File

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

View File

@ -0,0 +1,22 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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,22 @@
/* Copyright 2022 Vladislav Kucheriavykh
* 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/>.
*/
#pragma once
#include "test_common.h"
#define CHORDAL_HOLD
#define PERMISSIVE_HOLD

View File

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

View File

@ -0,0 +1,22 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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,174 @@
/* Copyright 2021 Stefan Kerkmann
* 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 "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, OSMWithAdditionalKeypress) {
TestDriver driver;
KeymapKey osm_key = GetParam().first;
KeymapKey regular_key = GetParam().second;
set_keymap({osm_key, regular_key});
// Press and release OSM.
EXPECT_NO_REPORT(driver);
tap_key(osm_key);
VERIFY_AND_CLEAR(driver);
// Press regular key.
EXPECT_REPORT(driver, (osm_key.report_code, regular_key.report_code));
regular_key.press();
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_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);
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,22 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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'},
};