Make 'd' a prefix key without introducing typing delays

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
eugenesv
Posts: 175
Joined: 21 Dec 2015, 10:11

Make 'd' a prefix key without introducing typing delays

21 Jun 2020, 15:27

I'm trying to create a more convenient way to insert some characters (e.g. underscore _ which currently requires a double-pinky keypress). I'd like to make one of the most convenient keys d to act like a modifier when it's been pressed for longer than 400ms (but, and this is the tricky part, otherwise act like a regular d and not impede any typing of the regular text — this prevents
making it a prefix key like this:

Code: Select all

d & u:: SendInput '_'
because I can't afford to wait until d is up while typing e.g. did

What I've come up is this: send d right away, then introduce an empty loop for 400ms and after the loop (but only if d is still down) check what key is pressed and if u is pressed send _ underscore
However, it seems to bug — when I hold d down I see the counter in the tooltip reaching the 400 threshold, but instead of moving to the other loop it cycles through the first one again, and only then goes for the second loop

Q1: how can I fix it?
Q2: is there a more reliable way to differentiate between a normal keypress during typing and a key combination for such a heavily used key like d
I've also tried to use InputHook to listen for 1 character, but that's inconvenient since once it is activated I can't break it by releasing d, and I'd like to be able to "change my mind" after holding d for longer than 400ms and release it without anything running

Code: Select all

#SingleInstance force
$d::{
  threshold := 400
  SendInput '{d}'
  while (A_TimeSinceThisHotkey<threshold and GetKeyState("d", "P")) { ; 'd' is down < threshold
    tooltip(A_TimeSinceThisHotkey)
  }
  tooltip()
  while GetKeyState("d", "P") {  ; 'd' is down > threshold
    ; tooltip(A_PriorKey)
    vk_code := Format("vk{:X}",GetKeyVK(A_PriorKey))
    if vk_code=="vk44" { ;d self
      tooltip(vk_code,,,2)
      ; continue ; not needed
    } else if vk_code == "vk55" { ;u
      tooltip(A_TimeSinceThisHotkey,,400,2)
      SendInput '{BackSpace}{_}'
      break
    } else {
      break
    }
  }
  tooltip()
  Return
}
!r::reload
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Make 'd' a prefix key without introducing typing delays

22 Jun 2020, 01:07

Code: Select all

global actAsModifier := false

enableAsModifier() => actAsModifier := true
doNothing(*) => ''

d::
normalPress(*) {
	SetTimer('enableAsModifier', -400)
	Hotkey('d', 'doNothing')
}

#HotIf !actAsModifier
*d Up::{
	Send 'd'
	SetTimer('enableAsModifier', 0)
	HotIf()
	Hotkey('d', 'normalPress')
}

#HotIf actAsModifier
*d Up::{
	actAsModifier := false
	HotIf()
	Hotkey('d', 'normalPress')
}	
u::Send '_'
eugenesv
Posts: 175
Joined: 21 Dec 2015, 10:11

Re: Make 'd' a prefix key without introducing typing delays

22 Jun 2020, 05:29

@swagfag thank you for the suggestion, but unfortunately it severly impedes typing by suppressing the first d, e.g. when I try to type did I get idd instead
Also, I'd like any keypress when d is held down before threshold is reached to cancel entering the modifier status since it's obvious that I didn't intend to use it as a modifier is I press something else too soon, that's a sign of regular typing.


P.S. I also thought that any non-u key press even after the threshold should cancel it. Basically, I only need to have du (and other letters I assign in addition to u for other symbols) to act once and only once, and only when d has been held longer than a threshold since this was only intended for passing single symbols like (parenthesis) or _underscored etc.
Though it's not that big of a deal if the only non-typing-impeding implementation will not be able to do such a cancellation

P.P.S.
By the way, I also thought about this nice trick for intercepting keys even before threshold has passed, which is useful for e.g. double quotes: can convert d+' to double quotes " as the d' is rarely used
However, it needs an enhancement to ignore keypresses before "d" has been activated.

Code: Select all

    while A_TimeSinceThisHotkey<threshold and GetKeyState("d", "P") { ; 'd' is down for less threshold
      ToolTip(A_TimeSinceThisHotkey)
      vk_code := Format("vk{:X}",GetKeyVK(A_PriorKey))
      if vk_code=="vkDE" { ;' single quote to double quote
      ;bugs: 'd combination sends '" since ' is a prior key before d, need to limit to only keys that were pressed only AFTER the while loop has started
        SendInput '{BackSpace}{"}'
      }
eugenesv
Posts: 175
Joined: 21 Dec 2015, 10:11

Re: Make 'd' a prefix key without introducing typing delays

23 Jun 2020, 05:45

@Helgef thanks! Can your grow a bit more beard on your smiley face ;) and help fix these two issues
1. [critical] typing dud too fast gets me _d
2. [less so] autorepeat in this combo: this will especially be a challenge during transition as I might press the new d prefix for a bit too long while trying to remember the second key I need to complete a combo (I was thinking of showing a helpful tooltip with key-symbol combos to aid in that). During my testing of various approaches with the `~` prefix I've also entertained the idea of disabling autorepeat function in Windows altogether, but then it disables the one very useful autorepeat — with space

So I'm still hoping for a no-compromise solution (while becoming even more partial to the while approach as it allows me precise control over what's going on, if only I understood why it's double-looping...)
eugenesv
Posts: 175
Joined: 21 Dec 2015, 10:11

Re: Make 'd' a prefix key without introducing typing delays

23 Jun 2020, 06:32

During some more testing I think I managed to get a better working code, but I got even more confused as to the reasons the previous one was broken — apparently, different values of threshold trigger the double or even infinite loops, while some other values work normal!!!
Since I don't really understand what's going on, it's hard for me to fix anything besides stumbling on a fix while trying different things semi-randomly.
Hence the Q: what's going on here :) and is there a robust solution?

Code: Select all

$d::{
  ; loop(10) { ;reset all tooltips
  ;   tooltip(,,,A_Index)
  ; }
  ; threshold := 1000 ; interesting BUG: this value just makes it autorepeat 'd' every second without entering the other loops. 999 or 1001 work fine
  ; threshold := 500 ; same bug as above (doesn't trigger empty else below, tooltip#8)
  ; threshold := 750 ; same bug as above (doesn't trigger empty else below, tooltip#8)
  ; threshold := 400 ; double loop (triggers empty else below, tooltip#8)
  ; threshold := 411 ; double loop (triggers empty else below, tooltip#8)
  ; threshold := 711 ; works fine (triggers empty else below, tooltip#8)
  threshold := 522 ; works fine (triggers empty else below, tooltip#8)
  SendInput '{d}'
  while GetKeyState("d", "P") { ; 'd' is down...
    tooltip("while1dDown: " A_TimeSinceThisHotkey " | Index: " A_Index,400,300,2)
    while (A_TimeSinceThisHotkey<threshold and GetKeyState("d", "P"))  { ;... and another test that 'd' is down and BEFORE the threshold
      tooltip("while11<<: " A_TimeSinceThisHotkey " | Index: " A_Index,500,400,3)
    }
    ; sleep(100) ; 2.1) this helps avoid the double loop!!! but prevents fast 'did' (becomes 'di')
    ; sleep(10) ; 2.2) shorter sleep allows fast 'did', but doesn't help avoid the double loop
 
    while (A_TimeSinceThisHotkey>threshold and GetKeyState("d", "P"))  { ;... and another test that 'd' is down, but AFTER the threshold
      tooltip("while12>>: " A_TimeSinceThisHotkey " | Index: " A_Index,600,500,4)
      ; tooltip(A_PriorKey)
      vk_code := Format("vk{:X}",GetKeyVK(A_PriorKey))
      if vk_code=="vk44" { ;d self
        tooltip("while12>> 'd': " vk_code,700,600,5)
        ; continue ; not needed
      } else if vk_code == "vk55" { ;u
        tooltip("while12>> 'u':" A_TimeSinceThisHotkey,800,700,6)
        tooltip("while12>> 'u':" vk_code,1100,700,6)
        SendInput '{BackSpace}_' ; NB!: doesn't delete the original 'd', only the new 'u'
        ; SendInput '{BackSpace}{BackSpace}_' ; deletes both 'd' and 'u', but only works if prefix is terminated after one keypress, otherwise repeated 'u' will "eat" previous letters since there are no more 'd'
        ; return ;3) altenative to break ;
        break
      } else if vk_code == "" { ;attempt to fix suspicion of bug of empty else, but this never gets called
        tooltip("while12>> empty vk_coded'':" ,900,800,7)
        continue
      } else { ; BUG?: this test to break away when any non 'd/u' keys pressed apparently caused a double-loop
        tooltip("while12>> empty else:" ,1000,900,8)
        break
      }
    }
    break ; 1) this helps toggle this prefix key only for one keypress, but enters a double-loop and requires a sleep(100) above to fix it
  }
  return
}
eugenesv
Posts: 175
Joined: 21 Dec 2015, 10:11

Re: Make 'd' a prefix key without introducing typing delays

23 Jun 2020, 09:13

eugenesv wrote:
22 Jun 2020, 05:29
However, it needs an enhancement to ignore keypresses before "d" has been activated.
At least I've found a way how to fix this issue! I though that I needed a timer since A_PriorKey to exclude all keys prior to firing up my d hotkey and while it'd be great if AutoHotkey had it, I've stumbled upon a workaround in the old forums:

Step A: declare a single key as a separate passthrough hotkey so that this keypress will trigger A_ThisHotkey

Code: Select all

 ~'::Return

Step B: check when A_ThisHotkey inside a loop matches this single key aka hotkey:

Code: Select all

    while(GetKeyState("d","P") and A_TimeSinceThisHotkey<threshold ) { ; 'd' is down for less than threshold
      if (A_ThisHotkey = "~'") { ; a single quote aka a single quote hotkey has been pressed...
        SendInput '{BackSpace}{BackSpace}"' ; ...delete it + delete the previous 'u' key and insert a double quote instead
        return
      }
    }

Return to “Ask for Help (v2)”

Who is online

Users browsing this forum: Aaqil, robinson, songdg and 62 guests