Skip to content
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

Tap and Send immediately #2

Open
eltoncezar opened this issue Jun 28, 2019 · 20 comments · May be fixed by #9
Open

Tap and Send immediately #2

eltoncezar opened this issue Jun 28, 2019 · 20 comments · May be fixed by #9

Comments

@eltoncezar
Copy link

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

What I want to achieve is something like that:

  • If I press "s", send "s"
  • If I hold "s", send "Ctrl+s"

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
Copy link
Owner

evilC commented Jun 28, 2019

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

@eltoncezar
Copy link
Author

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
Copy link
Owner

evilC commented Jul 8, 2019

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
Copy link
Owner

evilC commented Jul 8, 2019

Also added Timelines section to try and explain it further

@eltoncezar
Copy link
Author

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
Copy link
Owner

evilC commented Jul 12, 2019

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
Copy link

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
Copy link

kybernetikos commented Jul 6, 2020

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
Copy link

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
Copy link
Owner

evilC commented Jul 9, 2020

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
Copy link
Owner

evilC commented Jul 9, 2020

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
Copy link

jbone1313 commented Jul 12, 2020

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
Copy link

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
Copy link

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

@jbone1313
Copy link

jbone1313 commented Mar 28, 2021

@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
Copy link

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

@evilC
Copy link
Owner

evilC commented Jul 29, 2021

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
Copy link
Owner

evilC commented Jul 29, 2021

See here for an explanation of why this happens

@jbone1313
Copy link

jbone1313 commented Feb 13, 2022

@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
Copy link

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}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants