pctr 0.2.2__tar.gz → 0.3.0__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.0/PKG-INFO ADDED
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: pctr
3
+ Version: 0.3.0
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.0/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.0"
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.0"
@@ -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)
@@ -62,6 +62,49 @@ def _find_window(title_re, process=None, timeout=5.0, index=0):
62
62
  )
63
63
 
64
64
 
65
+ def _enum_top_windows():
66
+ """Top-level visible windows via Win32 EnumWindows (no UIA - survives a wedged UIA provider)."""
67
+ import ctypes
68
+ from ctypes import wintypes
69
+ user32 = ctypes.windll.user32
70
+ out = []
71
+ proc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
72
+
73
+ def cb(hwnd, lp):
74
+ n = user32.GetWindowTextLengthW(hwnd)
75
+ if n and user32.IsWindowVisible(hwnd):
76
+ buf = ctypes.create_unicode_buffer(n + 1)
77
+ user32.GetWindowTextW(hwnd, buf, n + 1)
78
+ pid = wintypes.DWORD()
79
+ user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
80
+ out.append((int(hwnd), buf.value, int(pid.value)))
81
+ return True
82
+
83
+ user32.EnumWindows(proc(cb), 0)
84
+ return out
85
+
86
+
87
+ def _find_hwnd(title_re, process=None, timeout=5.0):
88
+ """Find a top-level window handle by title regex (Win32, no UIA)."""
89
+ rx = _compile(title_re, "title") if title_re else None
90
+ deadline = time.time() + timeout
91
+ last = 0
92
+ while True:
93
+ matches = [
94
+ w for w in _enum_top_windows()
95
+ if (rx is None or rx.search(w[1])) and (process is None or w[2] == process)
96
+ ]
97
+ last = len(matches)
98
+ if matches:
99
+ return matches[0][0]
100
+ if time.time() >= deadline:
101
+ break
102
+ time.sleep(0.25)
103
+ raise SystemExit(
104
+ "window not found: title_re=%r process=%r (saw %d match(es))" % (title_re, process, last)
105
+ )
106
+
107
+
65
108
  def _matches(win, name=None, control_type=None, auto_id=None):
66
109
  rx_name = _compile(name, "name") if name is not None else None
67
110
  out = []
@@ -118,17 +161,12 @@ def _get_element(args):
118
161
  def cmd_windows(args):
119
162
  rx = _compile(args.filter, "filter") if args.filter else None
120
163
  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:
164
+ for hwnd, title, pid in _enum_top_windows():
165
+ if rx and rx.search(title) is None:
128
166
  continue
129
- if proc is not None and w.process_id() != proc:
167
+ if proc is not None and pid != proc:
130
168
  continue
131
- print("pid=%s | %s" % (w.process_id(), t))
169
+ print("pid=%s | %s" % (pid, title))
132
170
 
133
171
 
134
172
  def _walk(root, max_depth):
@@ -370,6 +408,179 @@ def cmd_size(args):
370
408
  print("%dx%d" % (w, h))
371
409
 
372
410
 
411
+ def _vdm() -> Any:
412
+ from ctypes import POINTER
413
+ from ctypes.wintypes import BOOL, HWND
414
+ import comtypes
415
+ from comtypes import COMMETHOD, GUID, HRESULT, IUnknown
416
+
417
+ class IVirtualDesktopManager(IUnknown):
418
+ _iid_ = GUID("{A5CD92FF-29BE-454C-8D04-D82879FB3F1B}")
419
+ _methods_ = [
420
+ COMMETHOD([], HRESULT, "IsWindowOnCurrentVirtualDesktop",
421
+ (["in"], HWND, "w"), (["out"], POINTER(BOOL), "on")),
422
+ COMMETHOD([], HRESULT, "GetWindowDesktopId",
423
+ (["in"], HWND, "w"), (["out"], POINTER(GUID), "did")),
424
+ COMMETHOD([], HRESULT, "MoveWindowToDesktop",
425
+ (["in"], HWND, "w"), (["in"], POINTER(GUID), "did")),
426
+ ]
427
+
428
+ return cast(Any, comtypes.CoCreateInstance(
429
+ GUID("{AA509086-5CA9-4C25-8F95-589D3C07B48A}"),
430
+ interface=IVirtualDesktopManager,
431
+ ))
432
+
433
+
434
+ def _guid_norm(g):
435
+ return str(g).strip("{}").lower()
436
+
437
+
438
+ def _vdesk_guids():
439
+ """Ordered desktop GUIDs from the shell's registry (no undocumented COM)."""
440
+ import uuid
441
+ import winreg
442
+ k = winreg.OpenKey(
443
+ winreg.HKEY_CURRENT_USER,
444
+ r"Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops",
445
+ )
446
+ try:
447
+ raw = winreg.QueryValueEx(k, "VirtualDesktopIDs")[0]
448
+ finally:
449
+ winreg.CloseKey(k)
450
+ return [_guid_norm(uuid.UUID(bytes_le=raw[i:i + 16])) for i in range(0, len(raw) - 15, 16)]
451
+
452
+
453
+ def _list_windows_with_desktop():
454
+ import ctypes
455
+ from ctypes import wintypes
456
+ vdm: Any = _vdm()
457
+ user32 = ctypes.windll.user32
458
+ out = []
459
+ proc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
460
+
461
+ def cb(hwnd, lp):
462
+ n = user32.GetWindowTextLengthW(hwnd)
463
+ if n and user32.IsWindowVisible(hwnd):
464
+ buf = ctypes.create_unicode_buffer(n + 1)
465
+ user32.GetWindowTextW(hwnd, buf, n + 1)
466
+ try:
467
+ d = _guid_norm(vdm.GetWindowDesktopId(wintypes.HWND(hwnd)))
468
+ except Exception:
469
+ d = ""
470
+ out.append((hwnd, buf.value, d))
471
+ return True
472
+
473
+ user32.EnumWindows(proc(cb), 0)
474
+ return out
475
+
476
+
477
+ def _desktop_list(args):
478
+ import ctypes
479
+ from ctypes import wintypes
480
+ guids = _vdesk_guids()
481
+ vdm: Any = _vdm()
482
+ fg = ctypes.windll.user32.GetForegroundWindow()
483
+ try:
484
+ cur = _guid_norm(vdm.GetWindowDesktopId(wintypes.HWND(fg)))
485
+ except Exception:
486
+ cur = ""
487
+ counts = {}
488
+ for _hwnd, _title, d in _list_windows_with_desktop():
489
+ counts[d] = counts.get(d, 0) + 1
490
+ for i, d in enumerate(guids):
491
+ print("#%d %s%s (%d windows)" % (i, d, " [current]" if d == cur else "", counts.get(d, 0)))
492
+
493
+
494
+ def _desktop_windows(args):
495
+ guids = _vdesk_guids()
496
+ want = getattr(args, "desktop", None)
497
+ for hwnd, title, d in _list_windows_with_desktop():
498
+ i = guids.index(d) if d in guids else -1
499
+ if want is not None and i != want:
500
+ continue
501
+ print("desktop=#%s | hwnd=%s | %s" % (i, hwnd, title))
502
+
503
+
504
+ def _desktop_where(args, bool_only):
505
+ if not getattr(args, "title", None) and getattr(args, "process", None) is None:
506
+ _fail("%s needs --title (or --process)" % args.action)
507
+ import ctypes
508
+ from ctypes import wintypes
509
+ vdm: Any = _vdm()
510
+ h = _find_hwnd(args.title, getattr(args, "process", None), getattr(args, "timeout", 5.0))
511
+ hwnd = wintypes.HWND(int(h))
512
+ if bool_only:
513
+ on = vdm.IsWindowOnCurrentVirtualDesktop(hwnd)
514
+ print("on_current=%s" % ("True" if on else "False"))
515
+ return
516
+ guids = _vdesk_guids()
517
+ d = _guid_norm(vdm.GetWindowDesktopId(hwnd))
518
+ i = guids.index(d) if d in guids else -1
519
+ print("desktop=#%d %s" % (i, d))
520
+
521
+
522
+ def _desktop_move(args):
523
+ import ctypes
524
+ from ctypes import wintypes
525
+ import comtypes
526
+ if not getattr(args, "title", None):
527
+ _fail("move-window needs --title")
528
+ if getattr(args, "to", None) is None:
529
+ _fail("move-window needs --to N")
530
+ guids = _vdesk_guids()
531
+ n = args.to
532
+ if n < 0 or n >= len(guids):
533
+ _fail("desktop #%d out of range (0-%d)" % (n, len(guids) - 1))
534
+ vdm: Any = _vdm()
535
+ h = _find_hwnd(args.title, getattr(args, "process", None), getattr(args, "timeout", 5.0))
536
+ hwnd = wintypes.HWND(int(h))
537
+ try:
538
+ vdm.MoveWindowToDesktop(hwnd, comtypes.GUID("{" + guids[n] + "}"))
539
+ except Exception as e:
540
+ msg = str(e).lower()
541
+ if "denied" in msg or "0x80070005" in msg or "access" in msg:
542
+ _fail(
543
+ "Windows 11 blocks third-party MoveWindowToDesktop (Access denied). "
544
+ "read (list/where/windows) and UIA actions still work cross-desktop; "
545
+ "moving windows between desktops works on Windows 10."
546
+ )
547
+ _fail("move-window failed: %s" % e)
548
+ print("moved %r to desktop #%d" % (args.title, n))
549
+
550
+
551
+ def cmd_desktop(args):
552
+ action = args.action
553
+ if action == "list":
554
+ return _desktop_list(args)
555
+ if action == "windows":
556
+ return _desktop_windows(args)
557
+ if action == "on-current":
558
+ return _desktop_where(args, bool_only=True)
559
+ if action == "where":
560
+ return _desktop_where(args, bool_only=False)
561
+ if action == "move-window":
562
+ return _desktop_move(args)
563
+ combos = {
564
+ "next": ("win", "ctrl", "right"),
565
+ "prev": ("win", "ctrl", "left"),
566
+ "new": ("win", "ctrl", "d"),
567
+ "close": ("win", "ctrl", "f4"),
568
+ }
569
+ combo = combos[action]
570
+ if getattr(args, "direct", False):
571
+ import pydirectinput
572
+ pydirectinput.PAUSE = 0.01
573
+ for k in combo:
574
+ pydirectinput.keyDown(k)
575
+ for k in reversed(combo):
576
+ pydirectinput.keyUp(k)
577
+ else:
578
+ import pyautogui
579
+ pyautogui.FAILSAFE = False
580
+ pyautogui.hotkey(*combo)
581
+ print("desktop: %s" % action)
582
+
583
+
373
584
  def cmd_drag(args):
374
585
  m = _mouse_backend(args)
375
586
  x1, y1 = _parse_pair(args.start)
@@ -775,6 +986,16 @@ def build_parser():
775
986
  sz = sub.add_parser("size", help="Print primary screen size as WxH")
776
987
  sz.set_defaults(func=cmd_size)
777
988
 
989
+ dp = sub.add_parser("desktop", help="Virtual desktops: list / windows / where / on-current / next / prev / new / close / move-window")
990
+ dp.add_argument("action", choices=["list", "windows", "where", "on-current", "next", "prev", "new", "close", "move-window"])
991
+ dp.add_argument("--direct", action="store_true", help="Use pydirectinput for the switch hotkey")
992
+ dp.add_argument("--title", default=None, help="Window target (where / on-current / move-window)")
993
+ dp.add_argument("--process", type=int, default=None)
994
+ dp.add_argument("--timeout", type=float, default=5.0)
995
+ dp.add_argument("--desktop", type=int, default=None, help="Filter `windows` to this desktop index")
996
+ dp.add_argument("--to", type=int, default=None, help="Target desktop index for move-window")
997
+ dp.set_defaults(func=cmd_desktop)
998
+
778
999
  dr = sub.add_parser("drag", help="Drag from --start to --end (x,y pairs)")
779
1000
  dr.add_argument("--start", required=True, help="x,y")
780
1001
  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.0
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