Open sunaku opened 1 year ago
I have fixed some corner cases, simplified the configuration, improved chording support, and upgraded to QMK 0.19.10. Eager mods are now enabled by default (so that mod-clicks Just Work out of the box) but you can delay them via #define
settings.
BILATERAL_COMBINATIONS_CHORDSIZE
setting since it's now hard-coded to the maximum allowed value in QMK.BILATERAL_COMBINATIONS_EAGERMODS
setting since "eager mods" are now always enabled by default.BILATERAL_COMBINATIONS_EAGERMASK
setting to BILATERAL_COMBINATIONS_DELAY_MODS_THAT_MATCH
and thereby inverted its meaning (it now specifies modifiers that should be delayed, not made eager).BILATERAL_COMBINATIONS_DEFERMODS
setting to BILATERAL_COMBINATIONS_DELAY_MATCHED_MODS_BY
.BILATERAL_COMBINATIONS_CROSSOVER
setting to BILATERAL_COMBINATIONS_ALLOW_CROSSOVER_AFTER
.BILATERAL_COMBINATIONS_SAMESIDED
setting to BILATERAL_COMBINATIONS_ALLOW_SAMESIDED_AFTER
.For example, here is a diff showing how my personal configuration settings have changed since I originally submitted this PR:
-#define BILATERAL_COMBINATIONS_EAGERMODS 1
-#define BILATERAL_COMBINATIONS_EAGERMASK (~MOD_MASK_GUI)
-#define BILATERAL_COMBINATIONS_DEFERMODS 100
-#define BILATERAL_COMBINATIONS_CROSSOVER 75
-#define BILATERAL_COMBINATIONS_SAMESIDED 3000
-#define BILATERAL_COMBINATIONS_CHORDSIZE 4 // one side GUI, Alt, Shift, Control
+#define BILATERAL_COMBINATIONS_DELAY_MODS_THAT_MATCH MOD_MASK_GUI
+#define BILATERAL_COMBINATIONS_DELAY_MATCHED_MODS_BY 100
+#define BILATERAL_COMBINATIONS_ALLOW_CROSSOVER_AFTER 75
+#define BILATERAL_COMBINATIONS_ALLOW_SAMESIDED_AFTER 3000
I have also updated the original description of this PR (located at the top of this page) with this new information accordingly.
Inspired by ZMK's global-quick-tap feature, I've implemented a typing streak timeout setting that suppresses home row mods while actively typing:
#define BILATERAL_COMBINATIONS_TYPING_STREAK_TIMEOUT 160 /* ms */
However, this tends to obstruct the Shift modifier when typing parentheses or punctuation marks such as !
and ?
at the end of a sentence; and it requires a dedicated Shift key as a workaround, per @urob's "timeless" mods for ZMK. So I went further and exempted Shift modifiers from typing streaks in a bitwise mask:
#define BILATERAL_COMBINATIONS_TYPING_STREAK_MODMASK (~MOD_MASK_SHIFT)
With all this, typing feels natural again! π€― No more unconscious fears about accidentally triggering home row mods. π Itβs a complete game changer! π€©
I've added a Tutorial section to this PR's description to make it easier for newcomers to try this out from scratch, as follows. Although this long-lived PR may seem complex, the underlying patch for this PR (compared to QMK mainline) is much simpler.
Clone a fresh copy of QMK and merge this PR (or skip down to the git remote
command if you already have a QMK clone):
$ git clone https://github.com/qmk/qmk_firmware --recurse-submodules --shallow-submodules
$ cd qmk_firmware
$ git remote add sunaku https://github.com/sunaku/qmk_firmware.git
$ git fetch sunaku miryoku_bilateral
$ git merge sunaku/miryoku_bilateral --no-edit
Add this line to your keyboard's specific rules.mk
file:
DEFERRED_EXEC_ENABLE = yes
Add this snippet to your keyboard's specific config.h
file:
/* QMK */
#define TAPPING_TERM 200
#define IGNORE_MOD_TAP_INTERRUPT /* for rolling on mod-tap keys */
/* Miryoku */
#define BILATERAL_COMBINATIONS_LIMIT_CHORD_TO_N_KEYS 4 /* GUI, Alt, Ctrl, Shift */
#define BILATERAL_COMBINATIONS_DELAY_MODS_THAT_MATCH MOD_MASK_GUI
#define BILATERAL_COMBINATIONS_DELAY_MATCHED_MODS_BY 120 /* ms */
#define BILATERAL_COMBINATIONS_ALLOW_CROSSOVER_AFTER 80 /* ms */
#define BILATERAL_COMBINATIONS_ALLOW_SAMESIDED_AFTER 3000 /* ms */
#define BILATERAL_COMBINATIONS_TYPING_STREAK_TIMEOUT 160 /* ms */
#define BILATERAL_COMBINATIONS_TYPING_STREAK_MODMASK (~MOD_MASK_SHIFT)
Finally, build your keyboard's specific firmware using QMK toolbox, as usual.
I'm also maintaining a Vial version of this PR as an alternative for those who can't use the native QMK version of this PR. :gift:
Thank you so much for sharing this! I've run into one minor annoyance that you might already be aware of. I'm using PERMISSIVE_HOLD
together with IGNORE_MOD_TAP_INTERRUPT
, and it seems like all the normal nested taps are working correctly, but the nested tap of Shift + '
is not sending "
as expected. I need to actually wait the full TAPPING_TERM
in order to do the double quote. Here's my full config.h
file:
// Copyright 2019 Manna Harbour
// https://github.com/manna-harbour/miryoku
// 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 "custom_config.h"
// default but used in macros
#undef TAPPING_TERM
// 200ms = 60wpm
#define TAPPING_TERM 200
// Prevent normal rollover on alphas from accidentally triggering mods.
#define IGNORE_MOD_TAP_INTERRUPT
// Allow nested taps to register instantly
#define PERMISSIVE_HOLD
// Enable rapid switch from tap to hold, disables double tap hold auto-repeat.
#define QUICK_TAP_TERM 0
// Auto Shift
#define NO_AUTO_SHIFT_ALPHA
#define AUTO_SHIFT_TIMEOUT TAPPING_TERM
#define AUTO_SHIFT_NO_SETUP
// Mouse key speed and acceleration.
#undef MOUSEKEY_DELAY
#define MOUSEKEY_DELAY 0
#undef MOUSEKEY_INTERVAL
#define MOUSEKEY_INTERVAL 16
#undef MOUSEKEY_WHEEL_DELAY
#define MOUSEKEY_WHEEL_DELAY 0
#undef MOUSEKEY_MAX_SPEED
#define MOUSEKEY_MAX_SPEED 6
#undef MOUSEKEY_TIME_TO_MAX
#define MOUSEKEY_TIME_TO_MAX 64
#define BILATERAL_COMBINATIONS_LIMIT_CHORD_TO_N_KEYS 4 /* GUI, Alt, Ctrl, Shift */
// Must be > 0, but don't want this limited really at all
#define BILATERAL_COMBINATIONS_ALLOW_CROSSOVER_AFTER 1 /* ms */
#define BILATERAL_COMBINATIONS_ALLOW_SAMESIDED_AFTER 3000 /* ms */
// 200ms = 60wpm
#define BILATERAL_COMBINATIONS_TYPING_STREAK_TIMEOUT 200 /* ms */
#define BILATERAL_COMBINATIONS_TYPING_STREAK_MODMASK (~MOD_MASK_SHIFT)
#define BILATERAL_COMBINATIONS
// Thumb Combos
#if defined (MIRYOKU_KLUDGE_THUMBCOMBOS)
#define COMBO_COUNT 8
#define COMBO_TERM 200
#define EXTRA_SHORT_COMBOS
#endif
Please let me know if you're not able to reproduce the issue, happy to share more info if needed!
EDIT: Curiously, the same also applies to Shift+,
for <
, but not Shift+.
for >
or Shift+/
for ?
.
Interesting, I can't imagine why only a subset of shifted combinations would be affected. Try disabling the AutoShift feature?
Ah, that fixed it! ππ»
There's one other minor annoyance I've come across that might be more directly related to the bilateral combinators. If I hold β and press tab a few times, then decide I want to also hold β§ in addition to (but without releasing) β while pressing tab to go backwards in the quick switcher, the β§ key is not registering at all. I wondered if it was because it was within the BILATERAL_COMBINATIONS_ALLOW_SAMESIDED_AFTER
but after holding shift for 3 seconds it doesn't seem to matter, the β§ key press is still not recognized. I also tried compiling without #define BILATERAL_COMBINATIONS_TYPING_STREAK_MODMASK (~MOD_MASK_SHIFT)
, but that didn't seem to matter either.
If I remove all of the BILATERAL_COMBINATIONS*
definitions though, it works as expected.
If this never gets fixed I will just live with it, this typing experience is that good! π€©
Thanks again for making it!
I was able to reproduce your issue: since your configuration didn't define it, a default catch-all value was being supplied for BILATERAL_COMBINATIONS_DELAY_MODS_THAT_MATCH
that wrongfully matched all modifiers. Fixed now in commit a92f39e9e79787ac6ae8a48a262c7b4f7e78261b.
Amazing! Thanks again! π₯°
Hi, i'm having the issue you've mentioned in https://github.com/manna-harbour/qmk_firmware/pull/48#issuecomment-1275291610 where holding a mod-tap button i still have to wait for the tapping term timeout before i can ctrl+click. Is that intended ?
Anyone else having this issue? I have MT(MOD_LCTL, KC_ESC)
on capslock and in combination with this patch when holding the capslock key + A for example ALSO sends the escape keycode:
holding a mod-tap button i still have to wait for the tapping term timeout before i can ctrl+click
That's correct: modifiers are activated only after holding down for the initial TAPPING_TERM, and then the same-sided and crossover timeouts are stacked on top of TAPPING_TERM. Notably, the TAPPING_TERM still governs how long you need to hold a mod-tap key in order to make QMK trigger its hold behavior (as opposed to its tap behavior). Thereafter, my enhancement kicks in and performs further processing---so it's stacked on top of the already expended TAPPING_TERM.
For example, imagine that TAPPING_TERM was 1 and SAMESIDED was 2 and CROSSOVER was 4. Then, to activate same-sided mods you would need to hold the mod-tap key for 1 + 2 = 3 milliseconds. Similarly, to activate crossover mods, you would need to hold the mod-tap key for 1 + 4 = 5 milliseconds. However, note that the mod is eagerly applied as soon as the mod-tap key is held for 1ms --- meaning that there's no extra waiting for mod activation. This allows for fast mod-click mouse usage, such as Shift-clicking multiple items in a file manager.
Nice! Just started to use it, works well so far! but I think you should add #define BILATERAL_COMBINATIONS
to usage where you show your config.
Hello,
Thank you for your work.
Unfortunatelly, the merge fails on my fresh qmk clone...
$ git merge sunaku/miryoku_bilateral --no-edit
Auto-merging docs/tap_hold.md
Auto-merging quantum/action.c
CONFLICT (content): Merge conflict in quantum/action.c
Automatic merge failed; fix conflicts and then commit the result.
Probably an upstream update that conflicts. Could you pull this change ?
Thank you !
Hi, have you interest in try to upstream that to QMK? I think that would be appreciated by a lot of people.
I very much support the notion of upstreaming this feature. Is there anything that would help you in this regard?
Is there any way that I can restrict the bilinear combinations from some keys? For instance, I want to be able to press my tab key when holding down both of my alt keycodes, but right now I can only do it whilst holding down my right alt.
Hey everyone, thanks for your interest in upstreaming this patch to QMK -- I'm short on free time (which is otherwise preoccupied with my ZMK port of this home row mods disambiguation logic, specifically for my new keyboard) so I hope to revisit this by the summer. Cheers.
Is there any way that I can restrict the bilinear combinations from some keys? For instance, I want to be able to press my tab key when holding down both of my alt keycodes, but right now I can only do it whilst holding down my right alt.
Yes, you should be able to edit the bilateral_combinations_left()
function body (or the calls to it) to handle your scenario.
fyi the current merge conflict is trivial to resolve. Just need to delete the conflict markers to resolve. IGNORE_MOD_TAP_INTERRUPT no longer exists in upstream qmk. It is now default. My branch fixes it https://github.com/sunaku/qmk_firmware/pull/1
People coming across this thread may also be interested in Achordion for a user-space library (patchless/standard QMK) with similar goals to this patch.
I'm noticing, on Dvorak, that I can't use the left-side shift to type capital D, B, or F. However, using the right-side shift for those keys works. It's as if those keys are interpreted as being on the left side of the keyboard. I don't have this issue with I, X, or Y combined with right-side shift.
any chance getting this more updated per @BlueDrink9 's comment? https://github.com/manna-harbour/qmk_firmware/pull/56#issuecomment-1963051827
it's easy as to do yourself fyi. Alternatively, I've found achordion a really good replacement
This patch enhances home row mods & chords handling to avoid accidental misfires for a more natural typing experience. :relieved::raised_hands: Don't be dismayed by GitHub's display of "10K+ commits" and "5K+ files changed" in this PR: π refer to the raw diff instead.
:star:Note: I've created a Vial version of this PR as well as a native QMK version of this PR to make it easy for users to try it out. :gift:
This PR supersedes #48 and #54 by implementing configurable multi-mod chord taps with uni/bilateral handling, delayed mods to solve #27, "eager mods" for mod-click mouse usage, and typing streaks for a natural typing rhythm. :star_struck: See the Flowchart below for a visualization of this PR's logic (see my article for its motivation) and the Documentation below for each
#define
.Usage #
Here is the relevant portion of my
config.h
file which activates these features to provide the best typing experience I've felt since switching to Miryoku's home row mods 2+ years ago, as detailed in Taming home row mods with Bilateral Combinations:You also need to add the following line to your
rules.mk
file to enable QMK's deferred execution for delayed mod activation:Tutorial #
Clone a fresh copy of QMK and merge this PR into it (or skip down to the
git remote
command if you already have a clone):Now follow the instructions in the Usage section above:
config.h
file.rules.mk
file.Finally, build your keymap's specific firmware using
make
or QMK toolbox as usual.Flowchart #
Three main user actions drive the logic: tapping, holding, and releasing keys. Everything happens depending on what keys they hold and how long they hold them.
#define
settings, documented individually below.Documentation #
To enable bilateral combinations:
config.h
file:rules.mk
file to enable QMK's deferred execution facility.To enable same-sided combinations (which start on one side of the keyboard and end on the same side, such as
RSFT_T(KC_J)
andRCTL_T(KC_K)
in the abbreviation "jk" which stands for "just kidding"), add the following line to yourconfig.h
and define a value: hold times greater than that value will permit same-sided combinations. For example, if you typedRSFT_T(KC_J)
andRCTL_T(KC_K)
faster than the defined value, the keysKC_J
andKC_K
would be sent to the computer. In contrast, if you typed slower than the defined value, the keysRSFT(KC_K)
would be sent to the computer.To enable crossover bilateral combinations (which start on one side of the keyboard and cross over to the other side, such as
RSFT_T(KC_J)
andLGUI_T(KC_A)
in the word "jam"), add the following line to yourconfig.h
and define a value: hold times greater than that value will permit crossover bilateral combinations. For example, if you typedRSFT_T(KC_J)
andLGUI_T(KC_A)
faster than the defined value, the keysKC_J
andKC_A
would be sent to the computer. In contrast, if you typed slower than the defined value, the keysRSFT(KC_A)
would be sent to the computer.To delay the registration of certain modifiers (such as
KC_LGUI
andKC_RGUI
, which are considered to be "flashing mods" because they suddenly "flash" or pop up the "Start Menu" in Microsoft Windows) during bilateral combinations, you can define aBILATERAL_COMBINATIONS_DELAY_MODS_THAT_MATCH
setting specifying which modifiers should be delayed, and aBILATERAL_COMBINATIONS_DELAY_MATCHED_MODS_BY
setting specifying how long that delay (measured in milliseconds) should be.config.h
and define a bitwise mask that matches the modifiers you want to delay. For example, here we are defining the mask to only match the GUI and ALT modifiers.config.h
and define a timeout value (measured in milliseconds) that specifies how long modifiers matched byBILATERAL_COMBINATIONS_DELAY_MODS_THAT_MATCH
should be delayed. For example, here we are defining the timeout to be 100 milliseconds long.To suppress mod-tap holds within a typing streak, add the following line to your
config.h
and define a timeout value: a typing streak ends when this much time passes after the last key in the streak is tapped. Until such time has passed, mod-tap holds are converted into regular taps. The default value of this definition is0
, which disables this feature entirely. Overall, this feature is similar in spirit to ZMK's global-quick-tap feature.If you wish to target only certain modifiers (instead of all possible modifiers) for the typing streak timeout setting described above, add the following line to your
config.h
and define a bit mask: only those modifiers that match this mask will be governed by the typing streak timeout. For example, to exempt Shift modifiers from the typing streak timeout while still targeting all other modifiers, you can specify the following mask.To monitor activations in the background, enable debugging, enable the console, enable terminal bell, add
#define DEBUG_ACTION
toconfig.h
, and use something like the following shell command line:Commits #
Although this PR is polluted with irrelevant commit history (which was merged in from the newer QMK release 0.18.6+ that provides the deferred execution facility needed by this PR), you can filter out the noise by looking only at my own commits. :bowtie: