Variable names and function names are not case senstitive.
TODO
Message Box
msgBox no paranthesis no quotes ; msgBox, but no text
msgBox "no paranthesis quotes"
msgBox( paranthesis no quotes ) ; msgBox, but no text
msgBox(" paranthesis quotes")
var := "some text"
nameOfVar := "var"
msgBox( var ) ; some text
msgBox(%nameOfVar%) ; some text
One liner
One of the arguably simplest Autohotkey scripts: As soon as hi, followed by a space, is typed, the hi is replaced with Hello World:
::hi::Hello World
Command line parameters
msgBox("count of parameters: " a_args.length())
for paramNo, paramValue in a_args {
msgBox("Parameter " paramNo " is " paramValue)
}
Getting text with input()
;
; L5 : max length
; T3 : timeout
;
txt := input("L5 T3", "{enter}")
;
; possible Values for errorLevel:
; Max
; Timeout
; EndKey:Enter
;
msgBox("txt = " txt ", errorLevel = " errorLevel)
Stopping a script
A running autohotkey script can be stopped with exitApp()
.
loop (10) {
if a_index = 4 {
msgBox('a_index reached 4, stopping script')
exitApp()
}
msgBox('a_index = ' . a_index . ' - not yet stopping this script')
}
Sleep
msgBox("Going to sleep for 2 seconds")
sleep(2000)
msgBox("I Woke up")
Dynamically create a hotkey
Hotkeys are either created by a (double colon) hotkey label (such as i::…
) or the hotkey(…)
function.
The function hotkey
allows to dynamically create a hotkey.
hotkey_trigger := '^a'
hotkey_function := 'ctrl_a'
hotkey(hotkey_trigger, hotkey_function) ; ctrl-a opens message box
hotkey '!q', 'exitApp' ; Alt-q quits application
ctrl_a() {
msgBox('ctrl_a was pressed')
}
Classes and objects (instances)
Autohotkey allows to declare classes.
The name of a class's constructur is __new
.
Member functions are declared like ordinary functions.
class cls {
;
; The constructur
;
__new(foo, bar, baz) {
this.foo := foo
this.bar := bar
this.baz := baz
}
;
; A member procedure / function:
;
msg() {
msgBox('foo = ' . this.foo .
'`nbar = ' . this.bar .
'`nbaz = ' . this.baz)
}
}
obj_1 := new cls( 1, 2, 3 )
obj_2 := new cls('one', 'two', 'three')
obj_1.msg()
obj_2.msg()
__get / __set
A class can be implemented with dynamic setters and getters: the name of the attribute to be set or get is passed as a string to __get(attr)
or __set(attr, val)
:
class C {
__get(attr) {
msgBox('Someone attempted to get the value of ' . attr)
return 42
}
__set(attr, val) {
msgBox('Someone attempted to set the value of ' . attr . ' to ' . val)
}
}
obj := new C()
obj.foo := obj.bar
__call
Similarly to __get
and __set
, __call
receives the name (and parameters) of a method whose name is not explicitly implemented.
class C {
__call(method_name, params*) {
;
param_str := ''
for ix in params {
param_str := param_str . ' - param_' . ix . '=' . params[ix]
}
msgBox('Someone tried to call ' . method_name . param_str)
;
; The return statement seems to be kind of important:
;
return
}
}
obj := new C
obj.a_func()
obj.another_func('foo', 'bar', 'baz')
a_tickCount
a_tickCount
is a counter of milli seconds with limited precision (Approx. 10 ms?). Better precision can be achieved with DllCall("QueryPerformanceCounter", "Int64*", ms)
.
tcBegin := a_tickCount
sleep 1000
tcEnd := a_tickCount
msgBox('Sleep took approximately ' . (tcEnd - tcBegin) . ' ms')
a_coordMode variables
msgBox ( 'a_coordModeToolTip = ' . a_coordModeToolTip .
'`na_coordModePixel = ' . a_coordModePixel .
'`na_coordModeMouse = ' . a_coordModeMouse .
'`na_coordModeCaret = ' . a_coordModeCaret .
'`na_coordModeMenu = ' . a_coordModeMenu
)
Variables that point to directories
Some a_
variables point to known directories
g := guiCreate()
g.onEvent('close', (*) => exitApp())
lst := g.add('listView',
'r13 ' . ; 13 rows
'w600 ' . ; 600 pixels wide
; 'vEdt ' . ; Name of edit is Edt
'readonly',
'Variable|Value'
)
g.show()
lst.add('', 'a_winDir' , a_winDir )
lst.add('', 'a_programFiles' , a_programFiles )
lst.add('', 'a_appData' , a_appData )
lst.add('', 'a_appDataCommon' , a_appDataCommon )
lst.add('', 'a_desktop' , a_desktop )
lst.add('', 'a_desktopCommon' , a_desktopCommon )
lst.add('', 'a_startMenu' , a_startMenu )
lst.add('', 'a_startMenuCommon' , a_startMenuCommon)
lst.add('', 'a_startUp' , a_startUp )
lst.add('', 'a_startUpCommon' , a_startUpCommon )
lst.add('', 'a_programs' , a_programs )
lst.add('', 'a_programsCommon' , a_programsCommon )
lst.add('', 'a_myDocuments' , a_myDocuments )
;
; Autofit column header:
;
lst.modifyCol
a_comSpec
The
a_comSpec
variable contains the path to
cmd.exe
.
msgBox(a_comSpec)
Run exe or switch to application
:*:gtexcel::
; msgBox "hot string"
; IfWinExist ahk_exe excel.exe
if (pid_excel := processExist("excel.exe"))
{
msgBox "excel found, pid_excel = " pid_excel
winActivate("ahk_pid " pid_excel)
}
else {
msgBox "excel not found found, starting"
; run, "C:\Users\xxx\AppData\Local\Google\Chrome\Application\chrome.exe"
run "C:\Program Files (x86)\Microsoft Office\Office15\excel.exe"
; msgBox "excel not found found, started"
WinWait ("ahk_exe excel.exe")
; WinActivate ahk_exe excel.exe
WinActivate("ahk_exe excel.exe")
; WinWaitActive ahk_exe excel.exe
}
return
Read a file line by line
The following script iterates over each line of a given file:
loop read 'read-file-line-by-line.ahk2' {
msgBox(a_loopReadLine)
}
noTrayIcon
With the #noTrayIcon
directive, no tray icon is shown. The (in-)visibility of the tray icon is stored in the variable a_iconHidden
.
;
; Don't show the tray icon:
;
#noTrayIcon
msgBox("do you see a tray icon, the value of a_iconHidden is: " a_iconHidden)
singleInstance
#singleInstance force
coordMode
coordMode
can be used (among others) to specifiy if the mouse coordinates (mouseGetPos
) are reported relative to the active window or the screen.
coordMode('mouse', 'relative')
mouseGetPos mouseX, mouseY
msgBox('mouse position relative to active window:`nX: ' . mouseX . ', Y: ' . mouseY)
coordMode('mouse', 'screen')
mouseGetPos mouseX, mouseY
msgBox('mouse position relative to screen:`nX: ' . mouseX . ', Y: ' . mouseY)
Variables
num:=42
txt:="The answer is %num%"
msgBox txt ; The message box shows «txt»
msgBox %txt% ; The message box shows the content of the variable txt: «The answer is 42»
msgBox(txt) ; The message box shows the content of the variable txt: «The answer is 42»
varValue := txt ; Assign value of variable txt to another variable
varName :="txt" ; Assign literal string to a variable
msgBox(varValue ) ; The message Box shows the value of varValue: «The answer is 42»
msgBox(varName ) ; The message box shows the value of varName : «txt»
msgBox(%varName%) ; The message box shows the value of the variable txt: «The answer is 42»
;msgBox(%txt%) ; Using percents within the paranthesis of msgBox causes an error
null value
Autohotkey defines the special null
value.
Apparently, a variable that hasn't been assigned a value is null
and indistingishable from a variable that was explicitly assigned null
.
check_if_null(var) {
if var = null
msgBox('var = null')
else
msgBox(var . ' != null')
}
var_1 := 42
var_2 := null
check_if_null(var_1) ; 42 != null
check_if_null(var_2) ; var = null
check_if_null(var_3) ; var = null
The
type of
null
is
String.
Types
type(…)
reveals the type of an expression.
i := 42
f := 99.9
s := 'hello world'
msgBox (
'type(i) = ' . type(i) . '`n' . ; Integer
'type(f) = ' . type(f) . '`n' . ; Float
'type(s) = ' . type(s) . '`n' . ; String
'type(null) = ' . type(null) ; String
)
Arrays
Arrays can be created with […, … ]
.
The
type of an array is
Object
.
The number of elements in an array is reported by .length()
or .count()
.
Arrays can be nested.
someArray := [42, 'text', 99]
nestedArray := ['xyz', someArray]
msgBox(type(someArray)) ; Object
msgBox('count of items in someArray: ' . someArray.count()) ; 3
msgBox('count of items in nestedArray: ' . nestedArray.length()) ; 2
iterateOverObject(nestedArray)
iterateOverObject(obj) {
for ix, val in obj {
if (type(val) == 'Object') {
iterateOverObject(val)
}
else {
msgBox (ix . ': ' . val)
}
}
}
Associative Arrays (Dictionaris, Hashes)
someDictionary := {
'the-number': 42 ,
'greeting' : 'Hello world',
'fruit' : 'apple' ,
}
msg(item) {
global someDictionary
msgBox(someDictionary[item])
}
msg('greeting')
Checking if a key exists
An associative array defines the method hasKey(k)
that determines if the key k
exists.
dict := {
'foo': 1 ,
'bar': null,
}
if dict.hasKey('foo')
msgBox('foo exists')
else
msgBox('foo does not exists')
if dict.hasKey('bar')
msgBox('bar exists')
else
msgBox('bar does not exists')
if dict.hasKey('baz')
msgBox('baz exists')
else
msgBox('baz does not exists')
Variable concatenation
var_1 := "hello"
var_2 := "world"
msgBox(var_1 " " var_2) ; implicit concatenation
msgBox(var_1 . " " . var_2) ; explicit concatenation
Determine version
msgBox(a_ahkVersion)
Warn
The #warn
directive warns about uninitialized variables.
#warn
initializedVariable := "some text"
msgBox(initializedVariable )
msgBox(uninitializedVariable) ; Warning: This variable has not been assigned a value
envGet
msgBox(envGet("USERPROFILE")) ; Show the user's home directory
setKeyDelay
As per the documentation, setKeyDelay
sets the delay that will occur after each keystroke sent by send
and controlSend
.
However, I was unable to figure out exactly what it is supposed to delay.
setKeyDelay 100, 100
send some text
keyWait
;
; D: wait for pressing Down
; T: Specify wait time in seconds
;
res := keyWait("k", "D T2.5")
if (res == 0) {
msgBox("You didn't press k for two and a halve seconds")
return
}
msgBox("You pressed k within two and a halve seconds")
hot keys
;
; Detect the simultaneous pressing of two keys (f and j)
;
f & j::
;
; Note: pressing f (even if not followed by j) won't
; put it into the message queue of the current process, it
; is consumed (or swallowed)
;
msgBox('f and j simultaneously')
return
;
; The following hot key detects if an f is pressed, released and
; followed by a j that is pressed.
;
; As soon as the f is pressed, it will be sent to the receiving
; application. If AHK detects the following j, the f is erased.
;
;
:*:fj::
msgBox('f followed by j')
return
This does not work how it was intended:
;
; Use $ so that the »send('f')« does not
; retrigger the hot key
;
$f::
res := keyWait('j', 'D T0.5')
msgBox('waiting ended, res = ' . res)
if (res == 0) { ; f was not followed by j
send 'f'
return
}
; msgBox('f followed by j')
return
tilde
A hot key can be prepended with a tilde in which case the triggering hot key is passed to the receiving application.
Without tilde, the hotkey is «swallowed» by AutoHotKey.
g::msgBox('g was pressed')
~f::msgBox('f was pressed')
q::exitApp()
dollar sign
A hot key can be prepended with a dollar sign in which case the hot key is not invoked if the body of the hotkey causes the triggering hot key to be sent again.
$a::
msgBox('a was pressed - sleep a second, then send key a')
sleep 1000
send('a')
return
q::exitApp()
Run application and wait for it to become active
;
; Run notepad and store its process
; identifier in notepadPid.
;
run('notepad.exe',,,notepadPid)
;
; Wait until Window with given pid
; becomes active
;
winWait('ahk_pid ' . notepadPid)
;
; Write something into the new window.
;
sendInput ('hello world')
fileAppend
fileAppend
appends some text to an existing file or creates a file with the given line.
fileAppend a line`n, fileAppend.txt
fileAppend another line`n, fileAppend.txt
order of definition
A function can be called even if it is defined later.
f()
f() {
msgBox('f was called')
}
if
num_1 := 10
num_2 := 32
if num_1 + num_2 = 42
msgBox("num_1 + num_2 = 42")
else
msgBox("num_1 + num_2 ≠ 42")
if 1 = 3 {
msgBox("1 = 3 statement 1")
msgBox("1 = 3 statement 2")
}
else if 1 = 2 {
msgBox("1 = 2 statement 1")
msgBox("1 = 2 statement 2")
}
else {
msgBox("else statement 1")
msgBox("else statement 2")
}
Gui
guiCreate
allows to create an «ordinary» window.
g := guiCreate()
g.options(
'+caption ' .
'+border ' .
'+toolWindow ' . ; +toolWindow avoids a taskbar button and an alt-tab menu item
'+alwaysOnTop'
)
g.backColor := 'ffcc88'
g.marginX := 30
g.marginY := 18
g.addText('' , 'HWND of g is ' . g.hwnd) ; First parameter is options.
g.addText('' , 'Enter some text: ')
g.addEdit('vTxt' ) ; Note the v that apparently indicates the name to later retrieve the value entered.
b := g.addButton('default', 'Go')
b.onEvent('click', (*) => clicked(g))
g.show(
'x30 ' .
'y10 '
)
; winSetTransColor ('ffcc88', 'gui.ahk2')
; winSetTransparent( 200 , 'gui.ahk2')
clicked(this) {
;
; submit()
; save the contents of the
; controls into an associative array.
;
ctrls := this.submit()
msgBox('txt = ' . ctrls.txt)
this.destroy()
}
Edit control
An edit control might be used for debugging purposes. The following example writes the mouse position every second:
g := guiCreate()
g.onEvent('close', (*) => exitApp())
edt := g.add('edit',
'r25 ' . ; 25 rows
'w300 ' . ; 300 pixels wide
'vEdt ' . ; Name of edit is Edt
'readonly'
)
; msgBox ('Type(edt) = ' . type(edt)) ; GuiEdit
setTimer 'writeMousePositionIntoEdit', 1000
writeMousePositionIntoEdit() {
coordMode('mouse', 'screen')
mouseGetPos mouseX, mouseY
global edt
edt.text := edt.text . mouseX . ' / ' . mouseY . "`r`n"
}
g.show()
Positioning controls with the section option
When creation a control, the option of a control can include section
. This allows subsequently created controls to be added on the same y coordinate by including ys
in their option.
g := guiCreate()
g.onEvent('close', (*) => exitApp())
; type , options , caption
; ---- , ----------- , -------
txt_1 := g.add('text', 'w50' , 'txt 1' )
txt_2 := g.add('text', 'w50' , 'txt 2' ) ; control added below previous one
txt_3 := g.add('text', 'w50 section', 'txt 3' ) ; control added below previous one - new section started
txt_6 := g.add('text', 'w50' , 'txt 6' ) ; contral added below previous one
txt_4 := g.add('text', 'w50 ys' , 'txt 4' ) ; Using ys to place control with same y as previous section, as left as possible
txt_5 := g.add('text', 'w50 ys' , 'txt 5' ) ; Using ys to place control with same y as previous section, as left as possible
g.show()
g := guiCreate()
g.onEvent('close', (*) => exitApp())
; type , options , caption
; ---- , ----------- , -------
txt_1 := g.add('text', 'w50' , 'txt 1' )
txt_2 := g.add('text', 'w50' , 'txt 2' ) ; control added below previous one
txt_3 := g.add('text', 'w50 section', 'txt 3' ) ; control added below previous one - new section started
txt_6 := g.add('text', 'w50' , 'txt 6' ) ; contral added below previous one
txt_4 := g.add('text', 'w50 ys' , 'txt 4' ) ; Using ys to place control with same y as previous section, as left as possible
txt_5 := g.add('text', 'w50 ys' , 'txt 5' ) ; Using ys to place control with same y as previous section, as left as possible
g.show()
Optional parameters
fnc(p_one, p_two := 42, p_three := 'hello world') {
msgBox('p_one = ' . p_one . '`n' .
'p_two = ' . p_two . '`n' .
'p_three = ' . p_three . '`n')
}
fnc('use all defaults')
;
; In the following call, the second parameter (p_two)
; will have the value 42.
;
fnc('override 3rd param', , 'overridden')
;
; In the following call, the p_three does not
; name a parameter. In fact, within the
; function p_two will have the value 'x'
; and p_three the value 'hello world'
;
fnc('override 3rd param', p_three := 'x')
Function references
saySomething(txt) {
msgBox(txt)
}
funcRef := func('saySomething')
%funcRef%('Calling func ref with %')
funcRef.call('calling func ref with call()')
boundFunc := funcRef.bind('bound parameter')
%boundFunc%()
Closures
globalVar := 'one'
createClosure(txt) {
f() {
msgBox('txt = ' . txt)
}
return func('f')
}
closure_one := createClosure('one')
closure_two := createClosure('two')
closure_one.call()
closure_two.call()
sysGet
;
; sysGet() seems to call the Windows API function GetSystemMetrics().
;
SM_CMONITORS := 80 ; The number of display monitors on a desktop. Counts visible displays only (Compare with EnumDisplayMonitors)
SM_CXHTHUMB := 10 ; Pixel-Width of the thumb box in a horizontal scrool bar.
nofMonitors := sysGet(SM_CMONITORS)
thumbWidth := sysGet(SM_CXHTHUMB )
msgBox('nofMonitors: ' . nofMonitors . '`n' .
'thumbWidth: ' . thumbWidth)
regexReplace
txt := 'The num is 42.'
;
; Find consecutive digits (\d+) and
; replace them with fixed string NUMBER.
;
res := regexReplace(txt, '\d+', 'NUMBER')
msgBox(res) ; The num is NUMBER
;
; Find anything, but non-greedy (.*?), then
; find consecutive digits and capture them
; in parantheses, then match rest. Replace
; with captured digits ($1):
;
num := regexReplace(txt, '.*?(\d+).*', '$1')
msgBox('num = ' . num)
Say something
sapi := comObjCreate('sapi.spVoice')
sapi.speak('Hello world')
winGetList
The following snippet gets all top level(?) Windows and writes their HWND and title into a list view:
g := guiCreate()
g.onEvent('close', (*) => exitApp())
lst := g.add('listView',
'r50 ' . ; 50 rows
'w600 ' . ; 600 pixels wide
'vEdt ' . ; Name of edit is Edt
'readonly',
'Index|HWND|Title'
)
g.show()
allWindows := winGetList()
lst.modifyCol(1, 'integer')
lst.modifyCol(2, 'integer')
for i, hwnd in allWindows {
; edt.text := edt.text . i . ': ' . hwnd . winGetTitle('ahk_id ' . hwnd) . "`r`n"
lst.add(, i, hwnd, winGetTitle('ahk_id ' . hwnd))
}
;
; Autofit column header:
;
lst.modifyCol
winGetList
can also be used to find windows by their title:
;
; WinGetList() by default searches case sensitively.
;
; By setting the match mode to regEx and using i), the
; behaviour can be changed to searching case insenstive.
;
setTitleMatchMode('regEx')
l := winGetList('i)prompt')
for i, hwnd in l {
msgBox(
'hwnd = ' . hwnd .
'`ntitle = ' . winGetTitle ('ahk_id ' . hwnd) .
'`nclass = ' . winGetClass ('ahk_id ' . hwnd) .
'`nProc = ' . winGetProcessName('ahk_id ' . hwnd)
)
}
winGetControls
run('notepad.exe',,,notepadPid) ; Start notepad
winWait('ahk_pid ' . notepadPid) ; Wait until active
sendInput ('hello world') ; Type something
sendInput ('!{f4}')
; sleep(1000)
winTitleSaveConfirmation := 'ahk_pid ' . notepadPid . ' ahk_class #32770'
winWait(winTitleSaveConfirmation)
h := winGetID(winTitleSaveConfirmation)
; msgBox('hwnd = ' . h)
ctrls := winGetControls(winTitleSaveConfirmation)
; ctrls := winGetControls('ahk_id ' . h)
ctrls_hwnd := winGetControlsHwnd(winTitleSaveConfirmation)
; msgBox('type: ' . type(ctrls))
; msgBox(ctrls)
; msgBox('cnts: ' . ctrls.length() . ' / ' . ctrls_hwnd.length())
for i, ctrl in (ctrls) {
msgBox (i . ': ' . ctrl . ': ' . ctrls_hwnd[i] . ' | ' . winGetTitle('ahk_id ' . ctrls_hwnd[i]))
; msgBox (i . ': ' . ctrl . ': ' . ctrls_hwnd[i])
}
Screen dimensions
The width and height of the screen are stored in a_width
and a_height
.
If using multiple monitors, only the dimensions of a single monitor (the primary one?) are reported.
msgBox('Screen dimension: ' . a_screenWidth . ' x ' . a_screenHeight)
Escape characters and Carriage return / line feed
The default
escape character in autohotkey is the back-tick. If followed by an
n
, it produces a
new line (
ASCII 10), if followed by an
r
, it produces a carriage return (ASCII 13).
Thus, it is possible to insert
line breaks into strings.
msgBox('line one`nline two`nAsc(``n) = ' . ord('`n') . '`nord(``r) = ' . ord('`r'))
Calling a function in a dll
dllCall
allows to call a function in a
DLL.
The first argument specifies the DLL and the function, separated by a backslash. In case of a
WinAPI function (more specifically: if the function is in one of the four DLLs
user32.dll,
kernel32.dll,
comctl32.dll or
gdi32.dll), the name of the DLL may be omitted.
The following parameters come in pairs of which the first argument specifies the data type and the second argument the valuer to be passed to the DLL.
Note: the data type
Str
is equivalent to
WStr
and specifies a wide character string. An
ASCII string is specifed by
AStr
. (See also
WinAPI: A and W functions)
MB_YESNO := 4
IDYES := 6
IDNO := 7
yes_or_no := dllCall('MessageBoxW',
'Int', "0" ,
'Str', 'Yes or no?',
'Str', 'Confess!' ,
'Int', MB_YESNO)
if yes_or_no = IDYES
msgBox 'You confessed'
else if yes_or_no = IDNO
msgBox 'You did not confess'
else
msgBox 'Unexpected answer'
Fat arrow / lambda expressions
The =>
operater is apparently known by the expression fat arrow and seems to make lambda expressions possible.
Lambda functions are useful to define callback-functions where they're used.
Apparently, only one statement is possible in a lambda expression.
sumFuncResultOnArray(ary, f) {
s := 0
for idx, itm in ary {
s := s + f.call(itm)
}
return s
}
msgbox (sumFuncResultOnArray([3, 6, 7], (e) => 2*e )) ; 32 (= 2*3 + 2*6 + 2*7 = 6+12+14)
;
; Why does this compile?
;
; Found @ https://www.autohotkey.com/boards/viewtopic.php?t=46150
;
() => {
lambda:
dllCall('one')
dllCall('two')
}
() => [
DllCall( 1 )
DllCall( 2 )
DllCall( 2 )
]
Anonymous event handlers
g := guiCreate()
txt := g.add('text', 'w200 r1')
txt.text := 'Hello!'
;
; This does not work (https://stackoverflow.com/questions/56849007)
;
; g.onEvent('close', (*) => function() {
; msgBox('Going to exit application')
; exitApp()
; })
;
; Any of the following do (https://stackoverflow.com/a/60476070/180275)
;
g.onEvent('close', (*) => (
msgBox('Going to exit application')
exitApp()
))
; g.onEvent('close', (*) => (
; msgBox('Going to exit application'),
; exitApp()
; ))
; g.onEvent('close', (*) => (msgBox('Going to exit application') exitApp()))
; g.onEvent('close', (*) => (msgBox('Going to exit application'), exitApp()))
g.show()