How to automate mouse clicks and keystrokes on macOS, Windows, and Linux
Every OS has a built-in way to synthesize a click — they're below, and for a one-off they're all you need. The part that bites comes later: the coordinate you hardcoded points where the UI was, and nothing in the return value says whether the click worked. The OS accepting an event is not the app reacting to one. Whatever tool you use, know what happens when the button moves.
macOS
Built in adjacent: cliclick (one brew install away) or AppleScript through System Events. Both are fine for a click you'll watch happen.
cliclick c:812,440 # click at (812,440) in logical points
osascript -e 'tell application "System Events" to click at {812, 440}'The pitfall: without an Accessibility grant, event posting is a silent no-op — the call succeeds and nothing happens. And the grant attaches to the application that launched the tool (your terminal), not the tool itself. pixelactions proves the grant instead of assuming it, then acts on regions a human marked:
pixelactions doctor --probe # move the cursor 1px, ask the OS, put it back
pixelactions run --session <dir> click:submit verify:done --yesA failed probe raises the system dialog and exits 3. Targets are re-located against a fresh capture before acting, and verify confirms the result from another capture — with the cursor-in-a-corner kill switch armed the whole run.
Windows
Built in: PowerShell can send keystrokes today; a mouse click takes a P/Invoke into user32. For anything past a one-liner, the institution is AutoHotkey (full comparison).
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait("hello{ENTER}") # keystrokes onlyThe pitfalls: per-monitor DPI scaling means the coordinate a screenshot gave you and the coordinate the input API wants can disagree per display — and input aimed at an elevated window is blocked unless the sender is elevated too.
Honesty note: pixelactions runs on Windows today, through SendInput across the whole virtual desktop rather than the primary monitor, with per-monitor DPI awareness declared at startup so a session's pixels and this process's pixels are the same quantity. A point off the desktop is refused by name rather than clamped to the nearest edge. The elevated-window limit above is Windows' own — UIPI, which no permission lifts; doctor reports whether this process is elevated, so the answer is a fact about your machine. Multi-monitor and mixed-DPI layouts are unit-tested but have not been run on real hardware yet.
Linux
Built in on X11: xdotool — the classic answer, still the right one for a one-off.
xdotool mousemove 812 440 click 1 # X11: physical pixels
ydotool click 0xC0 # Wayland: needs the ydotoold daemon + /dev/uinput accessThe pitfall is Wayland: its security design keeps applications from injecting input into each other. xdotool's synthesis reaches only XWayland windows, and ydotool works by becoming a virtual input device — which takes a running daemon and permission on /dev/uinput.
Honesty note: pixelactions runs on both Linux display servers today — X11 through XTEST on the root window, and Wayland through the sanctioned path (portal RemoteDesktop + EIS on GNOME and KDE). Which one it uses is decided from your session at run time, because injecting through XWayland on a Wayland session would reach only X clients and silently miss every native window. One caveat, on Wayland only: there is no way to read the pointer position, so the corner kill switch has nothing to watch and a flow must opt out of it deliberately with failsafe = false. X11 reports the pointer, so the kill switch stays armed there.
When one click isn't enough
A click you fire once answers today's question. If the same interaction matters tomorrow — in a script, a test, an agent — the coordinate needs to survive the UI moving, and the run needs to know whether it worked. That's the pixelactions contract: a human marks regions once in pixelcoords, then every run re-locates each target against a fresh capture, refuses rather than guesses, and reports verified as distinct from executed. The front page shows the whole loop in sixty seconds.
Questions people actually ask
Why does my automated click do nothing on macOS?
Almost always the Accessibility grant. Without it, posting an input event is a silent no-op — the call succeeds and nothing happens. The grant attaches to the application that launched your tool (the terminal), not the tool itself. pixelactions doctor --probe proves the permission empirically: it moves the cursor one pixel, asks the OS where it ended up, and puts it back — raising the system dialog if the grant is missing.
How do I automate mouse clicks on Wayland?
Through the sanctioned path: xdg-desktop-portal RemoteDesktop linked to a ScreenCast session, acting over EIS. You consent once (a screen-share dialog) and the grant is remembered, so later runs do not prompt. pixelactions ships this out of the box on GNOME and KDE — no ydotool daemon, no /dev/uinput permission, no XWayland limitations. One caveat: Wayland exposes no way to ask where the pointer is, so the corner kill switch does not apply and a flow must opt out of it deliberately (failsafe = false).
How can a script tell whether a click actually worked?
The OS accepting an event is not the app reacting to one — a click API returning normally proves nothing. Capture the screen again and check: pixelactions verifies a region against its saved crop after acting and reports verified as distinct from executed, with exit codes a script can gate on: 0 done, 1 a step failed, 2 malformed, 3 refused.
Can I drive desktop automation from my own language?
Yes. pixelactions serve speaks a JSON line protocol on stdin/stdout — one request per line, one response back — so a program in any language owns the loop: branching, retries, data. A complete client is forty lines of stdlib Python, in the docs. There is no embedded interpreter, deliberately.
Can an LLM or AI agent perform desktop actions safely?
pixelactions mcp serves the executor over the Model Context Protocol on stdio: a model gets three tools — plan a set of steps and see every coordinate, find where a region moved to, and act. Acting is off unless a human launched the server with --yes, which is a command-line flag a model cannot pass, so the consent stays with whoever wired the client. A refused or failed step comes back as an ordinary result with ok false rather than a protocol error, so the model reacts to it instead of retrying a tool that posts input. It acts only on regions a human marked in pixelcoords, re-locates each one before touching it, and records every run to a local audit log.
Is there a maintained alternative to PyAutoGUI?
Depends on the job. As of mid-2026, PyAutoGUI has not shipped a release since May 2023. The browser belongs to Playwright and Selenium; where an accessibility tree exists, a11y-first tools are more robust. For coordinate execution against human-marked regions — canvas apps, legacy software, streamed desktops — pixelactions is built for exactly that, on macOS, Windows, and Linux — both X11 and Wayland — today.
Two ways in
cargo install pixelactionsOr skip the toolchain: prebuilt binaries — download, unpack, run.
Rust 1.88+ for the cargo route. pixelactions drives the pixelcoords binary for capture-time work, and needs 0.7.6 or newer — doctor refuses an older one rather than failing mid-run. Install both:
cargo install pixelcoords pixelactionsmacOS asks for an Accessibility grant on first run. The grant attaches to the terminal that launches pixelactions, not the binary — doctor --probe proves the grant instead of assuming it.
Windows asks for nothing, and has nothing to install. What it has instead is a limit no permission lifts: a process at medium integrity cannot send input to an elevated window, the UAC dialog, or the login screen. doctor reports which of the two you are.
Linux/Wayland asks you to share a screen once, and remembers it; Linux/X11 asks nothing, because X11 has nothing to ask. Building on Linux needs the xkbcommon headers — libxkbcommon-dev and pkg-config.