Skip to main content

pctr

PCTR — Personal Computer Tactile Response

Element-based UI automation CLI for AI agents. Windows (UIA) and Linux X11 (AT-SPI2).

Instead of screenshotting and guessing pixel coordinates, pctr finds controls by their name / control type / automation id and acts on them. Window moves, DPI changes, and layout shifts don't break your script.

On Windows it is built on pywinauto (UI Automation) with pyautogui / pydirectinput for raw mouse and keyboard. On Linux it uses AT-SPI2 (over the accessibility D-Bus, no pyatspi needed), XTest (python-xlib) for input, mss/compositor capture for screenshots, and rapidocr/tesseract for OCR. On top of that it offers OCR and YOLO-World open-vocabulary detection for text and objects that aren't exposed as controls.

pctr driving by element name: enumerating windows, mapping virtual desktops, dumping the control tree, filling a field, then reading it back with OCR

Driven entirely by pctr itself - list windows, map virtual desktops, dump a control tree, set a field, read it back with OCR, screenshot it, and YOLO-World detect. Recorded with OBS, which pctr also piloted.

Install

Windows

pip install "pctr[windows]"   # UIA + input
pip install "pctr[vision]"    # + ultralytics, for `pctr look`
pip install "pctr[mcp]"       # + MCP SDK, for the MCP server
pip install "pctr[pad]"       # + vgamepad, for `pctr pad` (needs ViGEmBus)
pip install "pctr[serial]"    # + pyserial, for `pctr hid` / the serial-hid backend
pip install "pctr[all]"       # everything

Linux (X11 / XWayland)

pip install "pctr[linux]"     # python-xlib + dbus-next + rapidocr

Requires the accessibility bus to be running (at-spi2-core; on most desktops it already is). Element targeting uses AT-SPI2, so apps must expose accessibility (GTK/Qt/Electron do; many X11 toolkits and games do not). Screenshots use mss; under Wayland it falls back to spectacle (KDE) or grim. Global synthetic input goes through XTest, which works for X11/XWayland apps — native Wayland clients are not reachable (protocol design). Use --backend auto|windows|x11|wayland to pick explicitly.

Both

pip install "pctr[ocr]"       # rapidocr + pytesseract

MCP server

pctr ships a Model Context Protocol server so MCP clients (Claude Desktop, Cursor, opencode, ...) can drive the desktop directly:

pip install "pctr[mcp]"

Add it to your client config (stdio):

{ "mcpServers": { "pctr": { "command": "pctr-mcp" } } }

(or "command": "python", "args": ["-m", "pctr.mcp_server"])

Tools exposed: pctr_windows, pctr_tree, pctr_find, pctr_click, pctr_set, pctr_type, pctr_keys, pctr_hotkey, pctr_focus, pctr_wait, pctr_shot, pctr_ocr, pctr_ocrfind, pctr_ocrclick, pctr_look, pctr_desktop.

Or run/manage it from the CLI:

pctr mcp status                                          # SDK + server state
pctr mcp start --transport streamable-http --port 8765   # background HTTP server
pctr mcp restart | pctr mcp stop
pctr mcp help

stdio runs in the foreground (what a client spawns); sse / streamable-http run in the background, reachable at http://127.0.0.1:8765/mcp (or …/sse).

Commands

Command What it does
pctr windows [--filter RE] [--process PID] List top-level windows (pid=… | title).
pctr tree --title RE [--depth N] [--limit N] Dump the UIA control tree of a window.
pctr find --title RE [--name RE] [--limit N] [--nth N] List matching elements.
pctr click --title RE --name RE [--method auto|invoke|mouse] [--dbl] Click an element.
pctr set --title RE --name RE --text S Set a field via the UIA ValuePattern (exact, instant).
pctr type [--title RE --name RE] --text S [--delay 0.03] [--chunk 1] Send keystrokes (see notes).
pctr keys --keys "{ENTER}" Send a global key combo (pywinauto syntax).
pctr hotkey --keys ctrl+s Send a modifier combo (prefer this over keys "^s").
pctr focus --title RE Bring a window to the foreground.
pctr wait --title RE --name RE [--timeout S] Wait until an element exists.
pctr shot [--title RE] --out PATH Screenshot the screen or a window.
pctr size Print primary screen size as WxH.
pctr desktop <action> Virtual desktops: list / windows / where / on-current / next / prev / new / close / move-window.
pctr move --x N --y N / down / up / hold --ms N Raw mouse control (--direct for games).
pctr drag --start x,y --end x,y [--duration S] Drag between two points.
pctr keydown / keyup --key a / keyhold --key w --ms 1500 Hold keyboard keys (--direct for games).
pctr ocr [--title RE] [--lang TAG] OCR the screen/window; list words with boxes.
pctr ocrfind --text RE [--lang TAG] OCR then list matches with click centers (single words).
pctr ocrclick --text RE [--nth N] [--lang TAG] OCR then click a word.
pctr look --for "a dog" [--title RE] [--conf X] YOLO-World detect objects by text prompt.
pctr lookclick --for "a dog" [--nth N] Detect then click the top match.
pctr pad <button|stick|trigger|status> Virtual Xbox 360 gamepad via ViGEmBus.
pctr hid <action> --port COMx Serial-HID bridge to a real USB-HID microcontroller.
pctr mcp <start|stop|restart|status|help> Manage the MCP server (stdio or background http/sse).
pctr skill [--install] / pctr setup Print/install the agent skill + an AGENTS.md section.

Global flags: --json (machine-readable output) and --file FILE (run a batch of commands from a file).

Common filters: --title (regex), --name (regex), --control-type (Button, Edit, Pane, MenuItem, …), --auto-id, --nth, --process.

click, set, wait accept any selector - --name is not required. tree/find require a window selector (--title or --process).

The loop

pctr tree  --title "Notepad" --limit 60      # see what's clickable
pctr click --title "Notepad" --name "^File$"
pctr set   --title "Notepad" --name "Text Editor" --text "hello"

JSON output

Add --json (global, or per-command) for machine-readable output from windows, tree, find, ocr, ocrfind, look, and desktop:

pctr windows --json
# [{"hwnd":1234,"pid":5678,"title":"Untitled - Notepad"}, ...]

pctr desktop list --json
# [{"index":0,"guid":"ae37…","current":true,"windows":11}, ...]

pctr look --for "a car" --json
# [{"label":"a car","conf":0.86,"x":512,"y":300,"box":[400,220,620,380]}, ...]

Progress and warnings go to stderr, so stdout stays valid JSON.

Batch files

pctr --file script.txt          # one command per line; # comments and blanks skipped
pctr --json --file script.txt   # --json applies to every line

Stops on the first failing line and reports its line number.

Virtual desktops

pctr desktop list                       # desktops (#index, guid, [current], window count)
pctr desktop windows --desktop 1        # windows on desktop #1
pctr desktop where --title "App"        # which desktop a window is on
pctr desktop on-current --title "App"   # True / False
pctr desktop next | prev | new | close  # switch (moves the active desktop)
pctr desktop move-window --title RE --to N
  • Switching uses the standard Win+Ctrl hotkeys and moves the active desktop (you included). Add --direct if a game has focus.
  • Cross-desktop control: UIA actions (click --method invoke, set) reach windows on another desktop without switching (accessibility layer, not screen input). Raw mouse/keys only affect the active desktop.
  • move-window is blocked on Windows 11 (third-party MoveWindowToDesktop returns Access denied); it works on Windows 10.

Raw input backends

The OS flags SendInput events as injected, so anything built on user32 is detectable in principle. pctr can route raw mouse/keyboard through a different backend with --backend:

Backend How Detectable?
pyautogui (default / sendinput) user32 SendInput, virtual keys yes (injected flag)
pydirectinput (or --direct) SendInput with scancodes — better for DirectInput games yes
serial-hid a microcontroller presenting real USB HID over serial no — it is real hardware
pctr move --backend pydirectinput --x 800 --y 400
pctr keyhold --key w --ms 400 --backend pydirectinput
pctr type --text "hi" --backend serial-hid --port COM5   # needs a board

Applies to move / down / up / hold / keydown / keyup / keyhold / hotkey / drag / type / desktop.

Gamepad

pip install "pctr[pad]"     # + the ViGEmBus driver
pctr pad button  --name A --ms 150
pctr pad button  --name DPAD_UP --ms 100
pctr pad stick   --x 1.0 --y -0.5 --ms 400
pctr pad trigger --right 1.0 --ms 300

Serial-HID bridge

Firmware protocol, newline-terminated ASCII: MOVE x y · CLICK b · DOWN b · UP b · KEY k DOWN|UP · WRITE text · HOTKEY a+b

pip install "pctr[serial]"
pctr hid ports                                  # list COM ports
pctr hid move --x 500 --y 300 --port COM5
pctr hid type --text "hello" --port COM5

Exit codes

  • 0 success, 1 no match / window not found, 2 usage/validation error.
  • Errors print one error: … line - no tracebacks.

Notes

  • Prefer set (ValuePattern) over type for text fields - it can't drop or reorder characters. type's element path uses pywinauto type_keys, which interprets { } + ^ % ~ ( ) as key syntax, so it is not literal.
  • Global type / keys go to the OS-focused window - pctr focus first.
  • Prefer pctr hotkey --keys ctrl+s over keys "^s"; the ^ modifier is fragile through shells.
  • ocrfind/ocrclick match one word at a time (Windows OCR tokenizes words).
  • look reads its model from --model or the PCTR_YOLO_MODEL env var; progress bars go to stderr so stdout stays parseable.
  • Qt apps expose a rich tree including embedded webviews. Electron apps expose only the window frame (use focus + global keys, or drive them over CDP).
  • Games ignore synthetic input - pass --direct to mouse/key commands and focus the game first.

License

MIT


AI-generated with OpenCode — primary model: DeepSeek V4.1 Flash.

Metadata

Release files for pctr 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pctr 0.5.0
File Size Uploaded
pctr-0.5.0.tar.gz 47.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pctr 0.5.0
File Interpreter ABI Platform
pctr-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 95.8 kB

Release files / pctr-0.5.0.tar.gz

Download URL pctr-0.5.0.tar.gz
Size 47.4 kB
Tags Source
SHA-256 checksum
How to use checksums
919c4423c4c358886d3f8b685054820818cfc457fe0c4ceba41b70651004bbc9
BLAKE2b-256 checksum
How to use checksums
6debe62d6658af791dc9727f70aa9b20222f9c9f47da23c864bf27813ec5d710
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.8

Release files / pctr-0.5.0-py3-none-any.whl

Download URL pctr-0.5.0-py3-none-any.whl
Size 48.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
825f0adf133558e8273c2619f61e1c681153fe04407ab823e59ca9cb94d794da
BLAKE2b-256 checksum
How to use checksums
c9ce44f7cb9fedee937d84d1d9b1eaf8817c129f1f70af79b2a8ec989c8f5672
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.8

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.4.0

2 release files

0.3.5

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page