AutoMonkey 0.4.1__py3-none-any.whl
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.
- automonkey/__init__.py +37 -0
- automonkey/ai/__init__.py +54 -0
- automonkey/ai/__main__.py +76 -0
- automonkey/ai/mcp_launch.py +62 -0
- automonkey/ai/mcp_server.py +114 -0
- automonkey/ai/ocr.py +149 -0
- automonkey/ai/screen.py +202 -0
- automonkey/ai/script.py +190 -0
- automonkey/ai/windows.py +185 -0
- automonkey/app_funcs.py +201 -0
- automonkey/atspi_funcs.py +1093 -0
- automonkey/automonkey.py +300 -0
- automonkey/constants.py +120 -0
- automonkey/exceptions.py +16 -0
- automonkey/img_funcs.py +301 -0
- automonkey/key_funcs.py +279 -0
- automonkey/mouse_funcs.py +37 -0
- automonkey/mouse_tracker.py +133 -0
- automonkey/pywinauto_funcs.py +343 -0
- automonkey/utils.py +169 -0
- automonkey-0.4.1.dist-info/METADATA +517 -0
- automonkey-0.4.1.dist-info/RECORD +27 -0
- automonkey-0.4.1.dist-info/WHEEL +5 -0
- automonkey-0.4.1.dist-info/entry_points.txt +3 -0
- automonkey-0.4.1.dist-info/licenses/AUTHORS.rst +13 -0
- automonkey-0.4.1.dist-info/licenses/LICENSE +33 -0
- automonkey-0.4.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from time import sleep
|
|
2
|
+
|
|
3
|
+
from tkinter import Tk
|
|
4
|
+
from tkinter import Canvas
|
|
5
|
+
|
|
6
|
+
from pyautogui import size
|
|
7
|
+
from pyautogui import position
|
|
8
|
+
|
|
9
|
+
def track_mouse():
|
|
10
|
+
"""Tracks the mouse position and when the mouse stops moving
|
|
11
|
+
for 1 second it prints the position in the terminal
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
cur_pos = ""
|
|
15
|
+
print("Tracking mouse position started")
|
|
16
|
+
print("Press ctrl+c anytime to end tracking")
|
|
17
|
+
try:
|
|
18
|
+
while True:
|
|
19
|
+
if cur_pos != position():
|
|
20
|
+
cur_pos = position()
|
|
21
|
+
print(position())
|
|
22
|
+
sleep(1)
|
|
23
|
+
except KeyboardInterrupt:
|
|
24
|
+
print("Tracking mouse position stopped")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PositionTracker: # was Toplevel
|
|
28
|
+
"""PositionTracker
|
|
29
|
+
"""
|
|
30
|
+
def __init__(self, follow_mouse: bool = False):
|
|
31
|
+
self.follow_mouse = follow_mouse
|
|
32
|
+
self.window = Tk() # Was Tk()
|
|
33
|
+
self.window.canvas = None
|
|
34
|
+
self.coords = None
|
|
35
|
+
self.get_coords = False
|
|
36
|
+
self.after = None
|
|
37
|
+
|
|
38
|
+
def start(self, get_coords: bool = False):
|
|
39
|
+
"""Take the screenshot
|
|
40
|
+
"""
|
|
41
|
+
if self.after:
|
|
42
|
+
self.window.after_cancel(self.after)
|
|
43
|
+
if get_coords:
|
|
44
|
+
self.get_coords = True
|
|
45
|
+
self.window.bind('<Control-Button-1>', lambda e: self.destroy_())
|
|
46
|
+
else:
|
|
47
|
+
self.window.bind('<Escape>', lambda e: self.destroy_())
|
|
48
|
+
self.window.attributes('-fullscreen', True, '-alpha', 0.3)
|
|
49
|
+
self.window.configure(bg='black')
|
|
50
|
+
|
|
51
|
+
self.window.canvas = Canvas(
|
|
52
|
+
self.window,
|
|
53
|
+
width=self.window.winfo_screenwidth(),
|
|
54
|
+
height=self.window.winfo_screenheight(),
|
|
55
|
+
cursor="crosshair"
|
|
56
|
+
)
|
|
57
|
+
self.window.canvas.configure(highlightthickness=0, bg='black')
|
|
58
|
+
self.window.canvas.pack()
|
|
59
|
+
|
|
60
|
+
self.after = self.window.after(1, self._crosshair, None)
|
|
61
|
+
self.window.mainloop()
|
|
62
|
+
if get_coords:
|
|
63
|
+
return self.coords
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def _crosshair(self, coords):
|
|
67
|
+
if self.get_coords:
|
|
68
|
+
self.window.canvas.create_text(
|
|
69
|
+
400,
|
|
70
|
+
20,
|
|
71
|
+
text="CTRL+Left Click to get the cursor coordinates",
|
|
72
|
+
fill='red',
|
|
73
|
+
font=("Helvetica", 30),
|
|
74
|
+
)
|
|
75
|
+
self.coords = position()
|
|
76
|
+
else:
|
|
77
|
+
x_point, y_point = position()
|
|
78
|
+
|
|
79
|
+
self.window.canvas.delete(coords)
|
|
80
|
+
coords = self._make_coords(coords, x_point, y_point)
|
|
81
|
+
|
|
82
|
+
self.window.after(1, self._crosshair, coords)
|
|
83
|
+
|
|
84
|
+
def _make_coords(self, coords, x_point: int, y_point: int) -> any:
|
|
85
|
+
if coords is None:
|
|
86
|
+
self.window.canvas.create_text(
|
|
87
|
+
180,
|
|
88
|
+
20,
|
|
89
|
+
text="Press ESC to exit.",
|
|
90
|
+
fill='red',
|
|
91
|
+
font=("Helvetica", 30),
|
|
92
|
+
)
|
|
93
|
+
if self.follow_mouse:
|
|
94
|
+
coords = self._make_coords_1(x_point, y_point)
|
|
95
|
+
else:
|
|
96
|
+
coords = self._make_coords_2(x_point, y_point)
|
|
97
|
+
return coords
|
|
98
|
+
|
|
99
|
+
def _make_coords_1(self, x_point: int, y_point: int) -> any:
|
|
100
|
+
return self.window.canvas.create_text(
|
|
101
|
+
self._make_coords_1_x(x_point),
|
|
102
|
+
self._make_coords_1_y(x_point, y_point),
|
|
103
|
+
text=f"x={x_point}, y={y_point}",
|
|
104
|
+
fill='red',
|
|
105
|
+
font=("Helvetica", 20) if x_point < size()[0] + 100 else ("Helvetica", 40),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _make_coords_2(self, x_point: int, y_point: int) -> any:
|
|
109
|
+
return self.window.canvas.create_text(
|
|
110
|
+
size()[0] / 2,
|
|
111
|
+
size()[1] / 2,
|
|
112
|
+
text=f"x={x_point}, y={y_point}",
|
|
113
|
+
fill='red',
|
|
114
|
+
font=("Helvetica", 40),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _make_coords_1_x(x_point: int) -> int:
|
|
119
|
+
return x_point + 100 if x_point < size()[0] - 200 else x_point - 100 if x_point < size()[0] + 100 else size()[0] / 2
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def _make_coords_1_y(x_point: int, y_point: int) -> int:
|
|
123
|
+
if x_point > size()[0] + 100:
|
|
124
|
+
return size()[1] / 2
|
|
125
|
+
if (y_point < 70 and x_point < 300):
|
|
126
|
+
return y_point + 100
|
|
127
|
+
if y_point < size()[1] - 200:
|
|
128
|
+
return y_point + 20
|
|
129
|
+
return y_point - 100
|
|
130
|
+
|
|
131
|
+
def destroy_(self):
|
|
132
|
+
self.window.after_cancel(self.after)
|
|
133
|
+
self.window.destroy()
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""Control-level Windows automation based on pywinauto.
|
|
2
|
+
|
|
3
|
+
Unlike the mouse/image functions, these find controls (buttons, edits, menus, ...)
|
|
4
|
+
through the accessibility tree, so they do not depend on screen resolution,
|
|
5
|
+
theme or window position.
|
|
6
|
+
|
|
7
|
+
Every function takes:
|
|
8
|
+
window: title of the top-level window (regex, matched anywhere in the title,
|
|
9
|
+
like focus/close/minimize) or a pywinauto window object.
|
|
10
|
+
control: what to act on inside the window:
|
|
11
|
+
- None: the window itself
|
|
12
|
+
- str: pywinauto "best match" name, e.g. "Save", "SaveButton", "Edit"
|
|
13
|
+
- dict: pywinauto search criteria, e.g. {"title": "Save", "control_type": "Button"}
|
|
14
|
+
backend: "uia" (default, modern apps) or "win32" (old MFC/WinForms apps).
|
|
15
|
+
timeout: seconds to wait for the window/control to appear.
|
|
16
|
+
|
|
17
|
+
Use list_controls(window) to see the names of the controls in a window.
|
|
18
|
+
pywinauto is only available on Windows: pip install pywinauto
|
|
19
|
+
"""
|
|
20
|
+
from io import StringIO
|
|
21
|
+
from contextlib import redirect_stdout
|
|
22
|
+
|
|
23
|
+
from .utils import copy
|
|
24
|
+
|
|
25
|
+
_TYPE_KEYS_SPECIAL = "{}+^%~()"
|
|
26
|
+
|
|
27
|
+
_VALID_BACKENDS = ("uia", "win32", None)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _check_backend(backend: str):
|
|
31
|
+
if backend not in _VALID_BACKENDS:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
f"backend={backend!r} is not valid on Windows. Use backend='uia' (the default) "
|
|
34
|
+
"or 'win32' here; 'atspi' is the Linux backend (automonkey/atspi_funcs.py)."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _pywinauto():
|
|
39
|
+
try:
|
|
40
|
+
import pywinauto # pylint: disable=import-outside-toplevel
|
|
41
|
+
except ImportError as err:
|
|
42
|
+
raise ImportError(
|
|
43
|
+
"This function needs pywinauto (Windows only): pip install pywinauto"
|
|
44
|
+
) from err
|
|
45
|
+
return pywinauto
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_window(window, backend: str = "uia", timeout: float = 10):
|
|
49
|
+
"""Find a top-level window and wait until it is visible.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
window (str): title of the window, regex matched anywhere in the title.
|
|
53
|
+
A pywinauto window object is returned as it is.
|
|
54
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
55
|
+
timeout (float, optional): seconds to wait for the window. Defaults to 10.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
pywinauto WindowSpecification, for anything not covered by these functions.
|
|
59
|
+
"""
|
|
60
|
+
_check_backend(backend)
|
|
61
|
+
if not isinstance(window, str):
|
|
62
|
+
return window
|
|
63
|
+
spec = _pywinauto().Desktop(backend=backend).window(title_re=f".*?{window}.*?", found_index=0)
|
|
64
|
+
spec.wait("exists visible", timeout=timeout)
|
|
65
|
+
return spec
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _control_spec(win, control):
|
|
69
|
+
if control is None:
|
|
70
|
+
return win
|
|
71
|
+
if isinstance(control, dict):
|
|
72
|
+
return win.child_window(**control)
|
|
73
|
+
return win.child_window(best_match=control)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_control(window, control=None, backend: str = "uia", timeout: float = 10):
|
|
77
|
+
"""Find a control inside a window and wait until it exists.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
window (str): title of the window (regex) or a pywinauto window object.
|
|
81
|
+
control (str | dict, optional): best-match name or search criteria.
|
|
82
|
+
None returns the window itself.
|
|
83
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
84
|
+
timeout (float, optional): seconds to wait. Defaults to 10.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
pywinauto control wrapper
|
|
88
|
+
"""
|
|
89
|
+
return _control_spec(get_window(window, backend, timeout), control).wait("exists", timeout=timeout)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def start_app(command: str, backend: str = "uia", timeout: float = 10):
|
|
93
|
+
"""Start an application with pywinauto.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
command (str): command line / path of the executable, e.g. "notepad.exe"
|
|
97
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
98
|
+
timeout (float, optional): seconds to wait for the app to be ready. Defaults to 10.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
pywinauto Application
|
|
102
|
+
"""
|
|
103
|
+
_check_backend(backend)
|
|
104
|
+
return _pywinauto().Application(backend=backend).start(command, timeout=timeout)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def click_control(window, control=None, button: str = "left", double: bool = False,
|
|
108
|
+
invoke: bool = False, backend: str = "uia", timeout: float = 10):
|
|
109
|
+
"""Click a control.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
window (str): title of the window (regex) or a pywinauto window object.
|
|
113
|
+
control (str | dict, optional): the control to click. None clicks the window.
|
|
114
|
+
button (str, optional): "left", "right" or "middle". Defaults to "left".
|
|
115
|
+
double (bool, optional): double click. Defaults to False.
|
|
116
|
+
invoke (bool, optional): press the control through the accessibility API
|
|
117
|
+
instead of moving the mouse. Works for buttons/menu items even when
|
|
118
|
+
the window is covered. Defaults to False.
|
|
119
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
120
|
+
timeout (float, optional): seconds to wait for the control. Defaults to 10.
|
|
121
|
+
"""
|
|
122
|
+
ctrl = get_control(window, control, backend, timeout)
|
|
123
|
+
if invoke:
|
|
124
|
+
if hasattr(ctrl, "invoke"):
|
|
125
|
+
ctrl.invoke()
|
|
126
|
+
else:
|
|
127
|
+
ctrl.click()
|
|
128
|
+
return
|
|
129
|
+
ctrl.click_input(button=button, double=double)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def double_click_control(window, control=None, backend: str = "uia", timeout: float = 10):
|
|
133
|
+
"""Double click a control. See click_control."""
|
|
134
|
+
click_control(window, control, double=True, backend=backend, timeout=timeout)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def right_click_control(window, control=None, backend: str = "uia", timeout: float = 10):
|
|
138
|
+
"""Right click a control. See click_control."""
|
|
139
|
+
click_control(window, control, button="right", backend=backend, timeout=timeout)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _escape_keys(text: str) -> str:
|
|
143
|
+
return "".join(f"{{{char}}}" if char in _TYPE_KEYS_SPECIAL else char for char in text)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def type_in_control(window, control=None, text: str = "", literal: bool = True, pause: float = None,
|
|
147
|
+
backend: str = "uia", timeout: float = 10):
|
|
148
|
+
"""Focus a control and type text into it with simulated key presses.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
window (str): title of the window (regex) or a pywinauto window object.
|
|
152
|
+
control (str | dict, optional): the control to type into. None types into the window.
|
|
153
|
+
text (str): the text to type.
|
|
154
|
+
literal (bool, optional): type the text as it is. If False the text uses
|
|
155
|
+
pywinauto key syntax: {ENTER}, {TAB}, ^a (ctrl+a), %f (alt+f), +x (shift+x).
|
|
156
|
+
Defaults to True.
|
|
157
|
+
pause (float, optional): seconds between key presses. Increase it if a slow
|
|
158
|
+
or just started app loses characters. Defaults to pywinauto's 0.01.
|
|
159
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
160
|
+
timeout (float, optional): seconds to wait for the control. Defaults to 10.
|
|
161
|
+
"""
|
|
162
|
+
ctrl = get_control(window, control, backend, timeout)
|
|
163
|
+
keys = _escape_keys(text) if literal else text
|
|
164
|
+
ctrl.type_keys(keys, pause=pause, with_spaces=True, with_tabs=True, with_newlines=True, set_foreground=True)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def set_control_text(window, control=None, text: str = "", backend: str = "uia", timeout: float = 10):
|
|
168
|
+
"""Replace the text of an edit control directly, without typing.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
window (str): title of the window (regex) or a pywinauto window object.
|
|
172
|
+
control (str | dict, optional): the edit control.
|
|
173
|
+
text (str): the new text.
|
|
174
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
175
|
+
timeout (float, optional): seconds to wait for the control. Defaults to 10.
|
|
176
|
+
"""
|
|
177
|
+
ctrl = get_control(window, control, backend, timeout)
|
|
178
|
+
if hasattr(ctrl, "set_edit_text"):
|
|
179
|
+
ctrl.set_edit_text(text)
|
|
180
|
+
else: # generic uia control, e.g. a Document: use the Value pattern
|
|
181
|
+
ctrl.iface_value.SetValue(text)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def get_control_text(window, control=None, backend: str = "uia", timeout: float = 10) -> str:
|
|
185
|
+
"""Read the text of a control. The text is also copied to the clipboard.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
window (str): title of the window (regex) or a pywinauto window object.
|
|
189
|
+
control (str | dict, optional): the control. None reads the window title.
|
|
190
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
191
|
+
timeout (float, optional): seconds to wait for the control. Defaults to 10.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
str: the text of the control
|
|
195
|
+
"""
|
|
196
|
+
ctrl = get_control(window, control, backend, timeout)
|
|
197
|
+
text = None
|
|
198
|
+
if hasattr(ctrl, "get_value"): # uia edits: window_text() is only the label
|
|
199
|
+
try:
|
|
200
|
+
text = ctrl.get_value()
|
|
201
|
+
except Exception: # pylint: disable=broad-except
|
|
202
|
+
text = None # control has no Value pattern
|
|
203
|
+
if text is None:
|
|
204
|
+
texts = [_ for _ in ctrl.texts() if _]
|
|
205
|
+
text = "\n".join(texts) if texts else ctrl.window_text()
|
|
206
|
+
copy(text)
|
|
207
|
+
return text
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def select_menu(window, path: str, backend: str = "uia", timeout: float = 10):
|
|
211
|
+
"""Select a menu item.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
window (str): title of the window (regex) or a pywinauto window object.
|
|
215
|
+
path (str): menu path separated by "->", e.g. "File->Save As..."
|
|
216
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
217
|
+
timeout (float, optional): seconds to wait for the window. Defaults to 10.
|
|
218
|
+
"""
|
|
219
|
+
win = get_control(window, None, backend, timeout)
|
|
220
|
+
try:
|
|
221
|
+
win.menu_select(path)
|
|
222
|
+
except AttributeError: # uia finds only a menu bar that is a direct child of the window
|
|
223
|
+
_walk_menu(win, path, timeout)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _find_menu_item(win, name: str):
|
|
227
|
+
for item in win.descendants(control_type="MenuItem"):
|
|
228
|
+
if item.window_text().replace("&", "").strip().lower() == name.lower():
|
|
229
|
+
return item
|
|
230
|
+
raise LookupError(f"Menu item not found: {name}")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _walk_menu(win, path: str, timeout: float):
|
|
234
|
+
"""Open each menu of the path in turn; submenus show up as descendants of the window"""
|
|
235
|
+
wait_until_passes = _pywinauto().timings.wait_until_passes
|
|
236
|
+
names = [_.strip() for _ in path.split("->")]
|
|
237
|
+
for i, name in enumerate(names):
|
|
238
|
+
item = wait_until_passes(timeout, 0.1, _find_menu_item, LookupError, win, name)
|
|
239
|
+
try:
|
|
240
|
+
if i == len(names) - 1:
|
|
241
|
+
item.invoke()
|
|
242
|
+
else:
|
|
243
|
+
item.expand()
|
|
244
|
+
except Exception: # pylint: disable=broad-except
|
|
245
|
+
item.click_input() # item without Invoke / ExpandCollapse pattern
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def select_item(window, control, item, backend: str = "uia", timeout: float = 10):
|
|
249
|
+
"""Select an item in a combo box, list box, list view or tab control.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
window (str): title of the window (regex) or a pywinauto window object.
|
|
253
|
+
control (str | dict): the combo box / list control.
|
|
254
|
+
item (str | int): text or index of the item.
|
|
255
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
256
|
+
timeout (float, optional): seconds to wait for the control. Defaults to 10.
|
|
257
|
+
"""
|
|
258
|
+
get_control(window, control, backend, timeout).select(item)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def check_control(window, control, checked: bool = True, backend: str = "uia", timeout: float = 10):
|
|
262
|
+
"""Check or uncheck a check box / toggle button.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
window (str): title of the window (regex) or a pywinauto window object.
|
|
266
|
+
control (str | dict): the check box.
|
|
267
|
+
checked (bool, optional): wanted state. Defaults to True.
|
|
268
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
269
|
+
timeout (float, optional): seconds to wait for the control. Defaults to 10.
|
|
270
|
+
"""
|
|
271
|
+
ctrl = get_control(window, control, backend, timeout)
|
|
272
|
+
if hasattr(ctrl, "get_toggle_state"): # uia
|
|
273
|
+
if bool(ctrl.get_toggle_state()) != checked:
|
|
274
|
+
ctrl.toggle()
|
|
275
|
+
elif checked:
|
|
276
|
+
ctrl.check()
|
|
277
|
+
else:
|
|
278
|
+
ctrl.uncheck()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def uncheck_control(window, control, backend: str = "uia", timeout: float = 10):
|
|
282
|
+
"""Uncheck a check box / toggle button. See check_control."""
|
|
283
|
+
check_control(window, control, False, backend, timeout)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def wait_control(window, control=None, state: str = "exists visible",
|
|
287
|
+
backend: str = "uia", timeout: float = 10):
|
|
288
|
+
"""Wait until a control reaches a state.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
window (str): title of the window (regex) or a pywinauto window object.
|
|
292
|
+
control (str | dict, optional): the control. None waits for the window.
|
|
293
|
+
state (str, optional): space separated: exists, visible, enabled, ready, active.
|
|
294
|
+
Defaults to "exists visible".
|
|
295
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
296
|
+
timeout (float, optional): seconds to wait. Defaults to 10.
|
|
297
|
+
|
|
298
|
+
Raises:
|
|
299
|
+
pywinauto.timings.TimeoutError: if the state is not reached in time.
|
|
300
|
+
"""
|
|
301
|
+
return _control_spec(get_window(window, backend, timeout), control).wait(state, timeout=timeout)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def control_exists(window, control=None, backend: str = "uia", timeout: float = 0) -> bool:
|
|
305
|
+
"""Check if a window / control exists.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
window (str): title of the window (regex).
|
|
309
|
+
control (str | dict, optional): the control. None checks only the window.
|
|
310
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
311
|
+
timeout (float, optional): seconds to keep looking. Defaults to 0.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
bool: True if found
|
|
315
|
+
"""
|
|
316
|
+
pywinauto = _pywinauto()
|
|
317
|
+
try:
|
|
318
|
+
get_control(window, control, backend, timeout)
|
|
319
|
+
except (pywinauto.timings.TimeoutError, pywinauto.findwindows.ElementNotFoundError):
|
|
320
|
+
return False
|
|
321
|
+
return True
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def list_controls(window, depth: int = None, backend: str = "uia", timeout: float = 10) -> str:
|
|
325
|
+
"""Print the controls of a window with the names usable as `control`.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
window (str): title of the window (regex) or a pywinauto window object.
|
|
329
|
+
depth (int, optional): how many levels deep to go. Defaults to all.
|
|
330
|
+
backend (str, optional): "uia" or "win32". Defaults to "uia".
|
|
331
|
+
timeout (float, optional): seconds to wait for the window. Defaults to 10.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
str: the printed control tree
|
|
335
|
+
"""
|
|
336
|
+
win = get_window(window, backend, timeout)
|
|
337
|
+
out = StringIO()
|
|
338
|
+
with redirect_stdout(out):
|
|
339
|
+
win.print_control_identifiers(depth=depth)
|
|
340
|
+
tree = out.getvalue()
|
|
341
|
+
print(tree)
|
|
342
|
+
return tree
|
|
343
|
+
|
automonkey/utils.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from time import sleep
|
|
2
|
+
|
|
3
|
+
from clipboard import copy as copy1
|
|
4
|
+
from pyperclip import copy as copy2
|
|
5
|
+
|
|
6
|
+
from clipboard import paste
|
|
7
|
+
|
|
8
|
+
from pyautogui import click
|
|
9
|
+
from pyautogui import moveTo
|
|
10
|
+
from pyautogui import mouseUp
|
|
11
|
+
from pyautogui import mouseDown
|
|
12
|
+
from pyautogui import scroll as scrollup
|
|
13
|
+
from pyautogui import hscroll as scrollright
|
|
14
|
+
from .key_funcs import hotkey as keys2 # keyboard-layout aware
|
|
15
|
+
|
|
16
|
+
from .img_funcs import is_on_screen
|
|
17
|
+
|
|
18
|
+
def copy(text: str):
|
|
19
|
+
"""Copy text to clipboard using two functions, falling back to the next one on
|
|
20
|
+
failure - e.g. pyperclip's auto-detected "gtk" backend raises AttributeError
|
|
21
|
+
(not ImportError, so pyperclip's own guard doesn't catch it) once gi.repository
|
|
22
|
+
has been imported in-process, which the AT-SPI backend (atspi_funcs.py) does.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
text (str): text to be copied to clipboard
|
|
26
|
+
"""
|
|
27
|
+
for copy_fn in (copy1, copy2):
|
|
28
|
+
try:
|
|
29
|
+
copy_fn(text)
|
|
30
|
+
except Exception: # pylint: disable=broad-except
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def clear_clipboard():
|
|
35
|
+
"""Try to clear the clipboard by copying an empty string
|
|
36
|
+
Might not work in all cases, for example: in environments
|
|
37
|
+
with shared/multiple clipboards.
|
|
38
|
+
"""
|
|
39
|
+
for _ in range(0, 10):
|
|
40
|
+
copy('')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def copy_from_to(*args) -> str:
|
|
44
|
+
"""This function will copy text from one point to another.
|
|
45
|
+
Args:
|
|
46
|
+
point1 (PyAutoGUI point): PyAutoGUI start point (from)
|
|
47
|
+
point2 (PyAutoGUI point): PyAutoGUI end point (to)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
str: The copied text
|
|
51
|
+
"""
|
|
52
|
+
if len(args) == 1:
|
|
53
|
+
assert isinstance(args[0][0], tuple), "The argument must be a tuple. First point tuple"
|
|
54
|
+
assert isinstance(args[0][1], tuple), "The argument must be a tuple. Second point tuple"
|
|
55
|
+
assert isinstance(args[0][0][0], int), "The argument must be an integer. First point X"
|
|
56
|
+
assert isinstance(args[0][0][1], int), "The argument must be an integer. First point Y"
|
|
57
|
+
assert isinstance(args[0][1][0], int), "The argument must be an integer. Second point X"
|
|
58
|
+
assert isinstance(args[0][1][1], int), "The argument must be an integer. Second point Y"
|
|
59
|
+
point1 = (args[0][0][0], args[0][0][1])
|
|
60
|
+
point2 = (args[0][1][0], args[0][1][1])
|
|
61
|
+
elif len(args) == 2:
|
|
62
|
+
assert isinstance(args[0], tuple), "The argument must be a tuple. First point tuple"
|
|
63
|
+
assert isinstance(args[1], tuple), "The argument must be a tuple. Second point tuple"
|
|
64
|
+
assert isinstance(args[0][0], int), "The argument must be an integer. First point X"
|
|
65
|
+
assert isinstance(args[0][1], int), "The argument must be an integer. First point Y"
|
|
66
|
+
assert isinstance(args[1][0], int), "The argument must be an integer. Second point X"
|
|
67
|
+
assert isinstance(args[1][1], int), "The argument must be an integer. Second point Y"
|
|
68
|
+
point1 = args[0]
|
|
69
|
+
point2 = args[1]
|
|
70
|
+
|
|
71
|
+
mouseDown(point1)
|
|
72
|
+
moveTo(point2)
|
|
73
|
+
mouseUp()
|
|
74
|
+
clear_clipboard()
|
|
75
|
+
|
|
76
|
+
while str(paste()) == '':
|
|
77
|
+
keys2('ctrl', 'c')
|
|
78
|
+
|
|
79
|
+
copied = paste()
|
|
80
|
+
print(copied)
|
|
81
|
+
copy(copied)
|
|
82
|
+
return copied
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def copy_from(point) -> str:
|
|
86
|
+
"""This function will copy text from one point to the end of line.
|
|
87
|
+
This function uses select all functionality (ctrl+a) and as such
|
|
88
|
+
it should be used only when you are sure ctrl+a will select only
|
|
89
|
+
the content you want.
|
|
90
|
+
Args:
|
|
91
|
+
point (PyAutoGUI point): PyAutoGUI start point (from)
|
|
92
|
+
Returns:
|
|
93
|
+
[string]: The copied text.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
clear_clipboard()
|
|
97
|
+
click(point)
|
|
98
|
+
sleep(0.2)
|
|
99
|
+
keys2('ctrl', 'a')
|
|
100
|
+
sleep(0.2)
|
|
101
|
+
|
|
102
|
+
while str(paste()) == '':
|
|
103
|
+
keys2('ctrl', 'c')
|
|
104
|
+
|
|
105
|
+
copied = paste()
|
|
106
|
+
copy(copied)
|
|
107
|
+
|
|
108
|
+
return copied
|
|
109
|
+
|
|
110
|
+
def pastetext(text: str):
|
|
111
|
+
"""Copy the text and paste it in the applicable place
|
|
112
|
+
This type of text output can be used when write or type are not capable
|
|
113
|
+
of writting special characters. For example German or Chinese characters.
|
|
114
|
+
In those cases a simple solution would be to copy the text and just paste it in the
|
|
115
|
+
where needed.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
text (str): text to be output
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
temp_clipboard = paste()
|
|
122
|
+
while paste() != text:
|
|
123
|
+
copy(text)
|
|
124
|
+
keys2('ctrl', 'v')
|
|
125
|
+
copy(temp_clipboard)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def scrolldown(clicks: int):
|
|
129
|
+
"""Scroll down a given number of clicks
|
|
130
|
+
Note: You have to select the scrollable area first
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
clicks (int): number of clicks
|
|
134
|
+
"""
|
|
135
|
+
scrollup(-clicks)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def scrollleft(clicks: int):
|
|
139
|
+
"""Scroll left a given number of clicks
|
|
140
|
+
Note: You have to select the scrollable area first
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
clicks (int): number of clicks
|
|
144
|
+
"""
|
|
145
|
+
scrollright(-clicks)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def waitwhile(img: str):
|
|
149
|
+
"""While an image is on screen wait
|
|
150
|
+
For example a loading window.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
img (str): image location path + name
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
while is_on_screen(img):
|
|
157
|
+
sleep(0.1)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def waituntil(img: str):
|
|
161
|
+
"""Wait until an image appears on screen
|
|
162
|
+
For example wait for a software to start
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
img (str): image location path + name
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
while not is_on_screen(img):
|
|
169
|
+
sleep(0.1)
|