A wishlist 15 years in the making.

Propose new features and changes
User avatar
MC256
Posts: 2
Joined: 19 Dec 2020, 20:03

A wishlist 15 years in the making.

28 Mar 2024, 01:49

I've been using AutoHotkey in-depth for 15 years. Love it. This isn't a complaint!
I wanted to share my personal wishlist that I've collected over this time.
This is my first post. Sorry for the wall of text. It is what it is, I guess.

1. Add an option to send OutputDebug messages to the AutoHotkey GUI

(The GUI is the Window that's accessible via right-click on the tray icon, or if you run ListLines
(etc). The output should update automatically and not require manually refreshing. In my experience,
the primary tool people use for quickly experimenting, troubleshooting, and learning new AHK
features is MsgBox. However, because AutoHotkey has such a focus on interacting with GUI
applications, popping a MsgBox often breaks the flow of the script, confounding troubleshooting by
breaking application focus (for example). Yes, I know about all the available IDEs, fancy editors,
and DbgView. The new AHK LSP extenensions for VSCode are amazing, too (if you haven't tried them, be
sure to!). Although I have so much software development experience, I still often find it a hassle
to break out the debugger-capable software just to troubleshoot or experiment with a few simple
things. Another limitation of MsgBox is that, once you have left two or three "Debug MsgBoxes" in
your script, dismissing them gets annoying while testing (not to mention potentially breaking the
logic due to window focus switching.) So you comment them out. Then you realize you actually wanted
to see what was in one of them, so you comment it back in, and dance around it. And now that we have
refactor->rename in VSCode (which will not rename variables in commented-out code), it exascerbates
this, as your commented-out msgbox debugging statements get out of date with your renames.

I seriously think that the lack of an integrated realtime "print statement viewer" is a major
hinderance for newcomers to AutoHotkey. Every language should have a "Console.WriteLine" that "just
works" easily and simply. I think an integrated OutputDebug() viewer can be that features for
AutoHotkey. (Yes I know about FileWrite "*" to stdout -- most people are not running AHK with a
console attached. Yes I know you can write messages to a file and tail -f it. My point is, I know
all the alternatives), and I still feel the way I do about this. I think having such an integrated
output viewer would support the growth and accessibility of AHK for oldtimers and newcomers alike).

I'm pretty passionate about this being a Good Thing for growing and keeping and helping the AHK
community. I volunteer to help with the implementation if the maintainers are amenable to the idea.

2. Add a built-in RegExEscape

For a tool focused on Windows, we should not be left wondering about how to interpolate a filesystem
path that might contain \E into a \Q regex. e.g. C:\bin\Executable.exe. Yes I know "just double up
the backslashes", but if it's so obvious, why do so many major languages have this convenient
feature? :)
Python
Rust
Java
Go
DotNet
PHP
Ruby
JavaScript (soon™)


3. Add a hotkey modifier for "only act after all keys up" hotkeys

This happens infrequently, but when it does, it's difficult to diagnose and fix, and the
side-effects can be quite annoying. Many people probably just give up. I don't want to get into the
details (partially because I am uncertain of the exact details of all of the ...details). Suffice it
to say that certain apps and certain actions that AutoHotkey can execute, can result in misbehavior
when apps read a "partial" chord that was part of a AutoHotkey hotkey mapping. This might happen if
the focused app switches mid-hotkey, or if you're working with an application that has an unusual
way of reading keyboard inputs. The recommended fix on the forums for a long time has been to
sprinkle KeyWait()s into your hotkey definition. This works, but isn't obvious when/that you need to
do it. And it presents a maintenance headache when you're facing doing this for dozens of different
hotkeys. I was so happy when I saw a very recent post where someone starting working on a general,
reusable solution: viewtopic.php?t=116314 I think this approach
(which is older than the general solution idea) is worthy of promotion to a built-in modifier.

I suggest something similar to the existing "up" modifier:

patterned after: <hotkeyspecifier> up::
new feature : <hotkeyspecifier> allup::

It may be confusing how to implement this in a way that is compatible with all types of prefixes and
combinations. For example key1 & key2, *key, $key, ~key, etc. I propose that "allup" could be
only valid for "basic" hotstrings where the implementation is easy and clear.
For example, it might be invalid to use allup with:

Code: Select all

; nope
a & b allup::

; nope
!+a up::
!+a allup::

; maybe nope? (this is a pattern sometimes used with "up"):
!+a::
!+a allup::

; yep!
!+a allup::
One of my mottos is "don't let the Perfect be the enemy of the Good." That is, if a feature is
useful even though it's too difficult to implement it properly in all edge cases, it's still
worth considering for inclusion, with guard rails around the edge cases.

We don't really know how many people run into problems that "allup" would solve, since many of them
don't know the cause of their problem, and just stop using a hotkey for that task, but judging by
the Google search results on discussion sites for: "KeyWait" ("AutoHotkey" OR "AHK"), I think
it's worth consideration. Promoting this modifier to having first-class support would bring it
closer to people's eyeballs in the docs proper, and so it's something they could try out, without
having to resort to AHK Forum Posts, StackOverflow, subreddits, etc., which many people probably
never attempt anyway.

4. Run()ing apps that don't come to foreground

Consider providing some built-in solutions for apps that tend to not grab focus after Run()ing
them. Windows has some "vaguely documented" behavior that prevents apps from "stealing focus" if it
thinks you're actively working with the foreground app. There are workarounds for this, like sending
alt-down, alt-up or alt-esc, alt-shift-esc before running the app, but it might be nice to have some
built-in "official" solutions. Also, it's becoming increasingly common for apps to be
"single-instance" where the WinWaitActive("ahk_pid " .. pidFromRunWait) approach doesn't work. Here are
some snippets from my Main.ahk to maybe get some ideas going:

Code: Select all

DefaultRunWaitActiveEXEOptions := {
    wd: "",
    waitMainExe: "",
    waitExes: [],
    runOpts: "",
    titleSpec: "",
    timeout: 1.5
}
RunWaitActiveEXE(cmdline, opts := {}) {
    ObjSetBase(opts, DefaultRunWaitActiveEXEOptions)
    parsedCmdLine := ParseCommandLine(cmdline)
    parsedPath := ParsePath(parsedCmdLine.exe)
    exeToFind := parsedPath.filename
    if (opts.waitMainExe) {
        opts.waitExes.InsertAt(1, exeToFind)
    }
    RunWait(cmdline, opts.wd, opts.runOpts, &pid)
    regexen := StrJoin("|", opts.waitExes, (exe) => "\Q" exe "\E")
    titleSpec := opts.titleSpec " ahk_exe " regexen
    if (!WinWait(titleSpec, "", opts.timeout)) {
        msgbox("Timed out waiting for cmd's window: " titleSpec "`n" cmdline)
        return
    }
    WinActivate()
}

DefaultRunWaitActivePIDOptions := {
    wd: "",
    runOpts: "",
    vCenter: 1.0,
    hCenter: 1.0,
    center: false,
    titleSpec: "",
    detectHidden: false,
    timeout: 1.5
}
RunWaitActivePID(cmd, opts := {}) {
    ObjSetBase(opts, DefaultRunWaitActivePIDOptions)
    Run(cmd, opts.wd, opts.runOpts, &OutPID)
    titleSpec := opts.titleSpec " ahk_pid " OutPID
    if (opts.detectHidden) {
        DetectHiddenWindows 1
    }
    if (!WinWait(titleSpec, , opts.timeout)) {
        msgbox("Timed out waiting for cmd's window: " titleSpec "`n" cmd)
        return
    }
    WinActivate(titleSpec)
    if (opts.center) {
        CenterWindow(titleSpec)
        WinShow(titleSpec)
    }
}

RunForeground(cmd, opts := {}) {
    HotKeyWait()
    defaultOpts := {
        wd: "",
        runOpts: "",
    }
    ObjSetBase(opts, defaultOpts)
    ; Send("{Blind}{RAlt down}")
    ; KeyWait("RAlt", "LD")
    ; Send("{Blind}{RAlt up}")
    ; KeyWait("RAlt", "LU")
    Send("!{Esc}")
    Sleep(25)
    Send("!+{Esc}")
    Sleep(25)
    Run(cmd, opts.wd, opts.runOpts, &pid)
}

CenterWindow(WinTitle, VRatio := 1.0, HRatio := 1.0) {
    WinGetPos(, , &Width, &Height, WinTitle)
    newX := (A_ScreenWidth / 2) - (Width / 2)
    newY := (A_ScreenHeight / 2) - (Height / 2)
    newX := IsFloat(HRatio) ? newX * HRatio : newX
    newY := IsFloat(VRatio) ? newY * VRatio : newY
    WinMove(newX, newY, , , WinTitle)
}

ParseCommandLine(cmdline) {
    if (FileExist(cmdline)) {
        return {
            exe: cmdline,
            args: ""
        }
    }
    RegExMatch(cmdline, '(?:^"([^"]+)"|^(\S+))(\s+.*)?$', &Match)
    return {
        exe: Match[0] ? Match[1] : Match[2],
        args: Match.3
    }
}

ParsePath(path) {
    SplitPath(path, &OFilename, &ODirname, &OExt, &OStem, &ODrive)
    return {
        filename: OFilename,
        dirname: ODirname,
        ext: OExt,
        stem: OStem,
        drive: ODrive
    }
}

StrJoin(separator, enumerable, itemTransform := "") {
    result := ""
    for _, value in enumerable {
        value := itemTransform ? itemTransform.call(value) : value
        result .= value . separator
    }
    if (result != "")
        result := SubStr(result, 1, StrLen(result) - StrLen(separator))
    return result
}
5. GitHub Issues Roadmap Discussion ... Thing

Consider a roadmap for opening up GitHub Issues on the main project. My guess is that, one partial
reason for not doing this, at least somewhat, is that the project maintainers don't want to
divert traffic from the forums, since the forums have been such an important part of the history
of AutoHotkey and it's where the amazing community was built of problem solvers and solution-makers
that is one of AutoHotkey's big strengths. However, not having GitHub issues is losing out on an
opportunity to develop a community of contributors to the core runtime codebase. I am only gently
suggesting that the discussion about when / whether to ever do this be opened. I am not complaining
or blaming, just prompting some discussion.

6. Add a built-in AutoReload directive

I've wanted this since about 2 weeks into first using AutoHotkey, which was a veeerryy long time
ago. It automatically invokes Reload when the .ahk file behind the running script is modified
or replaced.

Q: But what does it do for compiled scripts?
A: Nothing.
Q: Isn't that surprising / weird?
A: Not if you read the docs.

Q: What does it do for non-singleinstance scripts?
A: Nothing. Maybe a warning.

Q: Isn't polling the filesystem every X milliseconds intrusive and ugly?
A: That's why ole Billy Boy invented ReadDirectoryChangesW
It blocks until a change to a file happens, then wakes up. Very efficient.
Q: Doesn't that mean spinning up a thread?
A: Threads are cheap these days. Besides, you can just not use it.
Q: What about I/O Completion Ports so we don't have to spin up a thread?
A: Every time I've tried to use I/OCP I've just given up and used blocking calls.

I feel a bit silly that I have to roll my own things like this:

Code: Select all

#Requires AutoHotkey v2.0
#UseHook true
#SingleInstance force

OutputDebug("Loaded " A_ScriptName " " A_AhkVersion " " A_AhkPath " " A_AhkVersion)
TrayTip("Reloaded", A_ScriptName, "iconi")
SetTimer(TimerFunction, 500)

ExitAppWithError(code, msg:="") {
    if (msg) {
        msg := " (" msg ") "
    }
    msg := A_ScriptName " exiting due to error" msg "code=" code " codeDesc=" OSError(code).Message
    OutputDebug(msg)
    MsgBox(msg)
    ExitApp(code)
}

; hashmap of scriptPath to boolean (firstrun)
firstRunMap := Map()
firstRunMap.Set(A_ScriptFullPath, true)

Watch(scriptPath) {
    if (! firstRunMap.Has(scriptPath)) {
        firstRunMap.Set(scriptPath, true)
        Run('"' scriptPath '"')
        FileSetAttrib("-A", scriptPath)
    }
    try {
        attrs := FileGetAttrib(scriptPath)
        if (InStr(attrs, "A")) {
            FileSetAttrib("-A", scriptPath)
            Sleep(250)
            Run('"' scriptPath '"') 
        }
    } catch as e {
        ExitAppWithError(A_LastError, "while monitoring scriptPath=" scriptPath)
    }
}

TimerFunction() {
    Watch(A_ScriptFullPath)
    Watch("C:\bin\ahk\Main_newV2.ahk")
    Watch("c:\bin\ahk\MyUIA.ahk")
}

OnExitFn(ExitReason, ExitCode) {
    OutputDebug("OnExitFn ExitReason=" ExitReason " ExitCode=" ExitCode)
}

OnExit(OnExitFn)
7. Built-in function shortlist wishlist

; For parity with most languages
StrJoin
; For parity with most languages
RegExEscape
; For parity with most languages
RegExEscapeReplacement
; For parity with most languages ([url=https://docs.python.org/3/library/re.html#re.split]cf.])
RegExSplit
; sorts out something you might pass to first arg of Run()
SplitCommandLine(cmdLine, &OVerb, &OExe, &OArgs)

8. Did you make it this far?
Have a 🍪
9. Without skipping very much?
Have two! 🍪 🍪 :)

Thanks to everyone who makes this amazing piece of software for free, that includes all the forum
scripters too! I'm sure I'll be back with another list of asks in (looks at the clock) about ~15
years again.
User avatar
DataLife
Posts: 458
Joined: 29 Sep 2013, 19:52

Re: A wishlist 15 years in the making.

Today, 01:29

I got 2 cookies.

I have had problems troubleshooting because the message box takes focus. Using a tool tip with a pause or sleep does not take focus from from the active window.
Check out my scripts. (MyIpChanger) (ClipBoard Manager) (SavePictureAs)
All my scripts are tested on Windows 10, AutoHotkey 32 bit Ansi unless otherwise stated.

Return to “Wish List”

Who is online

Users browsing this forum: No registered users and 28 guests