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 +136 -0
- pctr-0.3.0/README.md +106 -0
- {pctr-0.2.2 → pctr-0.3.0}/pyproject.toml +1 -1
- pctr-0.3.0/src/pctr/__init__.py +1 -0
- {pctr-0.2.2 → pctr-0.3.0}/src/pctr/cli.py +231 -10
- {pctr-0.2.2 → pctr-0.3.0}/src/pctr/data/AGENTS.md +1 -0
- {pctr-0.2.2 → pctr-0.3.0}/src/pctr/data/SKILL.md +19 -0
- pctr-0.3.0/src/pctr.egg-info/PKG-INFO +136 -0
- pctr-0.2.2/PKG-INFO +0 -96
- pctr-0.2.2/README.md +0 -66
- pctr-0.2.2/src/pctr/__init__.py +0 -1
- pctr-0.2.2/src/pctr.egg-info/PKG-INFO +0 -96
- {pctr-0.2.2 → pctr-0.3.0}/LICENSE +0 -0
- {pctr-0.2.2 → pctr-0.3.0}/setup.cfg +0 -0
- {pctr-0.2.2 → pctr-0.3.0}/src/pctr/__main__.py +0 -0
- {pctr-0.2.2 → pctr-0.3.0}/src/pctr.egg-info/SOURCES.txt +0 -0
- {pctr-0.2.2 → pctr-0.3.0}/src/pctr.egg-info/dependency_links.txt +0 -0
- {pctr-0.2.2 → pctr-0.3.0}/src/pctr.egg-info/entry_points.txt +0 -0
- {pctr-0.2.2 → pctr-0.3.0}/src/pctr.egg-info/requires.txt +0 -0
- {pctr-0.2.2 → pctr-0.3.0}/src/pctr.egg-info/top_level.txt +0 -0
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.
|
|
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
|
|
122
|
-
|
|
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
|
|
167
|
+
if proc is not None and pid != proc:
|
|
130
168
|
continue
|
|
131
|
-
print("pid=%s | %s" % (
|
|
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
|
pctr-0.2.2/src/pctr/__init__.py
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|