feat(behaviors): add non-overlap behavior
Pre-commit failed because of clang-format. I'm currently using v18. https://github.com/zmkfirmware/zmk/pull/2315
Usage: The default instance &nkp uses &kp binding. Any keycode should work.
Example: &nkp A &kp S &nkp D. This makes A and D do not overlap each other. More in docs.
https://deploy-preview-2391--zmk.netlify.app/docs/behaviors/non-overlap
Closes #2385
A couple of general thoughts moreso on the concept, rather than on the implementation:
- I think this should be generalised to non-overlapping behaviours, rather than non-overlapping keypresses
- It would be good if this was turned into a module, for others to use and trial, and to help judge whether this is a feature that makes sense to add to ZMK itself (and add to maintainers upkeep burden)
- It would be good to have this defined so that you can have multiple separate non-overlapping behaviours, ex. do not overlap a and d and also do not overlap space and shift, but a and space etc are fine to overlap.
- I personally am not a huge fan of the name, though that's just my taste.
I think this should be generalised to non-overlapping behaviours, rather than non-overlapping keypresses
It's always been a non-overlap behavior. The default instance is non_overlap_key_press because it uses key press binding.
Any other bindings can be used as well.
/ {
behaviors {
nl: non_overlap_layer {
compatible = "zmk,behavior-non-overlap";
#binding-cells = <1>;
bindings = <&mo>;
};
};
};
It would be good if this was turned into a module
I'm already working on it.
It would be good to have this defined so that you can have multiple separate non-overlapping behaviours, ex. do not overlap a and d and also do not overlap space and shift, but a and space etc are fine to overlap.
This is documented in "Multiple instances." https://deploy-preview-2391--zmk.netlify.app/docs/behaviors/non-overlap#multiple-instances
I personally am not a huge fan of the name, though that's just my taste.
I'm open to discussion.
The only terms I have in mind are 'zero overlapping', 'non-overlapping'. I drop the 'ing' because it feels clunky. 'No overlap' is also great.
I think this should be generalised to non-overlapping behaviours, rather than non-overlapping keypresses
It's always been a non-overlap behavior. The default instance is
non_overlap_key_pressbecause it uses key press binding.Any other bindings can be used as well.
/ { behaviors { nl: non_overlap_layer { compatible = "zmk,behavior-non-overlap"; #binding-cells = <1>; bindings = <&mo>; }; }; };
Excellent. This should probably be documented better, then. Apparently I was a bit blind w.r.t. multiple instances.
I personally am not a huge fan of the name, though that's just my taste.
I'm open to discussion.
The only terms I have in mind are 'zero overlapping', 'non-overlapping'. I drop the 'ing' because it feels clunky. 'No overlap' is also great.
SOCD, mutually exclusive, interrupt, XOR are all terms that come to mind which could inspire a name. I think my personal preference would be to call it the "Mutual Interrupt Behaviour", and rename/redefine no-active to resume (setting it causes the behaviour to "resume" when the interrupt is released, as long as the key is still held)
Peanut gallery: +1 to "interrupt" and "resume" as keywords, as they are plain English words whose meaning fits the behavior well. Before today, I had no idea that SOCD meant simultaneous opposing cardinal directions, and likewise those not steeped in programming or maths might not immediately read XOR as exclusive-or.
This should probably be documented better, then. Apparently I was a bit blind w.r.t. multiple instances.
I considered using 'multiple groups,' but the term 'group' is reserved for a future feature that groups multiple instances with different bindings. To prevent key presses and mouse keys from overlapping each other for example.
interrupt
"Only one key to be pressed" does not mean "only the most recent key to be pressed". It could also mean that the first key remains pressed when the second key is pressed, and the second key resumes only after the first key is released. I call this no-interrupt.
This is also reserved because I haven't figured out how to implement it yet, but the idea is there.
no-activetoresume
Fantastic. keep-active-size needs a more suitable name too. Could it be resume-size to align with the given term?
interrupt
"Only one key to be pressed" does not mean "only the most recent key to be pressed". It could also mean that the first key remains pressed when the second key is pressed, and the second key resumes only after the first key is released. I call this
no-interrupt.This is also reserved because I haven't figured out how to implement it yet, but the idea is there.
My suggestion here would be to use "interrupt" in the name of the behaviour, and call this property invert.
no-activetoresumeFantastic.
keep-active-sizeneeds a more suitable name too. Could it beresume-sizeto align with the given term?
resume-buffer-size perhaps? Or resume-capacity if that's too long for your taste?
My suggestion here would be to use "interrupt" in the name of the behaviour, and call this property
invert.
The name of the behavior is 'interrupt'. And the 'invert' of 'interrupt' is resuming the pressed keys in reverse order or having 'no interrupt' at all? How can 'no interrupt' be a feature of 'interrupt'? Wouldn't that just make it a regular key press?
I looked up dictionaries, asked a couple of LLMs and found these synonyms:
- Collide: This likely suits physical objects.
- Concurrent: Existence or time relevant. Must name it 'no-two-concurrent' or not at all.
- Simultaneous: Same as above. We have to name it 'no-two-simultaneous' which is clunky.
- Intersect: Can be used with non-physical things. This is so far the closest match to 'overlap'.
I also came across the internet and read about this topic. They either use the phrase 'no overlap' or 'no two simultaneous' to describe the key function.
Take a look at this post. With all the obscure names like Snap Tap, SOCD, and Null Bind, they still have to explain how it works. Using the two phrases 'no overlap' or 'no two simultaneous,' of course. https://mikecs1.substack.com/p/snap-tap-socd-detection-in-counter
resume-buffer-sizeperhaps? Orresume-capacityif that's too long for your taste?
resume-capacity is great. This really tells the specified number is the limit.
I'm thinking of max-resume-size since we would eventually explain it "the maximum keys that the behavior can resume". Probably shorten it to just max-resume. What do you think?
My suggestion here would be to use "interrupt" in the name of the behaviour, and call this property
invert.The name of the behavior is 'interrupt'. And the 'invert' of 'interrupt' is resuming the pressed keys in reverse order or having 'no interrupt' at all? How can 'no interrupt' be a feature of 'interrupt'? Wouldn't that just make it a regular key press?
I was thinking of inverting the way in which they interact, but I see how that can be confusing. I think "no overlap" and "no two simultaneous" are good keywords to describe the result, but not as good keywords to describe what is happening to cause that result.
Here's my new suggestion: Call it the mutex behaviour, and give it a flavor property like hold-tap. flavor could be set to interrupt (default) or to lock.
resume-buffer-sizeperhaps? Orresume-capacityif that's too long for your taste?
resume-capacityis great. This really tells the specified number is the limit.I'm thinking of
max-resume-sizesince we would eventually explain it "the maximum keys that the behavior can resume". Probably shorten it to justmax-resume. What do you think?
Is this as an alternative to resume-capacity or a new property you wanted to add? If it's the same one, I think "limit" and "capacity" both are synonymous with "max size".
good keywords to describe the result, but not as good keywords to describe what is happening to cause that result.
Here's my new suggestion: Call it the
mutexbehaviour, and give it aflavorproperty like hold-tap.flavorcould be set tointerrupt(default) or tolock.
We named a behavior 'key press' and not 'keycode send' for a reason. I would definitely use a descriptive name when it comes to the technical side. But keep in mind that we are naming the behavior for a broad audience.
Is this as an alternative to
resume-capacityor a new property you wanted to add? If it's the same one, I think "limit" and "capacity" both are synonymous with "max size".
Yes. I was referring to the same thing. As I was typing the previous text, I realized that max-resume is a bit technical. So, I'll go with resume-capacity.
no-active -> no-resume
keep-active-size -> resume-capacity
good keywords to describe the result, but not as good keywords to describe what is happening to cause that result.
Here's my new suggestion: Call it the
mutexbehaviour, and give it aflavorproperty like hold-tap.flavorcould be set tointerrupt(default) or tolock.We named a behavior 'key press' and not 'keycode send' for a reason. I would definitely use a descriptive name when it comes to the technical side. But keep in mind that we are naming the behavior for a broad audience.
That's a good point. The other point that I want to raise against non-overlap/no-resume is that it's generally not the best idea to begin names with a negation, for various reasons.
EDIT: I think we could note it as the "mutex" behaviour in the sidebar and call it the "Mutual Exclusion Behaviour" as a longer form term
names with a negation
Every existing non-overlap implementation has 'resume' enabled by default. Assuming we name this property resume, the default instance would look like this.
/ {
behaviors {
nkp: non_overlap_key_press {
compatible = "zmk,behavior-non-overlap";
#binding-cells = <1>;
bindings = <&kp>;
resume;
};
};
};
If users want 'no resume', they either have to create a new instance or use /delete-property/.
&nkp {
/delete-property/ resume;
};
Compare to no-resume.
/ {
behaviors {
nkp: non_overlap_key_press {
compatible = "zmk,behavior-non-overlap";
#binding-cells = <1>;
bindings = <&kp>;
};
};
};
&nkp {
no-resume;
};
We have a clear option to make it simple and easy to use, we should go for it.
And it's a common practice to use no-whatever as a property.
grep -r ' no-.*:' ../zephyr/dts
There's nothing wrong with negative prefixes. Non-profit organizations, non-smoker, non-fiction, etc.
EDIT: I think we could note it as the "mutex" behaviour in the sidebar and call it the "Mutual Exclusion Behaviour" as a longer form term
No one is familiar with the term 'mutual exclusion'. I wouldn't teach the entire multithreading course or statistics in the documentation just to explain how 'mutex' works.
You have a point about no-resume. Regarding the general name I'd still prefer a different one (not necessarily one of the ones I suggested), but I'll step aside for others to weigh in at this point.
Just had a thought that perhaps it would make sense to take inspiration from quick-tap-ms and delete the no-resume property, instead setting resume-capacity to -1 prevents resuming?
This PR has been automatically marked as stale because it has not had activity in 10 months. It will be closed in 14 days if no further activity occurs. Feel free to give a status update or re-open when it has been rebased and is ready for review (again). Thanks!
This PR was closed because it had no activity for over 10 months. Feel free to give a status update or re-open when it has been rebased and is ready for review (again).