Allow un-sinking the key that triggered a hotkey without resending it

Propose new features and changes
Saiapatsu
Posts: 17
Joined: 11 Jul 2019, 15:02

Allow un-sinking the key that triggered a hotkey without resending it

Post by Saiapatsu » 31 May 2023, 04:23

This is actually possible in AutoHotkey already even though I remember people telling me it's impossible years ago in IRC. See this script for an example:

The gist of it:

Code: Select all

#HotIf Foobar()
f::return
#HotIf

; Every odd F key press is consumed and every even F key press plays a sound.
Foobar()
{
	static asdf := 0
	if asdf := !asdf
	{
		return 0
	} else {
		SoundPlay "*16"
		; You can even put a Sleep 1000 here and F will send after a delay
		return 1
	}
}
See this script for another example of #HotIf abuse:
viewtopic.php?p=524178#p524178

This is NOT what I want:

Code: Select all

$f::
{
	static asdf := 1
	if asdf := !asdf
	{
		SoundPlay "*16"
		Send "f"
	}
}
This code will always sink the original F, then send an extra F afterward. Because of this, the hotkey needs a $ so that it doesn't call itself. This is stupid, the hotkey shouldn't have consumed the key in the first place. It should only consume odd keys, but pass through the original even keypress.

I want to be able to write something like this:

Code: Select all

#HotkeyManualSink
f::
{
	static asdf := 0
	if asdf := !asdf
	{
		SinkHotkey
	} else {
		SoundPlay "*16"
	}
}
or

Code: Select all

#HotkeyManualSink
f::
{
	static asdf := 0
	if asdf := !asdf
	{
		; nothing
	} else {
		HotkeyPassthrough
		SoundPlay "*16"
	}
}
The HotkeyPassthrough function should operate on the current hotkey running the function (so if it was executed by a timer or the auto-execute section, it should throw).
It is not possible to passthrough a hotkey multiple times and therefore emit the originally sent key multiple times, so SinkHotkey seems more foolproof than HotkeyPassthrough. I don't remember whether you had to explicitly forward a key to the next listener or explicitly sink the key in a keyboard hook, so do whatever's closer to the metal.

lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: Allow un-sinking the key that triggered a hotkey without resending it

Post by lexikos » 29 Jun 2023, 21:16

Whenever the keyboard hook is forced to wait for the script to evaluate a response to a keyboard event, there is an increased risk of input latency and the OS silently disabling the keyboard hook. Sending keystrokes and mouse clicks while the hook is waiting can also cause complications. Hotkeys and InputHook are therefore specifically designed to avoid making the hook thread wait. #HotIf is not (except when the documented optimizations are taken), hence the list of caveats in the documentation.

#HotIf threads are Critical by default (i.e. they suppress interruption by most other events), whereas hotkeys are not. If a "#HotkeyManualSink" hotkey is interrupted by another thread, or it calls KeyWait or similar, input would lag until the hook times out and stops waiting for a reply. By contrast, the limitations of the current design help to steer users away from these issues.

In many cases, there are reasonably simple ways to implement the hotkey which do not require the keyboard hook to wait for the script.

For example, when the script itself sets the conditions prior to the hotkey being pressed, as in your example, or when the script can detect the conditions changing before the hotkey is pressed, it can remove or apply the ~ prefix:

Code: Select all

f::
{
	static asdf := 0
	if asdf := !asdf
	{
		; This event was already suppressed. Don't suppress the next one.
        Hotkey "~f"
	} else {
        ; This event was not suppressed. Suppress the next one.
		Hotkey "f"
		SoundPlay "*16"
	}
}
Having said that, I do intend to improve hotkey and InputHook functionality eventually and will take your input into consideration (if I remember). Although I have some ideas of my own, I am not likely to work on hotkey functionality in the short term due to a combination of factors, such as having many other things I'd prefer to work on and having gotten rather sick of debugging the hotkey code last year.

If someone else wants to take a crack at implementing this, I have a couple of notes:
  • Hotkeys are functions and can therefore return a value.
  • As the keyboard hook would be waiting for a reply, and we want that wait to be as short as possible, the hotkey should be able to (and be designed to) give either answer ASAP. If it can do this by means other than return (which would still allow further action via SetTimer), it should allow both answers: suppress or don't suppress.

Post Reply

Return to “Wish List”