Releases: iagodpassos/autoitx
Release list
v0.1.2 — wait_for_any
wait_for_any — for actions with more than one outcome
An action in a legacy application rarely has one outcome. Committing a form
either closes it, raises an error dialog, or raises a different dialog meaning
something else entirely — and which one happened is the only way to know what
the application actually did.
AutoIt waits on one window at a time, so that race gets written by hand:
while (WinExists("Order Selection")
&& !WinExists("[CLASS:ui60Modal_W32]")
&& !WinExists("Blocked")) { Thread.Sleep(300); }That loop is in production twice, in two separate robots, byte for byte the
same. Neither copy has a timeout: if none of the three ever happens, the
robot hangs until someone notices.
match ai.wait_for_any(&[
(&orders, WinCondition::Gone), // the form closed: it saved
(&modal, WinCondition::Exists), // an error came up instead
(&blocked, WinCondition::Exists), // ... or a block notice did
], Some(Duration::from_secs(60)))? {
Some(0) => saved(),
Some(1) => report_error()?,
Some(2) => report_blocked()?,
_ => return Err(wedged()),
}Taking the timeout as a parameter is what removes the failure mode — there is
no version of this call that can hang. Returning the index rather than a bool is
the point: the caller has to branch on which outcome occurred.
WinCondition has four variants — Exists, Gone, Active, NotActive —
mirroring AutoIt's four single-window waits, so racing them against each other
needs no vocabulary you do not already have.
Design notes
- It is in the core, not
recipes. That module is for portable intent
whose mechanism differs per platform. This pollswin_exists/win_active
identically on both, and filing it there would have meant weakening the
module's own charter.autoitx-sysand its 1:1AU3_*surface are untouched. - Order is significant, and documented. Watches are evaluated in slice order
and the lower index wins a tie. Without a stated rule the return value would
depend on polling luck. - Polling runs at
WinWaitDelay(250 ms by default) — the option that
already paces the other waits, not a fresh literal. - An empty slice returns
Ok(None)at once. Blocking would hang forever
when the timeout isNone, so the degenerate case is the one worth
special-casing — and the one with a test, because getting it wrong hangs CI
rather than failing it. Ok(None)on timeout mirrorswin_waitreturningOk(false), rather
than anErr(Timeout)that reads as more expressive but less consistent.
Compatibility
Purely additive. Anyone on autoitx = "0.1" picks this up without editing a
manifest — hence 0.1.2 rather than 0.2.0.
autoitx 0.1.1
No library changes. Documentation and crates.io metadata only — upgrading from 0.1.0 requires nothing.
[dependencies]
autoitx = "0.1"Ten dead links on crates.io
crates.io does not serve the README from this repository. It renders the markdown itself and rewrites relative links against a base it derives on its own — and for this workspace that base was wrong twice over.
autoitx/Cargo.toml points at ../README.md, and the rewrite prepends the package directory regardless, so a link to the examples folder shipped as:
https://github.com/iagodpassos/autoitx/blob/HEAD/autoitx/autoitx/examples
^^^^^^^ ^^^^^^^
Relative links are also rewritten to /blob/ unconditionally, which cannot address a directory even with the correct base.
Every one of those links resolved correctly on GitHub, which is why 0.1.0 shipped with them. Heading anchors had the mirror-image problem: comrak and GitHub both id headings user-content-<slug>, and only GitHub ships the JavaScript that additionally resolves the bare #<slug>, so #platform-support worked in one place and not the other.
Every link is now absolute and every anchor prefixed. The MSRV badge also pointed at the Rust blog index rather than the 1.85 release post — the URL it used answered 200, but only after redirecting to a stub, the blog having changed its URL scheme.
A check, since none of this was visible before publishing
scripts/check-readme-links.py now runs in the lint job. It rejects relative links, verifies that every link into this repository names a path that actually exists with the right /blob/ or /tree/, requires the portable anchor form, and catches reference-style links that were defined but never used — those render as literal bracketed text instead of failing.
Pointed at the 0.1.0 README, it reports all fourteen.
Its --network mode fetches every outbound URL and is deliberately kept out of CI, where someone else's downtime would fail the build. Two things it needs that are worth knowing if you write something similar: crates.io answers 404 for a crate that plainly exists unless you send Accept: text/html, and 403 if you send no User-Agent at all.
macOS in the metadata
The crate supported macOS natively from 0.1.0, but said so nowhere crates.io indexes. Added macos to the keywords, which cost gui — five is the maximum, and gui is browsed by people building interfaces rather than driving someone else's, while macos next to windows is the one thing separating this from every other AutoIt binding.
Categories gained os::windows-apis and os::macos-apis beside the generic os.
Binaries
diagnose for Windows and both Mac architectures is attached below, unchanged in behaviour from 0.1.0. Run it first when something is wrong: it prints the DLL search order with a mark against each candidate on Windows, and which privacy grants the binary holds on macOS. The macOS builds are ad-hoc signed; verify with SHA256SUMS.txt.
📦 crates.io · 📖 docs.rs · ☕ Buy me a coffee
v0.1.0 — AutoItX's API in Rust, on Windows and macOS
AutoItX's API, in Rust, on Windows and macOS.
autoitx drives other applications' user interfaces — keystrokes, mouse, clipboard, windows, processes. The API is modeled on AutoItX, so existing AutoIt automation ports over almost mechanically. Unlike AutoItX, it also runs natively on macOS.
[dependencies]
autoitx = "0.1"Two backends, one API
| Windows | macOS | |
|---|---|---|
| Mechanism | AutoItX3_x64.dll, loaded at runtime |
Native — Accessibility, CGEvent, NSPasteboard |
| Extra install | The DLL (ships with AutoIt) | Nothing |
All 117 AU3_* entry points are reachable, and AutoIt::raw() gets at the ones without a safe wrapper.
Two bugs it makes impossible
Keystroke injection. Send interprets {}!+^#, so interpolating a name, a price, or a password into a send string lets that data execute as key commands. A password containing { is a live bug, not a theoretical one.
ai.send(Keys::text(&password))?; // escaped, always
ai.send(keys!("{CTRLDOWN}c{CTRLUP}"))?; // validated at compile timekeys! runs a const fn validator, so a typo like {CTRLDWN} fails the build — no proc macro, no build-time cost.
The clipboard race. Reading a screen you cannot query means select, copy, read — and the usual way to know when the copy landed is a sentinel:
AutoItX.ClipPut("NO-VALUE");
AutoItX.Send("^c");
if (AutoItX.ClipGet() == "NO-VALUE") { /* assume nothing copied */ }Three failure modes, all of which happen: the cell genuinely contains the sentinel, the copy rewrites the value that was already there so nothing appears to change, or the copy never happened and the stale clipboard is returned as this field's value.
recipes::read_screen_text waits on the OS clipboard sequence number instead — GetClipboardSequenceNumber on Windows, NSPasteboard.changeCount on macOS. It cannot collide with a real value, it notices identical rewrites, and a copy that never happened is reported rather than papered over.
Platform gaps are compile errors
What exists on only one platform lives in ext::windows / ext::macos. Using one from the wrong platform fails to build — it is not an Err(Unsupported) discovered at runtime, by which point a robot has half-completed a transaction in someone's ERP.
Where both platforms can do the same thing by different means, recipes gives one call. wait_until_idle polls the cursor shape on Windows (the idiom hand-written automation spells cursor == 2 || cursor == 5, here with the timeout that version invariably lacks) and probes the Accessibility messaging timeout on macOS. Your code says wait_until_idle.
Develop on a Mac, ship to Windows
The DLL is loaded at runtime, so there is no link-time Windows dependency and cargo check/clippy never invoke a linker. The entire Windows backend is type-checked, linted, and unit-tested from macOS — against a mock DLL that exercises all 117 signatures, UTF-16 marshalling, output-buffer growth, and the AU3_error protocol. A Windows machine is needed only to observe real behaviour, never to compile.
Verified against reality
The Windows backend's failure semantics were measured, not assumed. Calling each function against a window that exists and one that does not revealed that AutoItX has no single convention: some report failure in the return value, some in the error flag, some not at all, and WinGetProcess returns 0xFFFFFFFF rather than 0. WinGetPos's integer return is inverted. That table now sits at the top of backend/dll.rs, because the information exists nowhere else — the published documentation does not describe it.
The macOS backend has a live suite driving real applications, which caught four bugs a mock could never have found:
NSWorkspace::runningApplicationsreturns a list kept current by run-loop notifications, and an automation binary never runs a run loop. Applications launched after startup were invisible for the life of the process, so "launch the app, wait for its window" waited forever.{CTRLDOWN}c{CTRLUP}posted a correctly flagged event that applications ignored: macOS resolves a key equivalent from the virtual key code, and the Unicode-string path used for literal text carries key code 0.is_activereadAXMain, which the focused window reports asfalsewhileAXFocusedistrue— Electron apps and VM bridges never set it.visible_framereturnedNoneoff the main thread, which is where automation runs, sowin_set_state(Maximize)silently did nothing.
Getting started
cargo run --example diagnose is the first thing to run when something is wrong. It prints the DLL search order with a mark against each candidate on Windows, and which privacy grants the binary holds on macOS — the two failures that cost the most time, and both of which currently surface as something else entirely.
It is attached below as a prebuilt binary for Windows and both Mac architectures, so you can get an answer without installing a Rust toolchain. The macOS builds are ad-hoc signed; verify with SHA256SUMS.txt.
Eight examples ship in the repo, including a side-by-side porting guide from AutoItX.Dotnet.
Notes
MSRV 1.85. Licensed MIT OR Apache-2.0. 64-bit only — the DLL is x64, though Windows on ARM works fine under emulation.
The AutoItX3 DLL is not redistributed with this crate: AutoIt is freeware under a EULA, not an open-source licence. This project is not affiliated with, endorsed by, or sponsored by AutoIt Consulting Ltd. See NOTICE.
If it saves you time, you can buy me a coffee. ☕