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

Bilateral combinations #17715

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
51 changes: 51 additions & 0 deletions docs/tap_hold.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,57 @@ bool get_retro_tapping(uint16_t keycode, keyrecord_t *record) {

[Auto Shift,](feature_auto_shift.md) has its own version of `retro tapping` called `retro shift`. It is extremely similar to `retro tapping`, but holding the key past `AUTO_SHIFT_TIMEOUT` results in the value it sends being shifted. Other configurations also affect it differently; see [here](feature_auto_shift.md#retro-shift) for more information.

## Bilateral Combinations

The last mod-tap hold will be converted to its tap keycode if another key on the same hand is tapped during the hold, unless a key on the other hand is tapped first.

This option can be used to prevent accidental modifier combinations with mod-tap, in particular those caused by rollover on home row mods. As only the last mod-tap hold is affected, it should be enabled after adjusting settings and typing style so that accidental mods happen only occasionally, e.g. with a long enough tapping term, ignore mod tap interrupt, and deliberately brief keypresses.

Example:

- `SFT_T(KC_A)` Down
- wait until the tapping term expires...
- `KC_C` Down
- `KC_C` Up
- `SFT_T(KC_A)` Up

Assuming QWERTY layout with letters `a` and `c` configured for the same hand, this sequence will send capital letter `C`. With bilateral combination enabled, it will send `ac` to the host instead.

To enable bilateral combinations, add the following to your `config.h`:
DanielSincere marked this conversation as resolved.
Show resolved Hide resolved

```c
#define BILATERAL_COMBINATIONS
```

If `BILATERAL_COMBINATIONS` is defined to a value, hold times greater than that value will permit same hand combinations. For example:

```c
#define BILATERAL_COMBINATIONS 500
Comment on lines +487 to +493
Copy link
Member

Choose a reason for hiding this comment

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

Two different states for the same config option is probably going to make data driven config more complicated.

```

To monitor activations in the background, enable debugging, enable the console, enable terminal bell, add `#define DEBUG_ACTION` to `config.h`, and use something like the following shell command line:

```sh
hid_listen | sed -u 's/BILATERAL_COMBINATIONS: change/&\a/g'
```

### Per-key Bilateral Combinations

Enable Per-key Bilateral Combinations by adding `#define BILATERAL_COMBINATIONS_PER_KEY` to your config. Then, in your `keymap.c`, override `get_enable_bilateral_combinations_per_key` to return `false` to opt-out of bilateral enforcement for the given parameters. Or return `true` to opt-into bilateral combinations.

Here is an example implementation. For the Miryoku layout, this allows `GUI+Tab` with one hand to quickly switch apps on Mac. Notice the keycode is the full layer-tap keycode, and not `KC_TAB` keycode only.

```
bool get_enable_bilateral_combinations_per_key(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case LT(MOUSE, KC_TAB):
return false;
default:
return true;
}
}
```

## Why do we include the key record for the per key functions?

One thing that you may notice is that we include the key record for all of the "per key" functions, and may be wondering why we do that.
Expand Down
99 changes: 99 additions & 0 deletions quantum/action.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ __attribute__((weak)) bool pre_process_record_quantum(keyrecord_t *record) {
return true;
}

#if (BILATERAL_COMBINATIONS + 0)
# include "quantum.h"
#endif

/** \brief Called to execute an action.
*
* FIXME: Needs documentation.
Expand Down Expand Up @@ -306,6 +310,78 @@ void register_button(bool pressed, enum mouse_buttons button) {
}
#endif

#ifdef BILATERAL_COMBINATIONS_PER_KEY
__attribute__((weak)) bool get_enable_bilateral_combinations_per_key(uint16_t keycode, keyrecord_t *record) {
return true;
}
#endif

#ifdef BILATERAL_COMBINATIONS
static struct {
bool active;
uint8_t code;
uint8_t tap;
uint8_t mods;
bool left;
# if (BILATERAL_COMBINATIONS + 0)
uint16_t time;
# endif
} bilateral_combinations = {false};

__attribute__((weak)) bool bilateral_combinations_left(keypos_t key) {
# ifdef SPLIT_KEYBOARD
return key.row < MATRIX_ROWS / 2;
# else
if (MATRIX_COLS > MATRIX_ROWS) {
return key.col < MATRIX_COLS / 2;
} else {
return key.row < MATRIX_ROWS / 2;
}
# endif
}

static void bilateral_combinations_hold(action_t action, keyevent_t event) {
dprint("BILATERAL_COMBINATIONS: hold\n");
bilateral_combinations.active = true;
bilateral_combinations.code = action.key.code;
bilateral_combinations.tap = action.layer_tap.code;
bilateral_combinations.mods = (action.kind.id == ACT_LMODS_TAP) ? action.key.mods : action.key.mods << 4;
bilateral_combinations.left = bilateral_combinations_left(event.key);
# if (BILATERAL_COMBINATIONS + 0)
bilateral_combinations.time = event.time;
# endif
}

static void bilateral_combinations_release(uint8_t code) {
dprint("BILATERAL_COMBINATIONS: release\n");
if (bilateral_combinations.active && (code == bilateral_combinations.code)) {
bilateral_combinations.active = false;
}
}

static void bilateral_combinations_tap(keyevent_t event, keyrecord_t *record) {
dprint("BILATERAL_COMBINATIONS: tap\n");
if (bilateral_combinations.active) {
if (bilateral_combinations_left(event.key) == bilateral_combinations.left
# ifdef BILATERAL_COMBINATIONS_PER_KEY
&& get_enable_bilateral_combinations_per_key(get_event_keycode(record->event, false), record)
# endif
) {
# if (BILATERAL_COMBINATIONS + 0)
if (TIMER_DIFF_16(event.time, bilateral_combinations.time) > BILATERAL_COMBINATIONS) {
dprint("BILATERAL_COMBINATIONS: timeout\n");
return;
}
# endif
dprint("BILATERAL_COMBINATIONS: change\n");
unregister_mods(bilateral_combinations.mods);
tap_code(bilateral_combinations.tap);
}
bilateral_combinations.active = false;
}
}
#endif

/** \brief Take an action and processes it.
*
* FIXME: Needs documentation.
Expand Down Expand Up @@ -346,6 +422,12 @@ void process_action(keyrecord_t *record, action_t action) {
}
send_keyboard_report();
}
#ifdef BILATERAL_COMBINATIONS
if (!(IS_MOD(action.key.code) || action.key.code == KC_NO)) {
// regular keycode tap during mod-tap hold
bilateral_combinations_tap(event, record);
}
#endif
register_code(action.key.code);
} else {
unregister_code(action.key.code);
Expand Down Expand Up @@ -460,10 +542,19 @@ void process_action(keyrecord_t *record, action_t action) {
} else
# endif
{

# ifdef BILATERAL_COMBINATIONS
// mod-tap tap
bilateral_combinations_tap(event, record);
# endif
dprint("MODS_TAP: Tap: register_code\n");
register_code(action.key.code);
}
} else {
# ifdef BILATERAL_COMBINATIONS
// mod-tap hold
bilateral_combinations_hold(action, event);
# endif
dprint("MODS_TAP: No tap: add_mods\n");
register_mods(mods);
}
Expand All @@ -479,6 +570,10 @@ void process_action(keyrecord_t *record, action_t action) {
} else {
dprint("MODS_TAP: No tap: add_mods\n");
unregister_mods(mods);
# ifdef BILATERAL_COMBINATIONS
// mod-tap release
bilateral_combinations_release(action.key.code);
# endif
}
}
break;
Expand Down Expand Up @@ -660,6 +755,10 @@ void process_action(keyrecord_t *record, action_t action) {
/* tap key */
if (event.pressed) {
if (tap_count > 0) {
# ifdef BILATERAL_COMBINATIONS
// layer-tap tap
bilateral_combinations_tap(event, record);
# endif
dprint("KEYMAP_TAP_KEY: Tap: register_code\n");
register_code(action.layer_tap.code);
} else {
Expand Down
4 changes: 4 additions & 0 deletions quantum/action.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ bool is_tap_action(action_t action);
void process_record_tap_hint(keyrecord_t *record);
#endif

#ifdef BILATERAL_COMBINATIONS
__attribute__((weak)) bool bilateral_combinations_left(keypos_t key);
#endif

/* debug */
void debug_event(keyevent_t event);
void debug_record(keyrecord_t record);
Expand Down