pctr 0.2.2__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pctr-0.3.1/PKG-INFO ADDED
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: pctr
3
+ Version: 0.3.1
4
+ Summary: Element-based Windows UI Automation CLI for AI agents. Find controls by name/type/id and click, type, hold, drag, and screenshot - no pixel coordinates.
5
+ Author: Space-lab515
6
+ License-Expression: MIT
7
+ Keywords: automation,windows,uia,pywinauto,desktop,agent,computer-use,rpa
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Win32 (MS Windows)
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Classifier: Topic :: System :: Networking
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: pywinauto>=0.6.8; platform_system == "Windows"
23
+ Requires-Dist: uiautomation>=2.0; platform_system == "Windows"
24
+ Requires-Dist: pyautogui>=0.9.54; platform_system == "Windows"
25
+ Requires-Dist: pydirectinput>=1.0.4; platform_system == "Windows"
26
+ Requires-Dist: pillow>=9.0; platform_system == "Windows"
27
+ Provides-Extra: vision
28
+ Requires-Dist: ultralytics>=8.2; extra == "vision"
29
+ Dynamic: license-file
30
+
31
+ # pctr
32
+
33
+ Element-based Windows UI Automation CLI for AI agents.
34
+
35
+ Instead of screenshotting and guessing pixel coordinates, `pctr` finds controls
36
+ by their **name / control type / automation id** and acts on them. Window moves,
37
+ DPI changes, and layout shifts don't break your script.
38
+
39
+ Built on `pywinauto` (UI Automation) with `pyautogui` / `pydirectinput` for raw
40
+ mouse and keyboard input. On top of UIA it also offers **OCR**
41
+ (`Windows.Media.Ocr`) and **YOLO-World** open-vocabulary detection for text and
42
+ objects that aren't exposed as controls.
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install pctr
48
+ pip install "pctr[vision]" # adds ultralytics for `pctr look`
49
+ ```
50
+
51
+ Windows only.
52
+
53
+ ## Commands
54
+
55
+ | Command | What it does |
56
+ |---------|--------------|
57
+ | `pctr windows [--filter RE] [--process PID]` | List top-level windows (`pid=… \| title`). |
58
+ | `pctr tree --title RE [--depth N] [--limit N]` | Dump the UIA control tree of a window. |
59
+ | `pctr find --title RE [--name RE] [--limit N] [--nth N]` | List matching elements. |
60
+ | `pctr click --title RE --name RE [--method auto\|invoke\|mouse] [--dbl]` | Click an element. |
61
+ | `pctr set --title RE --name RE --text S` | Set a field via the UIA ValuePattern (exact, instant). |
62
+ | `pctr type [--title RE --name RE] --text S [--delay 0.03] [--chunk 1]` | Send keystrokes (see notes). |
63
+ | `pctr keys --keys "{ENTER}"` | Send a global key combo (pywinauto syntax). |
64
+ | `pctr hotkey --keys ctrl+s` | Send a modifier combo (prefer this over `keys "^s"`). |
65
+ | `pctr focus --title RE` | Bring a window to the foreground. |
66
+ | `pctr wait --title RE --name RE [--timeout S]` | Wait until an element exists. |
67
+ | `pctr shot [--title RE] --out PATH` | Screenshot the screen or a window. |
68
+ | `pctr size` | Print primary screen size as `WxH`. |
69
+ | `pctr desktop <action>` | Virtual desktops: `list` / `windows` / `where` / `on-current` / `next` / `prev` / `new` / `close` / `move-window`. |
70
+ | `pctr move --x N --y N` / `down` / `up` / `hold --ms N` | Raw mouse control (`--direct` for games). |
71
+ | `pctr drag --start x,y --end x,y [--duration S]` | Drag between two points. |
72
+ | `pctr keydown / keyup --key a` / `keyhold --key w --ms 1500` | Hold keyboard keys (`--direct` for games). |
73
+ | `pctr ocr [--title RE]` | OCR the screen/window; list words with boxes. |
74
+ | `pctr ocrfind --text RE` | OCR then list matches with click centers (single words). |
75
+ | `pctr ocrclick --text RE [--nth N]` | OCR then click a word. |
76
+ | `pctr look --for "a dog" [--title RE] [--conf X]` | YOLO-World detect objects by text prompt. |
77
+ | `pctr lookclick --for "a dog" [--nth N]` | Detect then click the top match. |
78
+ | `pctr skill [--install]` / `pctr setup` | Print/install the agent skill + an AGENTS.md section. |
79
+
80
+ Common filters: `--title` (regex), `--name` (regex), `--control-type`
81
+ (`Button`, `Edit`, `Pane`, `MenuItem`, …), `--auto-id`, `--nth`, `--process`.
82
+
83
+ `click`, `set`, `wait` accept **any** selector - `--name` is not required.
84
+ `tree`/`find` require a window selector (`--title` or `--process`).
85
+
86
+ ## The loop
87
+
88
+ ```bash
89
+ pctr tree --title "Notepad" --limit 60 # see what's clickable
90
+ pctr click --title "Notepad" --name "^File$"
91
+ pctr set --title "Notepad" --name "Text Editor" --text "hello"
92
+ ```
93
+
94
+ ## Virtual desktops
95
+
96
+ ```bash
97
+ pctr desktop list # desktops (#index, guid, [current], window count)
98
+ pctr desktop windows --desktop 1 # windows on desktop #1
99
+ pctr desktop where --title "App" # which desktop a window is on
100
+ pctr desktop on-current --title "App" # True / False
101
+ pctr desktop next | prev | new | close # switch (moves the active desktop)
102
+ pctr desktop move-window --title RE --to N
103
+ ```
104
+
105
+ - Switching uses the standard Win+Ctrl hotkeys and moves the **active** desktop
106
+ (you included). Add `--direct` if a game has focus.
107
+ - **Cross-desktop control:** UIA actions (`click --method invoke`, `set`) reach
108
+ windows on another desktop **without switching** (accessibility layer, not
109
+ screen input). Raw mouse/keys only affect the active desktop.
110
+ - **`move-window` is blocked on Windows 11** (third-party `MoveWindowToDesktop`
111
+ returns `Access denied`); it works on Windows 10.
112
+
113
+ ## Exit codes
114
+
115
+ - `0` success, `1` no match / window not found, `2` usage/validation error.
116
+ - Errors print one `error: …` line - no tracebacks.
117
+
118
+ ## Notes
119
+
120
+ - Prefer `set` (ValuePattern) over `type` for text fields - it can't drop or
121
+ reorder characters. `type`'s element path uses pywinauto `type_keys`, which
122
+ interprets `{ } + ^ % ~ ( )` as key syntax, so it is **not** literal.
123
+ - Global `type` / `keys` go to the OS-focused window - `pctr focus` first.
124
+ - Prefer `pctr hotkey --keys ctrl+s` over `keys "^s"`; the `^` modifier is
125
+ fragile through shells.
126
+ - `ocrfind`/`ocrclick` match one **word** at a time (Windows OCR tokenizes words).
127
+ - `look` reads its model from `--model` or the `PCTR_YOLO_MODEL` env var; progress
128
+ bars go to stderr so stdout stays parseable.
129
+ - Qt apps expose a rich tree including embedded webviews. Electron apps expose
130
+ only the window frame (use `focus` + global keys, or drive them over CDP).
131
+ - Games ignore synthetic input - pass `--direct` to mouse/key commands and focus
132
+ the game first.
133
+
134
+ ## License
135
+
136
+ MIT
pctr-0.3.1/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # pctr
2
+
3
+ Element-based Windows UI Automation CLI for AI agents.
4
+
5
+ Instead of screenshotting and guessing pixel coordinates, `pctr` finds controls
6
+ by their **name / control type / automation id** and acts on them. Window moves,
7
+ DPI changes, and layout shifts don't break your script.
8
+
9
+ Built on `pywinauto` (UI Automation) with `pyautogui` / `pydirectinput` for raw
10
+ mouse and keyboard input. On top of UIA it also offers **OCR**
11
+ (`Windows.Media.Ocr`) and **YOLO-World** open-vocabulary detection for text and
12
+ objects that aren't exposed as controls.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install pctr
18
+ pip install "pctr[vision]" # adds ultralytics for `pctr look`
19
+ ```
20
+
21
+ Windows only.
22
+
23
+ ## Commands
24
+
25
+ | Command | What it does |
26
+ |---------|--------------|
27
+ | `pctr windows [--filter RE] [--process PID]` | List top-level windows (`pid=… \| title`). |
28
+ | `pctr tree --title RE [--depth N] [--limit N]` | Dump the UIA control tree of a window. |
29
+ | `pctr find --title RE [--name RE] [--limit N] [--nth N]` | List matching elements. |
30
+ | `pctr click --title RE --name RE [--method auto\|invoke\|mouse] [--dbl]` | Click an element. |
31
+ | `pctr set --title RE --name RE --text S` | Set a field via the UIA ValuePattern (exact, instant). |
32
+ | `pctr type [--title RE --name RE] --text S [--delay 0.03] [--chunk 1]` | Send keystrokes (see notes). |
33
+ | `pctr keys --keys "{ENTER}"` | Send a global key combo (pywinauto syntax). |
34
+ | `pctr hotkey --keys ctrl+s` | Send a modifier combo (prefer this over `keys "^s"`). |
35
+ | `pctr focus --title RE` | Bring a window to the foreground. |
36
+ | `pctr wait --title RE --name RE [--timeout S]` | Wait until an element exists. |
37
+ | `pctr shot [--title RE] --out PATH` | Screenshot the screen or a window. |
38
+ | `pctr size` | Print primary screen size as `WxH`. |
39
+ | `pctr desktop <action>` | Virtual desktops: `list` / `windows` / `where` / `on-current` / `next` / `prev` / `new` / `close` / `move-window`. |
40
+ | `pctr move --x N --y N` / `down` / `up` / `hold --ms N` | Raw mouse control (`--direct` for games). |
41
+ | `pctr drag --start x,y --end x,y [--duration S]` | Drag between two points. |
42
+ | `pctr keydown / keyup --key a` / `keyhold --key w --ms 1500` | Hold keyboard keys (`--direct` for games). |
43
+ | `pctr ocr [--title RE]` | OCR the screen/window; list words with boxes. |
44
+ | `pctr ocrfind --text RE` | OCR then list matches with click centers (single words). |
45
+ | `pctr ocrclick --text RE [--nth N]` | OCR then click a word. |
46
+ | `pctr look --for "a dog" [--title RE] [--conf X]` | YOLO-World detect objects by text prompt. |
47
+ | `pctr lookclick --for "a dog" [--nth N]` | Detect then click the top match. |
48
+ | `pctr skill [--install]` / `pctr setup` | Print/install the agent skill + an AGENTS.md section. |
49
+
50
+ Common filters: `--title` (regex), `--name` (regex), `--control-type`
51
+ (`Button`, `Edit`, `Pane`, `MenuItem`, …), `--auto-id`, `--nth`, `--process`.
52
+
53
+ `click`, `set`, `wait` accept **any** selector - `--name` is not required.
54
+ `tree`/`find` require a window selector (`--title` or `--process`).
55
+
56
+ ## The loop
57
+
58
+ ```bash
59
+ pctr tree --title "Notepad" --limit 60 # see what's clickable
60
+ pctr click --title "Notepad" --name "^File$"
61
+ pctr set --title "Notepad" --name "Text Editor" --text "hello"
62
+ ```
63
+
64
+ ## Virtual desktops
65
+
66
+ ```bash
67
+ pctr desktop list # desktops (#index, guid, [current], window count)
68
+ pctr desktop windows --desktop 1 # windows on desktop #1
69
+ pctr desktop where --title "App" # which desktop a window is on
70
+ pctr desktop on-current --title "App" # True / False
71
+ pctr desktop next | prev | new | close # switch (moves the active desktop)
72
+ pctr desktop move-window --title RE --to N
73
+ ```
74
+
75
+ - Switching uses the standard Win+Ctrl hotkeys and moves the **active** desktop
76
+ (you included). Add `--direct` if a game has focus.
77
+ - **Cross-desktop control:** UIA actions (`click --method invoke`, `set`) reach
78
+ windows on another desktop **without switching** (accessibility layer, not
79
+ screen input). Raw mouse/keys only affect the active desktop.
80
+ - **`move-window` is blocked on Windows 11** (third-party `MoveWindowToDesktop`
81
+ returns `Access denied`); it works on Windows 10.
82
+
83
+ ## Exit codes
84
+
85
+ - `0` success, `1` no match / window not found, `2` usage/validation error.
86
+ - Errors print one `error: …` line - no tracebacks.
87
+
88
+ ## Notes
89
+
90
+ - Prefer `set` (ValuePattern) over `type` for text fields - it can't drop or
91
+ reorder characters. `type`'s element path uses pywinauto `type_keys`, which
92
+ interprets `{ } + ^ % ~ ( )` as key syntax, so it is **not** literal.
93
+ - Global `type` / `keys` go to the OS-focused window - `pctr focus` first.
94
+ - Prefer `pctr hotkey --keys ctrl+s` over `keys "^s"`; the `^` modifier is
95
+ fragile through shells.
96
+ - `ocrfind`/`ocrclick` match one **word** at a time (Windows OCR tokenizes words).
97
+ - `look` reads its model from `--model` or the `PCTR_YOLO_MODEL` env var; progress
98
+ bars go to stderr so stdout stays parseable.
99
+ - Qt apps expose a rich tree including embedded webviews. Electron apps expose
100
+ only the window frame (use `focus` + global keys, or drive them over CDP).
101
+ - Games ignore synthetic input - pass `--direct` to mouse/key commands and focus
102
+ the game first.
103
+
104
+ ## License
105
+
106
+ MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pctr"
7
- version = "0.2.2"
7
+ version = "0.3.1"
8
8
  description = "Element-based Windows UI Automation CLI for AI agents. Find controls by name/type/id and click, type, hold, drag, and screenshot - no pixel coordinates."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1 @@
1
+ __version__ = "0.3.1"
@@ -2,7 +2,7 @@ import argparse
2
2
  import re
3
3
  import sys
4
4
  import time
5
- from typing import NoReturn
5
+ from typing import Any, NoReturn, cast
6
6
 
7
7
  for _stream in (sys.stdout, sys.stderr):
8
8
  _reconf = getattr(_stream, "reconfigure", None)
@@ -36,29 +36,53 @@ def _iter_windows():
36
36
 
37
37
 
38
38
  def _find_window(title_re, process=None, timeout=5.0, index=0):
39
- rx_title = _compile(title_re, "title") if title_re else None
40
- desktop = Desktop(backend="uia")
39
+ # Locate the HWND with Win32 first (fast, and never touches UIA providers of
40
+ # windows on *other* virtual desktops, which don't answer and can deadlock a
41
+ # full desktop enumeration). Then bind a UIA wrapper to just that one window.
42
+ h = _find_hwnd(title_re, process, timeout)
43
+ return Desktop(backend="uia").window(handle=h)
44
+
45
+
46
+ def _enum_top_windows():
47
+ """Top-level visible windows via Win32 EnumWindows (no UIA - survives a wedged UIA provider)."""
48
+ import ctypes
49
+ from ctypes import wintypes
50
+ user32 = ctypes.windll.user32
51
+ out = []
52
+ proc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
53
+
54
+ def cb(hwnd, lp):
55
+ n = user32.GetWindowTextLengthW(hwnd)
56
+ if n and user32.IsWindowVisible(hwnd):
57
+ buf = ctypes.create_unicode_buffer(n + 1)
58
+ user32.GetWindowTextW(hwnd, buf, n + 1)
59
+ pid = wintypes.DWORD()
60
+ user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
61
+ out.append((int(hwnd), buf.value, int(pid.value)))
62
+ return True
63
+
64
+ user32.EnumWindows(proc(cb), 0)
65
+ return out
66
+
67
+
68
+ def _find_hwnd(title_re, process=None, timeout=5.0):
69
+ """Find a top-level window handle by title regex (Win32, no UIA)."""
70
+ rx = _compile(title_re, "title") if title_re else None
41
71
  deadline = time.time() + timeout
42
- last = None
43
- while time.time() < deadline:
44
- matches = []
45
- for w in desktop.windows():
46
- try:
47
- t = w.window_text()
48
- except Exception:
49
- continue
50
- if rx_title and not rx_title.search(t):
51
- continue
52
- if process is not None and w.process_id() != process:
53
- continue
54
- matches.append(w)
55
- if len(matches) > index:
56
- return matches[index]
57
- last = matches
72
+ last = 0
73
+ while True:
74
+ matches = [
75
+ w for w in _enum_top_windows()
76
+ if (rx is None or rx.search(w[1])) and (process is None or w[2] == process)
77
+ ]
78
+ last = len(matches)
79
+ if matches:
80
+ return matches[0][0]
81
+ if time.time() >= deadline:
82
+ break
58
83
  time.sleep(0.25)
59
84
  raise SystemExit(
60
- "window not found: title_re=%r process=%r (saw %d match(es))"
61
- % (title_re, process, len(last or []))
85
+ "window not found: title_re=%r process=%r (saw %d match(es))" % (title_re, process, last)
62
86
  )
63
87
 
64
88
 
@@ -118,17 +142,12 @@ def _get_element(args):
118
142
  def cmd_windows(args):
119
143
  rx = _compile(args.filter, "filter") if args.filter else None
120
144
  proc = getattr(args, "process", None)
121
- for w in _iter_windows():
122
- t = ""
123
- try:
124
- t = w.window_text()
125
- except Exception:
126
- pass
127
- if rx and rx.search(t) is None:
145
+ for hwnd, title, pid in _enum_top_windows():
146
+ if rx and rx.search(title) is None:
128
147
  continue
129
- if proc is not None and w.process_id() != proc:
148
+ if proc is not None and pid != proc:
130
149
  continue
131
- print("pid=%s | %s" % (w.process_id(), t))
150
+ print("pid=%s | %s" % (pid, title))
132
151
 
133
152
 
134
153
  def _walk(root, max_depth):
@@ -370,6 +389,179 @@ def cmd_size(args):
370
389
  print("%dx%d" % (w, h))
371
390
 
372
391
 
392
+ def _vdm() -> Any:
393
+ from ctypes import POINTER
394
+ from ctypes.wintypes import BOOL, HWND
395
+ import comtypes
396
+ from comtypes import COMMETHOD, GUID, HRESULT, IUnknown
397
+
398
+ class IVirtualDesktopManager(IUnknown):
399
+ _iid_ = GUID("{A5CD92FF-29BE-454C-8D04-D82879FB3F1B}")
400
+ _methods_ = [
401
+ COMMETHOD([], HRESULT, "IsWindowOnCurrentVirtualDesktop",
402
+ (["in"], HWND, "w"), (["out"], POINTER(BOOL), "on")),
403
+ COMMETHOD([], HRESULT, "GetWindowDesktopId",
404
+ (["in"], HWND, "w"), (["out"], POINTER(GUID), "did")),
405
+ COMMETHOD([], HRESULT, "MoveWindowToDesktop",
406
+ (["in"], HWND, "w"), (["in"], POINTER(GUID), "did")),
407
+ ]
408
+
409
+ return cast(Any, comtypes.CoCreateInstance(
410
+ GUID("{AA509086-5CA9-4C25-8F95-589D3C07B48A}"),
411
+ interface=IVirtualDesktopManager,
412
+ ))
413
+
414
+
415
+ def _guid_norm(g):
416
+ return str(g).strip("{}").lower()
417
+
418
+
419
+ def _vdesk_guids():
420
+ """Ordered desktop GUIDs from the shell's registry (no undocumented COM)."""
421
+ import uuid
422
+ import winreg
423
+ k = winreg.OpenKey(
424
+ winreg.HKEY_CURRENT_USER,
425
+ r"Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops",
426
+ )
427
+ try:
428
+ raw = winreg.QueryValueEx(k, "VirtualDesktopIDs")[0]
429
+ finally:
430
+ winreg.CloseKey(k)
431
+ return [_guid_norm(uuid.UUID(bytes_le=raw[i:i + 16])) for i in range(0, len(raw) - 15, 16)]
432
+
433
+
434
+ def _list_windows_with_desktop():
435
+ import ctypes
436
+ from ctypes import wintypes
437
+ vdm: Any = _vdm()
438
+ user32 = ctypes.windll.user32
439
+ out = []
440
+ proc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
441
+
442
+ def cb(hwnd, lp):
443
+ n = user32.GetWindowTextLengthW(hwnd)
444
+ if n and user32.IsWindowVisible(hwnd):
445
+ buf = ctypes.create_unicode_buffer(n + 1)
446
+ user32.GetWindowTextW(hwnd, buf, n + 1)
447
+ try:
448
+ d = _guid_norm(vdm.GetWindowDesktopId(wintypes.HWND(hwnd)))
449
+ except Exception:
450
+ d = ""
451
+ out.append((hwnd, buf.value, d))
452
+ return True
453
+
454
+ user32.EnumWindows(proc(cb), 0)
455
+ return out
456
+
457
+
458
+ def _desktop_list(args):
459
+ import ctypes
460
+ from ctypes import wintypes
461
+ guids = _vdesk_guids()
462
+ vdm: Any = _vdm()
463
+ fg = ctypes.windll.user32.GetForegroundWindow()
464
+ try:
465
+ cur = _guid_norm(vdm.GetWindowDesktopId(wintypes.HWND(fg)))
466
+ except Exception:
467
+ cur = ""
468
+ counts = {}
469
+ for _hwnd, _title, d in _list_windows_with_desktop():
470
+ counts[d] = counts.get(d, 0) + 1
471
+ for i, d in enumerate(guids):
472
+ print("#%d %s%s (%d windows)" % (i, d, " [current]" if d == cur else "", counts.get(d, 0)))
473
+
474
+
475
+ def _desktop_windows(args):
476
+ guids = _vdesk_guids()
477
+ want = getattr(args, "desktop", None)
478
+ for hwnd, title, d in _list_windows_with_desktop():
479
+ i = guids.index(d) if d in guids else -1
480
+ if want is not None and i != want:
481
+ continue
482
+ print("desktop=#%s | hwnd=%s | %s" % (i, hwnd, title))
483
+
484
+
485
+ def _desktop_where(args, bool_only):
486
+ if not getattr(args, "title", None) and getattr(args, "process", None) is None:
487
+ _fail("%s needs --title (or --process)" % args.action)
488
+ import ctypes
489
+ from ctypes import wintypes
490
+ vdm: Any = _vdm()
491
+ h = _find_hwnd(args.title, getattr(args, "process", None), getattr(args, "timeout", 5.0))
492
+ hwnd = wintypes.HWND(int(h))
493
+ if bool_only:
494
+ on = vdm.IsWindowOnCurrentVirtualDesktop(hwnd)
495
+ print("on_current=%s" % ("True" if on else "False"))
496
+ return
497
+ guids = _vdesk_guids()
498
+ d = _guid_norm(vdm.GetWindowDesktopId(hwnd))
499
+ i = guids.index(d) if d in guids else -1
500
+ print("desktop=#%d %s" % (i, d))
501
+
502
+
503
+ def _desktop_move(args):
504
+ import ctypes
505
+ from ctypes import wintypes
506
+ import comtypes
507
+ if not getattr(args, "title", None):
508
+ _fail("move-window needs --title")
509
+ if getattr(args, "to", None) is None:
510
+ _fail("move-window needs --to N")
511
+ guids = _vdesk_guids()
512
+ n = args.to
513
+ if n < 0 or n >= len(guids):
514
+ _fail("desktop #%d out of range (0-%d)" % (n, len(guids) - 1))
515
+ vdm: Any = _vdm()
516
+ h = _find_hwnd(args.title, getattr(args, "process", None), getattr(args, "timeout", 5.0))
517
+ hwnd = wintypes.HWND(int(h))
518
+ try:
519
+ vdm.MoveWindowToDesktop(hwnd, comtypes.GUID("{" + guids[n] + "}"))
520
+ except Exception as e:
521
+ msg = str(e).lower()
522
+ if "denied" in msg or "0x80070005" in msg or "access" in msg:
523
+ _fail(
524
+ "Windows 11 blocks third-party MoveWindowToDesktop (Access denied). "
525
+ "read (list/where/windows) and UIA actions still work cross-desktop; "
526
+ "moving windows between desktops works on Windows 10."
527
+ )
528
+ _fail("move-window failed: %s" % e)
529
+ print("moved %r to desktop #%d" % (args.title, n))
530
+
531
+
532
+ def cmd_desktop(args):
533
+ action = args.action
534
+ if action == "list":
535
+ return _desktop_list(args)
536
+ if action == "windows":
537
+ return _desktop_windows(args)
538
+ if action == "on-current":
539
+ return _desktop_where(args, bool_only=True)
540
+ if action == "where":
541
+ return _desktop_where(args, bool_only=False)
542
+ if action == "move-window":
543
+ return _desktop_move(args)
544
+ combos = {
545
+ "next": ("win", "ctrl", "right"),
546
+ "prev": ("win", "ctrl", "left"),
547
+ "new": ("win", "ctrl", "d"),
548
+ "close": ("win", "ctrl", "f4"),
549
+ }
550
+ combo = combos[action]
551
+ if getattr(args, "direct", False):
552
+ import pydirectinput
553
+ pydirectinput.PAUSE = 0.01
554
+ for k in combo:
555
+ pydirectinput.keyDown(k)
556
+ for k in reversed(combo):
557
+ pydirectinput.keyUp(k)
558
+ else:
559
+ import pyautogui
560
+ pyautogui.FAILSAFE = False
561
+ pyautogui.hotkey(*combo)
562
+ print("desktop: %s" % action)
563
+
564
+
373
565
  def cmd_drag(args):
374
566
  m = _mouse_backend(args)
375
567
  x1, y1 = _parse_pair(args.start)
@@ -775,6 +967,16 @@ def build_parser():
775
967
  sz = sub.add_parser("size", help="Print primary screen size as WxH")
776
968
  sz.set_defaults(func=cmd_size)
777
969
 
970
+ dp = sub.add_parser("desktop", help="Virtual desktops: list / windows / where / on-current / next / prev / new / close / move-window")
971
+ dp.add_argument("action", choices=["list", "windows", "where", "on-current", "next", "prev", "new", "close", "move-window"])
972
+ dp.add_argument("--direct", action="store_true", help="Use pydirectinput for the switch hotkey")
973
+ dp.add_argument("--title", default=None, help="Window target (where / on-current / move-window)")
974
+ dp.add_argument("--process", type=int, default=None)
975
+ dp.add_argument("--timeout", type=float, default=5.0)
976
+ dp.add_argument("--desktop", type=int, default=None, help="Filter `windows` to this desktop index")
977
+ dp.add_argument("--to", type=int, default=None, help="Target desktop index for move-window")
978
+ dp.set_defaults(func=cmd_desktop)
979
+
778
980
  dr = sub.add_parser("drag", help="Drag from --start to --end (x,y pairs)")
779
981
  dr.add_argument("--start", required=True, help="x,y")
780
982
  dr.add_argument("--end", required=True, help="x,y")
@@ -19,5 +19,6 @@ pctr look --for "a red error icon" # fallback: open-vocab object detection
19
19
  - Exit codes: `0` ok, `1` no match, `2` usage error (clean `error:` line, no tracebacks).
20
20
  - Games ignore synthetic input: add `--direct` to mouse/key commands.
21
21
  - Electron apps expose only the frame - use `focus` + global keys, or CDP.
22
+ - Virtual desktops: `pctr desktop list/windows/where/on-current` (read-only), `next/prev/new/close` (switches the active desktop), `move-window --to N`. UIA actions (`invoke`/`set`) reach windows on other desktops without switching; raw mouse/keys do not. `move-window` is blocked on Win11.
22
23
 
23
24
  Run `pctr -h` for all commands. Full skill: `pctr skill`.
@@ -45,6 +45,7 @@ back as `--name`, `--control-type`, `--auto-id`.
45
45
  | `wait --title RE --name RE [--timeout S]` | Wait for an element. |
46
46
  | `shot [--title RE] --out PATH` | Screenshot the screen or a window. |
47
47
  | `size` | Print primary screen size as WxH. |
48
+ | `desktop <action>` | Windows virtual desktops: `list` / `windows` / `where` / `on-current` / `next` / `prev` / `new` / `close` / `move-window`. |
48
49
  | `move --x N --y N` / `down` / `up` / `hold --ms N` | Raw mouse control (`--direct` for games). |
49
50
  | `drag --start x,y --end x,y [--duration S]` | Drag between points. |
50
51
  | `keydown / keyup --key a` / `keyhold --key w --ms 1500` | Hold keyboard keys (`--direct` for games). |
@@ -89,6 +90,24 @@ Errors print a single `error: ...` line (no tracebacks).
89
90
  - **Canvas / games / custom UI**: no tree - use `ocr` for text and `look` for objects.
90
91
  - **Games** (Minecraft, Trailmakers, ...) ignore synthetic input from `pyautogui`; pass `--direct` to mouse/key commands to use `pydirectinput`, and `pctr focus` the game first.
91
92
 
93
+ ## Virtual desktops
94
+
95
+ pctr can see and drive windows across Windows virtual desktops.
96
+
97
+ ```bash
98
+ pctr desktop list # desktops (#index, guid, [current], window count)
99
+ pctr desktop windows --desktop 1 # windows living on desktop #1
100
+ pctr desktop where --title "App" # which desktop a window is on
101
+ pctr desktop on-current --title "App" # True / False
102
+ pctr desktop next | prev | new | close # switch (moves the active desktop)
103
+ pctr desktop move-window --title RE --to N
104
+ ```
105
+
106
+ - **Switching** (`next`/`prev`/`new`/`close`) uses the standard Win+Ctrl hotkeys and moves the **active** desktop (you included). Add `--direct` if a game has focus.
107
+ - **Cross-desktop control:** UIA element actions (`click --method invoke`, `set`) reach windows on *another* desktop **without switching**, because they go through the accessibility layer, not screen input. Raw mouse/keys (mouse-fallback click, global `type`/`keys`, `--direct`) only affect the active desktop.
108
+ - `list` / `windows` / `where` / `on-current` are read-only.
109
+ - **`move-window` is blocked on Windows 11** - third-party `MoveWindowToDesktop` returns `Access denied` (works on Windows 10). Reads and UIA actions still work cross-desktop.
110
+
92
111
  ## OCR / YOLO notes
93
112
 
94
113
  - `ocrfind`/`ocrclick` match **one word at a time** (Windows OCR tokenizes into words) - match a single distinctive word, not a phrase.
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: pctr
3
+ Version: 0.3.1
4
+ Summary: Element-based Windows UI Automation CLI for AI agents. Find controls by name/type/id and click, type, hold, drag, and screenshot - no pixel coordinates.
5
+ Author: Space-lab515
6
+ License-Expression: MIT
7
+ Keywords: automation,windows,uia,pywinauto,desktop,agent,computer-use,rpa
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Win32 (MS Windows)
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Classifier: Topic :: System :: Networking
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: pywinauto>=0.6.8; platform_system == "Windows"
23
+ Requires-Dist: uiautomation>=2.0; platform_system == "Windows"
24
+ Requires-Dist: pyautogui>=0.9.54; platform_system == "Windows"
25
+ Requires-Dist: pydirectinput>=1.0.4; platform_system == "Windows"
26
+ Requires-Dist: pillow>=9.0; platform_system == "Windows"
27
+ Provides-Extra: vision
28
+ Requires-Dist: ultralytics>=8.2; extra == "vision"
29
+ Dynamic: license-file
30
+
31
+ # pctr
32
+
33
+ Element-based Windows UI Automation CLI for AI agents.
34
+
35
+ Instead of screenshotting and guessing pixel coordinates, `pctr` finds controls
36
+ by their **name / control type / automation id** and acts on them. Window moves,
37
+ DPI changes, and layout shifts don't break your script.
38
+
39
+ Built on `pywinauto` (UI Automation) with `pyautogui` / `pydirectinput` for raw
40
+ mouse and keyboard input. On top of UIA it also offers **OCR**
41
+ (`Windows.Media.Ocr`) and **YOLO-World** open-vocabulary detection for text and
42
+ objects that aren't exposed as controls.
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install pctr
48
+ pip install "pctr[vision]" # adds ultralytics for `pctr look`
49
+ ```
50
+
51
+ Windows only.
52
+
53
+ ## Commands
54
+
55
+ | Command | What it does |
56
+ |---------|--------------|
57
+ | `pctr windows [--filter RE] [--process PID]` | List top-level windows (`pid=… \| title`). |
58
+ | `pctr tree --title RE [--depth N] [--limit N]` | Dump the UIA control tree of a window. |
59
+ | `pctr find --title RE [--name RE] [--limit N] [--nth N]` | List matching elements. |
60
+ | `pctr click --title RE --name RE [--method auto\|invoke\|mouse] [--dbl]` | Click an element. |
61
+ | `pctr set --title RE --name RE --text S` | Set a field via the UIA ValuePattern (exact, instant). |
62
+ | `pctr type [--title RE --name RE] --text S [--delay 0.03] [--chunk 1]` | Send keystrokes (see notes). |
63
+ | `pctr keys --keys "{ENTER}"` | Send a global key combo (pywinauto syntax). |
64
+ | `pctr hotkey --keys ctrl+s` | Send a modifier combo (prefer this over `keys "^s"`). |
65
+ | `pctr focus --title RE` | Bring a window to the foreground. |
66
+ | `pctr wait --title RE --name RE [--timeout S]` | Wait until an element exists. |
67
+ | `pctr shot [--title RE] --out PATH` | Screenshot the screen or a window. |
68
+ | `pctr size` | Print primary screen size as `WxH`. |
69
+ | `pctr desktop <action>` | Virtual desktops: `list` / `windows` / `where` / `on-current` / `next` / `prev` / `new` / `close` / `move-window`. |
70
+ | `pctr move --x N --y N` / `down` / `up` / `hold --ms N` | Raw mouse control (`--direct` for games). |
71
+ | `pctr drag --start x,y --end x,y [--duration S]` | Drag between two points. |
72
+ | `pctr keydown / keyup --key a` / `keyhold --key w --ms 1500` | Hold keyboard keys (`--direct` for games). |
73
+ | `pctr ocr [--title RE]` | OCR the screen/window; list words with boxes. |
74
+ | `pctr ocrfind --text RE` | OCR then list matches with click centers (single words). |
75
+ | `pctr ocrclick --text RE [--nth N]` | OCR then click a word. |
76
+ | `pctr look --for "a dog" [--title RE] [--conf X]` | YOLO-World detect objects by text prompt. |
77
+ | `pctr lookclick --for "a dog" [--nth N]` | Detect then click the top match. |
78
+ | `pctr skill [--install]` / `pctr setup` | Print/install the agent skill + an AGENTS.md section. |
79
+
80
+ Common filters: `--title` (regex), `--name` (regex), `--control-type`
81
+ (`Button`, `Edit`, `Pane`, `MenuItem`, …), `--auto-id`, `--nth`, `--process`.
82
+
83
+ `click`, `set`, `wait` accept **any** selector - `--name` is not required.
84
+ `tree`/`find` require a window selector (`--title` or `--process`).
85
+
86
+ ## The loop
87
+
88
+ ```bash
89
+ pctr tree --title "Notepad" --limit 60 # see what's clickable
90
+ pctr click --title "Notepad" --name "^File$"
91
+ pctr set --title "Notepad" --name "Text Editor" --text "hello"
92
+ ```
93
+
94
+ ## Virtual desktops
95
+
96
+ ```bash
97
+ pctr desktop list # desktops (#index, guid, [current], window count)
98
+ pctr desktop windows --desktop 1 # windows on desktop #1
99
+ pctr desktop where --title "App" # which desktop a window is on
100
+ pctr desktop on-current --title "App" # True / False
101
+ pctr desktop next | prev | new | close # switch (moves the active desktop)
102
+ pctr desktop move-window --title RE --to N
103
+ ```
104
+
105
+ - Switching uses the standard Win+Ctrl hotkeys and moves the **active** desktop
106
+ (you included). Add `--direct` if a game has focus.
107
+ - **Cross-desktop control:** UIA actions (`click --method invoke`, `set`) reach
108
+ windows on another desktop **without switching** (accessibility layer, not
109
+ screen input). Raw mouse/keys only affect the active desktop.
110
+ - **`move-window` is blocked on Windows 11** (third-party `MoveWindowToDesktop`
111
+ returns `Access denied`); it works on Windows 10.
112
+
113
+ ## Exit codes
114
+
115
+ - `0` success, `1` no match / window not found, `2` usage/validation error.
116
+ - Errors print one `error: …` line - no tracebacks.
117
+
118
+ ## Notes
119
+
120
+ - Prefer `set` (ValuePattern) over `type` for text fields - it can't drop or
121
+ reorder characters. `type`'s element path uses pywinauto `type_keys`, which
122
+ interprets `{ } + ^ % ~ ( )` as key syntax, so it is **not** literal.
123
+ - Global `type` / `keys` go to the OS-focused window - `pctr focus` first.
124
+ - Prefer `pctr hotkey --keys ctrl+s` over `keys "^s"`; the `^` modifier is
125
+ fragile through shells.
126
+ - `ocrfind`/`ocrclick` match one **word** at a time (Windows OCR tokenizes words).
127
+ - `look` reads its model from `--model` or the `PCTR_YOLO_MODEL` env var; progress
128
+ bars go to stderr so stdout stays parseable.
129
+ - Qt apps expose a rich tree including embedded webviews. Electron apps expose
130
+ only the window frame (use `focus` + global keys, or drive them over CDP).
131
+ - Games ignore synthetic input - pass `--direct` to mouse/key commands and focus
132
+ the game first.
133
+
134
+ ## License
135
+
136
+ MIT
pctr-0.2.2/PKG-INFO DELETED
@@ -1,96 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: pctr
3
- Version: 0.2.2
4
- Summary: Element-based Windows UI Automation CLI for AI agents. Find controls by name/type/id and click, type, hold, drag, and screenshot - no pixel coordinates.
5
- Author: Space-lab515
6
- License-Expression: MIT
7
- Keywords: automation,windows,uia,pywinauto,desktop,agent,computer-use,rpa
8
- Classifier: Development Status :: 4 - Beta
9
- Classifier: Environment :: Win32 (MS Windows)
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Operating System :: Microsoft :: Windows
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Classifier: Programming Language :: Python :: 3.12
16
- Classifier: Programming Language :: Python :: 3.13
17
- Classifier: Topic :: Software Development :: Testing
18
- Classifier: Topic :: System :: Networking
19
- Requires-Python: >=3.10
20
- Description-Content-Type: text/markdown
21
- License-File: LICENSE
22
- Requires-Dist: pywinauto>=0.6.8; platform_system == "Windows"
23
- Requires-Dist: uiautomation>=2.0; platform_system == "Windows"
24
- Requires-Dist: pyautogui>=0.9.54; platform_system == "Windows"
25
- Requires-Dist: pydirectinput>=1.0.4; platform_system == "Windows"
26
- Requires-Dist: pillow>=9.0; platform_system == "Windows"
27
- Provides-Extra: vision
28
- Requires-Dist: ultralytics>=8.2; extra == "vision"
29
- Dynamic: license-file
30
-
31
- # pctr
32
-
33
- Element-based Windows UI Automation CLI for AI agents.
34
-
35
- Instead of screenshotting and guessing pixel coordinates, `pctr` finds controls
36
- by their **name / control type / automation id** and acts on them. Window moves,
37
- DPI changes, and layout shifts don't break your script.
38
-
39
- Built on `pywinauto` (UI Automation) with `pyautogui` / `pydirectinput` for raw
40
- mouse and keyboard input.
41
-
42
- ## Install
43
-
44
- ```bash
45
- pip install pctr
46
- ```
47
-
48
- Windows only.
49
-
50
- ## Commands
51
-
52
- | Command | What it does |
53
- |---------|--------------|
54
- | `pctr windows [--filter RE]` | List top-level windows (`pid=… \| title`). |
55
- | `pctr tree --title RE [--limit N]` | Dump the UIA control tree of a window. |
56
- | `pctr find --title RE --name RE [--control-type T] [--auto-id ID]` | List matching elements. |
57
- | `pctr click --title RE --name RE [--method auto\|invoke\|mouse] [--dbl]` | Click an element. |
58
- | `pctr set --title RE --name RE --text S` | Set a field via the UIA ValuePattern. |
59
- | `pctr type [--title RE --name RE] --text S [--delay 0.03] [--chunk 1]` | Type literal text. |
60
- | `pctr keys --keys "{ENTER}"` | Send a global key combo. |
61
- | `pctr hotkey --keys win+shift+s` | Send a modifier combo. |
62
- | `pctr focus --title RE` | Bring a window to the foreground. |
63
- | `pctr wait --title RE --name RE [--timeout S]` | Wait until an element exists. |
64
- | `pctr shot [--title RE] --out PATH` | Screenshot the screen or a window. |
65
- | `pctr size` | Print primary screen size as `WxH`. |
66
- | `pctr move --x N --y N` | Move the cursor. |
67
- | `pctr down / up [--button left]` | Press / release a mouse button. |
68
- | `pctr hold --ms 1000 [--x N --y N]` | Press, hold, release. |
69
- | `pctr drag --start x,y --end x,y [--duration S]` | Drag between two points. |
70
- | `pctr keydown / keyup --key a` | Hold / release a keyboard key. |
71
- | `pctr keyhold --key w --ms 1500` | Hold a key for a duration. |
72
-
73
- Common filters: `--title` (regex), `--name` (regex), `--control-type`
74
- (`Button`, `Edit`, `Pane`, `MenuItem`, …), `--auto-id`, `--nth`, `--process`.
75
-
76
- ## The loop
77
-
78
- ```bash
79
- pctr tree --title "Notepad" --limit 60 # see what's clickable
80
- pctr click --title "Notepad" --name "^File$"
81
- pctr set --title "Notepad" --name "Text Editor" --text "hello"
82
- ```
83
-
84
- ## Notes
85
-
86
- - Prefer `set` (ValuePattern) over `type` for text fields - it can't drop or
87
- reorder characters.
88
- - `type` is sent in small chunks (`--chunk 1 --delay 0.03`); raise the delay for
89
- Qt/Electron apps that garble fast input.
90
- - Global `type` / `keys` go to the OS-focused window - `pctr focus` first.
91
- - Qt apps expose a rich tree including embedded webviews. Electron apps expose
92
- only the window frame unless accessibility is enabled (use `focus` + `keys`).
93
-
94
- ## License
95
-
96
- MIT
pctr-0.2.2/README.md DELETED
@@ -1,66 +0,0 @@
1
- # pctr
2
-
3
- Element-based Windows UI Automation CLI for AI agents.
4
-
5
- Instead of screenshotting and guessing pixel coordinates, `pctr` finds controls
6
- by their **name / control type / automation id** and acts on them. Window moves,
7
- DPI changes, and layout shifts don't break your script.
8
-
9
- Built on `pywinauto` (UI Automation) with `pyautogui` / `pydirectinput` for raw
10
- mouse and keyboard input.
11
-
12
- ## Install
13
-
14
- ```bash
15
- pip install pctr
16
- ```
17
-
18
- Windows only.
19
-
20
- ## Commands
21
-
22
- | Command | What it does |
23
- |---------|--------------|
24
- | `pctr windows [--filter RE]` | List top-level windows (`pid=… \| title`). |
25
- | `pctr tree --title RE [--limit N]` | Dump the UIA control tree of a window. |
26
- | `pctr find --title RE --name RE [--control-type T] [--auto-id ID]` | List matching elements. |
27
- | `pctr click --title RE --name RE [--method auto\|invoke\|mouse] [--dbl]` | Click an element. |
28
- | `pctr set --title RE --name RE --text S` | Set a field via the UIA ValuePattern. |
29
- | `pctr type [--title RE --name RE] --text S [--delay 0.03] [--chunk 1]` | Type literal text. |
30
- | `pctr keys --keys "{ENTER}"` | Send a global key combo. |
31
- | `pctr hotkey --keys win+shift+s` | Send a modifier combo. |
32
- | `pctr focus --title RE` | Bring a window to the foreground. |
33
- | `pctr wait --title RE --name RE [--timeout S]` | Wait until an element exists. |
34
- | `pctr shot [--title RE] --out PATH` | Screenshot the screen or a window. |
35
- | `pctr size` | Print primary screen size as `WxH`. |
36
- | `pctr move --x N --y N` | Move the cursor. |
37
- | `pctr down / up [--button left]` | Press / release a mouse button. |
38
- | `pctr hold --ms 1000 [--x N --y N]` | Press, hold, release. |
39
- | `pctr drag --start x,y --end x,y [--duration S]` | Drag between two points. |
40
- | `pctr keydown / keyup --key a` | Hold / release a keyboard key. |
41
- | `pctr keyhold --key w --ms 1500` | Hold a key for a duration. |
42
-
43
- Common filters: `--title` (regex), `--name` (regex), `--control-type`
44
- (`Button`, `Edit`, `Pane`, `MenuItem`, …), `--auto-id`, `--nth`, `--process`.
45
-
46
- ## The loop
47
-
48
- ```bash
49
- pctr tree --title "Notepad" --limit 60 # see what's clickable
50
- pctr click --title "Notepad" --name "^File$"
51
- pctr set --title "Notepad" --name "Text Editor" --text "hello"
52
- ```
53
-
54
- ## Notes
55
-
56
- - Prefer `set` (ValuePattern) over `type` for text fields - it can't drop or
57
- reorder characters.
58
- - `type` is sent in small chunks (`--chunk 1 --delay 0.03`); raise the delay for
59
- Qt/Electron apps that garble fast input.
60
- - Global `type` / `keys` go to the OS-focused window - `pctr focus` first.
61
- - Qt apps expose a rich tree including embedded webviews. Electron apps expose
62
- only the window frame unless accessibility is enabled (use `focus` + `keys`).
63
-
64
- ## License
65
-
66
- MIT
@@ -1 +0,0 @@
1
- __version__ = "0.2.2"
@@ -1,96 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: pctr
3
- Version: 0.2.2
4
- Summary: Element-based Windows UI Automation CLI for AI agents. Find controls by name/type/id and click, type, hold, drag, and screenshot - no pixel coordinates.
5
- Author: Space-lab515
6
- License-Expression: MIT
7
- Keywords: automation,windows,uia,pywinauto,desktop,agent,computer-use,rpa
8
- Classifier: Development Status :: 4 - Beta
9
- Classifier: Environment :: Win32 (MS Windows)
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Operating System :: Microsoft :: Windows
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Classifier: Programming Language :: Python :: 3.12
16
- Classifier: Programming Language :: Python :: 3.13
17
- Classifier: Topic :: Software Development :: Testing
18
- Classifier: Topic :: System :: Networking
19
- Requires-Python: >=3.10
20
- Description-Content-Type: text/markdown
21
- License-File: LICENSE
22
- Requires-Dist: pywinauto>=0.6.8; platform_system == "Windows"
23
- Requires-Dist: uiautomation>=2.0; platform_system == "Windows"
24
- Requires-Dist: pyautogui>=0.9.54; platform_system == "Windows"
25
- Requires-Dist: pydirectinput>=1.0.4; platform_system == "Windows"
26
- Requires-Dist: pillow>=9.0; platform_system == "Windows"
27
- Provides-Extra: vision
28
- Requires-Dist: ultralytics>=8.2; extra == "vision"
29
- Dynamic: license-file
30
-
31
- # pctr
32
-
33
- Element-based Windows UI Automation CLI for AI agents.
34
-
35
- Instead of screenshotting and guessing pixel coordinates, `pctr` finds controls
36
- by their **name / control type / automation id** and acts on them. Window moves,
37
- DPI changes, and layout shifts don't break your script.
38
-
39
- Built on `pywinauto` (UI Automation) with `pyautogui` / `pydirectinput` for raw
40
- mouse and keyboard input.
41
-
42
- ## Install
43
-
44
- ```bash
45
- pip install pctr
46
- ```
47
-
48
- Windows only.
49
-
50
- ## Commands
51
-
52
- | Command | What it does |
53
- |---------|--------------|
54
- | `pctr windows [--filter RE]` | List top-level windows (`pid=… \| title`). |
55
- | `pctr tree --title RE [--limit N]` | Dump the UIA control tree of a window. |
56
- | `pctr find --title RE --name RE [--control-type T] [--auto-id ID]` | List matching elements. |
57
- | `pctr click --title RE --name RE [--method auto\|invoke\|mouse] [--dbl]` | Click an element. |
58
- | `pctr set --title RE --name RE --text S` | Set a field via the UIA ValuePattern. |
59
- | `pctr type [--title RE --name RE] --text S [--delay 0.03] [--chunk 1]` | Type literal text. |
60
- | `pctr keys --keys "{ENTER}"` | Send a global key combo. |
61
- | `pctr hotkey --keys win+shift+s` | Send a modifier combo. |
62
- | `pctr focus --title RE` | Bring a window to the foreground. |
63
- | `pctr wait --title RE --name RE [--timeout S]` | Wait until an element exists. |
64
- | `pctr shot [--title RE] --out PATH` | Screenshot the screen or a window. |
65
- | `pctr size` | Print primary screen size as `WxH`. |
66
- | `pctr move --x N --y N` | Move the cursor. |
67
- | `pctr down / up [--button left]` | Press / release a mouse button. |
68
- | `pctr hold --ms 1000 [--x N --y N]` | Press, hold, release. |
69
- | `pctr drag --start x,y --end x,y [--duration S]` | Drag between two points. |
70
- | `pctr keydown / keyup --key a` | Hold / release a keyboard key. |
71
- | `pctr keyhold --key w --ms 1500` | Hold a key for a duration. |
72
-
73
- Common filters: `--title` (regex), `--name` (regex), `--control-type`
74
- (`Button`, `Edit`, `Pane`, `MenuItem`, …), `--auto-id`, `--nth`, `--process`.
75
-
76
- ## The loop
77
-
78
- ```bash
79
- pctr tree --title "Notepad" --limit 60 # see what's clickable
80
- pctr click --title "Notepad" --name "^File$"
81
- pctr set --title "Notepad" --name "Text Editor" --text "hello"
82
- ```
83
-
84
- ## Notes
85
-
86
- - Prefer `set` (ValuePattern) over `type` for text fields - it can't drop or
87
- reorder characters.
88
- - `type` is sent in small chunks (`--chunk 1 --delay 0.03`); raise the delay for
89
- Qt/Electron apps that garble fast input.
90
- - Global `type` / `keys` go to the OS-focused window - `pctr focus` first.
91
- - Qt apps expose a rich tree including embedded webviews. Electron apps expose
92
- only the window frame unless accessibility is enabled (use `focus` + `keys`).
93
-
94
- ## License
95
-
96
- MIT
File without changes
File without changes
File without changes
File without changes