Skip to content

Commit

Permalink
Merge pull request qmk#17715 from FullQueueDeveloper/bilateral-combin…
Browse files Browse the repository at this point in the history
…ations-develop

Bilateral combinations

* https://github.com/qmk/qmk_firmware:
  Update docs/tap_hold.md
  Update docs/tap_hold.md
  cleanup
  example for `get_enable_bilateral_combinations_per_key`
  “bilateral_combinations_left” should be weak. For testing and for keyboards to override
  format
  docs
  per key Bilateral combinations
  copied in code and it compiles.
  WIP: include quantam & main implementation
  • Loading branch information
buztard committed Oct 27, 2022
2 parents dbc9519 + ec2f2e9 commit 5366f51
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 0 deletions.
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`:
```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
```
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 @@ -657,6 +752,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 @@ -112,6 +112,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

0 comments on commit 5366f51

Please sign in to comment.