-
-
Notifications
You must be signed in to change notification settings - Fork 39.4k
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
[Enhancement] Leader key timing (Per key basis) #370
Comments
very nice algo! I don't think latency is a factor here. Actually getting it implemented might be a little challenging, but that's something @jackhumbert can speak to better than I can. |
I think you can try this out by moving @@ -335,10 +335,10 @@ bool process_record_quantum(keyrecord_t *record) {
#ifndef DISABLE_LEADER
// Leader key set-up
if (record->event.pressed) {
+ leader_time = timer_read();
if (!leading && keycode == KC_LEAD) {
leader_start();
leading = true;
- leader_time = timer_read();
leader_sequence_size = 0;
leader_sequence[0] = 0;
leader_sequence[1] = 0; You can then change the timeout to 100ms or whatever your preference is. Feel free to mess with other stuff! |
That's awesome! @BeatVids let us know what it feels like in practice, maybe we'll make it the default. |
Man that's amazing that you thought of it that fast without even testing it out. I'm not confident in writing anything unless I try it out! I'm also surprised it was that simple. Thanks Jack for explaining it so well! It seems to be working perfect for me. I'm super happy with 250ms for now, it feels really relaxed as I no longer have a strict "deadline", and I can comfortably do a full macro with one hand without being really explosive, but that's just me of course. I do think it's default worthy already! One further concept I have is to auto break the timer if the last possible macro has been typed.
A simpler alternative, is to create a variable to set the maximum number of keys for macros, (3 is already the case currently).
With both of these ideas, we no longer have to wait an additional restarted timer. Assuming the timeout is set to 100ms, the macro will run at 274ms, instead of 374ms (in my case 524ms). This sounds like a challenge, especially the first one, but I completely understand if it will never see the light of day. I hope you like the concept though :) |
So the dictionary is actually just a shortcut for the header of an if block: #define LEADER_DICTIONARY() if (leading && timer_elapsed(leader_time) > LEADER_TIMEOUT) Rather than using the if (leading && (timer_elapsed(leader_time) > LEADER_TIMEOUT || leader_sequence_size >= 3)) { Keep in mind that 3 keys to a sequence was kind of arbitrary - this doesn't really need to have a max, and it could be 2 (for faster executions) or 20 as long as you have the SEQ_*_KEYS to back it up. |
Hi Jack! Just tried it! I'd love to hear your thoughts on the first method if you have any. It's probably the most efficient way macros can be done. Maybe instead of automatically detecting possible combinations, the user can be responsible for dictating if A+S needs to wait in case the user presses A+S+D, or run A+S immediately. If a setting were to be created that the user can manually toggle, it should be easier than creating a complex algorithm. But it's no biggie, it's only a matter of milliseconds, just another idea! Dictionary formatOne last thing I'd like to bring up, the register_code/unregister_code format is the only option for the leader key? I've tried the return MACRO() format, and it doesn't work, so I'm assuming it's the only way. I ask this because macros can turn into many lines, even if it is short. If there is a cleaner way to put macros in the dictionary, please let me know, otherwise, I'd like to share a function I have in my .bashrc to automatically write our macros for us:
Then we can run our function like this: Which generates this:
Doesn't support all the characters yet, but if anyone wants me to, I'll go ahead and work on it more. |
@BeatVids I believe you can just put |
Here's something I've been thinking about for text macros: bool shift_us_qwerty[0x80] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 31
0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, // 32 - 63
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 64 - 95
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 // 96 - 127
};
uint8_t ascii_us_qwerty[0x80] = {
0, 0, 0, 0, 0, 0, 0, 0, KC_BSPC, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KC_ESC, 0, 0, 0, 0, // 0 - 31
KC_SPC, KC_1, KC_QUOT, KC_3, KC_4, KC_5, KC_7, KC_QUOT, KC_9, KC_0, KC_8, KC_EQL, KC_COMM, KC_MINS, KC_DOT, // 32 - 46
KC_SLSH, KC_0, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_SCLN, KC_SCLN, KC_COMM, KC_EQL, // 47 - 61
KC_DOT, KC_SLSH, KC_2, KC_A, KC_B, KC_C, KC_D, KC_E, KC_F, KC_G, KC_H, KC_I, KC_J, KC_K, KC_L, KC_M, KC_N, // 62 - 78
KC_O, KC_P, KC_Q, KC_R, KC_S, KC_T, KC_U, KC_V, KC_W, KC_X, KC_Y, KC_Z, KC_LBRC, KC_BSLS, KC_RBRC, KC_6, // 79 - 94
KC_MINS, KC_GRV, KC_A, KC_B, KC_C, KC_D, KC_E, KC_F, KC_G, KC_H, KC_I, KC_J, KC_K, KC_L, KC_M, KC_N, KC_O, // 95 - 111
KC_P, KC_Q, KC_R, KC_S, KC_T, KC_U, KC_V, KC_W, KC_X, KC_Y, KC_Z, KC_LBRC, KC_BSLS, KC_RBRC, KC_GRV, KC_DEL // 112 - 127
};
void send_string(char str[]) {
for (int i = 0; str[i] != 0; i++) {
if (shift_us_qwerty[str[i]]) {
register_code(KC_LSFT);
register_code(ascii_us_qwerty[str[i]]);
unregister_code(ascii_us_qwerty[str[i]]);
unregister_code(KC_LSFT);
} else {
register_code(ascii_us_qwerty[str[i]]);
unregister_code(ascii_us_qwerty[str[i]]);
}
}
} You can then use |
On the "quick" sequence you described, the #define SEQ_TWO_KEYS(key1, key2) if (leader_sequence[0] == (key1) && leader_sequence[1] == (key2) && leader_sequence[2] == 0) So if you want to completely ignore the last character, you can do this instead of if (leader_sequence[0] == (key1) && leader_sequence[1] == (key2)) You'll also need to modify that if (leading && (timer_elapsed(leader_time) > LEADER_TIMEOUT || leader_sequence_size >= 2)) { The caveat here is that you'll need to put your ending block, this:
inside each of the // LEADER_DICTIONARY eqv
if (leading && (timer_elapsed(leader_time) > LEADER_TIMEOUT || leader_sequence_size >= 2)) {
// SEQ_TWO_KEYS eqv
if (leader_sequence[0] == (key1) && leader_sequence[1] == (key2)) {
leading = false;
leader_end();
send_string("Wharb");
}
} |
@jackhumbert You stole my idea (or maybe it's the other way around)! I was actually planning to throw the strings in flash and support other characters like |
You can support that by editing |
I was gonna make |
I've never seen anyone use KC_RETURN, so I don't know how useful that would be. There's actually enough room in the control space there that we could store the mod state bits there ( /* Mod bits: 43210
* bit 0 ||||+- Control
* bit 1 |||+-- Shift
* bit 2 ||+--- Alt
* bit 3 |+---- Gui
* bit 4 +----- LR flag(Left:0, Right:1)
*/ We could throw them in the strings like |
The modifiers can actually be stored in the high nibbles of the characters in the string (I think), as ASCII doesn't use anything past |
That's true - I was debating trying to support some of the extended ascii, though. I think there's enough unused stuff there that we could discard 32 characters for the mod stuff. This is just an easier way to send text macros - don't be a hater. |
I wasn't saying this function wasn't useful; I'm just not too sure why modifiers other than Shift need to be supported. |
@BeatVids let me know if that works for you - there's a situation where the leader mode won't exit with those settings if you hit the wrong key sequence. The workaround for that is having another if block at the end of your dictionary (still inside of it though) like this to catch the wrong seq after the timeout: if (timer_elapsed(leader_time) > LEADER_TIMEOUT) {
leading = false;
leader_end();
} |
Hi guys! Sorry, I'm a bit overwhelmed on where to begin now with all the stuff you guys talked about. So I guess I'll focus on the text macros. I really like the I'm also open to using @eltang's suggestion for the MACRO() syntax, but I would totally like to give the send_string format a go, since it looks pretty cool, and will be much more convenient in the long run. |
Do you use Colemak on your OS, or do have it configured in your keymap? Setting up a |
To be honest, the main reason why I bought an Ergodox initially was so I can have Colemak configured to the keymap so I no longer have to tamper with the OS. So yes, it's configured on my keymap. |
Nice. Then you shouldn't have to do anything - the send_string should work as-is, since it's sending characters directly to your OS. |
SWEET! I got it to work! |
The |
Awesome! :) @jackhumbert do you think we should maybe transition the leader to per-key timeout like that? I quite like the approach. |
Sure - it's an easy change :) |
Oh I see! Sorry about that Jack for being too early to the party :) After seeing your discussion with @eltang , I never knew there was a difference between RETURN and ENTER. You guys are so funny! This thread totally derailed, but I'm glad it did, because I always hoped for a function for strings precisely like the send_string one. But about the "last possible combination" macro, I'm not sure if my idea was clear and if you understood it. Also, I personally wasn't able to understand what you suggested. If I really feel like it would benefit the leader key, I'll bring it up again in the future if that's ok. |
@BeatVids The only problem I see with the function right now is that it can cause stack overflows if the code calls it too many times. The reason is that all strings are stored in RAM by default, and since each character is one byte, the remaining space for the stack and the heap is reduced considerably. @jackhumbert You don't mind me making a PR to fix this, do you? |
I added the newline as well as tab, so you can do stuff like this: send_string("if yes\n\tpeanut butter\nelse\n\trice snacks"); This could be easily used to debug things as well. I was running with the desire of wanting a 2-key sequence to fire as soon as it's hit, ignoring the last key. Otherwise, I wasn't sure what you meant by first method. Feel free to bring it up now or in another thread in the future. |
Ok I'll give it another go. Let's say I have I have 3 leader key sequences: If I do KC_LEAD, A, A, it would be sweet if the macro ran immediately after the second A is input. But for KC_LEAD, B, B, I wouldn't want it to run immediately after the second B, since what if the user wanted BBB instead? AlgorithmSo I was thinking maybe something can be made to detect if there is a possibility of another sequence. Are there any possible sequences after pressing BB? Yes, BBB is left!
Are there any possible sequences after pressing AA? No there's not!
User SettingThe above algorithm to automatically do this may be too complex, so here's an alternative that may be easier to make: Hope that was clear this time. And if you understood me the first time, my apologies, it would be my fault for not understanding your code in return. If you think this is too much and not worth your time, feel free to say "No Mr. Poopy Butthole, I will not do this!" No worries :) |
With the way the dictionary's being stored right now, each of the blocks is run independently, so scanning through them like that isn't possible. Your second implementation is what I was trying to do earlier, but the structure is designed to do one at time, once everything has ended. Maybe you could make a separate dictionary with the blocks I mentioned before your normal one. Here's an example: // quick-fire dictionary
if (leading && (timer_elapsed(leader_time) > LEADER_TIMEOUT || leader_sequence_size >= 2)) {
// SEQ_TWO_KEYS eqv
if (leader_sequence[0] == (KC_A) && leader_sequence[1] == (KC_A)) {
leading = false;
leader_end();
send_string("Wharb");
}
}
LEADER_DICTIONARY() {
leading = false;
leader_end();
SEQ_THREE_KEYS(KC_A, KC_A, KC_A) {
send_string("Oh wow");
}
} In the code above, AAA will never trigger, because AA will always be caught first. You can remove the
lines from the first block, which will allow both to be triggered, but AA will always fire before AAA does. I can see how this might be useful, but it's something we explicitly tried to avoid when writing the implementation. "Mr. Poopy Butthole" is a Rick & Morty reference - hopefully that wasn't taken as name-calling :) |
Sweet! That's just what I was looking for, thanks Jack! I will probably divide my dictionary like so, here is a draft if this helps any github browser in the future:
I didn't get the reference, but there's no way I took it like that. :) |
The idea of timeout per key press would make it possible to have temporary layer active as long as you keep pressing keys. This could make using arrows on alpha keys more comfortable as layer with arrow keys would be automatically deactivated when you stop navigation. |
Thanks @eltang ! Really appreciate the heads up! What does PSTR do anyways? Do you know if there are any codes for the brightness keys? I believe they are considered their own keys, much like VolUp VolDwn, etc. In Linux they are called XF86MonBrightnessUp + XF86MonBrightnessDown. They would be useful on devices that use them, such as laptops, etc. I'm surprised they weren't in the extensive key name list, that's the only general thing I can think of that wasn't in it. |
That macro forces the strings to stay in the flash memory. Otherwise, they're kept in the RAM during the whole time your keyboard is operating, and you can imagine how much memory that could eat up if you have more than a few strings. There are, but only for OS X. If you're curious, they're |
That makes perfect sense, thanks for clearing that up. Darn, I don't use OS X, but it's interesting that they use ScrollLock and Pause for that. Is there a certain reason why brightnessup + brightnessdown are currently omitted? |
In OSes like Windows, they're not regular keycodes. Usually, they're implemented with special drivers. |
Gotcha Eric. That sucks that they're not regular keycodes. |
did we make the change to time-out on per key basis? I too like that approach. |
Not officially (yet), but you can make the one line change mentioned if you'd like to try it out! |
I am reopening this because I need to make the change in the default keymap. |
With this addition to the default keymap, I recommend adding I personally will probably use some four key combos, but most likely not any more than that, but five keys may benefit others as well. Some common ones: ASAP, IMHO, and the most important BYOB of course, and whatever else everyone likes using the leader key for 😄 |
Sure! Added at 98f0807 :) Did you know - you can make a sequence out of literally whatever is in your keymap? eg |
That's awesome, even Thanks a lot for the four/five additions Jack, really appreciate it. |
I want to test this modification of the leader key because I think it might prove to be less stressful on my wrist then hammering down a fast paced key sequence. But… I can't get it to work, here's what I've done:
… with the example code from @BeatVids, so that I got this:
None of the sequences work. |
* Translate Print Keymap button to French It didn't exist when we started translations. * Review comments by gab-a
* Port Keychron V4 to VIAL * Change 2nd for unlock combo to correct index for the ISO variant of the V4
Hi gentlemen, it's me again with another wacky idea that I hope you would keep in mind!
Right now, the leader key expects everything to be done by a certain amount of time, in this case like you guys provide as an example: 300ms. I'm personally impressed that you guys have it so fast. I would prefer something more comfortable and slow, but unfortunately I would feel the lag too. So I have a suggestion that will be the best of both worlds.
Instead of 300ms for 1-3 keys altogether, how about having the time be for EACH keypress (100ms per key).
For every key pressed after the leader key, the timer is RESET, and it waits another 100ms.
Here's a brief rundown:
In total, 284ms passed, and then the A+S macro was ran.
Single + double macros will be potentially faster than the current state, for example:
In total, 180ms passed, instead of waiting all 300!
But if we want to get to 3 keys now, it may take a little longer (depending on the user), but it will be a lot more relaxed and balanced (100ms per key instead of 300 for 1-3 keys).
The benefit of this is that the leader key will be both time efficient, and relaxed as possible, and will allow us to go beyond SEQ_THREE_KEYS if we wanted to (although I know Erez mentioned that it kills the point; however, that was not a reason for this proposal!)
If I confused you in any way, please let me know. I'm certain latency is a factor, but I don't believe it to be a significant one.
The text was updated successfully, but these errors were encountered: