Thoughts for v2.0

This document was originally about brainstorming and planning changes that align with the goals of v2; mostly compatibility-breaking changes that cannot be made in v1. That stage of development has been over for some time.

These are various ideas that I (the developer, Lexikos) have collected over time, and might implement at some point in the future. There are also more elsewhere that I haven't "translated" yet.

Most of my notes are very terse and might not make sense without context. Some of those that I haven't categorized or elaborated are listed below:

Quick access to frequently-used scripts

Running AutoHotkey.exe or the Start menu shortcut used to launch A_MyDocuments "\AutoHotkey.ahk", so that was sometimes used to launch (or contain) the user's main always-running scripts. There are a number of substitutes, but it would be helpful if the Dash or onboarding facilitated these:

The Explorer context menu for .ahk files could include an option to pin the script. Pinned scripts are accessible via AutoHotkey's jump list, which is accessible if AutoHotkey is pinned to the taskbar or (in Windows 7-10) the Start menu.

Dragging a file to the taskbar probably works for pinning on Windows 7-10, but currently not Windows 11.

The jump list's frequent/recent and pinned scripts might also be listed directly on the Dash.

Onboarding

Some context for this is explained in the UX topic.

When a new user installs the program, the Dash opens and they are offered links to Tutorials. We could go further and actually help them to set up appropriate tools; e.g. automate download and installation of an editor that supports v2.

Documentation

The documentation needs more standalone tutorials, especially for concepts that are more heavily utilized in v2, such as objects and dealing with errors.

Errors and warnings

Currently all v2 warnings are raised at load time, with one warning per variable or unreachable line, and (for the "MsgBox" setting) one dialog per warning. Each dialog must be dismissed individually, unless the user exits the script. It would probably be better to collect all warnings during load and

Make the Help button more context-aware. It should open documentation either for the given error/warning type or for the function which raised it. Documentation can be improved with common causes of these errors and how to deal with them.

Add some kind of hook or integration of error dialogs with an external debugger or error-handling script, such as a button to launch a script for inspecting objects and local variables further up the call stack (ListVars is limited to the current function).

Dynamic variables

Dynamic creation of variables is currently disabled to ensure consistent variable scope/resolution. Although I'm not sure I've ever seen a case where dynamically creating variables was the best solution to a problem (given other options, such as Array and Map), users migrating from v1 miss the capability. It could be enabled by:

Unset

obj.prop := unset is allowed. However, a call to a property setter won't typically work because the value parameter is mandatory, so a new accessor function might be warranted. Helgef suggested obj.DefineProp('prop', {unset: ...}). Properties currently aren't supported by var? or ?? or IsSet anyway, so this should probably be deferred.

x.y? and x.y ?? z should be implemented at some point. This could be done by introducing a flag for the invocation (stored in SYM_FUNC's callsite) which permits the operation to return unset. (Internal: Objects already return INVOKE_NOT_HANDLED when a property isn't found, and the caller potentially throws a PropertyError; the flag would make them return SYM_MISSING instead.)

x[z]? and x.y[z]? should also be implemented, but require additional work since current implementations for Map and Array throw UnsetItemError() directly. (Internal: They would need to return a status like INVOKE_NOT_HANDLED, but distinct from that so x.y[z] is clear about whether x.y or x.y[z] is unset.)

f()? could also be implemented. This leads to some questions. To be useful, return unset must be supported. The default return value of "" should be reconsidered, but that probably needs to be deferred.

Objects

Provide a way to set an object to not call __delete (but still allow it to be deleted). Simplifies prototypes, for instance.

Provide a simple way to prevent new properties from being added to an object. (I don't remember why I put this on the list; currently it seems unnecessary since it can be done with __set. Maybe for efficiency. Maybe a more convenient option would improve the odds of it actually being used, making scripts more robust.)

Func properties

Let MinParams/MaxParams be set for BoundFunc to define an additional restriction on parameters (normally it's left for the target function, but that could be variadic). Maybe user-defined variadic functions as well, closures in particular.

Currently BoundFunc has MaxParams = 0, but it is essentially variadic (any restriction is enforced by the function that it calls; i.e. the function bound within). Having it calculate an initial MaxParams value from that of the target function would be more useful. (Not feasible or desirable for ObjBindMethod, only Func.Bind.)

BoundFunc is currently implemented as a target object, flags, (optional) property name and an Array of parameters. Allowing access to these as properties could improve flexibility and help debugging. I think I didn't do this in v1 because I was considering replacing the array with something more efficient (but v2 Array is more efficient than the v1 object/array).

RegRead/RegWrite

These functions were designed for AutoIt2, so REG_BINARY is passed/returned as hex. Buffer should probably be used, although changing the return value of RegRead might have to be deferred.

The source code uses the constant REG_NONE as meaning that the value type is invalid, but in fact, REG_NONE is a valid value type which can have an arbitrary binary value (although it's usually seen with zero bytes). However, there's probably no actual need to ever actually use REG_NONE, since anything expecting such a "value" probably won't be checking its type or reading it. If support is ever added, A_LoopRegType probably needs to continue reporting "" for these.

Catch and For loop variables

Currently Catch and For loop variables are "backed up" prior to executing the body of the statement and restored afterward, as an approximation of making the variable "local" to the statement. Ideally they would be actually local and independent of any variable with the same name outside the block. The difference can be observed when the block calls a function which references the same variable (perhaps it is a nested function or the variable is global).

Any kind of hacks to just make the variable names scoped to the block won't play well with closures, so this is being deferred until proper block-scoped variables can be implemented.

Misc

Gui.AddControl and GuiControl.Add currently require an array, but it would be trivial to support any enumerable object.

Maybe change WinGetID to WinGetHwnd for consistency with various other things. On the other hand, I don't want it to be inconsistent with ahk_id, and don't want to change that to ahk_hwnd. Reconsider this if the ahk_ WinTitle scheme is ever updated or removed.

FileGetShortcut and FileCreateShortcut have too many parameters, but I haven't decided whether to change them or just replace both with a function to get a ShellLinkObject (through COM), either built-in or as a trivial script. ShellLinkObject has all the same capabilities plus a Hotkey property and maybe others.

SplitPath could return an object when no output parameters are passed. Property names are probably easier to remember than parameter order, and easier to read and write than counting commas. This should have no negative impact on compatibility if added later, presuming scripts do not rely on SplitPath returning "".

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.

Exclusions

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 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).

Some other ideas might supersede all considerations about ExcludeTitle/ExcludeText:

Settings

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.

Expression Operators

Current thoughts as of 2020: This functionality is probably better off as methods of certain objects, such as arr.IndexOf(value) instead of value in arr.

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: https://autohotkey.com/boards/viewtopic.php?f=37&t=3847

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.)

Binary Strings

Strings beginning with Chr(0) are currently false, because the boolean checks assume a zero-terminated string rather than a binary counted one. Other cases may exist where the string is assumed to be empty.

Any operators or functions which expect a number will read only up to the first binary zero, and therefore may consider a string containing embedded nulls (possibly followed by other non-numeric characters) to be numeric.

In theory, all strings containing binary zero should be considered non-empty and non-numeric.

Deferred because: proper, consistent support for null characters in strings seems to be more work than it is worth (cost outweighs benefit).

As a compromise, perhaps support for strings containing null characters could be improved in specific cases, such as Loop Parse.

AltGr

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 (hotkeys with ^!key would be triggered by AltGr+key). 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).

Deferred because: I don't recall seeing any related issues, and I don't use a layout with AltGr. The possibility of treating AltGr as just RAlt (or treating the LCtrl as non-physical/ignored) can be explored after v2.0, perhaps as an optional mode.

Thread Sub-commands

The Thread function still has sub-commands.

Deferred because: It's used more rarely than WinSet, Control, Drive, etc. so leaving it with sub-commands (extra quote marks) doesn't seem like an issue.

Control Functions

We seem to lack functions for getting/setting the value of external controls of some types, such as Slider, UpDown, MonthCal (I have not tested ControlSetText on these).

ListView

ListViewGetContent has usage virtually identical to ControlGet List (when used on a ListView). It would be ideal to provide a similar interface for both external and script-made ListViews. Due to complexity and lack of interest (by me), this is being deferred indefinitely.

ImageSearch Optimizations

Some optimizations to the algorithm used by ImageSearch were developed in collaboration with Rseding91 in 2012. The new algorithm also supported variable transparency in the "needle" image, and an option to control search direction (which PixelSearch now handles by changing the order of the X/Y parameters). I lost motivation to complete the project because of the need for testing to ensure all changes in result are acceptable, combined with the fact that I don't actually use ImageSearch.

Another issue is that systems with the Desktop Window Manager enabled became the norm, and that creates a massive bottleneck for ImageSearch. When it retrieves image data from the screen, the DWM has to basically render a composite image from all of the visible window surfaces. Retrieving image data from a window is faster and has the side benefit that it can usually retrieve data that is visually obscured by other windows.

At some point I want to implement a new interface for image searching and integrate the optimized algorithm. The new interface might support creating bitmaps from a window, a portion of the screen, a file, or a GDI bitmap, and then searching for one bitmap within another bitmap, or searching for a pixel within a bitmap. It may also support calculating a hash of the bitmap data, such as for detecting when a portion of the screen is updated. A mask or alpha channel could be applied to bitmap data in order to detect changes in irregular portions of the screen.

However, as I have many ideas that are more widely beneficial, and I don't use ImageSearch, I probably won't work on this in the near future.