-
-
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
feat(behaviors): Leader key #1380
base: main
Are you sure you want to change the base?
Conversation
c986393
to
ddbc05d
Compare
b1cd1e8
to
6066fd2
Compare
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.
Thank you so much, leader key is a very nice feature to have. Just a couple of questions/suggestions regarding the documentation.
Can't wait for this to be merged 👍🏽
|
||
#### `timerless` | ||
|
||
By default, the leader key will have a timeout, and will not wait for a sequence to be completed or another key to be pressed. Specify `timerless` if you want a timeout. |
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 struggle to understand this part
- Where and how
timerless
should be specified? Is it a possible value fortimeout-ms
or is it a separate option alongside it? - Why is the opition called "timerless"? The statement says that we should specify it if we want a timeout, it might be a typo and you meant to write "don't want a timeout"? In the latter case, the naming is still a bit non-intuitive, maybe it should be replaced by
no-timeout = <true>
, or even removed in favour oftimeout-ms = <0>
; or<timeout-ms = <-1>
, or, if timerless is a possible value fortimeout-ms
, maybe a shorter= <false>
or even= <no>
could be a better option? (shooting up the sky here).
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.
- Timerless would be a separate option, defined in the dts for the leader key along with timeout-ms and the other options.
- Yeah, that was a typo, should be "don't want a timeout". Timeout-ms would need to be a positive number for the case of overlaps, unless we had something like overlap-timeout-ms and timeout-ms separate? Timerless is for when no sequence has been completed yet, and once an overlapped sequence is completed, timeout-ms is used.
- `key-positions` is an array of key positions. See the info section below about how to figure out the positions on your board. | ||
- `layers = <0 1...>` will allow limiting a sequence to specific layers. This is an _optional_ parameter, when omitted it defaults to global scope. | ||
- `bindings` is the behavior that is activated when all the key positions are pressed. | ||
- (advanced) you can specify `immediate-trigger` if you want the sequence to be triggered as soon as all key positions are pressed. The default is to wait for the leader key's timeout to trigger the sequence if it overlaps another. |
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.
Where and how?
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.
One important high level question on the design.
Thanks!
app/dts/behaviors/leader_key.dtsi
Outdated
|
||
/ { | ||
behaviors { | ||
/omit-if-no-ref/ leader: leader_key { |
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.
It seems to me we shouldn't limit this to just one global leader
key, as this limits folks to just one leader key, and it's associated sequences.
Is there really no use case for different leader keys, with different possible sequences?
Seems like you could have child props in the yaml binding, and something like:
leader_foo: leader_foo {
compatible = "zmk,behavior-leader";
...
seq1 {
};
seq2 {
};
};
That would give us a bit more flexibility.
If we really want to make it easy, we could still define one global leader you could enhance with children, e.g.:
&leader {
seq1 {
};
};
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.
Right now I use multiple leader keys with layers (see here). I use macros to switch layers before pressing the leader key which has the same effect although is a bit more work for the user.
properties: | ||
timerless: | ||
type: boolean | ||
timeout-ms: |
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 needs documenting as well. Also see the other reviewers question.
Any update on this branch merging to main? |
I don't understand the difference of this "leader key" and a sticky layer. Can someone differentiate? |
you can do more than 2 key presses and it can chain arbitrary long sequences as the triggers. e.g. "super > s > e" for "leader key" > "shortcuts" > "email" to trigger spelling your email address (or anything you want). |
Other uses I use leader keys in QMK is (all are for windows) Leader Key + B O O T = qmk reset I'm hoping that this gets reviewed and can be merged into the main branch though as this is a major component that makes my 32 key keyboard work in QMK (along with tap mods, tap dances autoshifts, custom autoshift keys) |
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.
A few more items from my first pass at looking at this again. Thanks again for working on this!
app/src/leader.c
Outdated
#define LEADER_INST(n) \ | ||
static struct leader_seq_cfg sequence_config_##n = { \ | ||
.virtual_key_position = ZMK_KEYMAP_LEN + __COUNTER__, \ | ||
.immediate_trigger = DT_PROP(n, immediate_trigger), \ | ||
.is_pressed = false, \ | ||
.key_positions = DT_PROP(n, key_positions), \ | ||
.key_position_len = DT_PROP_LEN(n, key_positions), \ | ||
.behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, n), \ | ||
.layers = DT_PROP(n, layers), \ | ||
.layers_len = DT_PROP_LEN(n, layers), \ | ||
}; | ||
|
||
DT_INST_FOREACH_CHILD(0, LEADER_INST) |
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.
If we really are only supporting one instance of this node, then the FOREACH mechanics are superfluous and misleading. Just create one static struct instance directly.
|
||
static int leader_init() { | ||
k_work_init_delayable(&release_timer, behavior_leader_key_timer_handler); | ||
DT_INST_FOREACH_CHILD(0, INTITIALIAZE_LEADER_SEQUENCES); |
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.
Ditto, this adds unnecessary complexity since only a single instance of the node is supported.
app/src/leader.c
Outdated
bool leader_status; | ||
int32_t press_count; | ||
int32_t release_count; | ||
int32_t timeout_ms; | ||
int32_t active_leader_position; | ||
int8_t layer; | ||
bool first_release; | ||
struct k_work_delayable release_timer; | ||
int64_t release_at; | ||
bool timer_started; | ||
bool timer_cancelled; | ||
bool timerless; |
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.
These all need to be static
.
app/include/zmk/leader.h
Outdated
|
||
void zmk_leader_activate(int32_t timeout, bool timeout_on_activation, uint32_t position); | ||
void zmk_leader_deactivate(); | ||
bool zmk_leader_get_status(); |
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.
status
I don't love... is active? is enabled? Something a bit more aligned with the bool return value here.
const struct device *dev = device_get_binding(binding->behavior_dev); | ||
const struct behavior_leader_key_config *cfg = dev->config; | ||
|
||
zmk_leader_activate(cfg->timeout_ms, cfg->timerless, event.position); |
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.
If you press this again while it's active, should the behavior be to deactivate to interrupt it?
app/src/leader.c
Outdated
|
||
#define LEADER_INST(n) \ | ||
static struct leader_seq_cfg sequence_config_##n = { \ | ||
.virtual_key_position = ZMK_KEYMAP_LEN + __COUNTER__, \ |
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 should be refactored to use the virtual key position work @joelspadin added for combos, etc.
app/src/leader.c
Outdated
const struct zmk_position_state_changed | ||
*leader_pressed_keys[CONFIG_ZMK_LEADER_MAX_KEYS_PER_SEQUENCE] = {NULL}; | ||
|
||
uint32_t current_sequence[CONFIG_ZMK_LEADER_MAX_KEYS_PER_SEQUENCE] = {-1}; | ||
// the set of candidate leader based on the currently leader_pressed_keys | ||
int num_candidates; | ||
struct leader_seq_cfg *sequence_candidates[CONFIG_ZMK_LEADER_MAX_SEQUENCES_PER_KEY]; | ||
int num_comp_candidates; | ||
struct leader_seq_cfg *completed_sequence_candidates[CONFIG_ZMK_LEADER_MAX_SEQUENCES_PER_KEY]; | ||
// a lookup dict that maps a key position to all sequences on that position | ||
struct leader_seq_cfg *sequence_lookup[ZMK_KEYMAP_LEN][CONFIG_ZMK_LEADER_MAX_SEQUENCES_PER_KEY] = { | ||
NULL}; |
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.
All should also be static
Please try to finish this feature... I will soon try to merge it on my branch, but it would be great to have it on master. I do not understand how people are not showing more interest for it. If it is easy to add key sequences(similar to vimrc) then it would be even better, but I think that can be done with a custom function... The main thing is having it on master and bugfree:) Thank you all but especially @nickconway and @petejohanson for working on it🙂 |
Is there any limitation regarding number of sequences that can be created? For combos there are (I think) 5 combos that can use the same key position. Could one create e.g. 900 sequences: leader + letter_1 + letter_2 = shortcut/macro? |
I was a happy user of the leader key feature in my previous zmk build. Recently I tried updating my firmware to the latest release of zmk (c9c620d) and also update from zephyr sdk 0.15 to 0.16. Everything seems to work, except for the leader key. The leader key acts like a transparent key. Also running the test tests/leader/basic fails:
Could someone verify if the leader key works with zephyr sdk 0.16 and a recent version of zmk? Maybe I'm doing something wrong... |
I am guessing this needs to be rebased for #2028. |
I found why it wasn't working in Zephyr 3.5. This is what needs to be changed in order to make it work again:
There are probably more changes to be made (#2028) to update everything to zephyr 3.5. |
Looking forward to this, great work! |
I have been using this for a while now and really loving it! Just a few small comments to nitpick here: Keycode vs position-based?I see advantages for both sides. But overall I tend to think that this one might be more useful being The main reason is that in my experience leader sequences are almost always mnemonic (or are otherwise remembered in terms of their alphanumeric sequence). With this presumption, making them Purpose of
|
If anyone is interested using this as a module that works without having to patch the ZMK firmware, I just pushed one to https://github.com/urob/zmk-leader-key.
EDIT: I ended up reimplementing the behavior, making some changes to fit my personal needs (keycode-based, allow multiple instances, ...). For anyone who prefers the original PR, there's a legacy branch that makes that available as module as well. |
Adds leader key functionality