Skip to content

v0.1.0 — AutoItX's API in Rust, on Windows and macOS

Choose a tag to compare

@iagodpassos iagodpassos released this 28 Jul 00:07
· 3 commits to main since this release

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"

📦 crates.io · 📖 docs.rs

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 time

keys! 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::runningApplications returns 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_active read AXMain, which the focused window reports as false while AXFocused is true — Electron apps and VM bridges never set it.
  • visible_frame returned None off the main thread, which is where automation runs, so win_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. ☕