cautogui 1.0.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.
- cautogui-1.0.0/LICENSE +21 -0
- cautogui-1.0.0/MANIFEST.in +4 -0
- cautogui-1.0.0/PKG-INFO +8 -0
- cautogui-1.0.0/README.md +40 -0
- cautogui-1.0.0/cautogui/__init__.py +53 -0
- cautogui-1.0.0/cautogui/cautogui.py +358 -0
- cautogui-1.0.0/cautogui.egg-info/PKG-INFO +8 -0
- cautogui-1.0.0/cautogui.egg-info/SOURCES.txt +13 -0
- cautogui-1.0.0/cautogui.egg-info/dependency_links.txt +1 -0
- cautogui-1.0.0/cautogui.egg-info/requires.txt +1 -0
- cautogui-1.0.0/cautogui.egg-info/top_level.txt +2 -0
- cautogui-1.0.0/pyproject.toml +12 -0
- cautogui-1.0.0/setup.cfg +4 -0
- cautogui-1.0.0/setup.py +15 -0
- cautogui-1.0.0/src/core.cpp +253 -0
cautogui-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 danckard
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
cautogui-1.0.0/PKG-INFO
ADDED
cautogui-1.0.0/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# CAutoGUI 🚀
|
|
2
|
+
**A Native High-Performance Multi-Monitor UI Automation Library.**
|
|
3
|
+
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://www.microsoft.com/windows)
|
|
6
|
+
[](src/core.cpp)
|
|
7
|
+
[](#-support-this-project)
|
|
8
|
+
|
|
9
|
+
CAutoGUI is a UI automation engine built for speed. Unlike other libraries that rely purely on Python, CAutoGUI uses a **custom-built C++ core** to interact directly with the Windows API and GDI, bypassing high-latency bottlenecks.
|
|
10
|
+
|
|
11
|
+
## 🌟 Key Features
|
|
12
|
+
- **Ultra-Fast Vision:** Image searching takes **0.09s** on average, up to 20x faster than traditional libraries.
|
|
13
|
+
- **DPI-Aware Captures:** Pixel-perfect accuracy even on screens with Windows scaling enabled.
|
|
14
|
+
- **Native Multi-Monitor Support:** Seamlessly control multiple displays using a unified coordinate system.
|
|
15
|
+
- **Advanced Tweening:** Professional easing functions (Bounce, Elastic, Exponential) for human-like mouse movement.
|
|
16
|
+
- **Compiled Efficiency:** Zero heavy dependencies like OpenCV; runs light and fast.
|
|
17
|
+
|
|
18
|
+
## 📊 Performance Benchmark (Full Screen Scan)
|
|
19
|
+
| Library | Search Time (Avg) | Technology |
|
|
20
|
+
| :--- | :--- | :--- |
|
|
21
|
+
| PyAutoGUI | ~1.50s - 2.10s | Pure Python / MSS |
|
|
22
|
+
| **CAutoGUI** | **0.09s** ⚡ | **C++ Native / GDI** |
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## 🛠️ Installation
|
|
27
|
+
|
|
28
|
+
### Using Pre-compiled Wheels (Recommended)
|
|
29
|
+
import cautogui as pyautogui
|
|
30
|
+
# Use it exactly like PyAutoGUI
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## ☕ Support this project
|
|
34
|
+
If CAutoGUI saved you time and improved your automation speed, consider supporting its development!
|
|
35
|
+
|
|
36
|
+
**USDT (TRC20):** `TZ9idiUf39JmSYCapp6wZEDdnpm5yJz5HX`
|
|
37
|
+
|
|
38
|
+
**USDT (ERC20):** `0x97246ff700f1504755eb329b619ebb6a6889c50d`
|
|
39
|
+
|
|
40
|
+
---
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAutoGUI - Motor de automatización de alto rendimiento
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .cautogui import (
|
|
6
|
+
cautogui,
|
|
7
|
+
Tweens,
|
|
8
|
+
# Exponemos los tweens más comunes directamente para facilidad de uso
|
|
9
|
+
linear,
|
|
10
|
+
easeInQuad,
|
|
11
|
+
easeOutQuad,
|
|
12
|
+
easeInOutQuad,
|
|
13
|
+
easeInCubic,
|
|
14
|
+
easeOutCubic,
|
|
15
|
+
easeInOutCubic,
|
|
16
|
+
easeInSine,
|
|
17
|
+
easeOutSine,
|
|
18
|
+
easeInOutSine,
|
|
19
|
+
easeInExpo,
|
|
20
|
+
easeOutExpo,
|
|
21
|
+
easeInElastic,
|
|
22
|
+
easeOutElastic,
|
|
23
|
+
easeInBack,
|
|
24
|
+
easeOutBack,
|
|
25
|
+
easeInBounce,
|
|
26
|
+
easeOutBounce
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Definimos qué se exporta cuando alguien hace 'from cautogui import *'
|
|
30
|
+
__all__ = [
|
|
31
|
+
'cautogui',
|
|
32
|
+
'Tweens',
|
|
33
|
+
'linear',
|
|
34
|
+
'easeInQuad',
|
|
35
|
+
'easeOutQuad',
|
|
36
|
+
'easeInOutQuad',
|
|
37
|
+
'easeInCubic',
|
|
38
|
+
'easeOutCubic',
|
|
39
|
+
'easeInOutCubic',
|
|
40
|
+
'easeInSine',
|
|
41
|
+
'easeOutSine',
|
|
42
|
+
'easeInOutSine',
|
|
43
|
+
'easeInExpo',
|
|
44
|
+
'easeOutExpo',
|
|
45
|
+
'easeInElastic',
|
|
46
|
+
'easeOutElastic',
|
|
47
|
+
'easeInBack',
|
|
48
|
+
'easeOutBack',
|
|
49
|
+
'easeInBounce',
|
|
50
|
+
'easeOutBounce'
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
__version__ = '1.0.0'
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import cautogui_core
|
|
2
|
+
from PIL import Image
|
|
3
|
+
import ctypes
|
|
4
|
+
import time
|
|
5
|
+
import math
|
|
6
|
+
|
|
7
|
+
class Tweens:
|
|
8
|
+
"""Mathematical easing functions for smooth mouse movement."""
|
|
9
|
+
@staticmethod
|
|
10
|
+
def linear(n):
|
|
11
|
+
return n
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def easeInQuad(n): return n**2
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def easeOutQuad(n): return -n * (n - 2)
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def easeInOutQuad(n):
|
|
21
|
+
n *= 2
|
|
22
|
+
if n < 1: return 0.5 * n**2
|
|
23
|
+
return -0.5 * ((n - 1) * (n - 3) - 1)
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def easeInCubic(n): return n**3
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def easeOutCubic(n): return (n - 1)**3 + 1
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def easeInOutCubic(n):
|
|
33
|
+
n *= 2
|
|
34
|
+
if n < 1: return 0.5 * n**3
|
|
35
|
+
return 0.5 * ((n - 2)**3 + 2)
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def easeInSine(n): return -1 * math.cos(n * (math.pi / 2)) + 1
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def easeOutSine(n): return math.sin(n * (math.pi / 2))
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def easeInOutSine(n): return -0.5 * (math.cos(math.pi * n) - 1)
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def easeInExpo(n): return 0 if n == 0 else 2**(10 * (n - 1))
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def easeOutExpo(n): return 1 if n == 1 else 1 - 2**(-10 * n)
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def easeInElastic(n, period=0.3):
|
|
54
|
+
if n == 0 or n == 1: return n
|
|
55
|
+
p = period
|
|
56
|
+
s = p / 4
|
|
57
|
+
return -(2**(10 * (n - 1)) * math.sin((n - 1 - s) * (2 * math.pi) / p))
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def easeOutElastic(n, period=0.3):
|
|
61
|
+
if n == 0 or n == 1: return n
|
|
62
|
+
p = period
|
|
63
|
+
s = p / 4
|
|
64
|
+
return (2**(-10 * n) * math.sin((n - s) * (2 * math.pi) / p) + 1)
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def easeInBack(n, s=1.70158): return n**2 * ((s + 1) * n - s)
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def easeOutBack(n, s=1.70158): return (n - 1)**2 * ((s + 1) * (n - 1) + s) + 1
|
|
71
|
+
@staticmethod
|
|
72
|
+
def easeOutBounce(n):
|
|
73
|
+
if n < 1 / 2.75:
|
|
74
|
+
return 7.5625 * n**2
|
|
75
|
+
elif n < 2 / 2.75:
|
|
76
|
+
n -= 1.5 / 2.75
|
|
77
|
+
return 7.5625 * n**2 + 0.75
|
|
78
|
+
elif n < 2.5 / 2.75:
|
|
79
|
+
n -= 2.25 / 2.75
|
|
80
|
+
return 7.5625 * n**2 + 0.9375
|
|
81
|
+
else:
|
|
82
|
+
n -= 2.625 / 2.75
|
|
83
|
+
return 7.5625 * n**2 + 0.984375
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def easeInBounce(n): return 1 - Tweens.easeOutBounce(1 - n)
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def easeInOutBounce(n):
|
|
90
|
+
if n < 0.5: return Tweens.easeInBounce(n * 2) * 0.5
|
|
91
|
+
return Tweens.easeOutBounce(n * 2 - 1) * 0.5 + 0.5
|
|
92
|
+
|
|
93
|
+
class CAutoGUI:
|
|
94
|
+
FAILSAFE = True
|
|
95
|
+
FAILSAFE_POINTS = [(0, 0)]
|
|
96
|
+
_TWEEN_MAP = {name: func for name, func in Tweens.__dict__.items()
|
|
97
|
+
if isinstance(func, staticmethod)}
|
|
98
|
+
KEY_MAP = {
|
|
99
|
+
'enter': 0x0D,
|
|
100
|
+
'esc': 0x1B,
|
|
101
|
+
'tab': 0x09,
|
|
102
|
+
'space': 0x20,
|
|
103
|
+
'backspace': 0x08,
|
|
104
|
+
'shift': 0x10,
|
|
105
|
+
'ctrl': 0x11,
|
|
106
|
+
'alt': 0x12,
|
|
107
|
+
'f5': 0x74,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def position(self):
|
|
112
|
+
"""
|
|
113
|
+
return the current xy coordinates of the mouse cursor as a two-integer tuple. multimonitor
|
|
114
|
+
"""
|
|
115
|
+
class POINT(ctypes.Structure):
|
|
116
|
+
_fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]
|
|
117
|
+
|
|
118
|
+
pt = POINT()
|
|
119
|
+
ctypes.windll.user32.GetCursorPos(ctypes.byref(pt))
|
|
120
|
+
return (pt.x, pt.y)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def __init__(self):
|
|
126
|
+
self.user32 = ctypes.windll.user32
|
|
127
|
+
|
|
128
|
+
def _check_failsafe(self):
|
|
129
|
+
if self.FAILSAFE:
|
|
130
|
+
pos = self.position()
|
|
131
|
+
if pos in self.FAILSAFE_POINTS:
|
|
132
|
+
raise Exception("Failsafe activated: Mouse moved to a corner.")
|
|
133
|
+
|
|
134
|
+
def moveTo(self, x, y, duration=0.0 , tween=None):
|
|
135
|
+
"""
|
|
136
|
+
Move the mouse cursor to absolute coordinates.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
x (int): Destination X coordinate.
|
|
140
|
+
y (int): Destination Y coordinate.
|
|
141
|
+
duration (float): Time in seconds to perform movement.
|
|
142
|
+
tween (str|callable): Easing function or alias.
|
|
143
|
+
"""
|
|
144
|
+
self._check_failsafe()
|
|
145
|
+
|
|
146
|
+
if isinstance(tween, str):
|
|
147
|
+
tween_func = self._TWEEN_MAP.get(tween, Tweens.linear)
|
|
148
|
+
elif callable(tween):
|
|
149
|
+
tween_func = tween
|
|
150
|
+
else:
|
|
151
|
+
tween_func = Tweens.linear
|
|
152
|
+
|
|
153
|
+
start_x, start_y = self.position()
|
|
154
|
+
dx, dy = x - start_x, y - start_y
|
|
155
|
+
|
|
156
|
+
if duration <= 0:
|
|
157
|
+
cautogui_core.move_mouse_abs(int(x), int(y))
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
start_time = time.perf_counter()
|
|
161
|
+
while True:
|
|
162
|
+
elapsed = time.perf_counter() - start_time
|
|
163
|
+
if elapsed >= duration:
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
# Calculate progress (0.0 to 1.0)
|
|
167
|
+
progress = tween(elapsed / duration)
|
|
168
|
+
|
|
169
|
+
curr_x = start_x + int(dx * progress)
|
|
170
|
+
curr_y = start_y + int(dy * progress)
|
|
171
|
+
|
|
172
|
+
cautogui_core.move_mouse_abs(curr_x, curr_y)
|
|
173
|
+
# Control frequency for smoothness
|
|
174
|
+
time.sleep(0.001)
|
|
175
|
+
|
|
176
|
+
# Asegurar posición final
|
|
177
|
+
cautogui_core.move_mouse_abs(int(x), int(y))
|
|
178
|
+
def dragTo(self, x, y, duration=0.5):
|
|
179
|
+
"""drag the mouse from its current position to (x, y)."""
|
|
180
|
+
self._check_failsafe()
|
|
181
|
+
# MOUSEEVENTF_LEFTDOWN = 0x0002
|
|
182
|
+
cautogui_core.mouse_event_raw(0x0002, 0, 0)
|
|
183
|
+
self.moveTo(x, y, duration)
|
|
184
|
+
# MOUSEEVENTF_LEFTUP = 0x0004
|
|
185
|
+
cautogui_core.mouse_event_raw(0x0004, 0, 0)
|
|
186
|
+
|
|
187
|
+
VK_SHIFT = 0x10
|
|
188
|
+
KEYEVENTF_KEYUP = 0x0002
|
|
189
|
+
|
|
190
|
+
def press(self, key):
|
|
191
|
+
"""press and release a physical key."""
|
|
192
|
+
vk = self._get_vk(key)
|
|
193
|
+
needs_shift = self._needs_shift(key)
|
|
194
|
+
|
|
195
|
+
if needs_shift:
|
|
196
|
+
cautogui_core.key_event(self.VK_SHIFT, 0) # Shift Down
|
|
197
|
+
|
|
198
|
+
cautogui_core.key_event(vk, 0) # Key Down
|
|
199
|
+
cautogui_core.key_event(vk, self.KEYEVENTF_KEYUP) # Key Up
|
|
200
|
+
|
|
201
|
+
if needs_shift:
|
|
202
|
+
cautogui_core.key_event(self.VK_SHIFT, self.KEYEVENTF_KEYUP) # Shift Up
|
|
203
|
+
|
|
204
|
+
def write(self, text, interval=0.01):
|
|
205
|
+
"""text typing handling for uppercase and lowercase."""
|
|
206
|
+
for char in text:
|
|
207
|
+
self.press(char)
|
|
208
|
+
if interval > 0:
|
|
209
|
+
time.sleep(interval)
|
|
210
|
+
|
|
211
|
+
def _get_vk(self, char):
|
|
212
|
+
"""convert a character to a Windows Virtual Key Code."""
|
|
213
|
+
# Handle special characters
|
|
214
|
+
special = {'enter': 0x0D, 'esc': 0x1B, 'tab': 0x09, ' ': 0x20}
|
|
215
|
+
if char.lower() in special:
|
|
216
|
+
return special[char.lower()]
|
|
217
|
+
|
|
218
|
+
# For letters and numbers
|
|
219
|
+
res = ctypes.windll.user32.VkKeyScanW(ord(char))
|
|
220
|
+
return res & 0xFF
|
|
221
|
+
|
|
222
|
+
def _needs_shift(self, char):
|
|
223
|
+
"""detect if the character requires the SHIFT key."""
|
|
224
|
+
if len(char) > 1: return False
|
|
225
|
+
res = ctypes.windll.user32.VkKeyScanW(ord(char))
|
|
226
|
+
return (res >> 8) & 0x01
|
|
227
|
+
|
|
228
|
+
def press(self, key):
|
|
229
|
+
"""Simulate pressing a physical key."""
|
|
230
|
+
key = key.lower()
|
|
231
|
+
if key in self.KEY_MAP:
|
|
232
|
+
code = self.KEY_MAP[key]
|
|
233
|
+
elif len(key) == 1:
|
|
234
|
+
code = ord(key.upper())
|
|
235
|
+
else:
|
|
236
|
+
raise ValueError(f"Key not recognized: {key}")
|
|
237
|
+
|
|
238
|
+
cautogui_core.press_key(code)
|
|
239
|
+
|
|
240
|
+
def typewrite(self, text, interval=0.0):
|
|
241
|
+
"""write a string of text."""
|
|
242
|
+
for char in text:
|
|
243
|
+
self.press(char)
|
|
244
|
+
if interval > 0:
|
|
245
|
+
time.sleep(interval)
|
|
246
|
+
|
|
247
|
+
def size(self):
|
|
248
|
+
w = self.user32.GetSystemMetrics(78) # SM_CXVIRTUALSCREEN
|
|
249
|
+
h = self.user32.GetSystemMetrics(79) # SM_CYVIRTUALSCREEN
|
|
250
|
+
return (w, h)
|
|
251
|
+
def locateAllOnScreen(self, image_path, confidence=0.9):
|
|
252
|
+
img = Image.open(image_path).convert('RGBA')
|
|
253
|
+
# Swap to BGRA for the C++ extension
|
|
254
|
+
r, g, b, a = img.split()
|
|
255
|
+
img_bgra = Image.merge("RGBA", (b, g, r, a))
|
|
256
|
+
|
|
257
|
+
tw, th = img_bgra.size
|
|
258
|
+
sw, sh = self.size()
|
|
259
|
+
screen_bytes = cautogui_core.capture_all()
|
|
260
|
+
|
|
261
|
+
# Return a list of tuples [(x,y), (x,y), ...]
|
|
262
|
+
return cautogui_core.locate_all(screen_bytes, sw, sh, img_bgra.tobytes(), tw, th, confidence)
|
|
263
|
+
def locateOnScreen(self, image_path, confidence=0.9, grayscale=False, region=None):
|
|
264
|
+
"""
|
|
265
|
+
image_path: path to target
|
|
266
|
+
confidence: 0.0 a 1.0 (tolerance)
|
|
267
|
+
grayscale: True/False (for compatibility)
|
|
268
|
+
region: (x, y, width, height) - Limit search to an area
|
|
269
|
+
"""
|
|
270
|
+
# 1. Load and prepare template
|
|
271
|
+
img = Image.open(image_path).convert('RGBA')
|
|
272
|
+
|
|
273
|
+
# Swap to BGRA for the C++ extension
|
|
274
|
+
r, g, b, a = img.split()
|
|
275
|
+
img_bgra = Image.merge("RGBA", (b, g, r, a))
|
|
276
|
+
tw, th = img_bgra.size
|
|
277
|
+
templ_bytes = img_bgra.tobytes()
|
|
278
|
+
|
|
279
|
+
# 2. Get screen capture
|
|
280
|
+
sw, sh = self.size()
|
|
281
|
+
screen_bytes = cautogui_core.capture_all()
|
|
282
|
+
|
|
283
|
+
# If there is a region, the C++ extension will only search within those limits
|
|
284
|
+
# region_data = (x_start, y_start, x_end, y_end)
|
|
285
|
+
if region:
|
|
286
|
+
# Convert region (x, y, w, h) to limits for the C++ loop
|
|
287
|
+
rx, ry, rw, rh = region
|
|
288
|
+
# Adjust virtual coordinates to local buffer
|
|
289
|
+
v_left = ctypes.windll.user32.GetSystemMetrics(76)
|
|
290
|
+
v_top = ctypes.windll.user32.GetSystemMetrics(77)
|
|
291
|
+
|
|
292
|
+
search_area = (
|
|
293
|
+
max(0, rx - v_left),
|
|
294
|
+
max(0, ry - v_top),
|
|
295
|
+
min(sw, rx - v_left + rw),
|
|
296
|
+
min(sh, ry - v_top + rh)
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
search_area = (0, 0, sw, sh)
|
|
300
|
+
|
|
301
|
+
# 3. Call the C++ extension
|
|
302
|
+
# The extension now receives: buffer, sw, sh, template, tw, th, conf, gray, search_area
|
|
303
|
+
return cautogui_core.find_image(
|
|
304
|
+
screen_bytes, sw, sh,
|
|
305
|
+
templ_bytes, tw, th,
|
|
306
|
+
confidence,
|
|
307
|
+
1 if grayscale else 0,
|
|
308
|
+
search_area
|
|
309
|
+
)
|
|
310
|
+
def locateCenterOnScreen(self, image_path, confidence=0.9, grayscale=False, region=None):
|
|
311
|
+
"""
|
|
312
|
+
Search for the image and return the center coordinates (x, y).
|
|
313
|
+
Useful for passing directly to cautogui.click().
|
|
314
|
+
"""
|
|
315
|
+
# 1. Search for the normal position (upper left corner)
|
|
316
|
+
coords = self.locateOnScreen(image_path, confidence, grayscale, region)
|
|
317
|
+
|
|
318
|
+
if coords:
|
|
319
|
+
# 2. Open the image only to know its dimensions
|
|
320
|
+
with Image.open(image_path) as img:
|
|
321
|
+
tw, th = img.size
|
|
322
|
+
|
|
323
|
+
# 3. Calculate the center
|
|
324
|
+
center_x = coords[0] + (tw // 2)
|
|
325
|
+
center_y = coords[1] + (th // 2)
|
|
326
|
+
|
|
327
|
+
return (center_x, center_y)
|
|
328
|
+
|
|
329
|
+
return None
|
|
330
|
+
def displayMousePosition(self):
|
|
331
|
+
"""show the current mouse position in real-time (useful for debugging)."""
|
|
332
|
+
print("Press Ctrl+C to exit.")
|
|
333
|
+
try:
|
|
334
|
+
while True:
|
|
335
|
+
x, y = self.position()
|
|
336
|
+
print(f"\rX: {str(x).ljust(5)} Y: {str(y).ljust(5)}", end="")
|
|
337
|
+
time.sleep(0.1)
|
|
338
|
+
except KeyboardInterrupt:
|
|
339
|
+
print("\nDone.")
|
|
340
|
+
def click(self, x=None, y=None, clicks=1, interval=0.1, button='left', duration=0.0, tween=Tweens.linear):
|
|
341
|
+
"""move the mouse to (x,y) with smooth movement and click."""
|
|
342
|
+
if x is not None and y is not None:
|
|
343
|
+
self.moveTo(x, y, duration=duration, tween=tween)
|
|
344
|
+
|
|
345
|
+
# Map of buttons for SendInput
|
|
346
|
+
down_flag = 0x0002 if button == 'left' else 0x0008
|
|
347
|
+
up_flag = 0x0004 if button == 'left' else 0x0010
|
|
348
|
+
|
|
349
|
+
for i in range(clicks):
|
|
350
|
+
self._check_failsafe()
|
|
351
|
+
cautogui_core.mouse_event_raw(down_flag, 0, 0)
|
|
352
|
+
time.sleep(0.01) # Small delay for physical movement
|
|
353
|
+
cautogui_core.mouse_event_raw(up_flag, 0, 0)
|
|
354
|
+
if i < clicks - 1:
|
|
355
|
+
time.sleep(interval)
|
|
356
|
+
cautogui = CAutoGUI()
|
|
357
|
+
for name, func in CAutoGUI._TWEEN_MAP.items():
|
|
358
|
+
globals()[name] = func.__func__
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.py
|
|
6
|
+
cautogui/__init__.py
|
|
7
|
+
cautogui/cautogui.py
|
|
8
|
+
cautogui.egg-info/PKG-INFO
|
|
9
|
+
cautogui.egg-info/SOURCES.txt
|
|
10
|
+
cautogui.egg-info/dependency_links.txt
|
|
11
|
+
cautogui.egg-info/requires.txt
|
|
12
|
+
cautogui.egg-info/top_level.txt
|
|
13
|
+
src/core.cpp
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Pillow>=9.0.0
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cautogui"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Native high-performance UI automation for Windows"
|
|
9
|
+
requires-python = ">=3.7"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"Pillow>=9.0.0",
|
|
12
|
+
]
|
cautogui-1.0.0/setup.cfg
ADDED
cautogui-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from setuptools import setup, Extension, find_packages
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
ext_module = Extension(
|
|
5
|
+
'cautogui_core',
|
|
6
|
+
sources=[os.path.join('src', 'core.cpp')],
|
|
7
|
+
libraries=['user32', 'gdi32', 'gdiplus'],
|
|
8
|
+
extra_compile_args=['/O2', '/std:c++17']
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
setup(
|
|
12
|
+
ext_modules=[ext_module],
|
|
13
|
+
packages=find_packages(),
|
|
14
|
+
include_package_data=True,
|
|
15
|
+
)
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#define PY_SSIZE_T_CLEAN
|
|
2
|
+
#include <Python.h>
|
|
3
|
+
#include <windows.h>
|
|
4
|
+
#include <vector>
|
|
5
|
+
|
|
6
|
+
struct MonitorData {
|
|
7
|
+
int x, y, w, h;
|
|
8
|
+
bool is_primary;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
BOOL CALLBACK EnumMonitorsProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
|
|
12
|
+
auto* monitors = reinterpret_cast<std::vector<MonitorData>*>(dwData);
|
|
13
|
+
MONITORINFO mi;
|
|
14
|
+
mi.cbSize = sizeof(mi);
|
|
15
|
+
if (GetMonitorInfo(hMonitor, &mi)) {
|
|
16
|
+
monitors->push_back({
|
|
17
|
+
(int)mi.rcMonitor.left,
|
|
18
|
+
(int)mi.rcMonitor.top,
|
|
19
|
+
(int)(mi.rcMonitor.right - mi.rcMonitor.left),
|
|
20
|
+
(int)(mi.rcMonitor.bottom - mi.rcMonitor.top),
|
|
21
|
+
(bool)(mi.dwFlags & MONITORINFOF_PRIMARY)
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return TRUE;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static PyObject* get_monitors(PyObject* self, PyObject* args) {
|
|
28
|
+
std::vector<MonitorData> monitors;
|
|
29
|
+
EnumDisplayMonitors(NULL, NULL, EnumMonitorsProc, reinterpret_cast<LPARAM>(&monitors));
|
|
30
|
+
PyObject* py_list = PyList_New(monitors.size());
|
|
31
|
+
for (size_t i = 0; i < monitors.size(); ++i) {
|
|
32
|
+
PyObject* dict = Py_BuildValue("{s:i, s:i, s:i, s:i, s:b}",
|
|
33
|
+
"x", monitors[i].x, "y", monitors[i].y,
|
|
34
|
+
"width", monitors[i].w, "height", monitors[i].h,
|
|
35
|
+
"is_primary", monitors[i].is_primary);
|
|
36
|
+
PyList_SetItem(py_list, i, dict);
|
|
37
|
+
}
|
|
38
|
+
return py_list;
|
|
39
|
+
}
|
|
40
|
+
static PyObject* key_event(PyObject* self, PyObject* args) {
|
|
41
|
+
int vk, flags;
|
|
42
|
+
if (!PyArg_ParseTuple(args, "ii", &vk, &flags)) return NULL;
|
|
43
|
+
|
|
44
|
+
INPUT input = {0};
|
|
45
|
+
input.type = INPUT_KEYBOARD;
|
|
46
|
+
input.ki.wVk = (WORD)vk;
|
|
47
|
+
input.ki.dwFlags = flags;
|
|
48
|
+
SendInput(1, &input, sizeof(INPUT));
|
|
49
|
+
|
|
50
|
+
Py_RETURN_NONE;
|
|
51
|
+
}
|
|
52
|
+
static PyObject* capture_all(PyObject* self, PyObject* args) {
|
|
53
|
+
int x = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
|
54
|
+
int y = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
|
55
|
+
int w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
|
56
|
+
int h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
|
57
|
+
|
|
58
|
+
HDC hScreen = GetDC(NULL);
|
|
59
|
+
HDC hDC = CreateCompatibleDC(hScreen);
|
|
60
|
+
HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, w, h);
|
|
61
|
+
SelectObject(hDC, hBitmap);
|
|
62
|
+
|
|
63
|
+
BitBlt(hDC, 0, 0, w, h, hScreen, x, y, SRCCOPY);
|
|
64
|
+
|
|
65
|
+
BITMAPINFOHEADER bi = { sizeof(BITMAPINFOHEADER), w, -h, 1, 32, BI_RGB };
|
|
66
|
+
std::vector<unsigned char> pixels(w * h * 4);
|
|
67
|
+
GetDIBits(hDC, hBitmap, 0, h, pixels.data(), (BITMAPINFO*)&bi, DIB_RGB_COLORS);
|
|
68
|
+
|
|
69
|
+
DeleteObject(hBitmap);
|
|
70
|
+
DeleteDC(hDC);
|
|
71
|
+
ReleaseDC(NULL, hScreen);
|
|
72
|
+
|
|
73
|
+
return PyBytes_FromStringAndSize((char*)pixels.data(), pixels.size());
|
|
74
|
+
}
|
|
75
|
+
static PyObject* locate_all(PyObject* self, PyObject* args) {
|
|
76
|
+
Py_buffer screen_buf, templ_buf;
|
|
77
|
+
int s_w, s_h, t_w, t_h;
|
|
78
|
+
float confidence;
|
|
79
|
+
|
|
80
|
+
if (!PyArg_ParseTuple(args, "y*iiy*iif", &screen_buf, &s_w, &s_h, &templ_buf, &t_w, &t_h, &confidence))
|
|
81
|
+
return NULL;
|
|
82
|
+
|
|
83
|
+
unsigned char* screen = (unsigned char*)screen_buf.buf;
|
|
84
|
+
unsigned char* templ = (unsigned char*)templ_buf.buf;
|
|
85
|
+
int max_diff = (int)((1.0f - confidence) * 255);
|
|
86
|
+
|
|
87
|
+
PyObject* results_list = PyList_New(0);
|
|
88
|
+
int v_left = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
|
89
|
+
int v_top = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
|
90
|
+
|
|
91
|
+
for (int y = 0; y <= s_h - t_h; ++y) {
|
|
92
|
+
for (int x = 0; x <= s_w - t_w; ++x) {
|
|
93
|
+
bool match = true;
|
|
94
|
+
for (int ty = 0; ty < t_h; ++ty) {
|
|
95
|
+
for (int tx = 0; tx < t_w; ++tx) {
|
|
96
|
+
int s_ptr = ((y + ty) * s_w + (x + tx)) * 4;
|
|
97
|
+
int t_ptr = (ty * t_w + tx) * 4;
|
|
98
|
+
|
|
99
|
+
if (abs(screen[s_ptr] - templ[t_ptr]) > max_diff ||
|
|
100
|
+
abs(screen[s_ptr+1] - templ[t_ptr+1]) > max_diff ||
|
|
101
|
+
abs(screen[s_ptr+2] - templ[t_ptr+2]) > max_diff) {
|
|
102
|
+
match = false;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!match) break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (match) {
|
|
110
|
+
PyObject* pos = Py_BuildValue("(ii)", x + v_left, y + v_top);
|
|
111
|
+
PyList_Append(results_list, pos);
|
|
112
|
+
Py_DECREF(pos);
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
x += (t_w - 1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
PyBuffer_Release(&screen_buf);
|
|
121
|
+
PyBuffer_Release(&templ_buf);
|
|
122
|
+
return results_list;
|
|
123
|
+
}
|
|
124
|
+
static PyObject* press_key(PyObject* self, PyObject* args) {
|
|
125
|
+
int key_code;
|
|
126
|
+
if (!PyArg_ParseTuple(args, "i", &key_code)) return NULL;
|
|
127
|
+
|
|
128
|
+
INPUT inputs[2] = {0};
|
|
129
|
+
|
|
130
|
+
// Key Down
|
|
131
|
+
inputs[0].type = INPUT_KEYBOARD;
|
|
132
|
+
inputs[0].ki.wVk = (WORD)key_code;
|
|
133
|
+
|
|
134
|
+
// Key Up
|
|
135
|
+
inputs[1].type = INPUT_KEYBOARD;
|
|
136
|
+
inputs[1].ki.wVk = (WORD)key_code;
|
|
137
|
+
inputs[1].ki.dwFlags = KEYEVENTF_KEYUP;
|
|
138
|
+
|
|
139
|
+
SendInput(2, inputs, sizeof(INPUT));
|
|
140
|
+
Py_RETURN_NONE;
|
|
141
|
+
}
|
|
142
|
+
static PyObject* find_image(PyObject* self, PyObject* args) {
|
|
143
|
+
Py_buffer screen_buf, templ_buf;
|
|
144
|
+
int s_w, s_h, t_w, t_h, grayscale;
|
|
145
|
+
float confidence;
|
|
146
|
+
PyObject* region_obj;
|
|
147
|
+
|
|
148
|
+
if (!PyArg_ParseTuple(args, "y*iiy*iifiO", &screen_buf, &s_w, &s_h, &templ_buf, &t_w, &t_h, &confidence, &grayscale, ®ion_obj))
|
|
149
|
+
return NULL;
|
|
150
|
+
|
|
151
|
+
// Extract region limits
|
|
152
|
+
int rx_start, ry_start, rx_end, ry_end;
|
|
153
|
+
PyArg_ParseTuple(region_obj, "iiii", &rx_start, &ry_start, &rx_end, &ry_end);
|
|
154
|
+
|
|
155
|
+
unsigned char* screen = (unsigned char*)screen_buf.buf;
|
|
156
|
+
unsigned char* templ = (unsigned char*)templ_buf.buf;
|
|
157
|
+
int max_diff = (int)((1.0f - confidence) * 255);
|
|
158
|
+
|
|
159
|
+
// The loop now only searches within the specified region
|
|
160
|
+
for (int y = ry_start; y <= ry_end - t_h; ++y) {
|
|
161
|
+
for (int x = rx_start; x <= rx_end - t_w; ++x) {
|
|
162
|
+
bool match = true;
|
|
163
|
+
for (int ty = 0; ty < t_h; ++ty) {
|
|
164
|
+
for (int tx = 0; tx < t_w; ++tx) {
|
|
165
|
+
int s_ptr = ((y + ty) * s_w + (x + tx)) * 4;
|
|
166
|
+
int t_ptr = (ty * t_w + tx) * 4;
|
|
167
|
+
|
|
168
|
+
if (grayscale) {
|
|
169
|
+
int s_lum = (screen[s_ptr+2]*2 + screen[s_ptr+1]*5 + screen[s_ptr]) >> 3;
|
|
170
|
+
int t_lum = (templ[t_ptr+2]*2 + templ[t_ptr+1]*5 + templ[t_ptr]) >> 3;
|
|
171
|
+
if (abs(s_lum - t_lum) > max_diff) { match = false; break; }
|
|
172
|
+
} else {
|
|
173
|
+
if (abs(screen[s_ptr] - templ[t_ptr]) > max_diff ||
|
|
174
|
+
abs(screen[s_ptr+1] - templ[t_ptr+1]) > max_diff ||
|
|
175
|
+
abs(screen[s_ptr+2] - templ[t_ptr+2]) > max_diff) {
|
|
176
|
+
match = false; break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!match) break;
|
|
181
|
+
}
|
|
182
|
+
if (match) {
|
|
183
|
+
int v_left = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
|
184
|
+
int v_top = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
|
185
|
+
PyBuffer_Release(&screen_buf);
|
|
186
|
+
PyBuffer_Release(&templ_buf);
|
|
187
|
+
return Py_BuildValue("(ii)", x + v_left, y + v_top);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
PyBuffer_Release(&screen_buf);
|
|
192
|
+
PyBuffer_Release(&templ_buf);
|
|
193
|
+
Py_RETURN_NONE;
|
|
194
|
+
}
|
|
195
|
+
static PyObject* move_mouse_abs(PyObject* self, PyObject* args) {
|
|
196
|
+
int x, y;
|
|
197
|
+
if (!PyArg_ParseTuple(args, "ii", &x, &y)) return NULL;
|
|
198
|
+
|
|
199
|
+
// Get dimensions of the entire virtual desktop
|
|
200
|
+
int v_left = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
|
201
|
+
int v_top = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
|
202
|
+
int v_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
|
203
|
+
int v_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
|
204
|
+
|
|
205
|
+
INPUT input = {0};
|
|
206
|
+
input.type = INPUT_MOUSE;
|
|
207
|
+
|
|
208
|
+
// Normalization to 0 to 65535 (required by MOUSEEVENTF_ABSOLUTE)
|
|
209
|
+
input.mi.dx = (long)((x - v_left) * (65536.0f / v_width));
|
|
210
|
+
input.mi.dy = (long)((y - v_top) * (65536.0f / v_height));
|
|
211
|
+
|
|
212
|
+
input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
|
|
213
|
+
|
|
214
|
+
SendInput(1, &input, sizeof(INPUT));
|
|
215
|
+
Py_RETURN_NONE;
|
|
216
|
+
}
|
|
217
|
+
// For pressing/releasing individual buttons (necessary for drag)
|
|
218
|
+
static PyObject* mouse_event_raw(PyObject* self, PyObject* args) {
|
|
219
|
+
int dwFlags, x, y;
|
|
220
|
+
if (!PyArg_ParseTuple(args, "iii", &dwFlags, &x, &y)) return NULL;
|
|
221
|
+
|
|
222
|
+
INPUT input = {0};
|
|
223
|
+
input.type = INPUT_MOUSE;
|
|
224
|
+
input.mi.dx = x * (65535 / GetSystemMetrics(SM_CXVIRTUALSCREEN));
|
|
225
|
+
input.mi.dy = y * (65535 / GetSystemMetrics(SM_CYVIRTUALSCREEN));
|
|
226
|
+
input.mi.dwFlags = dwFlags | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
|
|
227
|
+
|
|
228
|
+
SendInput(1, &input, sizeof(INPUT));
|
|
229
|
+
Py_RETURN_NONE;
|
|
230
|
+
}
|
|
231
|
+
// Actualiza tu tabla de métodos
|
|
232
|
+
static PyMethodDef CAutoGuiMethods[] = {
|
|
233
|
+
{"get_monitors", get_monitors, METH_VARARGS, "get all monitors"},
|
|
234
|
+
{"capture_all", capture_all, METH_VARARGS, "capture all screen"},
|
|
235
|
+
{"find_image", find_image, METH_VARARGS, "find an image in the buffer"},
|
|
236
|
+
{"locate_all", locate_all, METH_VARARGS, "find all instances"},
|
|
237
|
+
{"key_event", (PyCFunction)key_event, METH_VARARGS, "raw key event"},
|
|
238
|
+
{"press_key", (PyCFunction)press_key, METH_VARARGS, "simulate pressing and releasing a physical key"},
|
|
239
|
+
{"move_mouse_abs", (PyCFunction)move_mouse_abs, METH_VARARGS, "move mouse to an absolute position"},
|
|
240
|
+
{NULL, NULL, 0, NULL}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
static struct PyModuleDef cautogui_core_module = {
|
|
244
|
+
PyModuleDef_HEAD_INIT,
|
|
245
|
+
"cautogui_core",
|
|
246
|
+
"Internal C++ extension for high-performance UI automation.",
|
|
247
|
+
-1,
|
|
248
|
+
CAutoGuiMethods
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
PyMODINIT_FUNC PyInit_cautogui_core(void) {
|
|
252
|
+
return PyModule_Create(&cautogui_core_module);
|
|
253
|
+
}
|