 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
valenock
Joined: 14 Sep 2004 Posts: 16
|
Posted: Fri Feb 16, 2007 11:25 pm Post subject: Function CountPresses() - easily detect multiple presses |
|
|
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
}
|
|
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4012 Location: Pittsburgh
|
Posted: Fri Feb 16, 2007 11:55 pm Post subject: |
|
|
Nice idea to pack all these in a function.
Have you thought about using KeyWait instead of a timer? It might be simpler. |
|
| Back to top |
|
 |
jonny
Joined: 13 Nov 2004 Posts: 3004 Location: Minnesota
|
Posted: Sat Feb 17, 2007 1:34 am Post subject: |
|
|
| 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. |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4012 Location: Pittsburgh
|
Posted: Sat Feb 17, 2007 4:00 am Post subject: |
|
|
| 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%
} |
|
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4012 Location: Pittsburgh
|
Posted: Sun Feb 18, 2007 12:18 am Post subject: |
|
|
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. |
|
| Back to top |
|
 |
jonny
Joined: 13 Nov 2004 Posts: 3004 Location: Minnesota
|
Posted: Sun Feb 18, 2007 1:34 am Post subject: |
|
|
| 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. |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4012 Location: Pittsburgh
|
Posted: Sun Feb 18, 2007 2:40 am Post subject: |
|
|
| 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 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. |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4012 Location: Pittsburgh
|
Posted: Sun Feb 18, 2007 4:03 am Post subject: |
|
|
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 Sun Feb 18, 2007 5:55 pm; edited 1 time in total |
|
| Back to top |
|
 |
Titan
Joined: 11 Aug 2004 Posts: 5068 Location: imaginationland
|
Posted: Sun Feb 18, 2007 11:53 am Post subject: |
|
|
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
}
} |
_________________
RegExReplace("irc.freenode.net/ahk", "^(?=(.(?=[\0-r\[]*((?<=\.).))))(?:[c-\x73]{2,8}(\S))+((2)|\b[^\2-]){2}\D++$", "$u3$1$3$4$2") |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4012 Location: Pittsburgh
|
Posted: Sun Feb 18, 2007 5:13 pm Post subject: |
|
|
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
}
} |
|
|
| Back to top |
|
 |
sashabe
Joined: 09 Oct 2006 Posts: 14
|
Posted: Sun Feb 18, 2007 8:57 pm Post subject: |
|
|
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.. |
|
| Back to top |
|
 |
Richard B.
Joined: 27 Feb 2008 Posts: 10
|
Posted: Wed May 21, 2008 8:21 pm Post subject: |
|
|
| does it work for mouse?? |
|
| Back to top |
|
 |
infogulch
Joined: 27 Mar 2008 Posts: 127 Location: KC, MO
|
Posted: Fri May 23, 2008 5:25 pm Post subject: |
|
|
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 _________________
 |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|