bug fix. if 2 different mouse buttons were released at the same time, after the multiclick sleep, the a_thishotkey info was wrong. seems, to me, quite stable now. also now distinguishing the gesture, which is the tracking of the mouse, from the action that is called - the macro. as such, have renamed labels from gxxx to mxxx, and renamed a few variables etc.
Code:
multiclicktime=300 ; set to 0 if you don't want double/triple/multi clicking. otherwise set as fast as you can double-click
rockinstant:=false ; if true, rocking action fires on downclick (which is faster) but stops further tracking
sensitivity=4 ; adjust if required. if too low, a simple "down/up" gesture (V) might be recorded as "down/right/up" (U)
demomode:=true ; demonstration mode on/off
coordmode,mouse,screen
winget,windowD,id,ahk_class Progman ;windowD is the desktop
gosub cleanup
return
#MaxThreadsPerHotkey 5 ; needed when multiclicking
browser_search:: ; "find" key on logitech mx620 mouse, used here as an example
lbutton::
rbutton::
mbutton:: ; list whatever mouse buttons you want tracked.
xbutton1:: ; works best if all available listed
xbutton2::
thishotkey:=a_thishotkey="xbutton2" ? "f" : substr(a_thishotkey,1,1) ;buttons named from initial except xbutton2
stringreplace,buttonsdown,buttonsdown,l,,all
buttonsdown.=thishotkey . (getkeystate("lbutton","p") ? "l" : "")
gosub multiclick
mcalled=0 ; records if the macro has been called to avoid unintended double-click double calls
winget,windowA,id,A ; windowA is the active window, useful for some gestures
hotkey,lbutton,on ; disables normal left click during gesture
hotkey,lbutton up,off ; prevents next line of code from calling on lbutton up
click up ; prevents drag box appearing when left/right rocking and gesturing eg. gl/r(d)
hotkey,lbutton up,on
if (window1<>windowD)
window2:=window1 ; used in some gestures, window2 is the window the previous gesture acted on
mousegetpos xpos1,ypos1,window1 ; window1 is the window underneath the current gesture
if gtracking
{
gosub trackassign
record.=(getkeystate("lbutton","p") and not record ? "l/" : "") . (record and thishotkey<>substr(record,0,1) ? "/" : "") . thishotkey
settimer,gtrack,10 ; "/" is the rocking symbol
}
else
{
gosub parserecord ; finds position of last "/" in record for next line of code
record:=regexreplace(record,"/\w*","/" . thishotkey . multitrack,position,1,position) ; allows rocking with different buttons
} ; why "\w*"? \w* may represent a single, double or multiclick, but not a parenthesis eg /r, /rr, /rrr, as in gl/rrr(ud)
if instr(record,thishotkey . "/") or not instr(buttonsdown,substr(record,1,1))
{
gosub cleanup ; cleanup if otherwise fatal errors detected
return
}
if rock()="rocking" and rockinstant
{ ; calls action before mouse button released (rockinstant is user defined)
rockdown:=thishotkey ; advantage - faster action
goto rbutton up ; disadvantage - gesture called so no more tracking
}
rockdown=
return
browser_search up::
rbutton up::
lbutton up::
mbutton up::
xbutton1 up::
xbutton2 up::
thishotkey:=a_thishotkey="xbutton2" ? "f" : substr(a_thishotkey,1,1)
if a_thishotkey contains up
{
stringreplace,buttonsdown,buttonsdown,%thishotkey%,,all
if (rockdown=thishotkey)
return
}
buttonsup.=thishotkey
multiclick:=false
if not gtrack ; no need for delay to check for multiclick if button released after gesture tracking
sleep % multiclicktime-a_tickcount+mtime1 ;delay to check for multiclick
if not buttonsup or multiclick
return
thishotkey:=substr(buttonsup,1,1)
stringreplace,buttonsup,buttonsup,%thishotkey%,,all
gtracking:=false ; no more gesture tracking once a mouse button has been released (unless multiclicking)
gosub trackassign
cleanup:=(rock()<>"rocking") ? true : false ; this allows for repeat rocking
callmacro:=not wheel and record and not (rock()="rockover" and mcalled) ? true : false
if cleanup ; these lines allow for cleanup before the gesture action is called.
gosub cleanup ; (note that the gesture itself is not erased by the cleanup). this prevents a called
if callmacro ; action that takes a significant time stopping further tracking.
gosub callmacro
return
#maxthreadsperhotkey 1 ; autohotkey default setting. set to your usual setting
callmacro:
mcalled:=true
gesture.=(wheel ? "-" : "") . wheel
if demomode
msgbox,,Demo,%gesture%, % (wheel ? .5 : strlen(gesture)/5+1)
else
if islabel(gesture) ; checks if gesture is labelled
gosub %gesture%
return
cleanup:
hotkey,lbutton,off ; restore normal left mouse button function
hotkey,lbutton up,off
settimer,gtrack,off
record=
wheel=
gtracking:=true
gtrack=
return
gtrack:
critical off ; don't want this getting in way of double-click checking
mousegetpos xpos2,ypos2
track:=(abs(ypos1-ypos2)>=abs(xpos1-xpos2)) ? (ypos1>ypos2 ? "u" : "d") : (xpos1>xpos2 ? "l" : "r") ;determines up down left right
if (abs(ypos1-ypos2)>sensitivity or abs(xpos1-xpos2)>sensitivity) and (track<>SubStr(gtrack, 0, 1)) ;track if x or y changing and direction changing
gtrack.=track
xpos1:=xpos2
ypos1:=ypos2
return
parserecord: ; script allows for rocking more than once eg gr/l/x:
loop,parse,record ; finds position of last "/"
if a_loopfield=/
position:=a_index
return
multiclick:
mtime2:=mtime1 ; why use time difference and not keywait? in the time between the 2 clicks, you don't want to miss any
mtime1:=a_tickcount ; gtracking. the gesture gr(u) can be all over in less time than a double click takes
if (thishotkey=substr(record,0,1) and mtime1-mtime2<multiclicktime)
{
multiclick:=true
multitrack.=thishotkey
}
else
{ ; avoids rocking errors when time between clicks is too short
multiclick:=false
multitrack=
}
return
trackassign:
settimer,gtrack,off
if gtrack ; letters enclosed in parentheses
record.="(" . gtrack . ")" ; are gesture directions
gtrack=
gesture:="m" . record
return
esc::
exitapp
return
wheelup::
wheeldown::
wheel:=a_thishotkey
if record and gtracking
{
gosub trackassign
gosub callmacro
}
if not record
sendinput {%wheel%}
return
rock()
{
global
if record contains / ; "/" is the rocking symbol
{
gosub parserecord ; "position+1" is the first letter after the forward slash
if (thishotkey=substr(record,position+1,1))
return "rocking"
else
return "rockover" ; in the button up section, if the button down and
; the button up don't match, rocking is finished
}
}
mr: ;normal right click
click right,xpos1,ypos1
return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;; Examples ;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; wheel examples ;;;;;;;;;;
mr-wheelup: ;change volume with wheel - hold right button down and use wheel
Send {Volume_Up}
return
mr-wheeldown:
Send {Volume_Down}
return
;;;;;;;; multi click examples ;;;;;;;;;;
mrr:
msgbox,,,double right click,5
return
mrr(l):
msgbox,,,double right click then gesture left ,5
return
mrr/ll:
msgbox,,,double right click then rock with double right ,5
return
mr/l/m:
msgbox,,,right click then rock with left and then middle ,5
return
;;;;;;;; rocker examples ;;;;;;;;;;
mr/l:
msgbox,,,keep right held down and click left will repeat action,5
return
ml/r:
msgbox,,,rocker left then right. careful where you click,5
return
mr(l)/l:
msgbox,,,you gestured left and clicked,5
return
;;;;;;;;;;;; 4th mouse button example ;;;;;;;;;;
mx(l):
msgbox,,,you've gestures left with the 4th mouse button,5
return
mx/r:
msgbox,,,rocker 4th button then right,5
return
mr/x:
msgbox,,,rocker right then 4th button,5
return
;;;;;;;; other examples ;;;;;;;;;;;;;;
mr(u): ;; gesture up ;; maximize window under cursor. maximize last window if cursor over desktop
if (window1=windowD)
{
if (window2<>"")
winmaximize, ahk_id %window2%
}
else
winmaximize ahk_id %window1%
return
mrd: ;minimize window under cursor
if (window1<>windowD) ;don't minimize the desktop
winminimize, ahk_id %window1%
return
mr(l): ;browser back
winactivate ahk_id %window1%
sendinput {Browser_Back}
return
mr(r): ;browser forward
winactivate ahk_id %window1%
sendinput {Browser_Forward}
return
mr(dr): ;restore last window.
winrestore ahk_id %window2%
return
mr(ldr): ;close window under cursor (draw a C)
if (window1<>windowD)
winclose ahk_id %window1%
return