Last active
August 3, 2024 21:20
-
-
Save askvictor/c4703ec76900542415e74a61057889a9 to your computer and use it in GitHub Desktop.
AHK script to do middle-click scrolling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#commentflag // ; Change to C++ comment style | |
// Code stolen from http://superuser.com/questions/1029053/autohotkey-scrolling-middle-click-mouse-acceleration/1029054 | |
// Thanks to http://superuser.com/users/276424/user21820 for writing this | |
// Licence: CC-BY-SA as per StackExchange code licence prior to Feb 2016. | |
// Settings // | |
global rightbuttonscroll:=0 // set to 1 iff the right button is to be used for scrolling instead of the middle button | |
global scrollbeforeclick:=1 // set to 1 iff scrolling is allowed before the original button click otherwise scrolling is delayed for (clicklimit) | |
global leftrighttomiddle:=0 // set to 1 iff rightbuttonscroll==1 and pressing both left+right buttons together generates middle button press | |
global scrolllimit:=3.5 // maximum scroll allowed between scroll button press and release for the original button click to be generated | |
global clicklimit:=210 // maximum time allowed between scroll button press and release for the original button click to be generated | |
global resetdelay:=210 // minimum time required between scroll button release and press for the original button click to be allowed | |
global interval:=35 // minimum interval between scrolling updates | |
global timelimit:=70 // maximum time allowed for continuing the scrolling update in each direction | |
global dbg:=true | |
// Initialization // | |
#singleinstance,force | |
#keyhistory 0 | |
#usehook on | |
process priority,,H | |
setkeydelay 1 | |
setcontroldelay -1 | |
coordmode mouse,screen | |
global buttondown:=( rightbuttonscroll==1 ? 0x204 : 0x207 ) | |
global buttonup:=( rightbuttonscroll==1 ? 0x205 : 0x208 ) | |
global buttonoriginal:=( rightbuttonscroll==1 ? "RButton" : "MButton" ) | |
global speed | |
global handling:=0 | |
global scrolling:=0 | |
global scrolldrag:=0 | |
global scrollsticky:=0 | |
global clicksticky:=0 | |
global lefttopress:=0 | |
global righttopress:=0 | |
global leftphysical:=0 | |
global rightphysical:=0 | |
global middlepressed:=0 | |
global mx,my | |
global dx,dy | |
global lx:=0 | |
global ly:=0 | |
global sx:=0 | |
global sy:=0 | |
global totalx:=0 | |
global totaly:=0 | |
global ctrl,window,parent | |
global methodx | |
global methody | |
global scrollbarx | |
global scrollbary | |
global max16bit:=32767 | |
global sbinfo | |
varsetcapacity(sbinfo,28) | |
numput(28,sbinfo,0) | |
numput(23,sbinfo,4) | |
global mousehook:=dllcall("SetWindowsHookEx","int",14,"uint",RegisterCallback("handlemouse","fast"),"uint",0,"uint",0) | |
message("AutoHotkey loaded") | |
return | |
// Reload & Debug & Exit & Disable Mousehook // | |
LCtrl & RCtrl::reload | |
LCtrl & AppsKey::dbg:=!dbg | |
RCtrl & Esc::exitapp | |
RCtrl & LCtrl::dllcall("UnhookWindowsHookEx","uint",mousehook) | |
// Message // | |
messageoff: | |
tooltip | |
return | |
message(text) | |
{ | |
tooltip % text //,a_screenwidth-strlen(text)*7,a_screenheight-42 | |
settimer messageoff,-1000 | |
} | |
// Mouse Movement // | |
moveadjust(byref x,byref y) | |
{ | |
movespread(x,y) | |
z2:=x**2+y**2 | |
r:=(150+z2*3)/(150+z2) | |
x:=rtoz(x*r) | |
y:=rtoz(y*r) | |
} | |
movespread(byref x,byref y) | |
{ | |
nx:=rtoz(x/2) | |
ny:=rtoz(y/2) | |
x-=nx | |
y-=ny | |
x+=lx | |
y+=ly | |
lx:=nx | |
ly:=ny | |
settimer movereset,-210 | |
} | |
movereset: | |
lx:=0 | |
ly:=0 | |
return | |
getspeed() | |
{ | |
DllCall("SystemParametersInfo","Int",112,"Int",0,"UIntP",speed,"Int",0) | |
} | |
setspeed() | |
{ | |
DllCall("SystemParametersInfo","Int",113,"Int",0,"UInt",speed,"Int",2) | |
} | |
showspeed() | |
{ | |
message("Mouse speed = " . speed) | |
} | |
<^>!d:: | |
getspeed() | |
showspeed() | |
return | |
<^>!s:: | |
getspeed() | |
if( speed>1 ) | |
{ | |
speed:=speed-1 | |
} | |
setspeed() | |
showspeed() | |
return | |
<^>!f:: | |
getspeed() | |
if( speed<20 ) | |
{ | |
speed:=speed+1 | |
} | |
setspeed() | |
showspeed() | |
return | |
// Mouse Scrolling // | |
/* | |
Usage | |
Combination : Function | |
ScrollButton-Release : ScrollButton-Click (where the intervening time and scroll do not exceed the limits set) | |
ScrollButton-Modifiers-Move : Modifiers-Scroll (where Modifiers can be any combination of Shift and Ctrl and Alt) | |
If rightbuttonscroll = 1 : | |
LButton-RButton : MButton (where the intervening time does not exceed the limit set) | |
RButton-LButton : MButton (where the intervening time does not exceed the limit set) | |
Methods | |
Send wheel messages (0x20a/0x20e) to the control | |
The wheel amount is only 16-bit which is at most only 32767/120 wheel notches | |
Some applications do not handle the wheel amount correctly so you may need to send integer multiples of wheel notches or many wheel messages | |
Microsoft Word apparently assumes that wheel messages are always posted and behaves incorrectly unless PostMessage is used | |
Send scrollbars' thumb position to the control | |
This works for many scrollable controls but not all | |
Send scroll messages (0x115/0x114) to the control unless its scrollbar thumb position is already at the end | |
Many applications respond rather slowly to scroll messages | |
Send scroll messages to the scrollbars | |
This works for some applications and should be slightly faster than scroll messages to the parent | |
Many applications respond rather slowly so wheel messages are preferable if they work | |
Send scroll messages to the scrollbars' parents | |
This is supposed to be a standard way to control scrollbars that are separate from the control | |
Many applications respond rather slowly so wheel messages are preferable if they work | |
Send keys (Up/Down/Left/Right/PgUp/PgDn) to the scrollbars | |
This is equivalent to clicking the arrows or the empty space on either side of the thumb track | |
Some applications respond slowly so keys should not be sent too fast | |
Send keys to the control | |
Only for rare cases that do not respond to anything else | |
Call Office function SmallScroll | |
This function only exists for Office applications and even then it is broken in some like Powerpoint | |
Customization | |
scrollamount(x) | |
Can assume that x is non-negative | |
Must return non-negative output | |
scrolladjust(x,y) | |
Can modify scroll amounts (x,y) | |
gettarget() | |
Can assume that (mx,my) is the current mouse position | |
Must set ctrl to the handle of the control that is to be scrolled | |
Must set parent to getparent(ctrl) if scrolltoscrollbarparent() or keystoscrollbar() is used | |
*/ | |
scrollamount(x) | |
{ | |
return x**1.5/8 | |
} | |
scrolladjust(byref x,byref y) | |
{ | |
ax:=abs(x) | |
ay:=abs(y) | |
z:=sqrt(x**2+y**2) | |
if( ax>ay*5 ) | |
{ | |
x:=( x>0 ? z : -z ) | |
y:=0 | |
} | |
if( ay>ax*5 ) | |
{ | |
y:=( y>0 ? z : -z ) | |
x:=0 | |
} | |
} | |
gettarget() | |
{ | |
gosub messageoff | |
ctrl0:=getctrlat(mx,my) | |
window:=getwindow(ctrl0) | |
class:=getclass(window) | |
title:=gettitle(window) | |
ctrl:=ctrl0 | |
loop | |
{ | |
ctrlname:=getnameaschild(ctrl) | |
ctrlclass:=getclass(ctrl) | |
parent:=getparent(ctrl) | |
parentname:=getnameatroot(parent) | |
parentclass:=getclass(parent) | |
if( ctrl!=window and ( regexmatch(ctrlclass,"^(Button|T?ComboBox|CtrlNotifySink|SysLink)$")==1 or parentclass=="ComboBox" ) ) | |
{ | |
ctrl:=parent | |
continue | |
} | |
break | |
} | |
gp:=getparent(parent) | |
ggp:=getparent(gp) | |
gpname:=getnameaschild(gp) | |
ggpname:=getnameaschild(ggp) | |
methodx:="wheel" // needed for: Firefox , Gimp , ... | |
methody:="wheel" // needed for: Firefox , File Chooser , Explorer , Word , Outlook , IE , ... | |
scrollbarx:="" | |
scrollbary:="" | |
if( ctrlclass=="ComboLBox" ) // Standard Combo Boxes dropdown list | |
{ | |
methodx:="thumbpos" | |
methody:="thumbpos" | |
} | |
if( ctrlclass=="ListBox" ) // Standard List Boxes | |
{ | |
methodx:="thumbpos" | |
methody:="thumbpos" | |
} | |
if( ctrlclass=="ScrollBar" ) // Standard ScrollBar controls | |
{ | |
methodx:="scrolltoscrollbarparent" // "scroll" works for most places but not some ( Character Map , ... ) | |
methody:="scrolltoscrollbarparent" // "scroll" works for most places but not some ( Character Map , ... ) | |
scrollbarx:="scrollbar1" | |
scrollbary:="scrollbar1" | |
} | |
if( class=="OpusApp" ) // Microsoft Word | |
{ | |
methodx:="office" | |
methody:="postwheel" | |
if( gpname=="_WwB1" ) | |
{ | |
ctrl:=getdescendant(window,"_WwG1") | |
} | |
} | |
if( class=="XLMAIN" ) // Microsoft Excel | |
{ | |
methodx:="office" | |
if( gpname=="EXCEL71" ) | |
{ | |
ctrl:=gp | |
} | |
} | |
if( class=="PPTFrameClass" or class=="PP12FrameClass" ) // Microsoft Powerpoint | |
{ | |
methody:="wheelsingle" | |
ctrlnameatroot:=getnameatroot(ctrl) | |
if( ctrlnameatroot=="NetUIHWND3" or ctrlnameatroot=="NetUIHWND4" ) | |
{ | |
ctrl:=getdescendant(window,"paneClassDC1") | |
ctrlname:="paneClassDC1" | |
parent:=getparent(ctrl) | |
} | |
if( ctrlname=="paneClassDC1" ) | |
{ | |
methodx:="scrolltoscrollbarparent" | |
methody:="scrolltoscrollbarparent" // Powerpoint scroll up is broken when there are 9 slides at 100% zoom in normal mode | |
scrollbarx:="NUIScrollbar2" | |
scrollbary:="NUIScrollbar1" | |
//methody:="office" // Powerpoint does not update the view pane immediately and so it is disorienting | |
} | |
if( ctrlnameatroot=="NetUIHWND5" ) | |
{ | |
ctrl:=getdescendant(window,"paneClassDC2") | |
} | |
} | |
if( class=="rctrl_renwnd32" ) // Microsoft Outlook | |
{ | |
methodx:="office" | |
gpclass:=getclass(gp) | |
ctrlnameatroot:=getnameatroot(ctrl) | |
if( ctrlclass=="SUPERGRID" ) | |
{ | |
methodx:="scroll" | |
methody:="scroll" | |
} | |
if( gpclass=="SUPERGRID" ) | |
{ | |
methodx:="scroll" | |
methody:="scroll" | |
ctrl:=gp | |
} | |
if( gpname=="_WwB1" ) | |
{ | |
ctrl:=getdescendant(window,"_WwG1") | |
} | |
} | |
if( ctrlclass=="OUTEXVLB" ) // Microsoft Outlook { Address Book , Group membership , ... } | |
{ | |
methodx:="thumbpos" | |
if( regexmatch(title,"Global Address List")==0 ) | |
{ | |
methody:="scroll" | |
} | |
} | |
if( title=="Symbol" and regexmatch(class,"bosa_sdm_(msword|Microsoft Office Word 12.0|XL9|Mso96)")==1 ) // Microsoft Office Insert Symbol | |
{ | |
controlget v,visible,,ScrollBar1,ahk_id %ctrl% | |
if( v==0 ) | |
{ | |
ctrl:=getdescendant(window,"Edit1") | |
methody:="thumbpos" | |
} | |
} | |
if( class=="wndclass_desked_gsk" ) // Microsoft Visual Basic | |
{ | |
if( ctrlclass=="VbaWindow" ) | |
{ | |
methodx:="scrolltoscrollbarparent" | |
parent:=ctrl | |
scrollbarx:="scrollbar2" | |
} | |
} | |
if( regexmatch(ctrlclass,"^RichEdit20W(PT)?$")==1 ) // Windows Text Areas | |
{ | |
methodx:="wheel" | |
methody:="scroll" | |
} | |
if( class=="AcrobatSDIWindow" ) // Adobe Reader | |
{ | |
if( regexmatch(parentname,"AVL_AVView")==1 ) | |
{ | |
ctrl:=getdescendant(parent,"AVL_AVView4") | |
if( ctrl=="" ) | |
{ | |
ctrl:=getdescendant(parent,"AVL_AVView1") | |
} | |
methodx:="scrolltoscrollbarparent" | |
scrollbarx:="scrollbar1" | |
methody:="wheel" | |
} | |
} | |
if( ggpname=="SHELLDLL_DefView1" ) // Windows Explorer Scrollbars | |
{ | |
ctrl:=gp | |
ctrlname:=gpname | |
parentname:=ggpname | |
} | |
if( ctrlname=="DirectUIHWND1" ) | |
{ | |
if( parentname=="SHELLDLL_DefView1" ) // Windows Explorer (including Standard File Choosers) | |
{ | |
methodx:="scrolltoscrollbar" // "scrolltoscrollbarparent" also works | |
scrollbarx:="scrollbar1" | |
controlget v,visible,,ScrollBar2,ahk_id %ctrl% | |
methody:=( v==1 ? "wheel" : "" ) | |
} | |
if( class=="CabinetWClass" ) | |
{ | |
if( parentname=="XBabyHost1" ) // Control Panel | |
{ | |
methody:="scrolltoscrollbar" // "scrolltoscrollbarparent" also works | |
scrollbary:="scrollbar1" | |
if( title=="Personalization" ) | |
{ | |
scrollbary:="scrollbar3" | |
} | |
} | |
} | |
} | |
if( ctrlclass=="CharGridWClass" ) // Character Map | |
{ | |
methody:="scrolltoscrollbarparent" | |
scrollbary:="scrollbar1" | |
} | |
if( class=="ConsoleWindowClass" ) // Console Window | |
{ | |
methodx:="thumbpos" | |
methody:="thumbpos" | |
} | |
if( class=="ATL:006AD5B8" ) // Programmer's Notepad | |
{ | |
methodx:="scroll" | |
} | |
if( class=="SWT_Window0" or ctrlclass=="Internet Explorer_Server" ) // Eclipse | |
{ | |
methodx:="scroll" | |
} | |
if( ctrlclass=="TSynEdit" ) // TSynEdit ; Dev C++ , ... | |
{ | |
methodx:="thumbpos" | |
methody:="thumbpos" | |
} | |
if( ctrlclass=="TListView" ) // TlistView ; Dev C++ , ... | |
{ | |
methodx:="scroll" | |
} | |
if( ctrlclass=="TPSSynEdit" ) // TPSSynEdit ; PSPad , ... | |
{ | |
methodx:="thumbpos" | |
} | |
if( class=="QWidget" ) | |
{ | |
if( regexmatch(title,"LyX")==1 ) // Lyx | |
{ | |
methodx:="" | |
} | |
if( regexmatch(title,"TeXworks$")>0 ) // TeXWorks | |
{ | |
methodx:="wheelint" | |
methody:="wheelint" | |
} | |
} | |
if( class=="gdkWindowToplevel" ) // Gimp | |
{ | |
methody:="wheelsingle" // Gimp performs horizontal scrolling when the mouse is scrolled over the horizontal scrollbar | |
} | |
if( class=="SunAwtDialog" ) // Java AWT Dialogs ; GeoGebra , Logisim , ... | |
{ | |
methody:="wheelint" | |
} | |
if( regexmatch(ctrlname,"IupCanvas")==1 ) // IupCanvas | |
{ | |
methodx:="scroll" | |
} | |
if( class=="SunAwtFrame" ) | |
{ | |
if( regexmatch(title,"GeoGebra|.*\.ggb$")==1 ) // GeoGebra | |
{ | |
methody:="wheelint" | |
} | |
if( regexmatch(title,"Logisim")==1 ) // Logisim | |
{ | |
methodx:="keys" // performs scrolling only if the drawing area has the focus | |
} | |
} | |
if( class=="MSPaintApp" ) // MSPaint | |
{ | |
if( parentname=="MSPaintView1" ) | |
{ | |
methodx:="thumbpos" | |
methody:="thumbpos" | |
} | |
} | |
if( class=="ATL:643E3490" ) // Real World Paint | |
{ | |
if( ctrlclass=="RWViewImageEdit" ) | |
{ | |
methodx:="scroll" | |
methody:="scroll" | |
} | |
} | |
if( ctrlclass=="DSUI:PagesView" ) // PDF-XChange Viewer (also as a browser plugin) | |
{ | |
methodx:="scroll" | |
methody:="wheelint" | |
} | |
if( ctrlclass=="PuTTY" ) // PuTTY | |
{ | |
methody:="scroll" | |
} | |
if( dbg ) | |
{ | |
p:=getparent(ctrl) | |
gp:=getparent(p) | |
ggp:=getparent(gp) | |
message( "Root class = " class | |
. "`nRoot title = " title | |
. "`nTarget = [" ctrl0 "]" | |
. "`n`t(as child) " getnameaschild(ctrl0) | |
. "`n`t(at root) " getnameatroot(ctrl0) | |
. "`nControl = [" ctrl "]" | |
. "`n`t(at child) " getnameaschild(ctrl) | |
. "`n`t(as root) " getnameatroot(ctrl) | |
. "`nControl ancestors = " | |
. "`n`t < [" p "] " getnameatroot(p) | |
. "`n`t < [" gp "] " getnameatroot(gp) | |
. "`n`t < [" ggp "] " getnameatroot(ggp) | |
. "`nMethod = (" methodx "," methody ")" | |
. "`nScrollbars = (" scrollbarx "," scrollbary ")" ) | |
} | |
} | |
scroll: | |
critical on | |
if( getwindow(getctrlat(mx,my))!=window ) | |
{ | |
scrolling:=0 | |
} | |
if( scrolling==0 ) | |
{ | |
return | |
} | |
settimer scroll,-%interval% | |
if( scrollbeforeclick!=1 and scrolldrag==0 ) | |
{ | |
sx:=0 | |
sy:=0 | |
return | |
} | |
tx:=sx | |
ty:=sy | |
sx-=tx | |
sy-=ty | |
totalx+=tx | |
totaly+=ty | |
if( totalx**2+totaly**2>scrolllimit ) | |
{ | |
scrolldrag:=1 | |
} | |
scrolladjust(tx,ty) | |
rx:=0 | |
ry:=0 | |
comobjerror(false) | |
if( tx!=0 ) | |
{ | |
if( methodx=="wheel" ) | |
{ | |
sendwheel("h",tx) | |
} | |
else if( methodx=="postwheel" ) | |
{ | |
postwheel("h",tx) | |
} | |
else | |
{ | |
txi:=rtoz(tx) | |
rx:=tx-txi | |
if( txi!=0 ) | |
{ | |
if( methodx=="wheelint" ) | |
{ | |
sendwheel("h",txi) | |
} | |
else if( methodx=="wheelsingle" ) | |
{ | |
sendwheelsingle("h",txi) | |
} | |
else if( methodx=="thumbpos" ) | |
{ | |
sendthumbpos("h",txi) | |
} | |
else if( methodx=="scroll" ) | |
{ | |
sendscroll("h",txi) | |
} | |
else if( methodx=="scrolltoscrollbar" ) | |
{ | |
sendscrolltoscrollbar(scrollbarx,txi) | |
} | |
else if( methodx=="scrolltoscrollbarparent" ) | |
{ | |
sendscrolltoscrollbarparent(scrollbarx,"h",txi) | |
} | |
else if( methodx=="keys" ) | |
{ | |
sendkeys("h",txi) | |
} | |
else if( methodx=="keystoscrollbar" ) | |
{ | |
sendkeystoscrollbar(scrollbarx,txi) | |
} | |
else if( methodx=="office" ) | |
{ | |
Acc_ObjectFromWindow(ctrl,-16).SmallScroll(0,0,(txi>0?txi:0),(txi<0?-txi:0)) | |
} | |
} | |
} | |
} | |
if( ty!=0 ) | |
{ | |
if( methody=="wheel" ) | |
{ | |
sendwheel("v",-ty) | |
} | |
else if( methody=="postwheel" ) | |
{ | |
postwheel("v",-ty) | |
} | |
else | |
{ | |
tyi:=rtoz(ty) | |
ry:=ty-tyi | |
if( tyi!=0 ) | |
{ | |
if( methody=="wheelint" ) | |
{ | |
sendwheel("v",-tyi) | |
} | |
else if( methody=="wheelsingle" ) | |
{ | |
sendwheelsingle("v",-tyi) | |
} | |
else if( methody=="thumbpos" ) | |
{ | |
sendthumbpos("v",tyi) | |
} | |
else if( methody=="scroll" ) | |
{ | |
sendscroll("v",tyi) | |
} | |
else if( methody=="scrolltoscrollbar" ) | |
{ | |
sendscrolltoscrollbar(scrollbary,tyi) | |
} | |
else if( methody=="scrolltoscrollbarparent" ) | |
{ | |
sendscrolltoscrollbarparent(scrollbary,"v",tyi) | |
} | |
else if( methody=="keys" ) | |
{ | |
sendkeys("v",tyi) | |
} | |
else if( methody=="keystoscrollbar" ) | |
{ | |
sendkeystoscrollbar(scrollbary,tyi) | |
} | |
else if( methody=="office" ) | |
{ | |
Acc_ObjectFromWindow(ctrl,-16).SmallScroll((tyi>0?tyi:0),(tyi<0?-tyi:0)) | |
} | |
} | |
} | |
} | |
comobjerror(true) | |
sx:=rx | |
sy:=ry | |
return | |
sendwheel(dir,amount) | |
{ | |
t:=a_tickcount | |
msg:=( dir=="v" ? 0x20a : 0x20e ) | |
flags:=getkeystate("Ctrl")<<3|getkeystate("Shift")<<2 | |
amount*=120 | |
while( amount>max16bit ) | |
{ | |
sendmessage msg,max16bit<<16|flags,mx|my<<16,,ahk_id %ctrl%,,,,timelimit | |
amount-=max16bit | |
if( a_tickcount-t>=timelimit ) | |
{ | |
return | |
} | |
} | |
while( amount<-max16bit ) | |
{ | |
sendmessage msg,-max16bit<<16|flags,mx|my<<16,,ahk_id %ctrl%,,,,timelimit | |
amount+=max16bit | |
if( a_tickcount-t>=timelimit ) | |
{ | |
return | |
} | |
} | |
sendmessage msg,round(amount)<<16|flags,mx|my<<16,,ahk_id %ctrl%,,,,timelimit | |
} | |
postwheel(dir,amount) | |
{ | |
msg:=( dir=="v" ? 0x20a : 0x20e ) | |
flags:=getkeystate("Ctrl")<<3|getkeystate("Shift")<<2 | |
amount*=120 | |
while( amount>max16bit ) | |
{ | |
postmessage msg,max16bit<<16|flags,mx|my<<16,,ahk_id %ctrl% | |
amount-=max16bit | |
} | |
while( amount<-max16bit ) | |
{ | |
postmessage msg,-max16bit<<16|flags,mx|my<<16,,ahk_id %ctrl% | |
amount+=max16bit | |
} | |
postmessage msg,round(amount)<<16|flags,mx|my<<16,,ahk_id %ctrl% | |
} | |
sendwheelsingle(dir,amount) | |
{ | |
t:=a_tickcount | |
msg:=( dir=="v" ? 0x20a : 0x20e ) | |
flags:=getkeystate("Ctrl")<<3|getkeystate("Shift")<<2 | |
loop % abs(amount) | |
{ | |
sendmessage msg,(amount<0?-120:120)<<16|flags,mx|my<<16,,ahk_id %ctrl%,,,,timelimit | |
if( a_tickcount-t>=timelimit ) | |
{ | |
return | |
} | |
} | |
} | |
sendthumbpos(dir,amount) | |
{ | |
msg:=( dir=="v" ? 0x115 : 0x114 ) | |
sb:=dllcall("GetScrollInfo","uint",ctrl,"int",(dir=="v"?1:0),"uint",&sbinfo) | |
if( sb ) | |
{ | |
sbmin:=numget(sbinfo,8,"int") | |
sbmax:=numget(sbinfo,12,"int") | |
sbpos:=numget(sbinfo,20,"int") | |
if( amount>max16bit ) | |
{ | |
amount=max16bit | |
} | |
if( amount<-max16bit ) | |
{ | |
amount=-max16bit | |
} | |
pos:=sbpos+amount | |
if( pos<sbmin ) | |
{ | |
pos:=sbmin | |
} | |
if( pos>sbmax ) | |
{ | |
pos:=sbmax | |
} | |
sendmessage msg,pos<<16|4,,,ahk_id %ctrl%,,,,timelimit | |
} | |
} | |
sendscroll(dir,amount) | |
{ | |
t:=a_tickcount | |
msg:=( dir=="v" ? 0x115 : 0x114 ) | |
flag:=( amount<0 ? 0 : 1 ) | |
loop % abs(amount) | |
{ | |
sb:=dllcall("GetScrollInfo","uint",ctrl,"int",(dir=="v"?1:0),"uint",&sbinfo) | |
if( sb ) | |
{ | |
sbmin:=numget(sbinfo,8,"int") | |
sbmax:=numget(sbinfo,12,"int") | |
sbpage:=numget(sbinfo,16,"uint") | |
sbpos:=numget(sbinfo,20,"int") | |
if( ( sbpos==sbmin and amount<0 ) or ( sbpos+sbpage==sbmax+1 and amount>0 ) ) | |
{ | |
return | |
} | |
} | |
sendmessage msg,flag,,,ahk_id %ctrl%,,,,timelimit | |
if( a_tickcount-t>=timelimit ) | |
{ | |
return | |
} | |
} | |
} | |
sendscrolltoscrollbar(name,amount) | |
{ | |
t:=a_tickcount | |
flag:=( amount<0 ? 0 : 1 ) | |
loop % abs(amount) | |
{ | |
sendmessage 0x115,flag,,%name%,ahk_id %ctrl%,,,,timelimit | |
if( a_tickcount-t>=timelimit ) | |
{ | |
return | |
} | |
} | |
} | |
sendscrolltoscrollbarparent(name,dir,amount) | |
{ | |
sb:=getdescendant(parent,name) | |
sbp:=getparent(sb) | |
t:=a_tickcount | |
msg:=( dir=="v" ? 0x115 : 0x114 ) | |
flag:=( amount<0 ? 0 : 1 ) | |
loop % abs(amount) | |
{ | |
sendmessage msg,flag,sb,,ahk_id %sbp%,,,,timelimit | |
if( a_tickcount-t>=timelimit ) | |
{ | |
return | |
} | |
} | |
} | |
sendkeys(dir,amount) | |
{ | |
t:=a_tickcount | |
key:=( dir=="v" ? ( amount<0 ? "{Up}" : "{Down}" ) : ( amount<0 ? "{Left}" : "{Right}" ) ) | |
loop % abs(amount) | |
{ | |
controlsend, ,%key%,ahk_id %ctrl% | |
if( a_tickcount-t>=timelimit ) | |
{ | |
return | |
} | |
} | |
} | |
sendkeystoscrollbar(name,amount) | |
{ | |
t:=a_tickcount | |
key:=( amount<0 ? "{Up}" : "{Down}" ) | |
controlget e,enabled,,%name%,ahk_id %parent% | |
if( e==1 ) | |
{ | |
loop % abs(amount) | |
{ | |
controlsend %name%,%key%,ahk_id %parent% | |
if( a_tickcount-t>=timelimit ) | |
{ | |
break | |
} | |
} | |
} | |
} | |
scrollbuttonreset: | |
scrollsticky:=0 | |
return | |
scrollbuttoncannotclick: | |
scrolldrag:=1 | |
righttopress:=0 | |
return | |
clickreset: | |
clicksticky:=0 | |
return | |
scrollbuttondown: | |
critical on | |
mousegetpos mx,my | |
gettarget() | |
sx:=0 | |
sy:=0 | |
totalx:=0 | |
totaly:=0 | |
settimer scroll,-%interval% | |
settimer scrollbuttonreset,off | |
return | |
scrollbuttonup: | |
critical on | |
if( scrolldrag==0 and scrollsticky==0 ) | |
{ | |
handling++ | |
sendevent {Blind}{%buttonoriginal% down} | |
sendevent {Blind}{%buttonoriginal% up} | |
handling-- | |
scrolling:=0 | |
} | |
scrolldrag:=0 | |
righttopress:=0 | |
settimer scrollbuttondown,off | |
settimer scrollbuttoncannotclick,off | |
scrollsticky:=1 | |
settimer scrollbuttonreset,-%resetdelay% | |
return | |
leftdown: | |
critical on | |
lefttopress:=0 | |
handling++ | |
sendevent {Blind}{LButton down} | |
handling-- | |
return | |
rightdown: | |
critical on | |
righttopress:=0 | |
handling++ | |
sendevent {Blind}{RButton down} | |
handling-- | |
return | |
middledown: | |
critical on | |
scrolling:=0 | |
scrolldrag:=0 | |
middlepressed:=1 | |
settimer scrollbuttondown,off | |
settimer scrollbuttoncannotclick,off | |
handling++ | |
sendevent {Blind}{MButton down} | |
handling-- | |
return | |
leftup: | |
critical on | |
handling++ | |
sendevent {Blind}{LButton up} | |
handling-- | |
clicksticky:=1 | |
settimer clickreset,-%resetdelay% | |
return | |
rightup: | |
critical on | |
handling++ | |
sendevent {Blind}{RButton up} | |
handling-- | |
clicksticky:=1 | |
settimer clickreset,-%resetdelay% | |
return | |
middleup: | |
critical on | |
middlepressed:=0 | |
handling++ | |
sendevent {Blind}{MButton up} | |
handling-- | |
clicksticky:=1 | |
settimer clickreset,-%resetdelay% | |
return | |
leftclick: | |
critical on | |
gosub leftdown | |
gosub leftup | |
return | |
rightclick: | |
critical on | |
gosub rightdown | |
gosub rightup | |
return | |
// Mouse handler // | |
handlemouse(nCode,wParam,lParam) | |
{ | |
critical on | |
if( a_ispaused==1 ) | |
{ | |
exitapp // something goes wrong if it remains paused | |
} | |
o:=0 | |
extra:=numget(lParam+0,20,"uint") // extra==4291023949==0xFFC3D44F-2 iff the mouse event was generated by AutoHotkey SendEvent | |
if( extra!=4291023949 and nCode>=0 ) | |
{ | |
if( wParam==0x201 ) | |
{ | |
leftphysical:=1 | |
} | |
else if( wParam==0x204 ) | |
{ | |
rightphysical:=1 | |
} | |
else if( wParam==0x202 ) | |
{ | |
leftphysical:=0 | |
} | |
else if( wParam==0x205 ) | |
{ | |
rightphysical:=0 | |
} | |
} | |
if( handling==0 && nCode>=0 ) | |
{ | |
if( leftrighttomiddle==1 ) | |
{ | |
// Release middle button only when both left and right buttons are released // | |
if( middlepressed==1 and leftphysical==0 and rightphysical==0 ) | |
{ | |
settimer middleup,-0 | |
o:=1 | |
} | |
} | |
if( o==0 ) | |
{ | |
if( wParam==0x200 ) | |
{ | |
// Handle mouse move // | |
mousegetpos mx,my | |
x:=numget(lParam+0,0,"int") // the "+0" is necessary! | |
y:=numget(lParam+0,4,"int") // the "+0" is necessary! | |
dx:=x-mx | |
dy:=y-my | |
if( scrolling==0 ) | |
{ | |
// Click immediately on mouse drag // | |
if( leftrighttomiddle==1 ) | |
{ | |
if( lefttopress==1 ) | |
{ | |
settimer leftdown,-0 | |
} | |
if( righttopress==1 ) | |
{ | |
settimer rightdown,-0 | |
} | |
} | |
// Adjust mouse movement // | |
moveadjust(dx,dy) | |
handling++ | |
mousemove dx,dy,0,R | |
handling-- | |
if( dbg ) | |
{ | |
message( "Origin = " mx " " my "`nMove = " x-mx " " y-my "`n -> " dx " " dy ) | |
} | |
// Release mouse buttons if out of sync with physical state // | |
if( getkeystate("LButton")>leftphysical ) | |
{ | |
settimer leftup,-0 | |
} | |
if( getkeystate("RButton")>rightphysical ) | |
{ | |
settimer rightup,-0 | |
} | |
o:=1 | |
} | |
else | |
{ | |
// Handle mouse move when scrolling // | |
if( dx!=0 or dy!=0 ) | |
{ | |
sx+=( dx>0 ? scrollamount(dx) : -scrollamount(-dx) ) | |
sy+=( dy>0 ? scrollamount(dy) : -scrollamount(-dy) ) | |
if( dbg ) | |
{ | |
message( "Origin = " mx " " my "`nMove = " dx " " dy "`n -> " round(sx,2) " " round(sy,2) ) | |
} | |
} | |
o:=1 | |
} | |
} | |
else if( scrolling==0 ) | |
{ | |
if( wParam==buttondown && middlepressed==0 ) | |
{ | |
// Handle scroll button down // | |
if( getkeystate("Ctrl")==0 and getkeystate("Shift")==0 and getkeystate("Alt")==0 and lefttopress==0 ) | |
{ | |
// Handle scroll start // | |
scrolling:=1 | |
scrolldrag:=0 | |
righttopress:=1 | |
settimer scrollbuttondown,-0 | |
settimer scrollbuttoncannotclick,-%clicklimit% | |
o:=1 | |
} | |
} | |
} | |
else if( scrolling==1 ) | |
{ | |
if( wParam==buttonup ) | |
{ | |
// Handle scroll button up // | |
scrolling:=0 | |
settimer scrollbuttonup,-0 | |
o:=1 | |
} | |
} | |
} | |
if( middlepressed==1 ) | |
{ | |
o:=1 | |
} | |
else if( leftrighttomiddle==1 and o==0 and scrolldrag==0 ) | |
{ | |
// Process left+right=middle // | |
if( wParam==0x201 and scrollsticky==0 ) | |
{ | |
// Handle left button down // | |
if( righttopress==0 ) | |
{ | |
if( false and clicksticky==1 ) | |
{ | |
settimer leftdown,-0 | |
} | |
else | |
{ | |
lefttopress:=1 | |
settimer leftdown,-%clicklimit% | |
} | |
} | |
else | |
{ | |
righttopress:=0 | |
settimer rightdown,off | |
settimer middledown,-0 | |
} | |
o:=1 | |
} | |
else if( wParam==0x204 and scrollsticky==0 ) | |
{ | |
// Handle right button down // | |
if( lefttopress==0 ) | |
{ | |
if( clicksticky==1 ) | |
{ | |
settimer rightdown,-0 | |
} | |
else | |
{ | |
righttopress:=1 | |
settimer rightdown,-%clicklimit% | |
} | |
} | |
else | |
{ | |
lefttopress:=0 | |
settimer leftdown,off | |
settimer middledown,-0 | |
} | |
o:=1 | |
} | |
else if( wParam==0x202 ) | |
{ | |
// Handle left button up // | |
if( lefttopress==1 ) | |
{ | |
settimer leftdown,off | |
settimer leftclick,-0 | |
} | |
else | |
{ | |
settimer leftup,-0 | |
} | |
o:=1 | |
} | |
else if( wParam==0x205 ) | |
{ | |
// Handle right button up // | |
if( righttopress==1 ) | |
{ | |
settimer rightdown,off | |
settimer rightclick,-0 | |
} | |
else | |
{ | |
settimer rightup,-0 | |
} | |
o:=1 | |
} | |
} | |
} | |
// Pass on any other mouse events // | |
if( o==0 ) | |
{ | |
o:=dllcall("CallNextHookEx","uint",mousehook,"int",nCode,"uint",wParam,"uint",lParam) | |
} | |
return o | |
} | |
// Utilities // | |
rtoz(r) | |
{ | |
return ( r>0 ? floor(r) : ceil(r) ) | |
} | |
getparent(handle) | |
{ | |
return dllcall("GetParent","uint",handle) | |
} | |
getancestor(handle,steps) | |
{ | |
if( steps==0 ) | |
{ | |
return handle | |
} | |
if( steps>0 ) | |
{ | |
return getancestor(getparent(handle),steps-1) | |
} | |
return "" | |
} | |
getname(root,handle) | |
{ | |
local CH,CN,S,P | |
WinGet, CH, ControlListHwnd, ahk_id %root% | |
WinGet, CN, ControlList, ahk_id %root% | |
setformat integerfast,h | |
handle+=0 | |
handle.="" | |
setformat integerfast,d | |
LF:= "`n", CH:= LF CH LF, CN:= LF CN LF, S:= SubStr( CH, 1, InStr( CH, LF handle LF ) ) | |
StringReplace, S, S,`n,`n, UseErrorLevel | |
StringGetPos, P, CN, `n, L%ErrorLevel% | |
Return SubStr( CN, P+2, InStr( CN, LF, 0, P+2 ) -P-2 ) | |
} | |
getdescendant(handle,name) | |
{ | |
local CH,CN,S,P | |
WinGet, CH, ControlListHwnd, ahk_id %handle% | |
WinGet, CN, ControlList, ahk_id %handle% | |
setformat integerfast,h | |
handle+=0 | |
handle.="" | |
setformat integerfast,d | |
LF:= "`n", CH:= LF CH LF, CN:= LF CN LF, S:= SubStr( CN, 1, InStr( CN, LF name LF ) ) | |
StringReplace, S, S,`n,`n, UseErrorLevel | |
StringGetPos, P, CH, `n, L%ErrorLevel% | |
Return SubStr( CH, P+2, InStr( CH, LF, 0, P+2 ) -P-2 )*1 | |
} | |
getnameatroot(handle) | |
{ | |
return getname(dllcall("GetAncestor","uint",handle,"uint",2),handle) | |
} | |
getnameaschild(handle) | |
{ | |
return getname(getparent(handle),handle) | |
} | |
getclass(handle) | |
{ | |
local class | |
wingetclass class,ahk_id %handle% | |
return class | |
} | |
gettitle(handle) | |
{ | |
local title | |
wingettitle title,ahk_id %handle% | |
return title | |
} | |
getposition(handle,byref left,byref top,byref right,byref bottom) | |
{ | |
local rect | |
varsetcapacity(rect,16) | |
dllcall("GetWindowRect","uint",handle,"uint",&rect) | |
left:=numget(rect,0,"int") | |
top:=numget(rect,4,"int") | |
right:=numget(rect,8,"int") | |
bottom:=numget(rect,12,"int") | |
} | |
getctrlat2(x,y,first,current) | |
{ | |
/* | |
Pushes the following invisible container controls to the back because they are in front of their contents for no reason | |
SysTabControl32 : The usual class that contains tabbed panes ( Mouse properties , ... ) | |
Static : A class occasionally used to contain tabbed panes ( Programmer's Notepad Options > Fonts and Colours > Advanced , ... ) | |
Button : A typical class used to contain a List Box ( Outlook Contact > Properties > General > Members , ... ) | |
Executes WindowFromPoint again to access the contents of such container controls | |
*/ | |
local handle,class,style | |
class:=getclass(current) | |
winget style,style,ahk_id %current% | |
if( class=="SysTabControl32" or class=="Static" or ( class=="Button" and (style&0x7)==0x7 ) ) | |
{ | |
dllcall("SetWindowPos","uint",current,"uint",1,"int",0,"int",0,"int",0,"int",0,"uint",0x3) // push it to the back where it belongs | |
handle:=dllcall("WindowFromPoint","int",x,"int",y) | |
//handle:=DllCall( "WindowFromPoint", "int64", (my << 32) | (mx & 0xFFFFFFFF), "Ptr") // for negative 64-bit | |
if( handle==first ) | |
{ | |
return first | |
} | |
return getctrlat2(x,y,first,handle) | |
} | |
return current | |
} | |
getctrlat(x,y) | |
{ | |
local handle | |
handle:=dllcall("WindowFromPoint","int",x,"int",y) | |
//handle:=DllCall( "WindowFromPoint", "int64", (my << 32) | (mx & 0xFFFFFFFF), "Ptr") // for negative 64-bit | |
return getctrlat2(x,y,handle,handle) | |
} | |
getwindow(handle) | |
{ | |
return dllcall("GetAncestor","uint",handle,"uint",2) | |
} | |
Acc_Init() | |
{ | |
Static h | |
If Not h | |
h:=DllCall("LoadLibrary","Str","oleacc","Ptr") | |
} | |
Acc_ObjectFromWindow(hWnd, idObject = -4) | |
{ | |
local o | |
Acc_Init() | |
o:=DllCall("oleacc\AccessibleObjectFromWindow" | |
, "Ptr", hWnd | |
, "UInt", idObject&=0xFFFFFFFF | |
, "Ptr", -VarSetCapacity(IID,16)+NumPut(idObject==0xFFFFFFF0?0x46000000000000C0:0x719B3800AA000C81 | |
,NumPut(idObject==0xFFFFFFF0?0x0000000000020400:0x11CF3C3D618736E0,IID,"Int64") | |
,"Int64") | |
,"Ptr*", pacc) | |
if( o==0 ) | |
Return ComObjEnwrap(9,pacc,1) | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment