Thoughts for v2.0

Note that this document is not complete.

TODO (misc)

Validation of object type passed to Gui.AddControl or GuiControl.Add, and/or support for any enumerable object.


Detecting mispelled function names at load time is a useful feature of the language, but currently only applies to direct calls. References such as Func("name") or "name", if mispelled, will only be detected when the script attempts to call them (which might not be where they are referenced).

Func("name") is optimized (in v2) to resolve at load time, but cannot raise a load time error when the function does not exist because it is not an error. For instance, name can be defined in an optional #include *i, and Func is how it would be resolved to be called. If it raised a load time error, this would not work:

if f := Func("name")  ; ← load time error

Nor would this:

if IsFunc("name")
    MyRegisterEvent(Func("name"))  ; ← load time error

The most obvious solution is to add new syntax for a function reference which would be caught as an error at load time if the function does not exist.



Built-in variables (except A_StringCaseSense) and SoundGet have been changed to return 1 or 0 instead of "On" or "Off", since integers work better with boolean expressions. Also, passing a string is less convenient than before due to the quote marks. There's some value in keeping scripts consistent, so perhaps "On" and "Off" should be removed entirely. However, there are cases where it might be best to keep them:

The value -1 is currently allowed in place of "Toggle" for convenience (it is shorter), but it probably isn't intuitive to any who aren't familiar with the convention. Keeping the string seems worthwhile, and in that case, maybe the -1 value shouldn't be added after all.

Adding on and off as aliases of true and false (which just return 1 and 0) might improve readability and further obviate the need for strings "On" and "Off".

A_StringCaseSense might be better with strings, as "Off" and "Locale" both mean case-insensitive; i.e. using 0, 1 and "Locale" would make if A_StringCaseSense true if case-sensitive or case-insensitive using the locale method. 0 could instead mean case-sensitive, but that might be confusing.


When RAlt is pressed or sent, if the active window's keyboard layout has AltGr, the system generates LCtrl-down and LCtrl-up. (It also generates LCtrl-up when switching between keyboard layouts if the previous layout had AltGr, even if the new one also has AltGr.) This has some potentially undesirable consequences:

It is technically possible to identify AltGr's fake LCtrl by utilizing the little-known fact that KBDLLHOOKSTRUCT::scanCode has the 0x200 bit set for the fake LCtrl events. (KeyHistory doesn't show this because the scan code is truncated to 1 byte [and then 0x100 is added if the 0xE0 flag is set].)

It would only take a few lines of code to mark the fake LCtrl events as "ignored", which would avoid all of the "consequences" listed above, except that it would introduce inconsistency between "hook" hotkeys (which would treat AltGr as RAlt) and "reg" hotkeys (which would treat AltGr as Ctrl+Alt). AltGr would become just RAlt:: or >! (both of which always use the hook) and <^>! would mean LCtrl+AltGr. When only AltGr is down, GetKeyState would report LCtrl as physically up but logically down (reflecting the fact that the user hasn't physically pressed LCtrl, but the active window considers Ctrl to be down).

AutoHotkey uses some "heuristics" to detect the presence of AltGr during Send. This might be simplified by relying on the 0x200 bit. On the other hand, maybe the "heuristics" can be removed to reduce code size, since AltGr detection was improved (at least for Unicode builds) in commit 92c5a76e (v1.1.27.01). Testing has already shown the new method to be 100% accurate (which is better than before) for 203 standard layouts on Windows 10, but it would need testing on older OSes.


Commands with sub-commands

Some of the new functions which replace sub-commands return 1 on success and 0 on failure (in addition to setting ErrorLevel), while others only set ErrorLevel. This should be rectified.

There are still some issues with the Control/ControlGet functions, which may or may not be resolved in v2.0 depending on "developer motivation":

The Thread command still has sub-commands.

Click / MouseClick

Rename MouseClickDrag, perhaps to MouseDrag?

MouseClick has been superseded by Click. Should MouseClick be removed? Should Click be renamed to MouseClick, for consistency with MouseMove?


For convenience, it should be possible to pass a Gui object to a WinTitle parameter instead of "ahk_id " Gui.Hwnd. Current technical limitations make this infeasible for commands which have not yet been internally converted to functions.

FileGetVersion: Some executables (including AutoHotkey) have a version string which has the same numeric values but different (more correct) formatting than what FileGetVersion returns, since it is based on a different (pure numeric) field in the PE header. If there is potential for built-in support for version strings to be added, FileGetVersion should probably default to the more correct string.

OnMessage: Someone (I was unable to find the post) suggested the parameter order for the callback functions be changed, possibly because hwnd is needed more often due to the removal of A_Gui.

SoundGet/SoundSet: The component and control types are currently based on the Mixer API, which is only used on 2000 and XP. These should be updated to closer match the capabilities of the Vista+ API. Support for SoundGet/SoundSet on 2000 and XP may be dropped entirely to simplify this.

WinTitle and Window Groups

Background info: Each window group consists of a list of window specs. A window spec in v1 consists of WinTitle, WinText, ExcludeTitle and ExcludeText. Window specs are evaluated in the order they were added to the group. For convenience, "rule" means "window spec" in the text below.


Problems in v1:

The most likely solution is to introduce "negative" or "exclusive" rules. Rules are evaluated in the order they were added to the group, ending with the first rule (positive or negative) that matches the window. This provides the following benefits:

There are different ways this could be achieved:

Chris' comments:

ExcludeTitle doesn't support ahk_pid and similar things. All it does is what the help file says: "Windows whose titles include this value will not be considered." The reason for this is that the added code size doesn't seem justified given how rarely ExcludeTitle is used.

In light of the above, the second option above seems preferable.

In either case, a negative rule has no effect if there aren't any positive rules following it, because the absence of any subsequent positive rules means that the window won't be a match anyway. Therefore, for convenience, a group could match all windows by default if the last rule is negative. That way, if group N contains only negative rules, ahk_group N matches all windows except those excluded by the group (instead of always matching nothing at all).


An oft-requested feature is to allow global settings such as SetTitleMatchMode, DetectHiddenWindows and DetectHiddenText to be overruled temporarily for a given WinTitle or command. At the moment, the most likely form it could take is Title ahk_mode 3 ahk_dhw 1. Implementing this in a later release is unlikely to affect existing scripts; therefore this is not currently planned for v2.0.

Binary Data

It seems best to deprecate or remove the use of VarSetCapacity and &var for binary data, in favour of the new Buffer object.

Separating values from variables enables greater flexibility and avoids duplication, such as VarSetCapacity and Obj.SetCapacity, or &var and Obj.GetAddress(Name).

If not removed, VarSetCapacity should work with characters rather than bytes. Byte size is less meaningful when the data is always a UTF-16 string.

Removing &address-of or replacing it with more specific functions (separating address of string, address of object, and address of read/write buffer) might improve clarity and encourage patterns that are less error-prone.

Boolean: Strings beginning with Chr(0) are currently false, because the boolean checks assume a zero-terminated string rather than a binary counted one. It may be better to treat such strings as true. On the other hand, relative operators (< > etc.) currently only compare up to the null-terminator. Must take care that it is consistent with numeric treatment of strings with chr(0); i.e. if ("0" chr(0)) == 0, then it must be false.

Expression Operators

Although in and contains are already reserved in v2-alpha (so adding them later won't break anything), v2 might seem incomplete without them, since users converting existing scripts from v1 (or just used to using v1) will need to find some alternative that works in v2. They should probably be implemented before the v2.0 release, and they should be updated to be more modern (allow arrays).

Current usage: if var [not] in/contains MatchList. MatchList is a comma-delimited list. Var contains a string.

It seems logical to allow an array as a direct substitute for MatchList. Someone suggested that the new operators should never accept a comma-delimited list, but in that case a string would be interpreted one of the following ways:

  1. An error.
  2. Equivalent to a single-element array. That would mean:
  3. Equivalent to an array of the string's individual characters. That would mean:
  4. Having different meaning depending on the type of operand. Seems confusing. For example:

If arrays are supported on the left side of contains, how would that work? Traditionally the difference between in and contains is that in does a direct comparison of the value while contains does a substring match. Would arr1 contains arr2 search for each element of arr2 as a substring of each element of arr1? Or check for the existence of any common values between the two? Or all? Or check whether the exact sequence of values in arr2 exists within arr1 as a subset? The last option would be analogous to InStr(str1, str2), but not str1 contains str2.

Some (not very relevant) discussion here:

between probably won't be implemented at any stage. An alternative could be to allow operator chaining as in Python and Perl 6, where x < y < z is equivalent to (x < y) and (y < z). However, that would mean that parentheses would change the meaning of < (which doesn't seem right), such as in x < y < z vs (x < y) < z, because the latter case would compare the result of x < y (true or false) to z. Some users already try x < y < z and wonder at the result, so if it isn't implemented, maybe chained operators should be detected and cause a warning/error. (But if chained operators would be complicated to detect, cost vs benefit seems to be in favour of implementing chaining.)


new MyClass[a,b,c] could be reserved as a more convenient way to initialize a specialised array/collection type (new MyClass{a:b} is already "reserved" by way of being treated as a syntax error). Currently MyClass[a,b,c] could be a class to instantiate, but it's unlikely to have been used much (if at all), and is inconsistent with the v1 policy of requiring a class name after the new operator. v1 treats new x[y](z) as new c1 where c1 = x[y](z), while v2-a076 treats it as new c2(z) where c2 = x[y].

SafeArray objects should perhaps gain a .Length property for consistency with Array. Maybe not, since the need to use .MaxIndex() may serve as a reminder that SafeArrays are often zero-based.

ComObjConnect() events currently dereference ByRef VARIANTs automatically (since some languages pass this whenever a variable is passed from script), which prevents the function from being able to pass back a value in some cases. We could either a) pass a ByRef wrapper allowing assignments such as param[] := value or b) automatically pass a variable and update the VARIANT after the function is called. Other (i.e. non-VARIANT) ByRef combinations are not dereferenced, and param[] := value is already supported in v1.1.17+.


Make base a reserved word in class methods. Currently it is given special meaning when followed by . or [, except when the variable base has been assigned a value. Reserving the word simplifies the language and may improve performance. On the other hand, perhaps it would be clearer to use super.method()/super.prop[idx] for this and leave this.base as the property which returns this object's base object.