-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Antecedent Morph Behavior aka Adaptive Keys #2042
base: main
Are you sure you want to change the base?
Antecedent Morph Behavior aka Adaptive Keys #2042
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed the docs portion noting some improvements, but overall I noticed that the page organization isn't quite ideal. In particular:
- You will notice that any behavior page that has a "Behavior Binding" actually refers to pre-defined node labels, not labels from an example like
&ad_a
here - The descriptions of properties like
antecedents
andbindings
can go under "Configuration" header - The example you have, along with its usage in the keymap, can go under an "Example Usage" section.
I think a good example to look at is tap dance page here, since that is similarly a behavior type that doesn't have a pre-defined instance.
Other docs-related things:
- Need to add a link to the page in the sidebar
- Need to list it under https://zmk.dev/docs/config/behaviors
|
||
## Summary | ||
|
||
The Antecedent-Morph behavior (adaptive keys) sends different behaviors, depending on which key was most recently |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Antecedent-Morph behavior (adaptive keys) sends different behaviors, depending on which key was most recently | |
The Antecedent-Morph behavior (adaptive keys) sends different behaviors depending on which key was most recently |
The configuration of the behavior consists of an array of `antecedents`, key codes with implicit modifiers, as well as | ||
of a delay `max-delay-ms` in milli-seconds. If none of the `antecedents` was released during the `max-delay-ms` before | ||
the antecedent-morph behavior is pressed, the behavior invokes the `defaults` binding. If, however, the `n`-th of the | ||
key codes (with implicit modifiers) listed in the array `antecedents` was released within `max-delay-ms`, the behavior | ||
invokes the `n`-th of the bindings of the `bindings` property. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The configuration of the behavior consists of an array of `antecedents`, key codes with implicit modifiers, as well as | |
of a delay `max-delay-ms` in milli-seconds. If none of the `antecedents` was released during the `max-delay-ms` before | |
the antecedent-morph behavior is pressed, the behavior invokes the `defaults` binding. If, however, the `n`-th of the | |
key codes (with implicit modifiers) listed in the array `antecedents` was released within `max-delay-ms`, the behavior | |
invokes the `n`-th of the bindings of the `bindings` property. | |
The configuration of the behavior consists of an array of `antecedents` which consist of keycodes including [implicit modifiers](../codes/modifiers.md#modifier-functions), as well as | |
a delay `max-delay-ms` in milliseconds. If none of the `antecedents` were released during the `max-delay-ms` before | |
the antecedent-morph behavior is pressed, the behavior invokes the `defaults` binding. If, however, the `n`-th of the | |
keycodes (with implicit modifiers) listed in the array `antecedents` was released within `max-delay-ms`, the behavior | |
invokes the `n`-th binding in the `bindings` property. |
Finally, if the user is still holding down a Shift key when pressing the A that triggers the above antecedent-morph | ||
behavior, then this results in an upper case O rather than a lower case o. | ||
|
||
### Dead Antecedents |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this section is complicated and exposes too much of the ZMK internals for an end user. If dead keys functionality is really something that people need to be using, I would put it in a :::tip
box where you can briefly mention using e.g. K_CANCEL
as a dummy keycode.
RA(Y)`. Here, right Alt is called an *implicit modifier*. Antecedents are always considered with implicit modifiers. For | ||
example, | ||
|
||
```dts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to make sure to use 4 spaces instead of tabs consistently in examples.
example, A is replaced by O if preceded by Z, regardless of which modifier keys were held down while tapping the Z, | ||
i.e. in particular after a lower case z or after an upper case Z. | ||
|
||
Finally, if the user is still holding down a Shift key when pressing the A that triggers the above antecedent-morph |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems rather obvious to me, but I am not strictly opposed to having it if you feel differently.
|
||
### Explicit Modifiers | ||
|
||
The entire function of the antecedent-morph behavior is independent of the *explicit modifiers*. These are the modifiers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel that this can also go into a :::note
box rather than a dedicated header.
* Adaptive keys by the Antecedent Morph Behavior. * Allow rolling in Antecedent Morph. * Allow dead keys in Antecedent Morph. * Multiple antecedent morph compiles. * Documentation * Fix errors. --------- Co-authored-by: Void Your Warranty <voidyourwarranty@mailbox.org>
This PR is broken but it is fixed in https://github.com/klausweiss/zmk/tree/fix/adaptive-keys |
i hate to be that kinda person, but any updates on this? |
Please merge this 🙏 |
If anyone is interested, I wrapped this PR in a module to use without needing to maintain a fork - https://github.com/ssbb/zmk-antecedent-morph |
After some debugging, it looks like there's nothing wrong with Antecedent Morph Behavior. It was just the last straw that made it possible to detect that I accidentally overlapped global combos with Mouse behaviors and those combos were the main lag contributors. |
I cannot make it work together with hold-tap. I'm using the module made by @ssbb with the following configuration. Am I missing something ?
It fails NOTE: same error when trying to mix antecedent and mod morph. |
Hi, I'm very interested in having this feature available by default. What can I do to help this MR out? |
Do you have a specific issue with using the module version as linked above? This is a good candidate of a behavior that fits in the modular framework, rather than supported in tree. |
Yes - I would like to use the upstream Glove80 layout editor, which only supports the official/vanilla ZMK firmware. (It also uses a custom build process, which makes adding modules a bit annoying) On a more philosophical note, I personally think it's much better for end users to have reasonably popular functionality included by default, rather than having to track down mods. On top of being less discoverable and more of a hassle to install, out of tree code tends to bitrot. There is also the argument to be made that this style of behavior is incredibly flexible and can be used to implement a wide range of use cases (e.g. magic keys, adaptive letters. alternative repeat keys, etc); I consider it no less useful and versatile than other mainline feature such as combos or tap dances. If your goal is to minimize the size of the core code, surely these would be the first to go? |
ZMK is moving towards a modular approach, so making module usage easier is probably something that should be handled on the Glove80 build process side, I think. The main problem with moving such features upstream before they are stable enough is that you might end up needing something that isn’t supported yet. As a result, you’ll have three PRs with the desired changes, and you’ll need to maintain your fork and resolve merge conflicts, which is far more of a hassle compared to just using the modules. |
Let me try to address with my thoughts, as just one contributor to the project.
I think modular is the future of ZMK, and MoErgo might eventually want to account for that. But right now you can use the west-based workflow they provide. IMHO it would be great if they make their custom tweaks and publish them as a module, so they don't have to maintain a bespoke fork.
In tree code needs constant maintenance, which is harder when there isn't an interested contributor (as evidenced by the lack of updates in this PR, this author isn't). So if merged it will certainly increase maintenance burden on core maintainers. This justification is also related to the popularity argument, addressed below. The hassle to install is something that is planned to be improved with better tooling, such as ZMK CLI (which already has rudimentary support for configuring modules). There is also plans to implement a proper versioning scheme so bitrot can be managed better in external modules.
Edit: reworded below section. I think comparing the popularity of this feature to combos and tap dances might be a bit of an overestimation, but without data I won't comment much on it. Using the module and thus popularizing it further might be a good way to prove that it is popular enough to warrant inclusion in core, though. |
I think this argument grossly neglects the asymmetry of maintenance burden. Most maintenance tasks are significantly easier for the person doing the maintenance / refactor in question right now. rather than the person who contributed the module X years ago. (Or worse, an unaffiliated end user who knows nothing about the codebase prior to being forced to update the module in question) For a case study of exactly what I mean, consider commit d6de8a3. It would have been utterly trivial to extend this rote replacement task to one additional file. ( What happens in my experience with highly modular projects is that random modules keep randomly breaking or requiring changes, usually hitting completely unaffiliated end users, asking them to either research and patch (or crudely work around) the breakage in question themselves, stop using the module (or finding a more updated replacement), or simply stop updating the core software to avoid this fate. YMMV, but this is the reason I personally tend to avoid modular software these days - maintainer burden being shifted onto me, the uninformed and unaffiliated end user, does not lead to a pleasant software experience. (Tell me honestly, who has never cursed loudly at a sea of red error messages filling up the screen when launching |
My two cents: Modules are absolutely the future of ZMK, purely for board/shield definition reasons. Continuously adding keyboard definitions to ZMK would be a terrible approach, as we can see from QMK's example. The alternatives which aren't modules have proven to be inelegant and cause things to be messier than they should be. That said, your (justified) points are aimed more towards the usage of modules for features, rather than for keyboards (though they do apply to both). ZMK has been using modules for some time now, but their presence and the ecosystem has been small and lacking, both of which are expected to change with time. Rather than arguing against modules, I think it would be more productive to try and focus on how we can design/improve the ecosystem to minimize the pain points you raised. Caksoylar has already mentioned some of these in his comment. I am of course fully on board with including new popular features into ZMK directly. For behaviours specifically though, I personally think it is unlikely new behaviours (which aren't related to some additional feature such as studio unlock) will be added to ZMK prior to the above being worked out. |
790e9ff
to
8157b6e
Compare
Apologies for the leaving the patch unmaintained for so long, and thanks for all the fixes and updates that people provided in the meantime. I now found some time to rebase the patch to the current main branch, and so I hope the patch has become useful again. (Unfortunately, automatic rebasing screwed up, and I had to force push a current version). |
2851ea2
to
9790a48
Compare
934b441
to
6d42782
Compare
df5daac
to
7558895
Compare
77a133c
to
a47f497
Compare
f969ff1
to
dd6b7c1
Compare
32fcdda
to
cada819
Compare
cada819
to
9371919
Compare
9371919
to
872245a
Compare
I have been experimenting a bit with adaptive keys over the last few days and ended up with another implementation. The big differences to this one are:
If anyone wants to try it out, it's available here: https://github.com/urob/zmk-adaptive-key |
Side note re: the discussion above. My personal take is that if outsourcing new behaviors helps the core team contributing new features faster (without needing to consider implications on an increasing number of behaviors they may not be familiar with themselves), this is a good thing for everyone using ZMK. At the same time, I think @haasn makes some good points on the need to keep the maintenance burden down. The new ZMK versioning system is a great step in this direction, especially if releases come with detailed release notes on any breaking changes in the API (and not just for end users). Good automatic testing & CI practices on the side of module maintainers can also go a long way in reducing maintenance burden. For instance, my adaptive-key module comes with an extensive test suite that gets automatically run each time a new ZMK version is released. Hopefully, this quickly identifies any issues and along with some additional workflows keeps the module up to date without too much effort. |
872245a
to
e02c1ca
Compare
The Antecedent-Morph behavior (adaptive keys) sends different behaviors, depending on which key was most recently
released before the antecedent-morph behavior was pressed, if this occurs within a configurable time period.
Details in the docs.