zmkfirmware / zmk

ZMK Firmware Repository
https://zmk.dev/
MIT License
2.67k stars 2.74k forks source link

Feature request: Automatically held/released mods for certain keycodes for Alt-Tab window switching #997

Open caksoylar opened 2 years ago

caksoylar commented 2 years ago

This is an attempt to formalize the discussion at https://discord.com/channels/719497620560543766/719565084208398406/900412989033828444 with @bcat and @dxmh.

There are a few use cases around tapping a sequence of keys while another key (usually a modifier) is being held that folks, especially working with small keyboards, have been using with custom code in other firmware like QMK:

  1. Having a single key to do window switching with Alt+Tab or Cmd+Tab. The usual way this is implemented is with a timer (out of scope here), or first press includes the mod, layer switching and/or pressing/releasing any other key releases the mod. The most popular implementation is probably swapper feature by Callum Oakley in their QMK workspace. There are other bespoke implementations that are independently developed or extension to multiple keys (Tab, Shift+Tab on separate keys). Another use case is switching keyboard layouts in Windows/Mac OS with Ctrl+Space or GUI+Space.
  2. Having a "text selection mode" similar to the visual mode in Vim. In this case usually arrows keys are pressed while Shift is automatically held. This is not a good example since shift doesn't need to stay held between key presses typically, see below comments.

In terms of implementation there can be two approaches:

  1. A behavior that can be assigned to a single key (like for Alt+Tab window switching). It can keep track of its own modifier state and listen to layer change or key press/release events to release it. It could look something like this in the keymap:
    / {
    behaviors {
        at: alt_tabber {
            compatible = "zmk,mod-holder";
            label = "ALT_TABBER";
            #binding-cells = <1>;
            mods = <MOD_LALT>;
        };
    };
    keymap {
    [...] &at TAB [...]
    }
    };
  2. A keymap node similar to a combo where you specify the modifier, (a list of) layer(s) and a list of key positions. The first press on any of the positions on that layer would press the modifier key, along with triggering the usual behavior of that key. On layer change or another position press/release the modifier is released. This could look like:
    / {
    modholders {
        compatible = "zmk,mod-holder";
        window_switcher {
            layers = <0>;
            key-positions = <0 1>;
            mods = <MOD_LALT>;
        };
        visual_mode {
            layers = <1>;
            key-positions = <0 1 2 3>;
            mods = <MOD_LSHIFT>;
        };
    };
    keymap {
    [...] &kp TAB &kp LS(TAB) [...]
    [...] &kp LEFT &kp UP &kp DOWN &kp RIGHT [...]
    }
    };

    The first approach doesn't cover use case 2. above but arguably simpler and covers the most frequent one. The second approach would cover both, and also multiple keys for window switching.

It is also possible to extend all of the above to hold/release any behavior and not just modifier keycodes, if a useful scenario exists for that.

dxmh commented 2 years ago
  1. Having a "text selection mode" similar to the visual mode in Vim. In this case usually arrows keys are pressed while Shift is automatically held.

This functionality is actually somewhat achievable with a simple &kp LS(LEFT), etc. Because, unlike the alt-tab use case, shift does not need to remain held between keypresses. Alt-tabbing is a really niche behaviour in that respect.

In my opinion the focus should be on the mod-tabbing use-case. Its scope is clear and the resulting user functionality is well understood. Plus it would be useful to almost everyone! (Especially with an easy way to set it up, like the behaviour in your first suggested approach.)

bcat commented 2 years ago

Thanks again for writing this up!

+1 to the name "mod holder", by the way. I wasn't really sure what to call this feature, but I think that names a lot of sense.

The big question for me is when the held mod should be released. Options I'm aware of:

I'm a bit worried there's not an obviously correct answer here, and folks may have strong feelings about the release condition. I wish that ZMK had a notion of an "event matcher" so you could say things in your keymap like mod_release = <&layer_up LOWER> or mod_release = <&layer_change> or mod_release = <&timeout 1000>, but to my knowledge, there's not precedent for such a concept in ZMK today....

  1. Having a "text selection mode" similar to the visual mode in Vim. In this case usually arrows keys are pressed while Shift is automatically held.

This functionality is actually somewhat achievable with a simple &kp LS(LEFT), etc. Because, unlike the alt-tab use case, shift does not need to remain held between keypresses. Alt-tabbing is a really niche behaviour in that respect.

This is a good point. I wonder if that also provides an argument in favor of the first solution. (I liked the second because it seemed more general, but if the first actually meets all the real world needs, I think that's simpler.)

bcat commented 2 years ago

To be fair, I guess implementing all three of release_on_layer, release_on_key, release_on_timeout (or less bad names if possible) wouldn't be the end of the world. It just seems like something where different ZMK features (e.g., sticky-keys) would ideally offer consistent options.

caksoylar commented 2 years ago

Thanks for the comments! You are both right, with the visual mode use case moot there is less of an argument for implementation 2 (I really confused myself there!).

Regarding when to deactivate: only layer changes might be an issue for putting such a key on the base layer. I can see someone wanting to do that for a large keyboard with macro keys or for an hard-to-reach thumb key for Lily58 etc. On the other hand if we check for different keys then using multiple keys Tab and Shift+Tab would be impossible like @bcat pointed out. Having both release_on_layer and release_on_key options (and even timeout if that is useful) sounds like it would solve both cases, as long as the user is OK with the trade-off between base layer usage or multiple key usage.

(In QMK you can couple the behavior of multiple key positions with a single state arbitrarily but doing that without going all the way to implementation 2. seems like bad design to me in ZMK.)

Another point: Window switching is frequently used in QMK with rotary encoders, see the splitkb article. I realize that currently only special behaviors can be assigned to rotary encoder turns but if that changes in the future, this would be useful there too.

caksoylar commented 2 years ago

Worth noting: Another approach to solve window switching problem is having a behavior that activates a layer with a mod enabled at the same time. In QMK this exists as LM(layer, mod). I don't think this is as elegant a solution for window switching as above but it seems easier to implement, as well as potentially supporting different use cases.

dxmh commented 2 years ago

Another approach to solve window switching problem is having a behavior that activates a layer with a mod enabled at the same time.

Related thread:

dnaq commented 2 years ago

I think the first approach might solve the most use cases people might have. Even though the second is more generic I'm not sure how much the other cases would be used in practice.

From personal experience I've found that just having the mod be released on any other key press seems to work fine. That even sorts things out like pressing shift to switch direction since it will register as a new mod press next time you press the key.

nrako commented 2 years ago

I'm really green to ZMK and small custom keyboards. I'm coming on this thread from a discord discussion I had with @caksoylar where I inquired on the existence of a behavior which combines &mo and &kp.

I think close to what is proposed in #968

From my novice experience, I'd believe that sort of new behavior would really present an easy way of handling the "mod-tabbing" usage (CMD+TAB).

While I get how modifier + keycode (e.g LG(4) could be set on key 5 to allow SHIFT+CMD+4) or how more flexible configuration means could be more versatile, I believe a behavior that switches the layer and keypress a modifier (e.g &mokp NUMROW LGUI) would be much more straightforward and easy to reason about for newbs like me. Moreover, I think this works especially well with the behavior &trans. I'd use &trans on most keys except few exceptions that I'd override.

This also generally decouples the capacity of keys that would otherwise only be assigned to layer switching.

As a newcomer my aim is to stay as close as possible to my "non-painful" muscle-memories on a 3x6 layout (Corne). As a user of VIM/unix & macOS who is occasionally on the move on Macbook, I'd like to maintain as much as possible my fingers memories for ESC, CMD+TAB, CMD+SHIFT+(3-5), etc.

bcat commented 2 years ago

Worth noting: Another approach to solve window switching problem is having a behavior that activates a layer with a mod enabled at the same time. In QMK this exists as LM(layer, mod). I don't think this is as elegant a solution for window switching as above but it seems easier to implement, as well as potentially supporting different use cases.

Maybe I'm misunderstanding this... do you have a keymap sample that would demonstrate how it's used? While I can see how this might be useful for other cases, I don't see how it solves the window switching use case, unless one wants to effectively dedicate two keys to window switching (one for the layer-mod binding and the other for the Alt key).

From personal experience I've found that just having the mod be released on any other key press seems to work fine. That even sorts things out like pressing shift to switch direction since it will register as a new mod press next time you press the key.

Doesn't that mean that the Alt+Tab dialog briefly closes when Shift is pressed? If so, at least on Windows, I think it leads to unintuitive operation since the windows in the dialog can reorder themselves when Alt is released. So, at least for my use case, the ability to configure the behavior such that the modifier remains pressed throughout the operation is actually pretty important. (I don't really have a strong argument for configuring specific keys that do/don't keep the modifier pressed vs. just keeping it pressed till the layer state changes, though....)

bcat commented 2 years ago

Also, I don't mean to bikeshed this unnecessarily, to be clear. I'm just trying to figure out the minimum set of functionality that's needed to address my use case. If my use case is too specialized to bake into ZMK core, I totally understand that and will just wait for per-keymap custom behavior support in zmk-config. :)

dnaq commented 2 years ago

Doesn't that mean that the Alt+Tab dialog briefly closes when Shift is pressed?

I haven’t tried it on windows, so that might be an issue, or not. If it’s an issue a solution would be to ignore mods when deciding when to release the mod or not. And that could of course be generalised if needed.

bcat commented 2 years ago

Doesn't that mean that the Alt+Tab dialog briefly closes when Shift is pressed?

I haven’t tried it on windows, so that might be an issue, or not. If it’s an issue a solution would be to ignore mods when deciding when to release the mod or not. And that could of course be generalised if needed.

Ah, yeah, that seems like a good idea. Mods already feel "special" in various ways, so I don't think it's too farfetched to treat them differently here. It doesn't handle every use case that might come up (e.g., navigating with arrow keys while the window switcher dialog is up), but I suspect that practically, folks care most about Alt+Tab, then some folks also want Alt+Shift+Tab... and a much smaller group cares about keys besides those. (I'm just hypothesizing, though; it's not like I've done a survey or anything.)

jcmkk3 commented 2 years ago

I was doing some brainstorming around a layout with only a single key per thumb (2 total) the other day and the workflow that seemed to make sense to me was one where there was a single dedicated layer key and then the homerow keys on the same layer hand would have nested layers like SYM, NUM, FUN, etc. The way that would be nice for this to work would be for those keys to be sticky layers that would either function as a normal sticky layer if the original thumb key had been released. If the original thumb key was still pressed, they would stay on that layer until the layer thumb key was released.

It feels like the same type of functionality as what the Callum swapper implementation does, but in a different context. I know that this functionality can be looked at in different ways, but one of them might be a version of the sticky key behavior. There is already functionality requested (#834) for sticky keys to have the option of a 'cancel' key to be closer to callum behavior and this would be an additional option. From that request:

Current behaviour:

  • Sticky keys are cancelled after a timeout

Desired behaviour:

  • Sticky keys are cancelled after a timeout, OR
  • Sticky keys are cancelled with a key

With this, that would change to:

Current behaviour:

Desired behaviour:

metheon commented 2 years ago

Not sure anyone is working on this, but I just wanted to add my vote for an implementation that supports what Callum has done. What @bcat wrote in their first example in the OP.

caksoylar commented 1 year ago

For readers' reference, the first type of implementation along with a shared-key-positions property that allows passing through other key positions is currently available with #1366. Here is my swapper-like alt-tab implementation using that, for reference: https://github.com/caksoylar/zmk-config/commit/36d04be8a416ffa88e6c7e59e0ddee403906987b. However some users reported trouble assigning the behavior to a combo.