One of the easiest and most useful things AutoHotkey can do is allow you to create keyboard shortcuts (hotkeys) that manipulate windows. A script can activate, close, minimize, maximize, restore, hide, show or move almost any window. This is done by calling the appropriate Win function, specifying the window by title or some other criteria:
Run "notepad.exe" WinWait "Untitled - Notepad" WinActivate "Untitled - Notepad" WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2, "Untitled - Notepad"
This example should open a new Notepad window and then move it to fill a portion of the primary screen (¼ of its width and ½ its height). To learn how to try it out, refer to How to Run Example Code.
We won't go into detail about many of the functions for manipulating windows, since there's not much to it. For instance, to minimize a window instead of activating it, replace WinActivate
with WinMinimize
. See Win functions for a list of functions which can manipulate windows or retrieve information.
Most of this tutorial is about ways to identify which window you want to operate on, since that is often the most troublesome part. For instance, there are a number of problems with the example above:
We'll address these issues one at a time, after covering a few basics.
Tip: AutoHotkey comes with a script called Window Spy, which can be used to confirm the title, class and process name of a window. The class and process name are often used when identifying a window by title alone is not feasible. You can find Window Spy in the script's tray menu or the AutoHotkey Dash.
There are a few things to know when specifying a window by title:
See Matching Behaviour for more details.
To refer to the active window, use the letter "A" in place of a window title. For example, this minimizes the active window:
WinMinimize "A"
When WinWait, WinExist, WinActive, WinWaitActive or WinWaitNotActive find a matching window, they set it as the last found window. Most window functions allow the window title (and related parameters) to be omitted, and will in that case default to the last found window. For example:
Run "notepad.exe" WinWait "Untitled - Notepad" WinActivate WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2
This saves repeating the window title, which saves a little of your time, makes the script easier to update if the window title needs to be changed, and might make the code easier to read. It makes the script more reliable by ensuring that it operates on the same window each time, even when there are multiple matching windows, or if the window title changes after the window is "found". It also makes the script execute more efficiently, but not by much.
A window class is a set of attributes that is used as a template to create a window. Often the name of a window's class is related to the app or the purpose of the window. A window's class never changes while the window exists, so we can often use it to identify a window when identifying by title is impractical or impossible.
For example, instead of the window title "Untitled - Notepad", we can use the window's class, which in this case happens to be "Notepad" regardless of the system language:
Run "notepad.exe" WinWait "ahk_class Notepad" WinActivate WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2
A window class is distinguished from a title by using the word "ahk_class" as shown above. To combine multiple criteria, list the window title first. For example: "Untitled ahk_class Notepad"
.
Related: ahk_class
Windows can be identified by the process which created them by using the word "ahk_exe" followed by the name or path of the process. For example:
Run "notepad.exe" WinWait "ahk_exe notepad.exe" WinActivate WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2
Related: ahk_exe
Each process has an ID number which remains unique until the process exits. We can use this to make our Notepad example more reliable by ensuring that it ignores any Notepad window except the one that is created by the new process:
Run "notepad.exe",,, ¬epad_pid WinWait "ahk_pid " notepad_pid WinActivate WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2
We need three commas in a row; two of them are just to skip the unused WorkingDir and Options parameters of the Run function, since the one we want (OutputVarPID) is the fourth parameter.
Ampersand (&) is the reference operator. This is used to pass the notepad_pid
variable to the Run function by reference (in other words, to pass the variable itself instead of its value), allowing the function to assign a new value to the variable. Then notepad_pid
becomes a placeholder for the actual process ID.
The string "ahk_pid " is concatenated with the process ID contained by the notepad_pid variable by simply writing them in sequence, separated by whitespace. The result is a string like "ahk_pid 1040", but the number isn't predictable.
If the new process might create multiple windows, a window title and other criteria can be combined by delimiting them with spaces. The window title must always come first. For example: "Untitled ahk_class Notepad ahk_pid " notepad_pid
.
Related: ahk_pid
Each window has an ID number which remains unique until the window is destroyed. In programming parlance, this is known as a "window handle" or HWND. Although not as convenient as using the last found window, the window's ID can be stored in a variable so that the script can refer to it by a name of your choice, even if the title changes. There can be only one last found window at a time, but you can use as many window IDs as you can make up variable names for (or you can use an array).
A window ID is returned by WinWait, WinExist or WinActive, or can come from some other sources. The Notepad example can be rewritten to take advantage of this:
Run "notepad.exe" notepad_id := WinWait("Untitled - Notepad") WinActivate notepad_id WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2, notepad_id
This assigns the return value of WinWait to the variable "notepad_id". In other words, when WinWait finds the window, it produces the window's ID as its result, and the script then stores this result in the variable. "notepad_id" is just a name that I've made up for this example; you can use whatever variable names make sense to you (within certain constraints).
Notice that I added parentheses around the window title, immediately following the function name. Parentheses can be omitted in function call statements (that is, function calls at the very beginning of the line), but in that case you cannot get the function's return value.
The script can also retain the variable notepad_id
for later use, such as to close or reactivate the window or move it somewhere else.
Related: ahk_id
By default, WinWait will wait indefinitely for a matching window to appear. You can determine whether this has happened by opening the script's main window via the tray icon (unless you've disabled it). The window normally opens on ListLines view by default. If WinWait is still waiting, it will appear at the very bottom of the list of lines, followed by the number of seconds since it began waiting. The number doesn't update unless you select "Refresh" from the View menu.
Try running this example and opening the main window as described above:
WinWait "Untitled - Notpad" ; (intentional typo)
If the script is stuck waiting for a window, you will usually need to exit or reload the script to get it unstuck. To prevent that from happening in the first place (or happening again), you can use the Timeout parameter of WinWait. For example, this will wait at most 5 seconds for the window to appear:
Run "notepad.exe",,, ¬epad_pid if WinWait("ahk_pid " notepad_pid,, 5) { WinActivate WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2 }
The block below the if statement is executed only if WinWait finds a matching window. If it times out, the block is skipped and execution continues after the closing brace (if there is any code after it).
Note that the parentheses after "WinWait" are required when we want to use the function's result in an expression (such as the condition of an if statement). You can think of the function call itself as a substitute for the result of the function. For instance, if WinWait finds a match before timing out, the result is non-zero. if 1
would execute the block below the if statement, whereas if 0
would skip it.
Another way to write it is to return early (in other words, abort) if the wait times out. For example:
Run "notepad.exe",,, ¬epad_pid if !WinWait("ahk_pid " notepad_pid,, 5) return WinActivate WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2
The result is inverted by applying the logical-not operator (!
or not
). If WinWait times out, its result is 0. The result of !0
is 1, so when WinWait times out, the if statement executes the return
.
WinWait's result is actually the ID of the window (as described above) or zero if it timed out. If you also want to refer to the window by ID, you can assign the result to a variable instead of using it directly in the if statement:
Run "notepad.exe",,, ¬epad_pid notepad_id := WinWait("ahk_pid " notepad_pid,, 5) if notepad_id { WinActivate notepad_id WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2, notepad_id }
To avoid repeating the variable name, you can both assign the result to a variable and check that it is non-zero (true) at the same time:
Run "notepad.exe",,, ¬epad_pid if notepad_id := WinWait("ahk_pid " notepad_pid,, 2) { WinActivate notepad_id WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2, notepad_id }
In that case, be careful not to confuse :=
(assignment) with =
or ==
(comparison). For example, if myvar := 0
assigns a new value and gives the same result every time (false), whereas if myvar = 0
compares a previously-assigned value with 0.
When you want to move a window, it is often useful to move or size it relative to its previous position or size, which can be retrieved by using the WinGetPos function. For example, the following set of hotkeys can be used to move the active window by 10 pixels in each direction, by holding RCtrl and pressing the arrow keys:
>^Left:: MoveActiveWindowBy(-10, 0) >^Right:: MoveActiveWindowBy(+10, 0) >^Up:: MoveActiveWindowBy( 0, -10) >^Down:: MoveActiveWindowBy( 0, +10) MoveActiveWindowBy(x, y) { WinExist "A" ; Make the active window the Last Found Window WinGetPos ¤t_x, ¤t_y WinMove current_x + x, current_y + y }
The example defines a function to avoid repeating code several times. x
and y
become placeholders for the two numbers specified in each hotkey. WinGetPos stores the current position in current_x
and current_y
, which we then add to x
and y
.
Simple expressions such as this should look fairly familiar. For more details, see Expressions; but be aware there is a lot of detail that you probably won't need to learn immediately.