zmk icon indicating copy to clipboard operation
zmk copied to clipboard

Add option to not release modifier keys in combos when combined with other keys.

Open phcerdan opened this issue 1 year ago • 6 comments

The following, almost minimal example:

#define COMBO(NAME, BINDINGS, KEYPOS) \
  combos_##NAME { \
    timeout-ms = <35>; \
    bindings = <BINDINGS>; \
    key-positions = <KEYPOS>; \
    layers = <0>; \
  }

combos {
  compatible = "zmk,combos";

  COMBO(ctrl_l, &kp LCTRL, 37 38);
  COMBO(ctrl_r, &kp RCTRL,  41 42);

  COMBO(alt_l, &kp LALT, 36 37);
  COMBO(alt_r, &kp RALT,  42 43);

  COMBO(tmux_prefix_l, &kp LC(A), 38 39);
  COMBO(tmux_prefix_r, &kp RC(A), 40 41);
};

zmk_combo_release_mod

I start pressing the RALT combo, and it works as expected, RALT is kept pressed when I press the numbers. However, when I press the keys for the RC(A) combo, the RCTRL gets released as soon as I press any number, but the A is still pressed.

This is documented: https://zmk.dev/docs/codes/modifiers#modifier-functions

Modified keys can safely be rolled-over. Modifier functions are released when another key is pressed. Press &kp LS(A), then press &kp B, release &kp LS(A) and release &kp B results in Ab. Only the A is capitalized.

Is there a way to disable this feature of automatic release of mod keys? I would like to have the exact same behavior as I would when I keep pressed the CTRL+A keys.

phcerdan avatar Jan 29 '24 10:01 phcerdan

Currently this is not possible because ZMK does not remember the implicit modifiers attached to each key after it is pressed. I do think that this should probably just be the way ZMK works though, not an option that needs to be enabled.

When you press RC(A), ZMK records that you pressed A with implicit modifiers RCTRL. When you press a number, ZMK records that you pressed the number with no implicit modifiers. The implicit modifiers state is now overwritten to 0, and there is no way to restore it when the number is released.

One possible solution is to remember the implicit modifiers associated with each key position in a linked list, and when each key is released it is removed from the list. The implicit modifiers are then whatever key is at the tail of the list (the most recently pressed key). There are a couple problems with this though: some ways of triggering behaviors don't currently give them unique key positions IIRC, and implementing this without dynamic allocation may be tricky.

joelspadin avatar Jan 29 '24 16:01 joelspadin

Thanks for the answer. I wonder, and talking from zero knowledge of the internals of ZMK, Would it be possible to "tag" RC(A) (or RCTRL-A) as a new modifier? So it would be equivalent for the rest of the code to be pressing just RCTRL, or RALT, etc. So when I press a number along RC(A), RC(A) itself will be the implicit modifier...

phcerdan avatar Jan 29 '24 16:01 phcerdan

The current state for pressed keys is a list of key codes that are pressed, plus a bit field of implicit modifiers for the most recent key. If we write the modifiers explicitly into the key list, then they can persist until we remove them, but then they apply to everything. For example, RC(A) held followed by X would send RC(X), when the intention was probably to type X, not Ctrl+X. The solution to that problem is the implicit modifiers field, which is overwritten with each key press, but not restored to its previous state with each key release, causing this issue.

joelspadin avatar Jan 29 '24 16:01 joelspadin

Meanwhile, you can work around this by invoking a macro from your combo that literally presses ctrl and A keys while it is held:

ZMK_MACRO(ctrl_a,
    bindings = <&macro_press &kp RCTRL &kp A>,
               <&macro_pause_for_release>,
               <&macro_release &kp RCTRL &kp A>;
)

Since this isn't using implicit modifiers, it won't have that issue.

caksoylar avatar Jan 29 '24 18:01 caksoylar

Meanwhile, you can work around this by invoking a macro from your combo that literally presses ctrl and A keys while it is held:

ZMK_MACRO(ctrl_a,
    bindings = <&macro_press &kp RCTRL &kp A>,
               <&macro_pause_for_release>,
               <&macro_release &kp RCTRL &kp A>;
)

Since this isn't using implicit modifiers, it won't have that issue.

Thanks for the suggestion. I get ctrl-a repeatly when holding the combo with this macro, which is good. But as soon as I press a number, I get just ctrl+number. Tested in tmux and not working.

phcerdan avatar Jan 29 '24 23:01 phcerdan

I think maybe I am not understanding what you are trying to do, since my previous understanding doesn't make sense for a tmux use case.

For tmux, you typically use the "prefix" key (here, I assume yours is ctrl+a) to trigger certain commands by sending a sequence of taps: You first send the prefix (tap ctrl+a), then another key (e.g. tap 1). In this scenario you certainly don't want both ctrl and a held while sending 1.

If I had to guess, perhaps this is what you want: You want to hold down a combo related to the "prefix", then send *tap* prefix, *tap* ctrl+number sequences by pressing a number key? For example you hold down a combo, then tap &kp N1 to send prefix + C-1 (in tmux config syntax) which for instance you map in tmux with tmux bind-key C-1 display-message "hello world"?[^1]

[^1]: If this is indeed the case, note that some terminals may not be able to distinguish between 1 and ctrl+1.

caksoylar avatar Jan 30 '24 01:01 caksoylar