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

Oneshot with timeout and fallback #738

Closed
argenkiwi opened this issue May 3, 2024 · 7 comments
Closed

Oneshot with timeout and fallback #738

argenkiwi opened this issue May 3, 2024 · 7 comments

Comments

@argenkiwi
Copy link

I would like to propose a variant of the oneshot action with a timeout and a fallback or default action/key. For example:

capslock = oneshot(shift, 500, escape)

I am aware of the global setting oneshot_timeout introduces as part of #277, but in the use case I have in mind, and action should take place after the timeout. Consider the following behaviour for the capslock key:

  • Tap -> CapsLock
  • Hold -> Layer 1
  • Tap then hold -> Layer 2

My first attempt to achieve this looked similar to this

[main]

capslock = overload(layer1, timeout(oneshot(switch), 500, capslock))

[switch]

capslock = overload(layer2, capslock)

However, this does not seem to produce the expected result: tapping capslock the first time does eventually turn capslock mode on, but then it won't turn off after the next tap (possibly because oneshot is still active after the timeout).

Assuming this is not the intended use of timeout, what I would expect to be possible is the following:

[main]

capslock = overload(layer1, oneshot(switch, 500, capslock))

[switch]

capslock = overload(layer2, capslock)

Using the existing oneshot I can achieve what I want except capslock is only triggered by double-tapping, which is not ideal. Apologies again if what I want to achieve is already possible and I just haven't been able to figure it out.

@nsbgn
Copy link
Collaborator

nsbgn commented May 3, 2024

If I understand correctly, the problem is that you want to emit capslock on tap, except if you press it again within some time. To disambiguate, we'd have to wait and see if the timeout expires or another non-capslock key is struck.

I'm not convinced that an action at the end of the oneshot timeout is generally useful. Are there other use cases?

Would a solution that just immediately emits capslock on tap, and cancels it by emitting capslock again, be acceptable? Ie:

[global]
oneshot_timeout = 500

[main]
capslock = overload(layer1, oneshotm(switch, capslock))

[switch]
capslock = layerm(layer2, capslock)

As an aside: timeout(…) refers to the hold time of a key, it is not a general idle time expiration like oneshot_timeout is.

See also #671/#711 for related timeout trickery.

@argenkiwi
Copy link
Author

Thanks @nsbgn for quick response and useful suggestion. I think your approach may suffice in my particular at this stage.

I suppose that if I wanted capslock to behave like escape or backspace, like Vim and Colemak users like do, there wouldn't be an easy way to disable them. Tab is another key that I would be willing to wait for a fraction of a second before it is executed if that means I can add tap-hold capabilities to it. I would personally not use the proposed variant of oneshot for printable characters on the main layer. The aforementioned delay would be unbearable when typing.

But when you asked for other use cases, what came to my mind was how we used to type on the num-pads of our mobile phones before the iPhone revolution (I didn't have a BlackBerry 😆). You would tap each key multiple times to get the character you wanted and it would print:

  1. after a short period of idleness or
  2. if you pressed another character

What I am proposing does not suggest we support the latter (2). As far as I understand, the current implementation of oneshot will consider the shot "taken" when any key is pressed, within the timeout, regardless of whether it is specified in the selected layer or not (please correct me if I am wrong). That's what I expect and that's why I called it a variant of oneshot.

I would not want to replicate the dumb-phone typing experience exactly, but it could be useful for having a symbols layer which allows you to print different symbols depending on the number of taps. The same approach could be used for some foreign languages that have multiple variants of the same letter.

An alternative would be to focus on the tap-hold or multi-tap gestures specifically, but I assume that would require some form of disambiguation to discard key presses that don't match the key that triggered the action.

@rvaiya
Copy link
Owner

rvaiya commented May 4, 2024

I am inclined to agree with @nsbgn, using time exclusively to disambiguate intent is a bad idea (versions of this idea have been discussed many times before).

While it may seem desirable to have <capslock> <a> and <capslock> <500ms> <a> do different things, in practice the timeout either has to be long enough to make the second use case cumbersome, or short enough to make the first hard to achieve deliberately.

In this case I think you are trying to make capslock do too much work. In the example config you gave you can already activate capslock by double tapping it, so allowing it to be activated after a single tap and then a timeout seems superfluous and introduces unnecessary ambiguity.

But when you asked for other use cases, what came to my mind was how we used to type on the num-pads of our mobile phones before the iPhone revolution (I didn't have a BlackBerry 😆). You would tap each key multiple times to get the character you wanted and it would print:

  1. after a short period of idleness or
  2. if you pressed another character

Something like this could be achieved using the proposed togglet action in #671, which also has other uses and is more like to be implemented. It seems you desire a particular variant of it which emits a key on cancellation and seems less generally useful.

Edit: Sorry, to implement the feature you describe the action would indeed need to be capable of performing an action on timeout expiry. Having said that, I remain unconvinced of its practical utility.

@argenkiwi
Copy link
Author

argenkiwi commented May 4, 2024

Thanks for your feedback @rvaiya.

versions of this idea have been discussed many times before

I am sure this is the case and I will try to find the time to read more of the history of issues that have been raised in the past to understand why Keyd works as it does today.

While it may seem desirable to have and <500ms> do different things, in practice the timeout either has to be long enough to make the second use case cumbersome, or short enough to make the first hard to achieve deliberately.

The problem you describe has probably been the same since the invention of the double click. I am not as original as I'd like to be. What I am trying to achieve, and the challenges involved, could not have been explained any clearer by (the apparently famous) Ben Vallack in his video.

In this case I think you are trying to make capslock do too much work.

I am trying to extract as much value as possible from the CapsLock key, I won't deny that. The concept I am trying out is to have the CapsLock key being the entry and exit point of my layers (extend and numpad in my case) so I always know where I am.

It seems you desire a particular variant of it which emits a key on cancellation and seems less generally useful.

Referring to the video I shared above, I would not say emitting a key on cancellation is useful, but is apparently a necessary tradeoff to achieve tap-hold/double-tap. As Vallack, I do see potential in this capability.

Edit: Sorry, to implement the feature you describe the action would indeed need to be capable of performing an action on timeout expiry. Having said that, I remain unconvinced of its practical utility.

I understand if this is not the direction Keyd wants to take. @nsbgn's suggestion will allow me to test-drive the concept for the moment to better understand its practicality. I noticed your main competitors seem to support what I propose already, according to their README. I may have to dust my old config file and try it out, but I have left them for Keyd in the past because I much prefer the simplicity of use and configuration of your project.

Please feel free to do with this issue what you see fit. Many thanks.

UPDATE: I had some time this weekend to experiment with Kanata (a KMonad clone) and after quite a bit of fiddling I achieved what I described:

(defalias 
  pow (tap-hold 200 200 (tap-dance 200 (caps (layer-toggle numpad))) (layer-toggle extend))
  ...
)

Please don't draw any inspiration from this. 🙏

@srfsh
Copy link

srfsh commented Feb 20, 2025

I seem to have a usecase for this. I would like to use caps as esc on single-tap, and activate layer(nav) on double-tap ([nav] capslock = toggle(nav) also). Using the solution of oneshotm() seems to be fine until you realize most gui apps like web browers (when entering text, for instance) will lose focus on receiving esc from oneshotm(). Since the delay for the double-tap on the same key will be reasonable short, I think a timeout'd solution should be made available for cases like this.

I am hoping this is the appropriate thread to raise this issue, that is if I understood it all correctly. Please tell me if someone had mentioned this before. I wasn't able to find it. Many thanks.

rvaiya added a commit that referenced this issue Mar 2, 2025
This patch expands the semantics of timeout() to cover the case in which it is
used as a tap target. This facilitates a number of novel use cases, like
discriminating between single/double tap or implementing per-key oneshot
timeouts.

Specifically timeout() is now defined in terms of arbitrary key events rather
than the behaviour of the key to which it is bound (if any). The new definition
executes the second action if no key events occur before the timeout expires.

The implication of this is that when timeout() is executed as the result of a
tap action, action2 will be executed after the timeout expires unless another
key is struck in the interval. Note that this is backward compatible with the
old definition, since a key up event (i.e a tap) will result in a resolution to
the first action if timeout() is directly bound to a key.

For Example:

	tab = overload(control, timeout(a, 100, b))

will presently produce no effect when 'tab' is tapped. Under the expanded
definition, tapping tab will produce 'b' if 100 milliseconds elapse without an
interceding key event.
@rvaiya
Copy link
Owner

rvaiya commented Mar 2, 2025

The latest commit expands timeout() in a way which should make this possible.

Tap -> CapsLock
Hold -> Layer 1
Tap then hold -> Layer 2

capslock = overload(layer1, timeout(capslock, 100, oneshot(capslock)))

[capslock]

capslock = layer(layer2)

@rvaiya
Copy link
Owner

rvaiya commented Mar 2, 2025

I am closing this in favour of #879. Feel free to comment there, or reopen the issue if you feel it is distinct.

@rvaiya rvaiya closed this as completed Mar 2, 2025
meslubi2021 added a commit to Unity-for-manufacturing-assets-of-Unity/keyd that referenced this issue Mar 15, 2025
* man: Add note about config file exclusivity

* readme: Add explicit mention of wayland (rvaiya#559)

* man: Add note about maximum layer name length

* config: Allow 64 character layer names (rvaiya#558)

* Long overdue release

* docs: Fix changelog typos

* readme: Update arch package location

This has been imported into Arch's [extra] repository.

* make: Move service generation into install target (rvaiya#801)

* config: Increase the nested descriptor limit

* Enable GNOME extension for v47

* overloadi: Account for shifted symbols (rvaiya#875)

* Update README.md

since it says "Install AND start" adding `--now` might make more sense!

* keyd-application-mapper: Add support for the pop-os cosmic desktop

* keyd-application-mapper: Fix wlroots support for new windows

* readme: Combine systemctl `enable` and `start` commands by using `--now`

* readme: Fix broken QMK link

* ipc: Explicitly account for failure in the early stages of the connection (eliminates annoying compiler warning)

* evdev: Add support for AL_* family of keys found on some laptops

* listen: Eagerly terminate on pipe closure to accommodate more scripting use cases

* config: Make main a proper layout

* doc: Add Half-QWERTY layout to examples

This layout has been very useful to me for typing only with the
right hand. I did not test it for left-handed typing.

* doc: Add chromebook-linux.conf to examples

* keyd-application-mapper: Fix string escape bug causing issues on some platforms (rvaiya#649)

* layouts: Fix 0 in the FR layout

* core: Add special case for volume key devices in capabilities check

* doc: Add examples for common patterns.

* doc: Add additional key mappings to the chromeos example

* config: Increase macro size

* core: Improve support for thinkpad keyboards (rvaiya#905)

* layouts: Add graphite-angle-kp keyboard layout

* layouts: Add graphite keyboard layout

* core: Improve support for exotic mice

* core: Fix compilation on older systems

* Add support for scroll remapping

* doc: Add macos example to the readme

* Tweak readme example

* Improve mouse button support

* Add missing entries

` for the letter ḏal
Shift + ` for the shaddah
Shift + 0 for left parenthesis

* Add eastern Arabic zero

* core: Avoid interpreting remapped keys as part of a panic sequence (rvaiya#929)

* layout: Update graphite-angle-kp

Corrected a key, it was just a typo I guess.

ref : https://github.com/rdavison/graphite-layout

* doc: Update the application mapper man page

Clarifies that the verbose flag is needed to see window events in the output.

* keyd-application-mapper: Process SIGUSR1 on wayland (rvaiya#935)

* macro2: Fix timeout bug for nested macros

* doc: Update README

* keyd-application-mapper: Refactor wayland code

Clean up Wayland() and allow binding to multiple protocol objects simultaneously

* timeout: Add new behaviour when used as a tap target (rvaiya#879, rvaiya#738, rvaiya#944)

This patch expands the semantics of timeout() to cover the case in which it is
used as a tap target. This facilitates a number of novel use cases, like
discriminating between single/double tap or implementing per-key oneshot
timeouts.

Specifically timeout() is now defined in terms of arbitrary key events rather
than the behaviour of the key to which it is bound (if any). The new definition
executes the second action if no key events occur before the timeout expires.

The implication of this is that when timeout() is executed as the result of a
tap action, action2 will be executed after the timeout expires unless another
key is struck in the interval. Note that this is backward compatible with the
old definition, since a key up event (i.e a tap) will result in a resolution to
the first action if timeout() is directly bound to a key.

For Example:

	tab = overload(control, timeout(a, 100, b))

will presently produce no effect when 'tab' is tapped. Under the expanded
definition, tapping tab will produce 'b' if 100 milliseconds elapse without an
interceding key event.

* macro: Eagerly restore modifiers post execution (fixes rvaiya#947)

* macro: Ensure modifier state is properly tracked during macro execution

---------

Co-authored-by: Raheman Vaiya <[email protected]>
Co-authored-by: ainola <[email protected]>
Co-authored-by: birdbird <[email protected]>
Co-authored-by: Chris Schepman <[email protected]>
Co-authored-by: Pavel Slepushkin <[email protected]>
Co-authored-by: blankie <[email protected]>
Co-authored-by: Rajas Paranjpe <[email protected]>
Co-authored-by: diegorodriguezv <[email protected]>
Co-authored-by: Egor Pasko <[email protected]>
Co-authored-by: Merith <[email protected]>
Co-authored-by: Ridwan Mulyadi <[email protected]>
Co-authored-by: Fleefie <>
Co-authored-by: Garbaz <[email protected]>
Co-authored-by: Leandro M. Peralta <[email protected]>
Co-authored-by: Dick Marinus <[email protected]>
Co-authored-by: aljustiet <[email protected]>
Co-authored-by: Justin <[email protected]>
Co-authored-by: Awelaa <[email protected]>
Co-authored-by: Cédric <[email protected]>
Co-authored-by: Robert Benson <[email protected]>
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

4 participants