evilC / TapHoldManager

An AHK library for Long Press / Multi tap / Multi tap and hold
MIT License
124 stars 13 forks source link

Tap and Send immediately #2

Open eltoncezar opened 5 years ago

eltoncezar commented 5 years ago

Hi, first of all, thanks for the amazing work with this library!

What I want to achieve is something like that:

I came up with the following code:

thm := new TapHoldManager()
thm.Add("s", Func("MyFunc1"))

MyFunc1(isHold, taps, state){
    if (isHold) {
        Send ^s
    } else {
        Send s
    }
}

It works and sends the "s" correctly, but only after the default tapTime of 200ms, which is not desirable. I want to send the "s" immediately, but retaining the hold functionality.

What is the right way to achieve this using your library?

evilC commented 5 years ago

https://github.com/evilC/TapHoldManager#syntax tapTime and holdTime control the timings

eltoncezar commented 5 years ago

Thanks for your reply. I tried different parameters combinations, and ended up on this one:

thm := new TapHoldManager(0, 300)

It works, but still there's a delay on the actual keypress. I think that's because of the IF statement logic processing cost. The following code doesn't have a delay:

MyFunc1(isHold, taps, state) {
    Send s
}

While this one have:

MyFunc1(isHold, taps, state) {
    if (isHold) {
      Send ^s
    } else {
      Send s
    }
}

Do you have any suggestion on what can be done to improve this?

evilC commented 5 years ago

Are you saying that there is a delay with the second example if you tap s, whereas there is no delay with the first example if you tap s? That would be odd.
The second example is simply checking a boolean, so I don't see how it would incur any extra delay if isHold is false.
Is there any difference between

MyFunc1(isHold, taps, state) {
    Send s
}

and

MyFunc1(isHold, taps, state) {
    if (!isHold) {
      Send s
    }
}

?

Upon re-reading the documentation, I think it was unclear. My description of what tapTime did was misleading at best.
I have clarified the descriptions, plus added a flowchart to try to explain the logic used.
Any feedback as to whether this clarifies things sufficiently would be appreciated!

evilC commented 5 years ago

Also added Timelines section to try and explain it further

eltoncezar commented 5 years ago

Yes, the delay is real. In the first example, I can type just fine. In the escond example, I can't type juts fine. (actual example of typing 😄).

As you can see, the letter S is delayed with the if condition. Indeed, very odd. I don't believe this is related to your library, but maybe some autohotkey behavior.

About the docs, great job! It is clearer than before, and that flowchart is really something!

evilC commented 5 years ago

I tried to repro, but could not see any difference.
Is the code in the first post the entire script? Do you maybe have anything like SetBatchLines etc at the start of your script?

jbone1313 commented 4 years ago

I have this exact same issue. Exactly as eltoncezar describes.

One thing I will add: If I set two keys, say, "e" and "j" to basic tap and holds as described in the OP, then typing them in succession seems fine. If I have one key set to a basic tap and hold and another key without a tap and hold, then I get the described behavior.

(Many thanks for this otherwise awesome script!)

kybernetikos commented 4 years ago

I get similar behaviour, but I see it even with an immediate Send.

thm := new TapHoldManager(0, 150, 1)    ; TapTime / Prefix can now be set here

thm.Add("t", Func("Tchords"))

Tchords(isHold, taps, state) {
    Send t
}

With this code, if I type at my normal typing speed, the 't' will always appear late, e.g. I'll always end up writing 'hte' instead of 'the'.

I do notice that when I'm typing normally, I often hold some of the keys over the pressing of the other keys - e.g. if I type 'the', I might press 't' down, then press 'h' down, then release 't' then press 'e', then release 'h', then release 'e'.

I can see why this is - TapHoldManager can't send the tap until it knows whether it's a tap or a hold, which isn't until after holdtime has passed or the key has been released, but it does mean that I can't really use it for what I was hoping to (normal typing with taps, chordal expansions with hold -e.g. t\<hold>with h pressed becomes 'the' on release). I think the only way to avoid this really would be to queue up keypresses while you're deciding whether it's a tap or a hold, and replay them in the right order.

Mod-Tap in QMK has a setting called "Permissive Hold" https://beta.docs.qmk.fm/using-qmk/software-features/tap_hold I think that this means that if your tap overlaps with another keys tap, it sends taps for both keys, which would probably fix @eltoncezar's problem, but wouldn't help me with mine.

jbone1313 commented 4 years ago

That is a great reply, kypernetikos. Great explanation.

Yesterday, I rolled my own simple tap and hold, and I had the same issue. So, it does seem like it's just the physics of having to wait for the key release to know that it is not a hold.

"Permissive Hold" sounds really interesting. :)

evilC commented 4 years ago

Set the maxTaps parameter to avoid any delays. If you only want to support single-tap or single-hold, then setting maxTaps to 1 will mean that the instant you release a key on a tap, the function will be fired with no delay

evilC commented 4 years ago

Implementing stuff like permissive mode would require quite a lot of changes I think. Currently, each key's processing is completely independent of another - there is no one piece of code which handles all keys - each key is handled by an instance of the KeyManager class, and each key's KeyManager is unaware that other key's KeyManagers exist. Each KeyManager is aware that the parent TapHoldManager instance exists, so in order to pull something like this off, as each KeyManager processes input, it would need to communicate with the TapHoldManager to see if any taps or holds are in progress Maybe if someone can come up with a design of how it should work I could tackle it, but in the absence of that I am not sure that this is something I would be prepared to undertake. If anyone else wants to have a stab at implementing it, I would certainly be willing to offer guidance though

jbone1313 commented 4 years ago

Above, I wrote:

If I set two keys, say, "e" and "j" to basic tap and holds as described in the OP, then typing them in succession seems fine. If I have one key set to a basic tap and hold and another key without a tap and hold, then I get the described behavior.

I just tested doing that to all the letters. It seems to work fine. Here is why I think it works:

By mapping all the letters, we are essentially adding the delay to all of them. That makes them all have the same delay, which means you do not have the typing issue as explained by the OP. I do not seem to notice the delay while I am typing. I only notice weirdness when there is a mismatch between the letters.

Therefore, it is possible that TapHoldManager could be configured to optionally add a delay after each non-mapped key press. I.e., if the key is NOT setup in TapHoldManager, then add a configured offset in milliseconds to the key press. Presumably, this should be a parameter in the TapHoldManager constructor.

I have no idea what side-effects there would be if this is done, so obviously, it would need to be well beta-tested.

The basic idea is this: Creating a mapping for a given key as described by the OP induces a delay. Offsetting all other keys to have that same delay makes it seem like there is no delay.

jbone1313 commented 4 years ago

Quick update:

I mapped all of the non-modifier keys, and it seems to be working fine so far.

It was relatively easy to do them all, so perhaps it is not even worth updating TapHoldManager.

I will report back on how it goes after more testing.

mplattner commented 3 years ago

@jbone1313, what config did you end up with? I'm, trying to achieve the same. Thanks!

jbone1313 commented 3 years ago

@mplattner I ended up not using TapHoldManager. Instead, I simply do the below. This example is for the 6 key.

I could never figure out a way to send immediately. So, I only do this on keys where I am OK with the lag.

In any case, I cannot remember why I gave up on TapHoldManager (it is a fine piece of code, but I think it was more than I needed), and I am not sure how well that "map all the letters" thing I wrote in the above post from July works in practice.

Sorry, it has been a while since I was messing with this.

$6::
KeyWait, 6, %holdTime%
if ErrorLevel
{
    Send 6
    KeyWait 6
}
else
{
    Send #d
    Send {LWin up}
    Send {RWin up}
}
return
mplattner commented 3 years ago

Thank you @jbone1313! 👍 I only saw your reply today, sorry.

evilC commented 3 years ago

This code is likely to be buggy

$6::
KeyWait, 6, %holdTime%
if ErrorLevel
{
    Send 6
    KeyWait 6
}
else
{
    Send #d
    Send {LWin up}
    Send {RWin up}
}
return

Consider this snippet:

holdTime := 100
$6::
KeyWait, 6, %holdTime%
if ErrorLevel
{
    Send 6
    KeyWait 6
}
else
{
    Send #d
    Send {LWin up}
    Send {RWin up}
}
return

$7::
KeyWait, 7, %holdTime%
if ErrorLevel
{
    Send 7
    KeyWait 7
}
else
{
    Send #e
    Send {LWin up}
    Send {RWin up}
}
return

Press 6, press 7 within 100ms, release 6, release 7 You will get 7 then 6, not 6 then 7 (Or #e then #d, not #d then #e)

Hell, if HoldTime was 1s, and you did 6 down <wait 100ms> 7 down <wait 100ms> 6 up (NOTHING HAPPENS YET) <wait 1 sec> 7 up - then it would send 7 then 6 when you wanted to send #d then 7

TLDR to code stuff like this you should really be using SetTimer and key up hotkeys, not blocking waits such as KeyWait or a GetKeyState loop

evilC commented 3 years ago

See here for an explanation of why this happens

jbone1313 commented 2 years ago

@evilC I finally came back to this, and your reply made me think about it some more.

As a matter of interest, I might have found a way to implement tap/hold without the lag issue. Similar to what I wrote above, it essentially involves implementing the keystroke on the UP stroke for ALL keys. In that way, the user does not experience the weird typing, since all keystrokes are effectively delayed to fire after the key is released.

I am thinking about coding up a script to do it. Here is an example for handling tap/hold for the a key, which does not use TapHoldManager.

a::
    aFlag := false
    SetTimer, TimerA, %holdTimeInt%
        KeyWait a
return
a up::
    SetTimer, TimerA, Off
    if (aFlag = false && A_PriorHotKey != "Enter & " A_PriorKey)
        if (GetKeyState("Capslock","T") = true)
            Send A
        else
            Send a
return
TimerA:
    aFlag := true
    Send #1
return

Then, for all the other keys, I would do something like this. This is an example for b, which is a key on which no tap/hold would be configured. It starts to get tricky dealing with control key combinations, but it seems like it can work.

b up::Send {b}
+b up::Send {B}

If you have any feedback, I am all ears.

jbone1313 commented 2 years ago

Quick update: I setup the script as I described above, and it seems to work great. I setup some tap/holds on my home row keys, and I am blissfully typing away like it's no big deal.

In summary: setting up tap/holds as I described above is not a problem if one also implements Sends for all the other keys like this:

b up::Send {b}
+b up::Send {B}
^b up::Send ^{b}