AutoHotkey Community

It is currently May 24th, 2012, 6:19 pm

All times are UTC [ DST ]




Post new topic Reply to topic  [ 14 posts ] 
Author Message
PostPosted: February 16th, 2007, 11:25 pm 
Offline

Joined: September 14th, 2004, 11:03 pm
Posts: 21
The function below can help you easily detect multiple consecutive presses of a hotkey.
For example, you can run 3 different actions if "WIN + F1" hotkey was pressed once within 0.4 seconds, twice or three times in a row.

I use it quite often for functions like Mute / Unmute, Dial / HangUp, etc.
It's nice to run an on screen reminder (OSD) to inform what action was actually chosen.

Thanks to Chris and PhiLho who helped to debug it. Much of the code was stolen from Autohotkey help file.

CountPresses(param1, param2, param3) takes 3 params. Each must be a name of the label where you want to be GoSub'd in case hotkey is pressed once, twice or three times.

Code:
$#F1::
CountPresses("DownloadON", "DownloadOFF", "NoActionDefined")
return

DownloadON:
OSD("Download::ON")
return

DownloadOFF:
OSD("Download::OFF")
return

NoActionDefined:
OSD("WARNING !!! no action has been defined for this number of presses")
return


CountPresses(param1, param2, param3)
{
   static key_presses = 0
   
   static pressed1 = 0
   static pressed2 = 0
   static pressed3 = 0
   
   pressed1 = %param1%
   pressed2 = %param2%
   pressed3 = %param3%
   
   if key_presses > 0 ; SetTimer already started, so we log the keypress instead.
   {
      key_presses += 1
      return
   }
   ; Otherwise, this is the first press of a new series. Set count to 1 and start
   ; the timer:
   key_presses = 1
   SetTimer, WaitKeyPress, 400 ; Wait for more presses within a 400 millisecond window.
   return

   WaitKeyPress:
   SetTimer, WaitKeyPress, off
   
   if key_presses = 1 ; The key was pressed once.
   {
      if(pressed1 = "")
         MsgBox "CountPresses(): No action was assigned to this hotkey"
      else
      GoSub, %pressed1%
   }
   else if key_presses = 2 ; The key was pressed twice.
   {
      if(pressed2 = "")
         MsgBox "CountPresses(): No action was assigned to this hotkey"
      else
      GoSub, %pressed2%
   }
   else if key_presses = 3 ; The key was pressed twice.
   {
      if(pressed3 = "")
         MsgBox "CountPresses(): No action was assigned to this hotkey"
      else
         GoSub, %pressed3%
   }
   ; Regardless of which action above was triggered, reset the count to
   ; prepare for the next series of presses:
   key_presses = 0
   
   return
}


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: February 16th, 2007, 11:55 pm 
Offline

Joined: February 14th, 2005, 4:05 pm
Posts: 4710
Location: Boulder, CO
Nice idea to pack all these in a function.
Have you thought about using KeyWait instead of a timer? It might be simpler.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: February 17th, 2007, 1:34 am 
Offline

Joined: November 13th, 2004, 4:08 am
Posts: 2951
Location: Minnesota
I don't think it would be simpler, because you'd need more involved code to track the 400-millisecond range when it doesn't time out.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: February 17th, 2007, 4:00 am 
Offline

Joined: February 14th, 2005, 4:05 pm
Posts: 4710
Location: Boulder, CO
Code:
$#F1 UP::CountPresses("M1","M2","M3")
M1:
   MsgBox 1
Return
M2:
   MsgBox 2
Return
M3:
   MsgBox 3
Return

CountPresses(Label1, Label2, Label3) {
   Static Count
   key := RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^( UP)]")
   If ( A_ThisHotKey = A_PriorHotKey and A_TimeSincePriorHotkey < 400 )
        Count += Count < 3 ? 1 : 0
   Else Count = 1
   KeyWait %key%, DT0.4
   If (ErrorLevel)
      GoSub % Label%Count%
}


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: February 18th, 2007, 12:18 am 
Offline

Joined: February 14th, 2005, 4:05 pm
Posts: 4710
Location: Boulder, CO
The timer-less version of valenock's script works best, if the hotkey is activated when it is released (UP).

Sometimes you want the original function of the hotkey at single press, like F1 can call up normally the help of the current application, but double pressed F1 could invoke the AHK help. Below the subroutine KEY0 computes the key part of the current hotkey and sends it to the foreground application. It can be reused for many CountPresses calls.

Returning the press count can be useful, e.g. a subroutine label can be reused for different press count values, when the action it performs depends on the actual press count. Therefore, making the PressCount variable global (instead of Static) is often advantageous.

Here is an even compacter version of valenock's script, using only 5 AHK commands:
Code:
+F1 UP::CountPresses("KEY0","M2","Mx")

KEY0:        ; Send hotkey for original function
   SendInput % RegExReplace(A_ThisHotKey,"[\*\~\$]*([\#\+\!\^]*)(.+)( UP)","$1{$2}")
Return
M2:
   MsgBox Double
Return
Mx:
   MsgBox Counts = %PressCount%
Return

CountPresses(Labl1, Labl2, Labl3) {
   Global PressCount ; useful outside
   PressCount := A_ThisHotKey = A_PriorHotKey && A_TimeSincePriorHotkey < 400 ? PressCount+1 : 1
   KeyWait % RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^( UP)]"), DT0.4
   If (ErrorLevel)
      GoSub % PressCount < 3 ? Labl%PressCount% : Labl3
}
The Hotkey is Shift-F1. It should work also with scan codes, normal keys or special keys, like LEFT or ENTER.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: February 18th, 2007, 1:34 am 
Offline

Joined: November 13th, 2004, 4:08 am
Posts: 2951
Location: Minnesota
Quote:
Here is an even compacter version of valenock's script, using only 5 AHK commands:


Personally I would be a lot more impressed if it were readable and elegant.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: February 18th, 2007, 2:40 am 
Offline

Joined: February 14th, 2005, 4:05 pm
Posts: 4710
Location: Boulder, CO
jonny wrote:
I don't think it would be simpler...

jonny wrote:
Personally I would be a lot more impressed if it were readable and elegant.
It is hard to satisfy you :wink: A solution with one thread is simpler and more readable than the original with a second thread started inside a function.

I think your problem is that regular expressions are cryptic to the untrained eyes. I agree. You should complain to their developers. The other four lines are readable. If they are elegant? It is in the eyes of the beholder. Could you give a description of what you consider elegant? I will write a version just for you. :)

There is also a serious weakness of the original approach (regardless of the complexity of the mixed-up scopes of the variables in the timer subroutine and the surrounding function): all the key presses must finish within 400ms from the first key press. You cannot extend the function to handle more key presses, because the 400ms will expire in the middle of the key sequence. It is much better to allow 400ms for each key press, so you can reliably detect even 20 repetitions. This is, how the timer-less function works.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: February 18th, 2007, 4:03 am 
Offline

Joined: February 14th, 2005, 4:05 pm
Posts: 4710
Location: Boulder, CO
In the original function you have to use unintuitive assignments to static variables and rely on the funny behavior of GoSub %label%, just to hide some internal variables. If you don't mind using globals, you can simplify the timer based function (and allow 400ms delay between keystrokes):
Code:
$+F1::CountPresses("KEY0","M2","Mx") ; use $ with a KEY0 label, or #usehook

KEY0:        ; Send hotkey for original function
   SendInput % RegExReplace(A_ThisHotKey,"[\*\~\$]*([\#\+\!\^]*)(.+)( UP)*","$1{$2}")
Return
M2:
   MsgBox Double
Return
Mx:
   MsgBox Counts = %PressCount%
Return

CountPresses(param1, param2, param3) {
   Global
   pressed1 := param1, pressed2 := param2, pressed3 := param3
   PressCount += 1
   SetTimer WaitKeyPress, 400 ; Restart waiting for more presses
}

WaitKeyPress:
   SetTimer WaitKeyPress, off ; run timer only once
   GoSub % PressCount < 3 ? pressed%PressCount% : pressed3
   PressCount := 0
Return
Here the function and the timer subroutine is separated. This way the scopes of variables don't mix up, the code becomes clearer, more readable.

Edit 20070118: made UP optional in KEY0


Last edited by Laszlo on February 18th, 2007, 5:55 pm, edited 1 time in total.

Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: February 18th, 2007, 11:53 am 
Offline
User avatar

Joined: August 11th, 2004, 1:47 am
Posts: 5346
Location: UK
Great idea. Here's a similar version that simply returns the number of key presses in the given time:

Code:
If CountPresses("LButton") = 2
   MsgBox, you double clicked

press := CountPresses("a", 3000)
MsgBox, you pressed 'a' %press% times in 3 seconds
; -----

CountPresses(key, delay = 0, presses = 0) {
   If !delay and DllCall("SystemParametersInfo", "UInt", 22 ; SPI_GETKEYBOARDDELAY
      , "UInt", 0, "UIntP", delay)
      delay *= 250 + 250
   timeout := delay / 1000, start := A_TickCount
   Loop
      If (presses++ and A_TickCount - start > delay)
         Return, --presses - ErrorLevel
      Else {
         KeyWait, %key%, T%timeout%
         KeyWait, %key%, T%timeout% D L
      }
}

_________________
GitHubScriptsIronAHK Contact by email not private message.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: February 18th, 2007, 5:13 pm 
Offline

Joined: February 14th, 2005, 4:05 pm
Posts: 4710
Location: Boulder, CO
A few questions, Titan:
- Why do you default to 500 times to the system keyboard delay? Was not it better to set the default delay to 1000 or 500 in the parameter list?
- Did 250+250 (instead of 500) remained from a previous version?
- The DllCall returns an error in my XP SP2 system (A-4). Is there a problem with the call?
- Still, delay is set to 1 by the dll call. Should not it be 30ms or something similar?
- Because of the fix time window, it is hard to achieve large press counts. Is not it better to allow a delay between consecutive presses, and stop counting if there is a longer pause?

Still, it is a useful function. You can use it like
Code:
^#a::
   press := CountPresses("a")
   MsgBox, you pressed 'a' %press% times ; or any action you want
Return

The function below finds the hotkey itself, making it a little more convenient to use. It does not count the hotkeys in a fixed time interval, but until they are pressed without long pause in between:
Code:
+F1 UP::
^#a::MsgBox % "you pressed the hotkey " CountPresses() " times"

CountPresses(timeout = 0.4) {
   key := RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^( UP)]")
   Loop {
      KeyWait %key%, T%timeout%
      KeyWait %key%,DT%timeout%
      If (ErrorLevel)
         Return A_Index
   }
}


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: February 18th, 2007, 8:57 pm 
Offline

Joined: October 9th, 2006, 9:27 am
Posts: 21
Thanks a lot, invaluable scripts! Multiple presses should be incorporated into AHK engine, it seems, to lighten code.
Could please the author make the script insensible to long pressings, in the way the shift key is held for selecting text? I tried to attach a delete action to the shift double-pressed, but its common functionality stopped working..


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: May 21st, 2008, 8:21 pm 
Offline

Joined: February 27th, 2008, 12:15 am
Posts: 10
does it work for mouse??


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: May 23rd, 2008, 5:25 pm 
Offline

Joined: March 27th, 2008, 2:14 pm
Posts: 700
This one gives you the option of up to four labels/presses. It immediately fires the last most label you specify on encountering that press. What this means is you can specify nothing (blank "") for labels, and that number of presses won't do anything. Also, if you specify a space, (" ") instead of blank for the first label, it sends the current hotkey's down-event. (e.g. the hotkey "a::" would send "{a Down}". Make it send an up event with "$a Up::Send {a Up}" and remember to include the dollar sign) This is nice to allow the first press to go through, but subsequent presses are captured by the program, and not sent to the window.:

Code:
;Note: The MTime first paramater is milliseconds. I find 150-200 about right for pressing several times. Also, the MTime is timing between strokes, not capturing how many strokes in a set time. This makes it easier on you since you don't have to change the time if you specify more or less labels.

CountPresses( MTime = 150, Label1 = "", Label2 = "", Label3 = "", Label4 = "") {
   If ( Label1 = " " ) {
      SendKey := RegExReplace(A_ThisHotKey,"i)([\*\~\$( UP)])?(.*?)(\w+)", "$2{$3 Down}")
      Send, %SendKey%
   }
   Time := MTime * .001
   LastLabel := IsLabel( Label4 ) ? 4 : IsLabel( Label3 ) ? 3 : IsLabel( Label2 ) ? 2 : 1
   Key := RegExReplace(A_ThisHotKey,"i)[\*\~\$\#\+\!\^( UP)]")
   AddPress:
   Presses += 1
   KeyWait, %Key%, T%Time%
   If ( Presses < LastLabel AND !ErrorLevel) {
      KeyWait, %Key%, D T%Time%
      If !ErrorLevel
         GoTo AddPress
   }
   If IsLabel( Label%Presses% )
      Gosub, % Label%Presses%
   Return Presses
}

Ctrl::CountPresses(150, " ", "Second", "Third")
$Ctrl Up::Send {Ctrl Up}

First:
Second:
Third:
Fourth:
   On := !On
   If On
      ToolTip The label: "%A_ThisLabel%" ran
   Else
      ToolTip
return

It includes an example of useage using Ctrl (like google desktop) there at the end.
to fix:
I'd like to fix how it removes the Autorepeat of your keyboard if you use the space " " option. (the repeat changes to whatever mtime you set.)
Maybe change {$2 Down} to ..DownTemp}?? i don't know what difference that would make, but there's something about that in the Remapping Keys and Buttons

_________________
Scripts - License


Report this post
Top
 Profile  
Reply with quote  
 Post subject: A little help please
PostPosted: August 19th, 2009, 3:53 pm 
Offline

Joined: March 17th, 2009, 9:08 am
Posts: 8
Laszlo wrote:
A few questions, Titan:
- Why do you default to 500 times to the system keyboard delay? Was not it better to set the default delay to 1000 or 500 in the parameter list?
- Did 250+250 (instead of 500) remained from a previous version?
- The DllCall returns an error in my XP SP2 system (A-4). Is there a problem with the call?
- Still, delay is set to 1 by the dll call. Should not it be 30ms or something similar?
- Because of the fix time window, it is hard to achieve large press counts. Is not it better to allow a delay between consecutive presses, and stop counting if there is a longer pause?

Still, it is a useful function. You can use it like
Code:
^#a::
   press := CountPresses("a")
   MsgBox, you pressed 'a' %press% times ; or any action you want
Return

The function below finds the hotkey itself, making it a little more convenient to use. It does not count the hotkeys in a fixed time interval, but until they are pressed without long pause in between:
Code:
+F1 UP::
^#a::MsgBox % "you pressed the hotkey " CountPresses() " times"

CountPresses(timeout = 0.4) {
   key := RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^( UP)]")
   Loop {
      KeyWait %key%, T%timeout%
      KeyWait %key%,DT%timeout%
      If (ErrorLevel)
         Return A_Index
   }
}


The following is my requirement:
If i press the key "u" and then press the space bar key once - nothing should happen.
If i press the key "u" and then press the space bar key twice- I want it to be hotstringed to "you".

I tried the following, but it doesn't seem to work. Be warned I'm a complete newb.

Using this count press script

Code:
CountPresses(timeout = 0.4) {
   key := RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^( UP)]")
   Loop {
      KeyWait %key%, T%timeout%
      KeyWait %key%,DT%timeout%
      If (ErrorLevel)
         Return A_Index
   }
}

u space UP::
If (CountPresses() = "2")
{
  SendInput  you
}
Else
{
  SendInput {raw}u
}


I know this code must probable give you guys a heart attack, but bare with me. It says invalid hotkey.

one issue is how do i have "u and then space" as a hotkey
secondly how do i make it play with countpress.

Many thanks


Report this post
Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 14 posts ] 

All times are UTC [ DST ]


Who is online

Users browsing this forum: Aravind, Yahoo [Bot] and 10 guests


You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group