qmk / qmk_firmware

Open-source keyboard firmware for Atmel AVR and Arm USB families
https://qmk.fm
GNU General Public License v2.0
17.96k stars 38.59k forks source link

[Enhancement] Leader key timing (Per key basis) #370

Closed BeatVids closed 8 years ago

BeatVids commented 8 years ago

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:

*KC_LEAD is pressed*  --  *Timer starts at 0*
94ms pass
*KC_A is pressed*  --  *Timer is restarted to 0*
90ms pass
*KC_S is pressed*  --  *Timer is restarted to 0*
100ms pass
*User did not input anything within the last 100ms, break, and run macro*

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:

*KC_LEAD is pressed*  --  *Timer starts at 0*
80ms pass
*KC_A is pressed*  --  *Timer is restarted to 0*
100ms pass
*User did not input anything within the last 100ms, break, and run macro*

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.

ezuk commented 8 years ago

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.

jackhumbert commented 8 years ago

I think you can try this out by moving leader_time = timer_read(); to just outside the if (!leading && keycode == KC_LEAD) block in quantum.c. Here's the diff:

@@ -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!

ezuk commented 8 years ago

That's awesome! @BeatVids let us know what it feels like in practice, maybe we'll make it the default.

BeatVids commented 8 years ago

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. Let's say we have macros A+S and A+S+D. If we use A+S, then we have to wait because there is another possible macro we can use (A+S+D). But if we use A+S+D, there is no other possible macro we can use afterwards, so just run the macro immediately.

*KC_LEAD is pressed*  --  *Timer starts at 0*
94ms pass
*KC_A is pressed*  --  *Timer is restarted to 0*
90ms pass
*KC_S is pressed*  --  *Timer is restarted to 0*
90ms pass
*KC_D is pressed*
*A+S+D is the last possible combination, break, and run macro*

A simpler alternative, is to create a variable to set the maximum number of keys for macros, (3 is already the case currently).

*KC_LEAD is pressed*  --  *Timer starts at 0*
94ms pass
*KC_A is pressed*  --  *Timer is restarted to 0*
90ms pass
*KC_S is pressed*  --  *Timer is restarted to 0*
90ms pass
*KC_D is pressed*
*MAX_LEADER_KEYS (3) HAVE BEEN PRESSED, NO NEED TO WAIT, BREAK, AND RUN MACRO*

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 :)

jackhumbert commented 8 years ago

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 LEADER_DICTIONARY() {, I think you could use this instead, which will fire things as soon as the third key is pressed:

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.

BeatVids commented 8 years ago

Hi Jack! Just tried it! It is indeed very noticeable and snappy once I get to the third key. I will keep it on my keymap forever now, thanks!

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 format

One 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:

qmkmacro() {
    string="$@"
    for ((c=0;c<${#string};++c)); do
        CAPS=0
        char=${string:c:1}
        [[ $char =~ [A-Z~!@#$%^\&*()] ]] && CAPS=1
        char=$(echo $char | tr '[:lower:]' '[:upper:]')

        [[ $char == ""  ]] && char="SPC"
        [[ $char == "." ]] && char="DOT"
        [[ $char == "!" ]] && char="1"
        [[ $char == "@" ]] && char="2"
        [[ $char == "#" ]] && char="3"
        [[ $char == "$" ]] && char="4"
        [[ $char == "%" ]] && char="5"
        [[ $char == "^" ]] && char="6"
        [[ $char == "&" ]] && char="7"
        [[ $char == "*" ]] && char="8"
        [[ $char == "(" ]] && char="9"
        [[ $char == ")" ]] && char="0"

        [ $CAPS == 1 ] && echo "register_code(KC_LSFT);"
        echo "register_code(KC_$char);"
        echo "unregister_code(KC_$char);"
        [ $CAPS == 1 ] && echo "unregister_code(KC_LSFT);"
        echo
    done
}

Then we can run our function like this: qmkmacro "Ergodox EZ"

Which generates this:

register_code(KC_LSFT);
register_code(KC_E);
unregister_code(KC_E);
unregister_code(KC_LSFT);

register_code(KC_R);
unregister_code(KC_R);

register_code(KC_G);
unregister_code(KC_G);

register_code(KC_O);
unregister_code(KC_O);

register_code(KC_D);
unregister_code(KC_D);

register_code(KC_O);
unregister_code(KC_O);

register_code(KC_X);
unregister_code(KC_X);

register_code(KC_SPC);
unregister_code(KC_SPC);

register_code(KC_LSFT);
register_code(KC_E);
unregister_code(KC_E);
unregister_code(KC_LSFT);

register_code(KC_LSFT);
register_code(KC_Z);
unregister_code(KC_Z);
unregister_code(KC_LSFT);

Doesn't support all the characters yet, but if anyone wants me to, I'll go ahead and work on it more. The only thing that will need tweaking is the last "EZ" at the end, it would be better to share the KC_LSFT for both E and Z, but that's about it!

eltang commented 8 years ago

@BeatVids I believe you can just put #include "action_macro.h" at the top of your keymap file and put action_macro_play(action_get_macro(&(keyrecord_t){ .event.pressed = 1 }, id, 0); in your dictionary. If the macro executes something on the key release, put action_macro_play(action_get_macro(&(keyrecord_t){ .event.pressed = 0 }, id, 0); below that.

jackhumbert commented 8 years ago

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 send_string("Mr. Poopy Butthole"); and it should output that string. It supports ASCII, and the dictionaries I have there are for US Qwerty layouts, but others could easily be supported in similarly named arrays. Adding a way to handle other mods/keys should be trivial. Sticking literal keys in <>, and treating mods as toggles may make sense. Maybe even [] for down and {} for up or something. I know there are some other macro formats (AHK, etc), so if you think anything would make sense here, let me know.

jackhumbert commented 8 years ago

On the "quick" sequence you described, the SEQ_TWO_KEYS() is defined like this:

#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 SEQ_TWO_KEYS:

if (leader_sequence[0] == (key1) && leader_sequence[1] == (key2))

You'll also need to modify that LEADER_DICTIONARY() { line to be this:

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:

leading = false;
leader_end(); 

inside each of the SEQ blocks, instead of the above them, like this:

// 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");
  }
}
eltang commented 8 years ago

@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 \n.

jackhumbert commented 8 years ago

You can support that by editing ascii_us_qwerty - the first line are the control characters of the ascii table. \r is another that could be KC_ENT. It might be neat to use some others for the mods, but it could be complicated to format vs using some sort of markup (like <>[]{}).

eltang commented 8 years ago

I was gonna make \r convert to KC_RETURN instead, but I haven't had a chance to test if that keycode actually works or not. Why would modifiers other than Shift be useful, though?

jackhumbert commented 8 years ago

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 (0bxxxxx):

/* 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 \x04 for KC_LALT being turned-on/toggled. We wouldn't be able to use \x00 to clear though, since that's the string terminator, but maybe we could use \x7F instead.

eltang commented 8 years ago

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 0x7F. That might be a little hard to represent, though. Using rarely-used ASCII codes works too, but that feels like an unnecessary hack to me. I guess codes above 0x7F can be used if the modifiers are to be stored separately. Again, why is this useful? I thought this was a function for sending strings.

jackhumbert commented 8 years ago

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.

eltang commented 8 years ago

I wasn't saying this function wasn't useful; I'm just not too sure why modifiers other than Shift need to be supported.

jackhumbert commented 8 years ago

@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();
}
BeatVids commented 8 years ago

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 send_string("Mr. Poopy Butthole"); syntax, that looks really promising and very simple. I also don't use qwerty unfortunately, I use Colemak, so would would I have to change?

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.

jackhumbert commented 8 years ago

Do you use Colemak on your OS, or do have it configured in your keymap? Setting up a ascii_us_colemak would make sense, but a function to convert qwerty keycodes to colemak ones would be cool too (and something that's been on my list).

BeatVids commented 8 years ago

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.

jackhumbert commented 8 years ago

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.

BeatVids commented 8 years ago

SWEET! I got it to work! @ezuk I think this is also default worthy. Is there any reason why this isn't in the docs?

jackhumbert commented 8 years ago

The send_string function? I'm working on adding it now ;)

ezuk commented 8 years ago

Awesome! :)

@jackhumbert do you think we should maybe transition the leader to per-key timeout like that? I quite like the approach.

jackhumbert commented 8 years ago

Sure - it's an easy change :)

BeatVids commented 8 years ago

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.

eltang commented 8 years ago

@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?

jackhumbert commented 8 years ago

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.

BeatVids commented 8 years ago

Ok I'll give it another go. Let's say I have I have 3 leader key sequences: AA BB BBB

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?

Algorithm

So 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 Setting

The above algorithm to automatically do this may be too complex, so here's an alternative that may be easier to make: The user can define in their dictionary that AA has permission to run immediately as it is typed. They can also make it so BB runs before BBB, so BBB is impossible to use, but that would be the user's responsibility.

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 :)

jackhumbert commented 8 years ago

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

leading = false;
leader_end();

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 :)

BeatVids commented 8 years ago

Sweet! That's just what I was looking for, thanks Jack! Sorry about not understanding you earlier, putting it as a separate dictionary helped me understand what you meant.

I will probably divide my dictionary like so, here is a draft if this helps any github browser in the future:

    // Instant One
    if (leading && (timer_elapsed(leader_time) > LEADER_TIMEOUT || leader_sequence_size >= 1)) {
      if (leader_sequence[0] == (KC_A)) {
        leading = false;
        leader_end();
        send_string("Instant One!\n\t");
      }
    }

    // Instant Two
    if (leading && (timer_elapsed(leader_time) > LEADER_TIMEOUT || leader_sequence_size >= 2)) {

      //This gets overwritten by Instant One!
      if (leader_sequence[0] == (KC_A) && leader_sequence[1] == (KC_A)) {
        leading = false;
        leader_end();
        send_string("Instant Two!\n\t");
      }

      if (leader_sequence[0] == (KC_B) && leader_sequence[1] == (KC_B)) {
        leading = false;
        leader_end();
        send_string("Instant Two!\n\t");
      }
    }

    // Instant Three
    if (leading && (timer_elapsed(leader_time) > LEADER_TIMEOUT || leader_sequence_size >= 3)) {
      if (leader_sequence[0] == (KC_C) && leader_sequence[1] == (KC_C) && leader_sequence[1] == (KC_C)) {
        leading = false;
        leader_end();
        send_string("Instant Three!\n\t");
      }
    }

I didn't get the reference, but there's no way I took it like that. :) Thank you for the help Jack, you helped me A TON today. Good luck with completing the send_string function!

piotr-dobrogost commented 8 years ago

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.

eltang commented 8 years ago

@BeatVids Just a heads-up: as of #376, you must use SEND_STRING rather than send_string in your keymap.

BeatVids commented 8 years ago

Thanks @eltang ! Really appreciate the heads up!

What does PSTR do anyways? #define SEND_STRING(str) send_string(PSTR(str))


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.

eltang commented 8 years ago

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 KC_SLCK and KC_PAUS.

BeatVids commented 8 years ago

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?

eltang commented 8 years ago

In OSes like Windows, they're not regular keycodes. Usually, they're implemented with special drivers.

BeatVids commented 8 years ago

Gotcha Eric. That sucks that they're not regular keycodes. I try to make my keymap work on all OSs identically, as best as possible. Unfortunately, screen brightness won't be a part of that. Thanks for the heads up once again.

moonrumble commented 8 years ago

did we make the change to time-out on per key basis? I too like that approach.

jackhumbert commented 8 years ago

Not officially (yet), but you can make the one line change mentioned if you'd like to try it out!

ezuk commented 8 years ago

I am reopening this because I need to make the change in the default keymap.

BeatVids commented 8 years ago

With this addition to the default keymap, I recommend adding SEQ_FOUR_KEYS and even SEQ_FIVE_KEYS, since getting that many keystrokes will now be feasible.

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 :smile:

jackhumbert commented 8 years ago

Sure! Added at 98f0807 :)

Did you know - you can make a sequence out of literally whatever is in your keymap? eg M(1), MO(2), KC_SCLN, FN1 etc? :)

BeatVids commented 8 years ago

That's awesome, even SEQ_ONE_KEY(KC_LEAD) { SEND_STRING("leadception"); } works :laughing:

Thanks a lot for the four/five additions Jack, really appreciate it. Just tested them, and they work as expected. :tada:

patrickwelker commented 7 years ago

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:

  1. In ./qmk_firmware/quantum/process_keycode/process_leader.c I moved leader_time = timer_read(); like suggested by @jackhumbert here.
  2. To test if this mod works I replaced this code in my keymap.c

    #define LEADER_TIMEOUT 300
    LEADER_EXTERNS();
    
    void matrix_scan_user(void) {
        // LEADER FUNCTIONS
        LEADER_DICTIONARY() {
          leading = false;
          leader_end();
    
          SEQ_ONE_KEY(KC_I) {
            SEND_STRING ("https://");
          }
          SEQ_THREE_KEYS(KC_M, KC_I, KC_L) {
            SEND_STRING ("![]()");
            //TAP_ONCE (KC_LEFT); TAP_ONCE (KC_LEFT); TAP_ONCE (KC_LEFT);
          }
        }
      };

… with the example code from @BeatVids, so that I got this:

    #define LEADER_TIMEOUT 300
    LEADER_EXTERNS();

    void matrix_scan_user(void) {
        // LEADER FUNCTIONS
        // Instant One
        if (leading && (timer_elapsed(leader_time) > LEADER_TIMEOUT || leader_sequence_size >= 1)) {
          if (leader_sequence[0] == (KC_A)) {
            leading = false;
            leader_end();
            send_string("Instant One!\n\t");
          }
        }

        // Instant Two
        if (leading && (timer_elapsed(leader_time) > LEADER_TIMEOUT || leader_sequence_size >= 2)) {

          //This gets overwritten by Instant One!
          if (leader_sequence[0] == (KC_A) && leader_sequence[1] == (KC_A)) {
            leading = false;
            leader_end();
            send_string("Instant Two!\n\t");
          }

          if (leader_sequence[0] == (KC_B) && leader_sequence[1] == (KC_B)) {
            leading = false;
            leader_end();
            send_string("Instant Two!\n\t");
          }
        }

        // Instant Three
        if (leading && (timer_elapsed(leader_time) > LEADER_TIMEOUT || leader_sequence_size >= 3)) {
          if (leader_sequence[0] == (KC_C) && leader_sequence[1] == (KC_C) && leader_sequence[1] == (KC_C)) {
            leading = false;
            leader_end();
            send_string("Instant Three!\n\t");
          }
        }
     };

None of the sequences work.KC_LEAD->KC_A yields a SPACE, not the string. Anyone knows whats missing here?