evilC / TapHoldManager

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

Add option to engage hold behaviour immediately #10

Open Gazareth opened 1 year ago

Gazareth commented 1 year ago

First of all thanks for the repo! Looks sturdy and well written, a lot better than my personal attempts at this kind of behaviour in AHK! So I can only apologise for how much I've strayed away from the code style here, but I thought it would be a useful feature for other people.

Problem

When you set up hold and tap behaviours for a key, and let's say your hold behaviour is to hold down the shift key. You want to be able to immediately press the button and get the shift behaviour, but you can't, because you have to wait out the timers.

Setting the hold time to 0 means the key is always in hold behaviour, so you never get the tap behaviour.

Solution

Set the hold time to 0, but now this engages "soft hold"- The state sent to the callback function is that the key is in hold mode. However, internally there is now a "full hold" mode, which is only activated once the tap time has also been breached.

If you've made it to "full hold" state, and you release the key, you won't get a tap, but if you release before the full hold mode kicks in, you will.

All this time, hold mode has been active, so your attached functionality for hold mode has been available the whole time.

Side notes

Now, obviously you won't always want this, if for e.g. you send a letter or symbol on hold, you don't want this to appear immediately, you want it to appear after the hold time, in which case you set your hold time >= tap time, and then you get existing behaviour.

evilC commented 1 year ago

Hi, many thanks for your contribution. It's late here, and I was just about to go to bed and saw this - not really had time to wrap my head around the use-case, but a quick glance at the code looks to me like it's not completely crazy ;) If I don't get back to you within a day or two, gimme a poke (Or feel free to drop by my discord channel and discuss)

jbone1313 commented 1 year ago

Another user submitted pull request #9 to address issue #2, which seems related to this pull request.

I would suggest both of you have a look at that and compare.

Gazareth commented 1 year ago

Had a look at #9. seems like it is solving a slightly different problem. That one aims to immediately trigger an arbitrary non-hold action on key down. This PR aims to provide the option (via setting the hold time to 0) to engage the hold behaviour immediately.

Gazareth commented 1 year ago

After trying to use this in a new way, I noticed that double-taps don't cause the state to be set to TAP, because FullHold was taking too much priority. It now checks the internal state so as to disengage itself if appropriate, making way for double taps to come through and set the state to tap mode

eugenesvk commented 11 months ago

Immediately setting the soft shift mode would mean that if you type fast and release your shift-on-hold key after the next one has been pressed, instead of getting two lowercased letters you get one capitalized

f↓j↓f↑j↑ should be fj, not J (f is the hold-shift key)

I've tried to tackle it recently in this modtap branch where I track the down/up key sequences and decide whether to engage a 'soft hold' or not based on the following sequence of key events

Do you think it'd be a workable approach for this otherwise great library so that we could finally get proper tap vs hold behavior without timing compromises?

evilC commented 11 months ago

I'm sorry, I still do not understand the intent. Could you maybe supply a sample script to help me understand the use-case and logic?

eugenesvk commented 11 months ago

The sample script as well as a more detailed description of the approach is the linked branch above, e.g., here is the script (and it's launcher script)

The use case is having home row modifiers without affecting your regular typing, e.g., having f act as on hold, but type letter f on tap. This is impossible to achieve with purely delayed-based functions as those would exclude the ability to quicky capitalize words since you'd have to wait for the hold delay to pass

Reducing the hold timer to 0 would solve that issue, but would also break fast typing as in the example above where j is pressed before f is released

evilC commented 11 months ago

A glance at the sample script certainly helps, I think I understand the use-case now.
What may help me understand it fully (And potentially see if I have a better implementation) would be to see an example of the same script using THM

But just off the top of my head... Is time not irrelevant for the mod key? Is it not only important that another key is pressed before the mod key is released?

If this is the case, then why not just have some mechanism for any given key (eg u) to tie it in some way to the modifier key? eg with f as a modifier key and u as a modifyable key... When declaring u, have a method such as .isModifiedBy() and pass it the reference to f's KeyManager instance. Then, in KeyEvent, check if the key is modified by another, and if it is (And the modifier is held), then...

  1. Fire the callback to indicate this
  2. Signal to the key manager of the modifier key to behave as we want (Stop all timers?)
eugenesvk commented 11 months ago

(btw, I've updated the script and now it shows debug status symbol near your text caret when on-hold shift is activated with f)

What may help me understand it fully (And potentially see if I have a better implementation) would be to see an example of the same script using THM

As far as I understood, THM only deals with timings, so I didn't look further, so I don't know how to use THM for the same purpose. Hence my original comment - I was thinking that the folks much more knowledgeable about THM could see if THM could incorporate this approach

But just off the top of my head... Is time not irrelevant for the mod key?

Only in the simplest case: you press and hold a key longer than, say, 1 second, then it's obvious that it's a modifier.

Is it not only important that another key is pressed before the mod key is released?

No, typing fa fast means that you always have a pressed before f is released, yet it doesn't mean that f acts as a mod key, it continues to act as a regular key.

If this is the case, then why not just have some mechanism for any given key (eg u) to tie it in some way to the modifier key? eg with f as a modifier key and u as a modifyable key... When declaring u, have a method such as .isModifiedBy() and pass it the reference to f's KeyManager instance. Then, in KeyEvent, check if the key is modified by another, and if it is (And the modifier is held), then...

  1. Fire the callback to indicate this
  2. Signal to the key manager of the modifier key to behave as we want (Stop all timers?)

In this description I don't see the key up even, which is crucial for differentiating fu as typing and fu as ⇧u. You need to have callbacks on key down (block any action since at this point you don't know the future and don't know what role f should play) and key up (now you can decide, if both u down and up events happen after f down, then f is a modifier, so it's modifier mode should be globally enabled, but if u down happened before f down, then it's a stray u up, so just typing). That's what inputhook provides, key down/up callbacks

Also, you need to handle f and decide whether to type it or not, so it's not only about u being modifyable, but also f being modifyable depending on u

eugenesvk commented 11 months ago

When declaring u,

by the way, you'd need to declare the whole keyboard layout this way since modifier would affect all keys (though maybe that's not a big deal if THM can declare something like a Loop 123...abc...uxyz))

evilC commented 11 months ago

Maybe I am getting confused here, but what I was asking for was a sample script that utilizes the code in this PR, so that I can clearly see intent and results, and evaluate whether I feel that it's the best solution to the problem