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

Selective timeouts, or the disambiguation of nested overloads #81

Closed
nsbgn opened this issue Jan 2, 2022 · 11 comments
Closed

Selective timeouts, or the disambiguation of nested overloads #81

nsbgn opened this issue Jan 2, 2022 · 11 comments

Comments

@nsbgn
Copy link

nsbgn commented Jan 2, 2022

I realize that this behaviour might not be achievable without compromising reasonable design decisions. I'll first defend my use case, then some ideas on cracking it. Nesting overloads might sound arcane, but I think that the way I have crammed functions into my alt keys is pretty intuitive:

[main]
leftalt = overload(M, esc)
rightalt = overload(S, enter)

[M]
rightalt = overload(M-S, compose)

[S]
leftalt = overload(M-S, \)

For the most part, it works really well. However, I'll often tap enter; esc in rapid succession, which really often results in a backslash rather than the intended sequence. It's clear what's happening (I haven't released rightalt before pressing leftalt). It's less clear how that should be solved, because in most cases, that is the desired method of disambiguation. How else would you be able to type \ when that is the intention?

  • The best timeoutless idea I can come up with at the moment is to be able to set specific keys to be evaluated on keyup rather than the default keydown. If rightalt is still held during the keyup event of leftalt, we get a backslash, and otherwise enter; escape. I know that one goal of keyd is to avoid having users think about individual key events, so this is undesirable.

  • An additional timeout mechanism might solve this. For example, one that takes into account the time that keys have overlapped, rather than the time elapsed since the first keydown. Probably way too heavy-handed for a relatively obscure use case like this --- I'm mentioning it anyway since there might be others. This would also damn all keys in the layer accessed by the overloaded key to only be generated on keyup, since only then is the overlap time known.

  • Putting a timeout on the [main] overload of rightalt would work, but that would also affect the other keys of the layer in the ways discussed in SpaceFN is a bit too slow #34. This is unacceptable unless the timeout could somehow be taken into account only for specific keys.

This last idea is certainly simplest. Selective timeouts could be expressed easily, too: a timeout without a 4th argument would apply to all keys in the layer, otherwise it applies only to the keys mentioned in the 4+ith arguments. So, for me:

rightalt = overload(S, enter, 140, leftalt)

Is there a better way to go about this? If not, are selective timeouts a feature that you would consider?

@lemnos
Copy link
Contributor

lemnos commented Jan 2, 2022

Thanks for the lucid exposition. You seem to have a good grasp of the problem domain.

If rightalt is still held during the keyup event of leftalt, we get a backslash, and otherwise enter; escape. I know that one goal of keyd is to avoid having users think about individual key events, so this is undesirable.

The problem with this is that it is predicated on the assumption that your enter->escape sequence always takes the form:

<rightalt down> <leftalt down> <rightalt up> <leftalt up>

and your backslash sequence always takes the form:

<rightalt down> <leftalt down> <leftalt up> <rightalt up>.

This is the same problem encountered in the sequence based disambiguation experiments I described in #34.

To wit:

It is tempting to believe that we always do

<a down> <b down> <a up> <b up>

when we mean ab and:

<a down> <b down> <b up> <a up>

when we mean control+b (where a is overloaded as `control), but in practice the order of the last two key events is non deterministic.

An additional timeout mechanism might solve this. For example, one that takes into account the time that keys have overlapped, rather than the time elapsed since the first keydown.

This assumes that control-b as an intention always produces:

<a down> <b down> <timeout> <b up> <b down>

and ab always yields:

<a down> <b down> <b up> <b down>

which (barring retraining) is even less likely to be true than the first assumption.

This would also damn all keys in the layer accessed by the overloaded key to only be generated on keyup, since only then is the overlap time known.

In theory it would be possible to apply this only to nested overloads and emit any non overloaded keys immediately upon key down, but the implementation would be fairly involved for (arguably) little benefit.

Putting a timeout on the [main] overload of rightalt would work, but that would also affect the other keys of the layer in the ways discussed in SpaceFN is a bit too slow #34. This is unacceptable unless the timeout could somehow be taken into account only for specific keys.

Indeed :P.

rightalt = overload(S, enter, 140, leftalt)

This is an interesting idea, but it seems to have quite a niche application. I imagine you also wouldn't want to completely forfeit the timout which disambiguates enter and shift, in which case you would be looking at adding yet another argument.

Is there a better way to go about this? If not, are selective timeouts a feature that you would consider?

I'm not aware of a cleaner way to achieve what you have described. You seem to be running into the same hurdles I encountered when I (in my impetuous youth) was struck by the same overload all the things bug. My ultimate solution was to shake my fist at the heavens and buy a kinesis advantage (not necessarily in that order).

Having said that, it is entirely possible that you are a better man than I :P.

I am willing to consider the addition of such a feature if one or both of the following hold:

  1. It is possible to achieve this using another ubiquitous remapping tool

  2. You have some personal experience with such a setup and have managed to use it for a non trivial amount of time (> 1 week) without throwing your keyboard at the wall or going into a homicidal rage.

@nsbgn
Copy link
Author

nsbgn commented Jan 3, 2022

The problem with this is that it is predicated on the assumption...

Indeed. I was grappling with the problem as I was writing and realized only after unplugging that my first two suggestions rehashed assumptions that we've already touched upon in #34. Woops. Thanks for your patient explanations. I'm hanging on to the third suggestion, though.

This is an interesting idea, but it seems to have quite a niche application.

Yeah, my biggest (only?) worry is that it is a feature that would help me but only serves to increase cognitive load for others. (Although, naturally, my setup shall be perfect and there is no reason for anyone to deviate from it.)

On the other hand, as the timeout weapon is already in our arsenal and we know of its dangers, it doesn't seem like a bad idea to have a method for narrowing its area of effect.

The most general case I can formulate is: you want a key to do something else when combined with another (→ overloads), but you want to still be able to use the tap keys in a keystroke (→ timeouts), without any other key combinations being affected by the resulting pitfalls (→ selective timeouts). Those combos would still be affected by the pitfalls of overloads in general, which I think is what limits the application domain so much. I'll think for a bit about how to address that.

I imagine you also wouldn't want to completely forfeit the timout which disambiguates enter and shift, in which case you would be looking at adding yet another argument.

My initial suggestion was indeed going to be more elaborate, allowing for different timeouts per key, but that was too much for even my questionable conscience.

In my particular case, though, it is not a concern. It needs no general timeout to disambiguate enter/shift or esc/meta. The saving grace is that enter and esc both tend to mark the end of a keystroke, which means that overlaps with subsequent keys are exceedingly rare (except when they overlap eachother, which is exactly what prompted my issue).

You seem to be running into the same hurdles I encountered when I (in my impetuous youth) was struck by the same overload all the things bug.

Your wise words have already managed to shatter my dreams of overloading space. Don't take this away from me, too. :P

You have some personal experience with such a setup and have managed to use it for a non trivial amount of time (> 1 week) without throwing your keyboard at the wall or going into a homicidal rage.

That's fair. I'll report back soon, assuming the authorities don't seize me.

(I'm not too concerned since I'm actually already using this layout and it feels nearly perfect, with the esc;enter misfire as the only major annoyance.)

@nsbgn
Copy link
Author

nsbgn commented Jan 3, 2022

Those combos would still be affected by the pitfalls of overloads in general, which I think is what limits the application domain so much. I'll think for a bit about how to address that.

I gave it some thought. How about this: the optional fourth argument is a 'timeout layer' instead. If no such argument is given, the lack of pause means that both keys are interpreted as taps, as always. If one IS given, then any keys that overlap before the timeout activate this layer instead of the standard one.

It's simpler, yet would still allow for my use case, since I would just make a timeout layer that inherits from S but sets leftalt = macro(esc enter). It's also more flexible, possibly accommodating other use cases. (I could expand upon this once I'm back at my computer.)

Edit: I guess that should be leftalt = overload(M-S, esc enter) or overload(M-S, macro(esc enter)) but can't check if that's legal right now

@rvaiya
Copy link
Owner

rvaiya commented Jan 4, 2022

I will need some time to properly digest this. In the meantime can you provide some simple sample configs that demonstrate the general utility of this approach?

P.S

Neither of the last two mappings you listed are valid. I have been considering permitting macros to be used in place of key sequences within overload(), but I have yet to implement it (probably because no one has complained :P).

@nsbgn
Copy link
Author

nsbgn commented Jan 4, 2022

I will need some time to properly digest this.

No worries, and don't let me trick you into accepting feature creep. In fact, I also have to digest it better. Thanks for humouring me so far.

(probably because no one has complained :P).

I'll do it.

In the meantime can you provide some simple sample configs that demonstrate the general utility of this approach?

I realize now that I overstated the generality. I thought it would allow control over overloading per key combination, but that's wrong. (I did come up with other ideas for that, but I'll only burden you with them if I see a particularly compelling use case rather than some hypothetical flexibility.) So, I'm having a hard time coming up with reasons why one would use the feature (except my own).

Plan B

Perhaps a less complicated, yet more generally useful way to scratch the overload-everything itch, is to disregard all this nested overloading nonsense and instead add a dual timeout.

overload(<layer>, <keyseq>[,<hold_timeout>[,<tap_timeout>[,<cancel_key>]]])

The timeout that we have right now is a sort of "lower" time limit on holds. An additional "upper" time limit on taps would provide a mechanism to "cancel" a tap by holding the key for a long time without combining it with any other key. (Since it would be emitted on keyup, this cancellation need only apply to the tap; you can still combine with another key at any time to obtain a hold instead.)

The key emitted upon cancellation would be noop by default --- but if it is configurable, that would give a lot of flexibility. Like, what about turning shift into capslock if you hold it for a second? And for my own usecase:

[main]
leftalt = overload(M, esc, 0, 400, \)
rightalt = overload(S, enter, 0, 400, compose)

(From what I can tell, this idea is close to kmonad's :timeout-button, but not exactly the same.)

@rvaiya
Copy link
Owner

rvaiya commented Jan 4, 2022

Thanks for humouring me so far.

I find this quite useful, so I should be thanking you :).

Plan B....

Putting aside the general case for a moment, if I understand you correctly then:

leftalt = overload(M, esc, 0, 400, )

under this new scheme would cause the following:

<lalt down> <400 ms> <lalt up>

to yield \ while:

<lalt down> <200 ms> <lalt up>

would yield esc.

I imagine this would make for quite a fraught typing experience. A single timeout is hard enough to contend with on its own, but using the length of time to generate different keys adds a larger amount of cognitive overhead and would probably cause a lot of accidental actuations. Have you used such a setup before?

@rvaiya
Copy link
Owner

rvaiya commented Jan 4, 2022

I gave it some thought. How about this: the optional fourth argument is a 'timeout layer' instead. If no such argument is given, the lack of pause means that both keys are interpreted as taps, as always. If one IS given, then any keys that overlap before the timeout activate this layer instead of the standard one.

If my understanding is correct, then it seems that primary the goal of this approach is to take advantage of implcit key-specific sequence based disambiguation (1). The simplest version of this would be to change the stock behaviour of the overload action to produce <overloaded key><unmapped key> when encountering unmapped keys within <layer>.

E.G

a = overload(alayer, a)

[alayer]
d = C-d

would produce C-d in the case of <a down> <d down> <d up> <a up> and ax in the case of <a down> <x down> <x up> <a up> for all unmapped <x>.

You could simulate something like this with the current rules as:

a = overload(alayer, a)

[alayer]

b = macro(ab)
c = macro(ac)
d = C-d
e = macro(ae)
...

The main disadvantage of this (aside from adding complexity :P) is that it introduces the same sort of visual latency discussed in #34, but I suppose at this point that is par for the course. Another problem is that it conditions the user to develop an instinctive aversion to certain key sequences, though this is probably less of an issue if the target layer doesn't include any letter keys.

(1) I believe I played with this sort of key-oriented disambiguation at one point, but ultimately decided against it because it felt like a bad idea to have <a down> <b down> <b up> <a up> and <a down> <f down> <f up> <a up> do different things without having anything else to distinguish them. Having said that, there are probably some sequences which are sufficiently rare to warrant exclusively interpreting one way.

@rvaiya
Copy link
Owner

rvaiya commented Jan 4, 2022

If one IS given, then any keys that overlap before the timeout activate this layer instead of the standard one.

Actually, it occurs to me that you may simply have been suggesting unambiguously interpreting any interleaved events within the first timeout as activating the supplied layer (its not clear what you meant by 'overlap'). If so, then you are still left with the problem of interpreting the inherently ambiguous case of:

<a down> <b down> <a up> <b up>

which may well be ab.

@nsbgn
Copy link
Author

nsbgn commented Jan 4, 2022

The simplest version of this would be to change the stock behaviour of the overload action to produce when encountering unmapped keys within .

Hah, as it happens, that is exactly one of the 'other ideas' that I mentioned earlier. It sounded like a reasonable feature and it's encouraging to see that we toyed with the same idea, but it wouldn't address the initial use case (of allowing rightalt-hold-leftalt-tap to produce backslash without misfiring on rightalt-tap-leftalt-tap for enter; esc). I was indeed "suggesting unambiguously interpreting any interleaved events within the first timeout as activating the supplied layer".

then you are still left with the problem of interpreting the inherently ambiguous case

Well, the idea was that the lack of time between them would disambiguate it.

The reason for moderating my enthusiasm and coming up with a plan B was that I was struck by a realization: while my earlier suggestion does make sense for individual key pairs, I'll often have rightalt already down for some longer keystroke, and having to consciously remember to release and press down on it again to avoid an esc turning into a backslash might just trigger the homicidal rage you've been warning against.

if I understand you correctly

You do.

Have you used such a setup before?

Technically, yes, because it's generally the way that on-screen smartphone keyboards handle shift/capslock. But for any serious purposes, no.

A single timeout is hard enough to contend with on its own, but using the length of time to generate different keys adds a larger amount of cognitive overhead and would probably cause a lot of accidental actuations

I see what you're saying. My expectation is that a tap tends to involve a near-instantaneous release, so there should be some length of time that is reliably non-instantaneous, yet short enough so that you don't have to guess whether you've been pressing down for long enough. I could be wrong --- I'm just going to have to try it out. At least this timeout would involve only one key, so the interactions aren't as subtly treacherous.

@rvaiya
Copy link
Owner

rvaiya commented Jan 4, 2022

but it wouldn't address the initial use case (of allowing rightalt-hold-leftalt-tap to produce backslash without misfiring on rightalt-tap-leftalt-tap for enter; esc).

Indeed. I thought it seemed orthogonal to your main goal :P.

I'll often have rightalt already down for some longer keystroke, and having to consciously remember to release and press down on it again to avoid an esc turning into a backslash might just trigger the homicidal rage you've been warning against.

It seems the fundamental problem you are trying to contend with is having one key behave as three keys (in this case leftalt as a \, esc and M-S). The fact that this is happening within a layer is incidental.

Have you used such a setup before?
Technically, yes, because it's generally the way that on-screen smartphone keyboards handle shift/capslock. But for any serious purposes, no.

I would argue that capslock/shift are uniquely suited to this sort of thing because capslock is rarely used and is intended to signal the activation of a mode (and even then it is still a pain). This is not true for most other keys.

so there should be some length of time that is reliably non-instantaneous, yet short enough so that you don't have to guess whether you've been pressing down for long enough.

I'm not sure I follow. As far as I am aware, there is no natural occurrence of this phenomenon when one types normally. You would almost certainly have to retrain yourself to distinguish between

<capslock down> <200 ms> <capslock up> (e.g esc)

and

<capslock down> <400 ms> <capslock up> (e.g \)

Let's suppose you are in the process of escaping some nefarious regex in vi (or some similarly bedeviled editor). You prepare to do battle with the machine and start issuing:

f / i \ esc

As your fingers start to work their magic you begin to become one with the machine. You experience a brief moment of euphoria as you feel the power of Bill Joy flowing through you.

All of a sudden there is a disturbance in the force. You begin to doubt yourself.

Did you type f / i capslock capslock or f / i capslock capslock?

It was the former. No, the latter.

The colour drains from your face as you realize what you have done. In the reflection of your screen you see a grinning Shia Labeouf.

The game is over. You are out of pennies.

@nsbgn
Copy link
Author

nsbgn commented Jan 5, 2022

Eloquent as ever with the imagery.

The fact that this is happening within a layer is incidental.

Agreed, although the fact that it is in a layer in this case conceals the subtleties of why it's a Bad Idea.

Anyway, this has now morphed into quite a different beast. It's not longer about selective timeouts, nor about nested overloads. I will close this so that it may serve as a dire warning for any other unfortunate pursuers of the Left Hand Path.

As far as I am aware, there is no natural occurrence of this phenomenon when one types normally. You would almost certainly have to retrain yourself

Maybe so. Trying it out on a phantom keyboard feels natural. Then again, so did my initial suggestions.

With renewed, yet entirely unwarranted stubbornness, I'll try it out anyway. If successful, I may open another issue later, specifically for tap timeouts. (Even if I'm the only person for whom it feels natural, it would still be useful only for the tap cancellation, shift/capslock case, and maybe for generally combining the functions of holding + oneshotting + toggling?)

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

No branches or pull requests

3 participants