Tap, double-tap, hold function wanted - choice of primary actions not working as expected

I want to have a key (on a mod layer) to have three (or possibly later even more) functions, depending if I tap, double tap or hold a key.

// tap: ctrl-v
// double-tap: ctrl-shift-p (paste without formatting)
// hold: shift-ctrl-v (clipboard manager)
set secondaryRole.advanced.timeout 250
ifSecondary advancedStrategy goTo secondaryaction
primaryaction: 
    final tapKey LC-v
    final ifDoubletap tapKey LCS-p 
secondaryaction: final tapKey SC-v

I never get to the double tap function. If I omit the final both commands (tap and doubletap) are carried out. When I move the double tap out of the primary action I also do not get it working.

I have seen examples in the forum with ifGesture for double-taps, but do not understand why that approach is chosen, when there is a double-tap command already. Or are those examples old and not needed any longer?

Lets break that down:

ifDoubletap <action> just checks if the same macro was executed in the last ~400 ms and immediately continues - i.e., either carries out the action or not.

final <action> terminates the macro after action is finished.

Actions are performed line by line from top to bottom. (It is not a declarative language. It is imperative.)

So:

    final tapKey a
    final ifDoubletap tapKey b

always prints just a, because you told it to terminate after the first line is carried out.

    tapKey a
    ifDoubletap tapKey b

Always produces a, and sometimes produces b.

    ifDoubletap final tapKey b
    tapKey a

Produces only b on second tap of a doubletap, but the first tap of doubletap produces a, so you get “ab”.

ifGesture $thisKeyId <action> uses our time machine for looking into the future. Thanks to that it is possible to know during the first tap whether another tap will come in.

So what you are after is probably:

set secondaryRole.advanced.timeout 250
ifSecondary advancedStrategy final tapKey SC-v // hold behavior
ifGesture $thisKeyId final tapKey LCS-p  // doubletap behavior
tapKey LC-v //single tap behavior

(Note to self, we really should add an ifHold command so that people don’t have to misuse ifSecondary for simple hold logic.)

(As for nonintuitiveness of ifGesture vs ifDoubletap, I am happy to hear any language refactor proposals.)

2 Likes

ifKeySequence or ifFollowedBy…?

That doesn’t help to clear up the confusion between using ifGesture as ifDoubletap, does it? :roll_eyes:

Thanks a lot for the explanations Karel. Now I understand it mostly (need still to get a bit of practice with that).

But

ifGesture $thisKeyId final tapKey LCS-p // doubletap behavior

I still do not understand why that is waiting for a doubletap and what the ifDoubletap command is used for then. Maybe the name is indeed contributing to the confusion of myself. A gesture is a (hand) movement and I do not understand what the name means here.

In the user guide it is stated

  • binding arbitrary shortcuts or gestures

which I also do not understand what gesture means in that context (pointer device use? if so, why is that also used for keys like in the doubletap example?)

What would be the difference? Just possibly easier to read or is secondaryRole something basically different?

I still do not understand why that is waiting for a doubletap

Well, if pretending that its name is ifFollowedBy instead helps, then please feel free to pretend so :).

The idea behind ifGesture is supporting discontinuous action sequences similar to for instance vim’s controls. There are control sequences like gg, gt and gT (or generally g+something), c+movement command, where the first key can be released before the next key is pressed, yet the sequence still has a special meaning.

ifGesture takes a string of key ids (hardware key identifiers). If I parametrize it with $thisKeyId, it will wait for tap of the same key, i.e., effectively doubletap.

Does this explain it?

and what the ifDoubletap command is used for then. Maybe the name is indeed contributing to the confusion of myself. A gesture is a (hand) movement and I do not understand what the name means here.

ifDoubletap is historically much older than ifGesture. There are many “dead key” actions on UHK, for which it is reasonable to want the keys to retain realtime execution, and for which the side effects of realtime execution have no negative consequences.

For instance:

holdLayer fn
ifDoubletap toggleLayer fn
holdKey iLS //act as a normal left shift
ifDoubletap tapKey capsLock //lock "shift" by doubletapping shift

which I also do not understand what gesture means in that context (pointer device use? if so, why is that also used for keys like in the doubletap example?)

Still not? A possibly discontinuous sequence of key taps that has a specific meaning. Just like a string of consecutive mouse movements, or whatever.

What would be the difference? Just possibly easier to read or is secondaryRole something basically different?

The way I see it it is different semantics. The advanced secondary role contains pretty sophisticated logic that tries to distinguish intentional primary and secondary roles from normal typing. While some configurations are based on length of the hold, others rely more on exact key release order, or on consecutiveness of the taps. With some configurations, hold can on the contrary lead to a press of the primary key, not secondary.

On the other hand, ifHold would be logically very straighforward: tell the user whether the key is being briefly tapped or held. Do not pressume anything about intended meaning or usecase.

ifGesture, ifShortcut, ifPrimary and ifSecondary are sophisticated implementation for specific usecase. ifHold would be a simple basic block for tapdance like usecases.

Part of the point is that if you use secondary role for detection of simple hold… …then you can’t use it for any sophisticated secondary role resolution.

2 Likes

I have tried to improve the explanation a little: DOCS: improve explanation of shortcuts and gestures. · UltimateHackingKeyboard/firmware@ff9604f · GitHub . If you have any specific suggestions, I am happy to hear them!

1 Like

Thanks a lot Karel for the explanations and the updated docs! :slight_smile:

1 Like

Minor language point: should it be called ifHold or ifHeld? i.e. it is only determined after the fact, isn’t it?

I like the idea of ifHeld. There should just be some really clear explaination in the user-guide or in the reference manual how all of these work together.

Another side question: Does ifGesture also detect a sequence when the keys are (partially) pressed together (i.e. their ‘down’ time overlaps)? Or do they always have to be pressed individually one after another?

How would that be differentiated from shortcuts? Can I have a sequence (e.g. asdf) both as a shortcut (activates only if all of them pressed together), and as a gesture (activates whenever it is NOT a shortcut but they have been tapped in sequence, maybe with some overlap)?

Minor language point: should it be called ifHold or ifHeld? i.e. it is only determined after the fact, isn’t it?

That’s a rather philosophical question, given that UHK features a time machine that can query the future state of the keyboard :slight_smile: .

But it is a fair point that ifHeld maybe sounds more natural linguistically.

Another side question: Does ifGesture also detect a sequence when the keys are (partially) pressed together (i.e. their ‘down’ time overlaps)? Or do they always have to be pressed individually one after another?

Yes, they can overlap.

How would that be differentiated from shortcuts? Can I have a sequence (e.g. asdf) both as a shortcut (activates only if all of them pressed together), and as a gesture (activates whenever it is NOT a shortcut but they have been tapped in sequence, maybe with some overlap)?

Yes, you can have both. Which activates will then be determined by command order, so ifShortcut should come first as its condition is more strict.

When ifGesture is meant to detect a sequence it would be much clearer IMO to name it accordingly ifSequence. The meaning of gesture I still can not associate with the ifGesture command.

I agree. I think ‘Gesture’ is not a very intuitive name. To a minor extent, also ‘Shortcut’ is not precise.

In my brain:
Shortcut = simultaneous press = chord => ifChord
Gesture = tap sequence => ifFollowedBy or ifSequence

Not sure if that thread is the correct place for that discussion, but possibly it is.

When I understand the commands correctly then even the ‘if’ is not the correct part of the command-name, because it is not a check, but a command what to do. Also there is no meaningful case for the ifNot... versions of both commands, right? But rather for a specific event (key press, hold or whatever) a sequence (or chord) will be triggered. When my understanding is correct a better name could be:

Chord or makeChord or similar and
Sequence or makeSequence

instead of the word ‘make’ one could also use ‘produce’ or ‘create’ or ‘trigger’ or similar words when one opts for the second way!?

Sounds quite reasonable.

When I understand the commands correctly then even the ‘if’ is not the correct part of the command-name, because it is not a check, but a command what to do.

How so?

ifGesture a b c d write foo
^ condition       ^ command

It is a check into the future, but it is a check, isn’t it?

But rather for a specific event (key press, hold or whatever) a sequence (or chord) will be triggered.

You sure you understand it correctly? The ifShortcut itself produces no events. It serves for mapping actions to chords. Not for producing key chords.

Also there is no meaningful case for the ifNot... versions of both commands, right?

ifNotGesture a goTo next
...
next: ifNotGesture b goTo next
...
next: ifNotGesture c goTo next
...
next: ...

Although since the {} were added, this is admittedly not a very practical example.

When at it, I am also thinking of clearly distinguishing time-machine commands:

ifFutureSequence
ifFutureChord
ifFutureDoubletap

which would actually help to clear up the confusion about ifDoubletap which caused this entire discussion…

Possibly I did not understand the commands correct yet indeed. Therefore my disclaimer (‘when I understand correct…’). Thanks for the detailed answer!

The suggestion with future in the name sounds logical and might indeed make it clearer.

For me, that ..Future.. is confusing.

How does this work for real? Will the macro pause at the ifSequence command (I realise it has to wait for future keystrokes)? What if nothing is typed? So there is a timeout. That must be similiar to the advanced Primary/Secondary detection – it also has a timeout. But there we don’t call it “Future”.

???

@kareltucek Just to make it clear: I think you have done a marvellous job - starting with your firmware fork - in creating a flexible implementation with awesome features. Your postponing system is pretty amazing, and the UHK firmware and it’s macro language has become so feature-rich!

What I am concerned is: how to package the amount of possible features into comprehensible, logically consistent, concise and precise command names. There must be a way to make this easily understandable.

1 Like

How does this work for real? Will the macro pause at the ifSequence command (I realise it has to wait for future keystrokes)? What if nothing is typed? So there is a timeout. That must be similiar to the advanced Primary/Secondary detection – it also has a timeout.

Yes.

But there we don’t call it “Future”.

I was thinking about that, and my arguments are following:

  • Meaning of secondary roles seems pretty clear and no one complains about it.
  • Secondary roles by definition refer to consequently pressed keys.
  • Secondary roles too have two modes of operation - immediate (w.r.t action key) and postponed (in case of advanced strategy)
  • Linguistically ifSecondary and ifPrimary refer to the role of the activation key, not to the future keys. The ifFuture... variants on the other hand linguistically refer to a key sequence that is to come.

So technically yes, you are right that it could be called Future, but I (yet) don’t see sufficiently strong reason to change tha language because of it.

I am trying to argue against calling it Future; to me this is confusing.

I’d rather try to express what the author of the macro is trying to achieve, detection of

  • Primary/Secondary use of a key
  • a Key held down
  • a Sequence of keys pressed in succession
  • a Chord pressed together (possibly in any sequence? but the current implementation needs to be bound on a single initial key!)

From that view, I don’t care if there is magic about future or past, so I wouldn’t add any Future.

Maybe a few diagrams could help, showing a sequence of keypresses and pauses, and showing which ifXXX would trigger at which step in the sequence?

1 Like