From 4e0b573f5563c054d2df82aec4b1be052dd0d7c8 Mon Sep 17 00:00:00 2001 From: Manna Harbour <51143715+manna-harbour@users.noreply.github.com> Date: Tue, 22 Sep 2020 15:03:46 +1000 Subject: [PATCH] Import manna-harbour/bilateral-combinations. https://github.com/manna-harbour/qmk_firmware/issues/29 Perhaps this will tame my home row mods? --- docs/tap_hold.md | 24 +++++++++++++ quantum/action.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/docs/tap_hold.md b/docs/tap_hold.md index 094a10753a1a..b9f26d6d5238 100644 --- a/docs/tap_hold.md +++ b/docs/tap_hold.md @@ -417,6 +417,30 @@ 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`. +## Bilateral Combinations + +The last mod-tap hold will be converted to the corresponding mod-tap tap 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. + +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' +``` + ## Retro Tapping To enable `retro tapping`, add the following to your `config.h`: diff --git a/quantum/action.c b/quantum/action.c index 6368f7398c61..5e0e1d75787c 100644 --- a/quantum/action.c +++ b/quantum/action.c @@ -60,6 +60,14 @@ __attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyreco } #endif +#if (BILATERAL_COMBINATIONS + 0) +# include "quantum.h" +#endif + +#ifdef IGNORE_MOD_TAP_INTERRUPT_PER_KEY +__attribute__((weak)) bool get_ignore_mod_tap_interrupt(uint16_t keycode, keyrecord_t *record) { return false; } +#endif + #ifdef RETRO_TAPPING_PER_KEY __attribute__((weak)) bool get_retro_tapping(uint16_t keycode, keyrecord_t *record) { return false; @@ -358,6 +366,68 @@ void register_mouse(uint8_t mouse_keycode, bool pressed) { #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 }; + +static 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) { + dprint("BILATERAL_COMBINATIONS: tap\n"); + if (bilateral_combinations.active) { + if (bilateral_combinations_left(event.key) == bilateral_combinations.left) { +# 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. @@ -403,6 +473,12 @@ void process_action(keyrecord_t *record, action_t action) { } send_keyboard_report(); } +#ifdef BILATERAL_COMBINATIONS + if (!(IS_MODIFIER_KEYCODE(action.key.code) || action.key.code == KC_NO)) { + // regular keycode tap during mod-tap hold + bilateral_combinations_tap(event); + } +#endif register_code(action.key.code); } else { unregister_code(action.key.code); @@ -510,12 +586,20 @@ void process_action(keyrecord_t *record, action_t action) { } else # endif { +# ifdef BILATERAL_COMBINATIONS + // mod-tap tap + bilateral_combinations_tap(event); +# endif ac_dprintf("MODS_TAP: Tap: register_code\n"); register_code(action.key.code); } } else { ac_dprintf("MODS_TAP: No tap: add_mods\n"); register_mods(mods); +# ifdef BILATERAL_COMBINATIONS + // mod-tap hold + bilateral_combinations_hold(action, event); +# endif } } else { if (tap_count > 0) { @@ -536,6 +620,10 @@ void process_action(keyrecord_t *record, action_t action) { } # endif unregister_mods(mods); +# ifdef BILATERAL_COMBINATIONS + // mod-tap release + bilateral_combinations_release(action.key.code); +# endif } } break; @@ -693,6 +781,10 @@ void process_action(keyrecord_t *record, action_t action) { # ifndef NO_ACTION_TAPPING /* tap key */ if (event.pressed) { if (tap_count > 0) { +# ifdef BILATERAL_COMBINATIONS + // layer-tap tap + bilateral_combinations_tap(event); +# endif ac_dprintf("KEYMAP_TAP_KEY: Tap: register_code\n"); register_code(action.layer_tap.code); } else {