windows-mcp 0.5.7__py3-none-any.whl → 0.5.8__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.
- windows_mcp/__main__.py +314 -312
- windows_mcp/analytics.py +175 -171
- windows_mcp/desktop/config.py +20 -20
- windows_mcp/desktop/service.py +457 -457
- windows_mcp/desktop/views.py +57 -57
- windows_mcp/tree/config.py +50 -50
- windows_mcp/tree/service.py +600 -466
- windows_mcp/tree/utils.py +21 -21
- windows_mcp/tree/views.py +115 -115
- windows_mcp/uia/__init__.py +4 -0
- windows_mcp/uia/controls.py +4781 -0
- windows_mcp/uia/core.py +3269 -0
- windows_mcp/uia/enums.py +1963 -0
- windows_mcp/uia/events.py +83 -0
- windows_mcp/uia/patterns.py +2106 -0
- windows_mcp/watchdog/__init__.py +1 -0
- windows_mcp/watchdog/event_handlers.py +51 -0
- windows_mcp/watchdog/service.py +188 -0
- {windows_mcp-0.5.7.dist-info → windows_mcp-0.5.8.dist-info}/METADATA +4 -4
- windows_mcp-0.5.8.dist-info/RECORD +26 -0
- windows_mcp-0.5.7.dist-info/RECORD +0 -17
- {windows_mcp-0.5.7.dist-info → windows_mcp-0.5.8.dist-info}/WHEEL +0 -0
- {windows_mcp-0.5.7.dist-info → windows_mcp-0.5.8.dist-info}/entry_points.txt +0 -0
- {windows_mcp-0.5.7.dist-info → windows_mcp-0.5.8.dist-info}/licenses/LICENSE.md +0 -0
windows_mcp/uia/core.py
ADDED
|
@@ -0,0 +1,3269 @@
|
|
|
1
|
+
'''
|
|
2
|
+
uiautomation for Python 3.
|
|
3
|
+
Author: yinkaisheng
|
|
4
|
+
Source: https://github.com/yinkaisheng/Python-UIAutomation-for-Windows
|
|
5
|
+
|
|
6
|
+
This module is for UIAutomation on Windows(Windows XP with SP3, Windows Vista and Windows 7/8/8.1/10).
|
|
7
|
+
It supports UIAutomation for the applications which implmented IUIAutomation, such as MFC, Windows Form, WPF, Modern UI(Metro UI), Qt, Firefox and Chrome.
|
|
8
|
+
Run 'automation.py -h' for help.
|
|
9
|
+
|
|
10
|
+
uiautomation is shared under the Apache Licene 2.0.
|
|
11
|
+
This means that the code can be freely copied and distributed, and costs nothing to use.
|
|
12
|
+
'''
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
import datetime
|
|
18
|
+
import re
|
|
19
|
+
import shlex
|
|
20
|
+
import struct
|
|
21
|
+
import atexit
|
|
22
|
+
import threading
|
|
23
|
+
import ctypes
|
|
24
|
+
import ctypes.wintypes
|
|
25
|
+
import comtypes
|
|
26
|
+
import comtypes.client
|
|
27
|
+
from io import TextIOWrapper
|
|
28
|
+
from typing import (Any, Callable, Dict, Generator, List, Tuple, Optional, Union, Sequence)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
METRO_WINDOW_CLASS_NAME = 'Windows.UI.Core.CoreWindow' # for Windows 8 and 8.1
|
|
32
|
+
SEARCH_INTERVAL = 0.5 # search control interval seconds
|
|
33
|
+
MAX_MOVE_SECOND = 1 # simulate mouse move or drag max seconds
|
|
34
|
+
TIME_OUT_SECOND = 10
|
|
35
|
+
OPERATION_WAIT_TIME = 0.5
|
|
36
|
+
MAX_PATH = 260
|
|
37
|
+
DEBUG_SEARCH_TIME = False
|
|
38
|
+
DEBUG_EXIST_DISAPPEAR = False
|
|
39
|
+
S_OK = 0
|
|
40
|
+
|
|
41
|
+
IsPy38OrHigher = sys.version_info[:2] >= (3, 8)
|
|
42
|
+
IsNT6orHigher = os.sys.getwindowsversion().major >= 6
|
|
43
|
+
CurrentProcessIs64Bit = sys.maxsize > 0xFFFFFFFF
|
|
44
|
+
ProcessTime = time.perf_counter # this returns nearly 0 when first call it if python version <= 3.6
|
|
45
|
+
ProcessTime() # need to call it once if python version <= 3.6
|
|
46
|
+
TreeNode = Any
|
|
47
|
+
from .enums import *
|
|
48
|
+
import ctypes.wintypes
|
|
49
|
+
|
|
50
|
+
def WheelDown(wheelTimes: int = 1, interval: float = 0.05, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
51
|
+
for _ in range(wheelTimes):
|
|
52
|
+
mouse_event(MouseEventFlag.Wheel, 0, 0, -120, 0)
|
|
53
|
+
time.sleep(interval)
|
|
54
|
+
time.sleep(waitTime)
|
|
55
|
+
|
|
56
|
+
def WheelUp(wheelTimes: int = 1, interval: float = 0.05, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
57
|
+
for _ in range(wheelTimes):
|
|
58
|
+
mouse_event(MouseEventFlag.Wheel, 0, 0, 120, 0)
|
|
59
|
+
time.sleep(interval)
|
|
60
|
+
time.sleep(waitTime)
|
|
61
|
+
|
|
62
|
+
def GetScreenSize() -> Tuple[int, int]:
|
|
63
|
+
return ctypes.windll.user32.GetSystemMetrics(0), ctypes.windll.user32.GetSystemMetrics(1)
|
|
64
|
+
|
|
65
|
+
def IsTopLevelWindow(handle: int) -> bool:
|
|
66
|
+
return ctypes.windll.user32.GetParent(handle) == 0
|
|
67
|
+
|
|
68
|
+
def IsIconic(handle: int) -> bool:
|
|
69
|
+
return bool(ctypes.windll.user32.IsIconic(handle))
|
|
70
|
+
|
|
71
|
+
def IsZoomed(handle: int) -> bool:
|
|
72
|
+
return bool(ctypes.windll.user32.IsZoomed(handle))
|
|
73
|
+
|
|
74
|
+
def IsWindowVisible(handle: int) -> bool:
|
|
75
|
+
return bool(ctypes.windll.user32.IsWindowVisible(handle))
|
|
76
|
+
|
|
77
|
+
def ShowWindow(handle: int, cmdShow: int) -> bool:
|
|
78
|
+
return bool(ctypes.windll.user32.ShowWindow(handle, cmdShow))
|
|
79
|
+
|
|
80
|
+
def GetForegroundWindow() -> int:
|
|
81
|
+
return ctypes.windll.user32.GetForegroundWindow()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class _AutomationClient:
|
|
85
|
+
_instance = None
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def instance(cls) -> '_AutomationClient':
|
|
89
|
+
"""Singleton instance (this prevents com creation on import)."""
|
|
90
|
+
if cls._instance is None:
|
|
91
|
+
cls._instance = cls()
|
|
92
|
+
return cls._instance
|
|
93
|
+
|
|
94
|
+
def __init__(self):
|
|
95
|
+
tryCount = 3
|
|
96
|
+
for retry in range(tryCount):
|
|
97
|
+
try:
|
|
98
|
+
self.UIAutomationCore = comtypes.client.GetModule("UIAutomationCore.dll")
|
|
99
|
+
self.IUIAutomation = comtypes.client.CreateObject("{ff48dba4-60ef-4201-aa87-54103eef594e}", interface=self.UIAutomationCore.IUIAutomation)
|
|
100
|
+
self.ViewWalker = self.IUIAutomation.RawViewWalker
|
|
101
|
+
#self.ViewWalker = self.IUIAutomation.ControlViewWalker
|
|
102
|
+
break
|
|
103
|
+
except Exception as ex:
|
|
104
|
+
if retry + 1 == tryCount:
|
|
105
|
+
Logger.WriteLine('''
|
|
106
|
+
{}
|
|
107
|
+
Can not load UIAutomationCore.dll.
|
|
108
|
+
1, You may need to install Windows Update KB971513 if your OS is Windows XP, see https://github.com/yinkaisheng/WindowsUpdateKB971513ForIUIAutomation
|
|
109
|
+
2, You need to use an UIAutomationInitializerInThread object if use uiautomation in a thread, see demos/uiautomation_in_thread.py'''.format(ex), ConsoleColor.Yellow)
|
|
110
|
+
raise ex
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class _DllClient:
|
|
114
|
+
_instance = None
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def instance(cls) -> '_DllClient':
|
|
118
|
+
"""Singleton instance (this prevents com creation on import)."""
|
|
119
|
+
if cls._instance is None:
|
|
120
|
+
cls._instance = cls()
|
|
121
|
+
return cls._instance
|
|
122
|
+
|
|
123
|
+
def __init__(self):
|
|
124
|
+
binPath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
|
|
125
|
+
os.environ["PATH"] = binPath + os.pathsep + os.environ["PATH"]
|
|
126
|
+
load = False
|
|
127
|
+
if IsPy38OrHigher and IsNT6orHigher:
|
|
128
|
+
os.add_dll_directory(binPath)
|
|
129
|
+
if CurrentProcessIs64Bit:
|
|
130
|
+
try:
|
|
131
|
+
self.dll = ctypes.cdll.UIAutomationClient_VC140_X64
|
|
132
|
+
load = True
|
|
133
|
+
except Exception as ex:
|
|
134
|
+
print(ex)
|
|
135
|
+
else:
|
|
136
|
+
try:
|
|
137
|
+
self.dll = ctypes.cdll.UIAutomationClient_VC140_X86
|
|
138
|
+
load = True
|
|
139
|
+
except Exception as ex:
|
|
140
|
+
print(ex)
|
|
141
|
+
if load:
|
|
142
|
+
self.dll.BitmapCreate.restype = ctypes.c_size_t
|
|
143
|
+
self.dll.BitmapGetWidthAndHeight.restype = ctypes.c_uint64
|
|
144
|
+
self.dll.BitmapFromWindow.restype = ctypes.c_size_t
|
|
145
|
+
self.dll.BitmapFromHBITMAP.restype = ctypes.c_size_t
|
|
146
|
+
self.dll.BitmapToHBITMAP.restype = ctypes.c_size_t
|
|
147
|
+
self.dll.BitmapFromFile.restype = ctypes.c_size_t
|
|
148
|
+
self.dll.BitmapFromBytes.restype = ctypes.c_size_t
|
|
149
|
+
self.dll.BitmapResize.restype = ctypes.c_size_t
|
|
150
|
+
self.dll.BitmapRotate.restype = ctypes.c_size_t
|
|
151
|
+
self.dll.BitmapGetPixel.restype = ctypes.c_uint32
|
|
152
|
+
self.dll.Initialize()
|
|
153
|
+
else:
|
|
154
|
+
self.dll = None
|
|
155
|
+
Logger.WriteLine('Can not load dll.\nFunctionalities related to Bitmap are not available.\nYou may need to install Microsoft Visual C++ 2015 Redistributable Package.', ConsoleColor.Yellow)
|
|
156
|
+
|
|
157
|
+
def __del__(self):
|
|
158
|
+
if self.dll:
|
|
159
|
+
self.dll.Uninitialize()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# set Windows dll restype
|
|
163
|
+
ctypes.windll.user32.GetAncestor.restype = ctypes.c_void_p
|
|
164
|
+
ctypes.windll.user32.GetClipboardData.restype = ctypes.c_void_p
|
|
165
|
+
ctypes.windll.user32.GetDC.restype = ctypes.c_void_p
|
|
166
|
+
ctypes.windll.user32.GetForegroundWindow.restype = ctypes.c_void_p
|
|
167
|
+
ctypes.windll.user32.GetWindowDC.restype = ctypes.c_void_p
|
|
168
|
+
ctypes.windll.user32.GetWindowLongW.restype = ctypes.wintypes.LONG
|
|
169
|
+
ctypes.windll.user32.OpenDesktopW.restype = ctypes.c_void_p
|
|
170
|
+
ctypes.windll.user32.SendMessageW.restype = ctypes.wintypes.LONG
|
|
171
|
+
ctypes.windll.user32.WindowFromPoint.restype = ctypes.c_void_p
|
|
172
|
+
ctypes.windll.gdi32.CreateBitmap.restype = ctypes.c_void_p
|
|
173
|
+
ctypes.windll.gdi32.CreateCompatibleDC.restype = ctypes.c_void_p
|
|
174
|
+
ctypes.windll.gdi32.SelectObject.restype = ctypes.c_void_p
|
|
175
|
+
ctypes.windll.kernel32.GetConsoleWindow.restype = ctypes.c_void_p
|
|
176
|
+
ctypes.windll.kernel32.GetStdHandle.restype = ctypes.c_void_p
|
|
177
|
+
ctypes.windll.kernel32.GlobalAlloc.restype = ctypes.c_void_p
|
|
178
|
+
ctypes.windll.kernel32.GlobalLock.restype = ctypes.c_void_p
|
|
179
|
+
ctypes.windll.kernel32.OpenProcess.restype = ctypes.c_void_p
|
|
180
|
+
ctypes.windll.ntdll.NtQueryInformationProcess.restype = ctypes.c_uint32
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _GetDictKeyName(theDict: Dict[str, Any], theValue: Any, keyCondition: Optional[Callable[[str], bool]] = None) -> str:
|
|
184
|
+
for key, value in theDict.items():
|
|
185
|
+
if keyCondition:
|
|
186
|
+
if keyCondition(key) and theValue == value:
|
|
187
|
+
return key
|
|
188
|
+
else:
|
|
189
|
+
if theValue == value:
|
|
190
|
+
return key
|
|
191
|
+
return ''
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
_StdOutputHandle = -11
|
|
195
|
+
_ConsoleOutputHandle = ctypes.c_void_p(0)
|
|
196
|
+
_DefaultConsoleColor = None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def SetConsoleColor(color: int) -> bool:
|
|
200
|
+
"""
|
|
201
|
+
Change the text color on console window.
|
|
202
|
+
color: int, a value in class `ConsoleColor`.
|
|
203
|
+
Return bool, True if succeed otherwise False.
|
|
204
|
+
"""
|
|
205
|
+
global _ConsoleOutputHandle
|
|
206
|
+
global _DefaultConsoleColor
|
|
207
|
+
if not _DefaultConsoleColor:
|
|
208
|
+
if not _ConsoleOutputHandle:
|
|
209
|
+
_ConsoleOutputHandle = ctypes.c_void_p(ctypes.windll.kernel32.GetStdHandle(_StdOutputHandle))
|
|
210
|
+
bufferInfo = ConsoleScreenBufferInfo()
|
|
211
|
+
ctypes.windll.kernel32.GetConsoleScreenBufferInfo(_ConsoleOutputHandle, ctypes.byref(bufferInfo))
|
|
212
|
+
_DefaultConsoleColor = int(bufferInfo.wAttributes & 0xFF)
|
|
213
|
+
if sys.stdout:
|
|
214
|
+
sys.stdout.flush()
|
|
215
|
+
return bool(ctypes.windll.kernel32.SetConsoleTextAttribute(_ConsoleOutputHandle, ctypes.c_ushort(color)))
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def ResetConsoleColor() -> bool:
|
|
219
|
+
"""
|
|
220
|
+
Reset to the default text color on console window.
|
|
221
|
+
Return bool, True if succeed otherwise False.
|
|
222
|
+
"""
|
|
223
|
+
if sys.stdout:
|
|
224
|
+
sys.stdout.flush()
|
|
225
|
+
assert _DefaultConsoleColor is not None, 'SetConsoleColor not previously called.'
|
|
226
|
+
return bool(ctypes.windll.kernel32.SetConsoleTextAttribute(_ConsoleOutputHandle, ctypes.c_ushort(_DefaultConsoleColor)))
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def WindowFromPoint(x: int, y: int) -> int:
|
|
230
|
+
"""
|
|
231
|
+
WindowFromPoint from Win32.
|
|
232
|
+
Return int, a native window handle.
|
|
233
|
+
"""
|
|
234
|
+
return ctypes.windll.user32.WindowFromPoint(ctypes.wintypes.POINT(x, y)) # or ctypes.windll.user32.WindowFromPoint(x, y)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def GetCursorPos() -> Tuple[int, int]:
|
|
238
|
+
"""
|
|
239
|
+
GetCursorPos from Win32.
|
|
240
|
+
Get current mouse cursor positon.
|
|
241
|
+
Return Tuple[int, int], two ints tuple (x, y).
|
|
242
|
+
"""
|
|
243
|
+
point = ctypes.wintypes.POINT(0, 0)
|
|
244
|
+
ctypes.windll.user32.GetCursorPos(ctypes.byref(point))
|
|
245
|
+
return point.x, point.y
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def GetPhysicalCursorPos() -> Tuple[int, int]:
|
|
249
|
+
"""
|
|
250
|
+
GetPhysicalCursorPos from Win32.
|
|
251
|
+
Get current mouse cursor positon.
|
|
252
|
+
Return Tuple[int, int], two ints tuple (x, y).
|
|
253
|
+
"""
|
|
254
|
+
point = ctypes.wintypes.POINT(0, 0)
|
|
255
|
+
ctypes.windll.user32.GetPhysicalCursorPos(ctypes.byref(point))
|
|
256
|
+
return point.x, point.y
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def SetCursorPos(x: int, y: int) -> bool:
|
|
260
|
+
"""
|
|
261
|
+
SetCursorPos from Win32.
|
|
262
|
+
Set mouse cursor to point x, y.
|
|
263
|
+
x: int.
|
|
264
|
+
y: int.
|
|
265
|
+
Return bool, True if succeed otherwise False.
|
|
266
|
+
"""
|
|
267
|
+
return bool(ctypes.windll.user32.SetCursorPos(x, y))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def GetDoubleClickTime() -> int:
|
|
271
|
+
"""
|
|
272
|
+
GetDoubleClickTime from Win32.
|
|
273
|
+
Return int, in milliseconds.
|
|
274
|
+
"""
|
|
275
|
+
return ctypes.windll.user32.GetDoubleClickTime()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def mouse_event(dwFlags: int, dx: int, dy: int, dwData: int, dwExtraInfo: int) -> None:
|
|
279
|
+
"""mouse_event from Win32."""
|
|
280
|
+
ctypes.windll.user32.mouse_event(dwFlags, dx, dy, dwData, dwExtraInfo)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def keybd_event(bVk: int, bScan: int, dwFlags: int, dwExtraInfo: int) -> None:
|
|
284
|
+
"""keybd_event from Win32."""
|
|
285
|
+
ctypes.windll.user32.keybd_event(bVk, bScan, dwFlags, dwExtraInfo)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def PostMessage(handle: int, msg: int, wParam: int, lParam: int) -> bool:
|
|
289
|
+
"""
|
|
290
|
+
PostMessage from Win32.
|
|
291
|
+
Return bool, True if succeed otherwise False.
|
|
292
|
+
"""
|
|
293
|
+
return bool(ctypes.windll.user32.PostMessageW(ctypes.c_void_p(handle), ctypes.c_uint(msg), ctypes.wintypes.WPARAM(wParam), ctypes.wintypes.LPARAM(lParam)))
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def SendMessage(handle: int, msg: int, wParam: int, lParam: int) -> int:
|
|
297
|
+
"""
|
|
298
|
+
SendMessage from Win32.
|
|
299
|
+
Return int, the return value specifies the result of the message processing;
|
|
300
|
+
it depends on the message sent.
|
|
301
|
+
"""
|
|
302
|
+
return ctypes.windll.user32.SendMessageW(ctypes.c_void_p(handle), ctypes.c_uint(msg), ctypes.wintypes.WPARAM(wParam), ctypes.wintypes.LPARAM(lParam))
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def Click(x: int, y: int, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
306
|
+
"""
|
|
307
|
+
Simulate mouse click at point x, y.
|
|
308
|
+
x: int.
|
|
309
|
+
y: int.
|
|
310
|
+
waitTime: float.
|
|
311
|
+
"""
|
|
312
|
+
SetCursorPos(x, y)
|
|
313
|
+
screenWidth, screenHeight = GetScreenSize()
|
|
314
|
+
mouse_event(MouseEventFlag.LeftDown | MouseEventFlag.Absolute, x * 65535 // screenWidth, y * 65535 // screenHeight, 0, 0)
|
|
315
|
+
time.sleep(0.05)
|
|
316
|
+
mouse_event(MouseEventFlag.LeftUp | MouseEventFlag.Absolute, x * 65535 // screenWidth, y * 65535 // screenHeight, 0, 0)
|
|
317
|
+
time.sleep(waitTime)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def MiddleClick(x: int, y: int, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
321
|
+
"""
|
|
322
|
+
Simulate mouse middle click at point x, y.
|
|
323
|
+
x: int.
|
|
324
|
+
y: int.
|
|
325
|
+
waitTime: float.
|
|
326
|
+
"""
|
|
327
|
+
SetCursorPos(x, y)
|
|
328
|
+
screenWidth, screenHeight = GetScreenSize()
|
|
329
|
+
mouse_event(MouseEventFlag.MiddleDown | MouseEventFlag.Absolute, x * 65535 // screenWidth, y * 65535 // screenHeight, 0, 0)
|
|
330
|
+
time.sleep(0.05)
|
|
331
|
+
mouse_event(MouseEventFlag.MiddleUp | MouseEventFlag.Absolute, x * 65535 // screenWidth, y * 65535 // screenHeight, 0, 0)
|
|
332
|
+
time.sleep(waitTime)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def RightClick(x: int, y: int, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
336
|
+
"""
|
|
337
|
+
Simulate mouse right click at point x, y.
|
|
338
|
+
x: int.
|
|
339
|
+
y: int.
|
|
340
|
+
waitTime: float.
|
|
341
|
+
"""
|
|
342
|
+
SetCursorPos(x, y)
|
|
343
|
+
screenWidth, screenHeight = GetScreenSize()
|
|
344
|
+
mouse_event(MouseEventFlag.RightDown | MouseEventFlag.Absolute, x * 65535 // screenWidth, y * 65535 // screenHeight, 0, 0)
|
|
345
|
+
time.sleep(0.05)
|
|
346
|
+
mouse_event(MouseEventFlag.RightUp | MouseEventFlag.Absolute, x * 65535 // screenWidth, y * 65535 // screenHeight, 0, 0)
|
|
347
|
+
time.sleep(waitTime)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def PressMouse(x: int, y: int, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
351
|
+
"""
|
|
352
|
+
Press left mouse.
|
|
353
|
+
x: int.
|
|
354
|
+
y: int.
|
|
355
|
+
waitTime: float.
|
|
356
|
+
"""
|
|
357
|
+
SetCursorPos(x, y)
|
|
358
|
+
screenWidth, screenHeight = GetScreenSize()
|
|
359
|
+
mouse_event(MouseEventFlag.LeftDown | MouseEventFlag.Absolute, x * 65535 // screenWidth, y * 65535 // screenHeight, 0, 0)
|
|
360
|
+
time.sleep(waitTime)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def ReleaseMouse(waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
364
|
+
"""
|
|
365
|
+
Release left mouse.
|
|
366
|
+
waitTime: float.
|
|
367
|
+
"""
|
|
368
|
+
x, y = GetCursorPos()
|
|
369
|
+
screenWidth, screenHeight = GetScreenSize()
|
|
370
|
+
mouse_event(MouseEventFlag.LeftUp | MouseEventFlag.Absolute, x * 65535 // screenWidth, y * 65535 // screenHeight, 0, 0)
|
|
371
|
+
time.sleep(waitTime)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def RightPressMouse(x: int, y: int, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
375
|
+
"""
|
|
376
|
+
Press right mouse.
|
|
377
|
+
x: int.
|
|
378
|
+
y: int.
|
|
379
|
+
waitTime: float.
|
|
380
|
+
"""
|
|
381
|
+
SetCursorPos(x, y)
|
|
382
|
+
screenWidth, screenHeight = GetScreenSize()
|
|
383
|
+
mouse_event(MouseEventFlag.RightDown | MouseEventFlag.Absolute, x * 65535 // screenWidth, y * 65535 // screenHeight, 0, 0)
|
|
384
|
+
time.sleep(waitTime)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def RightReleaseMouse(waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
388
|
+
"""
|
|
389
|
+
Release right mouse.
|
|
390
|
+
waitTime: float.
|
|
391
|
+
"""
|
|
392
|
+
x, y = GetCursorPos()
|
|
393
|
+
screenWidth, screenHeight = GetScreenSize()
|
|
394
|
+
mouse_event(MouseEventFlag.RightUp | MouseEventFlag.Absolute, x * 65535 // screenWidth, y * 65535 // screenHeight, 0, 0)
|
|
395
|
+
time.sleep(waitTime)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def MiddlePressMouse(x: int, y: int, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
399
|
+
"""
|
|
400
|
+
Press middle mouse.
|
|
401
|
+
x: int.
|
|
402
|
+
y: int.
|
|
403
|
+
waitTime: float.
|
|
404
|
+
"""
|
|
405
|
+
SetCursorPos(x, y)
|
|
406
|
+
screenWidth, screenHeight = GetScreenSize()
|
|
407
|
+
mouse_event(MouseEventFlag.MiddleDown | MouseEventFlag.Absolute, x * 65535 // screenWidth, y * 65535 // screenHeight, 0, 0)
|
|
408
|
+
time.sleep(waitTime)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def MiddleReleaseMouse(waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
412
|
+
"""
|
|
413
|
+
Release middle mouse.
|
|
414
|
+
waitTime: float.
|
|
415
|
+
"""
|
|
416
|
+
x, y = GetCursorPos()
|
|
417
|
+
screenWidth, screenHeight = GetScreenSize()
|
|
418
|
+
mouse_event(MouseEventFlag.MiddleUp | MouseEventFlag.Absolute, x * 65535 // screenWidth, y * 65535 // screenHeight, 0, 0)
|
|
419
|
+
time.sleep(waitTime)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def MoveTo(x: int, y: int, moveSpeed: float = 1, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
423
|
+
"""
|
|
424
|
+
Simulate mouse move to point x, y from current cursor.
|
|
425
|
+
x: int.
|
|
426
|
+
y: int.
|
|
427
|
+
moveSpeed: float, 1 normal speed, < 1 move slower, > 1 move faster.
|
|
428
|
+
waitTime: float.
|
|
429
|
+
"""
|
|
430
|
+
if moveSpeed <= 0:
|
|
431
|
+
moveTime = 0.
|
|
432
|
+
else:
|
|
433
|
+
moveTime = MAX_MOVE_SECOND / moveSpeed
|
|
434
|
+
curX, curY = GetCursorPos()
|
|
435
|
+
xCount = abs(x - curX)
|
|
436
|
+
yCount = abs(y - curY)
|
|
437
|
+
maxPoint = max(xCount, yCount)
|
|
438
|
+
screenWidth, screenHeight = GetScreenSize()
|
|
439
|
+
maxSide = max(screenWidth, screenHeight)
|
|
440
|
+
minSide = min(screenWidth, screenHeight)
|
|
441
|
+
if maxPoint > minSide:
|
|
442
|
+
maxPoint = minSide
|
|
443
|
+
if maxPoint < maxSide:
|
|
444
|
+
maxPoint = 100 + int((maxSide - 100) / maxSide * maxPoint)
|
|
445
|
+
moveTime = moveTime * maxPoint * 1.0 / maxSide
|
|
446
|
+
stepCount = maxPoint // 20
|
|
447
|
+
if stepCount > 1:
|
|
448
|
+
xStep = (x - curX) * 1.0 / stepCount
|
|
449
|
+
yStep = (y - curY) * 1.0 / stepCount
|
|
450
|
+
interval = moveTime / stepCount
|
|
451
|
+
for i in range(stepCount):
|
|
452
|
+
cx = curX + int(xStep * i)
|
|
453
|
+
cy = curY + int(yStep * i)
|
|
454
|
+
# upper-left(0,0), lower-right(65536,65536)
|
|
455
|
+
# mouse_event(MouseEventFlag.Move | MouseEventFlag.Absolute, cx*65536//screenWidth, cy*65536//screenHeight, 0, 0)
|
|
456
|
+
SetCursorPos(cx, cy)
|
|
457
|
+
time.sleep(interval)
|
|
458
|
+
SetCursorPos(x, y)
|
|
459
|
+
time.sleep(waitTime)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def DragDrop(x1: int, y1: int, x2: int, y2: int, moveSpeed: float = 1, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
463
|
+
"""
|
|
464
|
+
Simulate mouse left button drag from point x1, y1 drop to point x2, y2.
|
|
465
|
+
x1: int.
|
|
466
|
+
y1: int.
|
|
467
|
+
x2: int.
|
|
468
|
+
y2: int.
|
|
469
|
+
moveSpeed: float, 1 normal speed, < 1 move slower, > 1 move faster.
|
|
470
|
+
waitTime: float.
|
|
471
|
+
"""
|
|
472
|
+
PressMouse(x1, y1, 0.05)
|
|
473
|
+
MoveTo(x2, y2, moveSpeed, 0.05)
|
|
474
|
+
ReleaseMouse(waitTime)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def RightDragDrop(x1: int, y1: int, x2: int, y2: int, moveSpeed: float = 1, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
478
|
+
"""
|
|
479
|
+
Simulate mouse right button drag from point x1, y1 drop to point x2, y2.
|
|
480
|
+
x1: int.
|
|
481
|
+
y1: int.
|
|
482
|
+
x2: int.
|
|
483
|
+
y2: int.
|
|
484
|
+
moveSpeed: float, 1 normal speed, < 1 move slower, > 1 move faster.
|
|
485
|
+
waitTime: float.
|
|
486
|
+
"""
|
|
487
|
+
RightPressMouse(x1, y1, 0.05)
|
|
488
|
+
MoveTo(x2, y2, moveSpeed, 0.05)
|
|
489
|
+
RightReleaseMouse(waitTime)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def MiddleDragDrop(x1: int, y1: int, x2: int, y2: int, moveSpeed: float = 1, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
493
|
+
"""
|
|
494
|
+
Simulate mouse middle button drag from point x1, y1 drop to point x2, y2.
|
|
495
|
+
x1: int.
|
|
496
|
+
y1: int.
|
|
497
|
+
x2: int.
|
|
498
|
+
y2: int.
|
|
499
|
+
moveSpeed: float, 1 normal speed, < 1 move slower, > 1 move faster.
|
|
500
|
+
waitTime: float.
|
|
501
|
+
"""
|
|
502
|
+
MiddlePressMouse(x1, y1, 0.05)
|
|
503
|
+
MoveTo(x2, y2, moveSpeed, 0.05)
|
|
504
|
+
MiddleReleaseMouse(waitTime)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def WheelDown(wheelTimes: int = 1, interval: float = 0.05, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
508
|
+
"""
|
|
509
|
+
Simulate mouse wheel down.
|
|
510
|
+
wheelTimes: int.
|
|
511
|
+
interval: float.
|
|
512
|
+
waitTime: float.
|
|
513
|
+
"""
|
|
514
|
+
for _i in range(wheelTimes):
|
|
515
|
+
mouse_event(MouseEventFlag.Wheel, 0, 0, -120, 0) # WHEEL_DELTA=120
|
|
516
|
+
time.sleep(interval)
|
|
517
|
+
time.sleep(waitTime)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def WheelUp(wheelTimes: int = 1, interval: float = 0.05, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
521
|
+
"""
|
|
522
|
+
Simulate mouse wheel up.
|
|
523
|
+
wheelTimes: int.
|
|
524
|
+
interval: float.
|
|
525
|
+
waitTime: float.
|
|
526
|
+
"""
|
|
527
|
+
for _i in range(wheelTimes):
|
|
528
|
+
mouse_event(MouseEventFlag.Wheel, 0, 0, 120, 0) # WHEEL_DELTA=120
|
|
529
|
+
time.sleep(interval)
|
|
530
|
+
time.sleep(waitTime)
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def GetScreenSize() -> Tuple[int, int]:
|
|
534
|
+
"""
|
|
535
|
+
Return Tuple[int, int], two ints tuple (width, height).
|
|
536
|
+
"""
|
|
537
|
+
SM_CXSCREEN = 0
|
|
538
|
+
SM_CYSCREEN = 1
|
|
539
|
+
w = ctypes.windll.user32.GetSystemMetrics(SM_CXSCREEN)
|
|
540
|
+
h = ctypes.windll.user32.GetSystemMetrics(SM_CYSCREEN)
|
|
541
|
+
return w, h
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def SetScreenSize(width: int, height: int) -> bool:
|
|
545
|
+
"""
|
|
546
|
+
Return bool.
|
|
547
|
+
"""
|
|
548
|
+
# the size of DEVMODEW structure is too big for wrapping in ctypes,
|
|
549
|
+
# so I use bytearray to simulate the structure.
|
|
550
|
+
devModeSize = 220
|
|
551
|
+
dmSizeOffset = 68
|
|
552
|
+
dmFieldsOffset = 72
|
|
553
|
+
dmPelsWidthOffset = 172
|
|
554
|
+
DM_PELSWIDTH = 0x00080000
|
|
555
|
+
DM_PELSHEIGHT = 0x00100000
|
|
556
|
+
DISP_CHANGE_SUCCESSFUL = 0
|
|
557
|
+
devMode = bytearray(devModeSize)
|
|
558
|
+
devMode[dmSizeOffset:dmSizeOffset+2] = struct.pack("<H", devModeSize)
|
|
559
|
+
cDevMode = (ctypes.c_byte*devModeSize).from_buffer(devMode)
|
|
560
|
+
if ctypes.windll.user32.EnumDisplaySettingsW(None, ctypes.wintypes.DWORD(-1), cDevMode):
|
|
561
|
+
curWidth, curHeight = struct.unpack("<II", devMode[dmPelsWidthOffset:dmPelsWidthOffset+8])
|
|
562
|
+
if curWidth == width and curHeight == height:
|
|
563
|
+
return True
|
|
564
|
+
devMode[dmFieldsOffset:dmFieldsOffset+4] = struct.pack("<I", DM_PELSWIDTH | DM_PELSHEIGHT)
|
|
565
|
+
devMode[dmPelsWidthOffset:dmPelsWidthOffset+8] = struct.pack("<II", width, height)
|
|
566
|
+
if ctypes.windll.user32.ChangeDisplaySettingsW(cDevMode, 0) == DISP_CHANGE_SUCCESSFUL:
|
|
567
|
+
return True
|
|
568
|
+
return False
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def GetVirtualScreenSize() -> Tuple[int, int]:
|
|
572
|
+
"""
|
|
573
|
+
Return Tuple[int, int], two ints tuple (width, height).
|
|
574
|
+
"""
|
|
575
|
+
SM_CXVIRTUALSCREEN = 78
|
|
576
|
+
SM_CYVIRTUALSCREEN = 79
|
|
577
|
+
w = ctypes.windll.user32.GetSystemMetrics(SM_CXVIRTUALSCREEN)
|
|
578
|
+
h = ctypes.windll.user32.GetSystemMetrics(SM_CYVIRTUALSCREEN)
|
|
579
|
+
return w, h
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def GetMonitorsRect() -> List[Rect]:
|
|
583
|
+
"""
|
|
584
|
+
Get monitors' rect.
|
|
585
|
+
Return List[Rect].
|
|
586
|
+
"""
|
|
587
|
+
MonitorEnumProc = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_size_t, ctypes.c_size_t, ctypes.POINTER(ctypes.wintypes.RECT), ctypes.c_size_t)
|
|
588
|
+
rects = []
|
|
589
|
+
|
|
590
|
+
def MonitorCallback(hMonitor: int, hdcMonitor: int, lprcMonitor: ctypes.POINTER(ctypes.wintypes.RECT), dwData: int):
|
|
591
|
+
rect = Rect(lprcMonitor.contents.left, lprcMonitor.contents.top, lprcMonitor.contents.right, lprcMonitor.contents.bottom)
|
|
592
|
+
rects.append(rect)
|
|
593
|
+
return 1
|
|
594
|
+
ret = ctypes.windll.user32.EnumDisplayMonitors(ctypes.c_void_p(0), ctypes.c_void_p(0), MonitorEnumProc(MonitorCallback), 0)
|
|
595
|
+
return rects
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def GetPixelColor(x: int, y: int, handle: int = 0) -> int:
|
|
599
|
+
"""
|
|
600
|
+
Get pixel color of a native window.
|
|
601
|
+
x: int.
|
|
602
|
+
y: int.
|
|
603
|
+
handle: int, the handle of a native window.
|
|
604
|
+
Return int, the bgr value of point (x,y).
|
|
605
|
+
r = bgr & 0x0000FF
|
|
606
|
+
g = (bgr & 0x00FF00) >> 8
|
|
607
|
+
b = (bgr & 0xFF0000) >> 16
|
|
608
|
+
If handle is 0, get pixel from Desktop window(root control).
|
|
609
|
+
Note:
|
|
610
|
+
Not all devices support GetPixel.
|
|
611
|
+
An application should call GetDeviceCaps to determine whether a specified device supports this function.
|
|
612
|
+
For example, console window doesn't support.
|
|
613
|
+
"""
|
|
614
|
+
hdc = ctypes.windll.user32.GetWindowDC(ctypes.c_void_p(handle))
|
|
615
|
+
bgr = ctypes.windll.gdi32.GetPixel(hdc, x, y)
|
|
616
|
+
ctypes.windll.user32.ReleaseDC(ctypes.c_void_p(handle), ctypes.c_void_p(hdc))
|
|
617
|
+
return bgr
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def MessageBox(content: str, title: str, flags: int = MB.Ok) -> int:
|
|
621
|
+
"""
|
|
622
|
+
MessageBox from Win32.
|
|
623
|
+
content: str.
|
|
624
|
+
title: str.
|
|
625
|
+
flags: int, a value or some combined values in class `MB`.
|
|
626
|
+
Return int, a value in MB whose name starts with Id, such as MB.IdOk
|
|
627
|
+
"""
|
|
628
|
+
return ctypes.windll.user32.MessageBoxW(ctypes.c_void_p(0), ctypes.c_wchar_p(content), ctypes.c_wchar_p(title), ctypes.c_uint(flags))
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def SetForegroundWindow(handle: int) -> bool:
|
|
632
|
+
"""
|
|
633
|
+
SetForegroundWindow from Win32.
|
|
634
|
+
handle: int, the handle of a native window.
|
|
635
|
+
Return bool, True if succeed otherwise False.
|
|
636
|
+
"""
|
|
637
|
+
return bool(ctypes.windll.user32.SetForegroundWindow(ctypes.c_void_p(handle)))
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def BringWindowToTop(handle: int) -> bool:
|
|
641
|
+
"""
|
|
642
|
+
BringWindowToTop from Win32.
|
|
643
|
+
handle: int, the handle of a native window.
|
|
644
|
+
Return bool, True if succeed otherwise False.
|
|
645
|
+
"""
|
|
646
|
+
return bool(ctypes.windll.user32.BringWindowToTop(ctypes.c_void_p(handle)))
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def SwitchToThisWindow(handle: int) -> None:
|
|
650
|
+
"""
|
|
651
|
+
SwitchToThisWindow from Win32.
|
|
652
|
+
handle: int, the handle of a native window.
|
|
653
|
+
"""
|
|
654
|
+
ctypes.windll.user32.SwitchToThisWindow(ctypes.c_void_p(handle), ctypes.c_int(1)) # void function, no return
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def GetAncestor(handle: int, flag: int) -> int:
|
|
658
|
+
"""
|
|
659
|
+
GetAncestor from Win32.
|
|
660
|
+
handle: int, the handle of a native window.
|
|
661
|
+
index: int, a value in class `GAFlag`.
|
|
662
|
+
Return int, a native window handle.
|
|
663
|
+
"""
|
|
664
|
+
return ctypes.windll.user32.GetAncestor(ctypes.c_void_p(handle), ctypes.c_int(flag))
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def IsTopLevelWindow(handle: int) -> bool:
|
|
668
|
+
"""
|
|
669
|
+
IsTopLevelWindow from Win32.
|
|
670
|
+
handle: int, the handle of a native window.
|
|
671
|
+
Return bool.
|
|
672
|
+
Only available on Windows 7 or Higher.
|
|
673
|
+
"""
|
|
674
|
+
return bool(ctypes.windll.user32.IsTopLevelWindow(ctypes.c_void_p(handle)))
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def GetWindowLong(handle: int, index: int) -> int:
|
|
678
|
+
"""
|
|
679
|
+
GetWindowLong from Win32.
|
|
680
|
+
handle: int, the handle of a native window.
|
|
681
|
+
index: int.
|
|
682
|
+
"""
|
|
683
|
+
return ctypes.windll.user32.GetWindowLongW(ctypes.c_void_p(handle), ctypes.c_int(index))
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def SetWindowLong(handle: int, index: int, value: int) -> int:
|
|
687
|
+
"""
|
|
688
|
+
SetWindowLong from Win32.
|
|
689
|
+
handle: int, the handle of a native window.
|
|
690
|
+
index: int.
|
|
691
|
+
value: int.
|
|
692
|
+
Return int, the previous value before set.
|
|
693
|
+
"""
|
|
694
|
+
return ctypes.windll.user32.SetWindowLongW(ctypes.c_void_p(handle), index, value)
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def IsIconic(handle: int) -> bool:
|
|
698
|
+
"""
|
|
699
|
+
IsIconic from Win32.
|
|
700
|
+
Determine whether a native window is minimized.
|
|
701
|
+
handle: int, the handle of a native window.
|
|
702
|
+
Return bool.
|
|
703
|
+
"""
|
|
704
|
+
return bool(ctypes.windll.user32.IsIconic(ctypes.c_void_p(handle)))
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def IsZoomed(handle: int) -> bool:
|
|
708
|
+
"""
|
|
709
|
+
IsZoomed from Win32.
|
|
710
|
+
Determine whether a native window is maximized.
|
|
711
|
+
handle: int, the handle of a native window.
|
|
712
|
+
Return bool.
|
|
713
|
+
"""
|
|
714
|
+
return bool(ctypes.windll.user32.IsZoomed(ctypes.c_void_p(handle)))
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def IsWindowVisible(handle: int) -> bool:
|
|
718
|
+
"""
|
|
719
|
+
IsWindowVisible from Win32.
|
|
720
|
+
handle: int, the handle of a native window.
|
|
721
|
+
Return bool.
|
|
722
|
+
"""
|
|
723
|
+
return bool(ctypes.windll.user32.IsWindowVisible(ctypes.c_void_p(handle)))
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def ShowWindow(handle: int, cmdShow: int) -> bool:
|
|
727
|
+
"""
|
|
728
|
+
ShowWindow from Win32.
|
|
729
|
+
handle: int, the handle of a native window.
|
|
730
|
+
cmdShow: int, a value in clas `SW`.
|
|
731
|
+
Return bool, True if succeed otherwise False.
|
|
732
|
+
"""
|
|
733
|
+
return bool(ctypes.windll.user32.ShowWindow(ctypes.c_void_p(handle), ctypes.c_int(cmdShow)))
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def MoveWindow(handle: int, x: int, y: int, width: int, height: int, repaint: int = 1) -> bool:
|
|
737
|
+
"""
|
|
738
|
+
MoveWindow from Win32.
|
|
739
|
+
handle: int, the handle of a native window.
|
|
740
|
+
x: int.
|
|
741
|
+
y: int.
|
|
742
|
+
width: int.
|
|
743
|
+
height: int.
|
|
744
|
+
repaint: int, use 1 or 0.
|
|
745
|
+
Return bool, True if succeed otherwise False.
|
|
746
|
+
"""
|
|
747
|
+
return bool(ctypes.windll.user32.MoveWindow(ctypes.c_void_p(handle), ctypes.c_int(x), ctypes.c_int(y), ctypes.c_int(width), ctypes.c_int(height), ctypes.c_int(repaint)))
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def SetWindowPos(handle: int, hWndInsertAfter: int, x: int, y: int, width: int, height: int, flags: int) -> bool:
|
|
751
|
+
"""
|
|
752
|
+
SetWindowPos from Win32.
|
|
753
|
+
handle: int, the handle of a native window.
|
|
754
|
+
hWndInsertAfter: int, a value whose name starts with 'HWND' in class SWP.
|
|
755
|
+
x: int.
|
|
756
|
+
y: int.
|
|
757
|
+
width: int.
|
|
758
|
+
height: int.
|
|
759
|
+
flags: int, values whose name starts with 'SWP' in class `SWP`.
|
|
760
|
+
Return bool, True if succeed otherwise False.
|
|
761
|
+
"""
|
|
762
|
+
return bool(ctypes.windll.user32.SetWindowPos(ctypes.c_void_p(handle), ctypes.c_void_p(hWndInsertAfter), ctypes.c_int(x), ctypes.c_int(y), ctypes.c_int(width), ctypes.c_int(height), ctypes.c_uint(flags)))
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def SetWindowTopmost(handle: int, isTopmost: bool) -> bool:
|
|
766
|
+
"""
|
|
767
|
+
handle: int, the handle of a native window.
|
|
768
|
+
isTopmost: bool
|
|
769
|
+
Return bool, True if succeed otherwise False.
|
|
770
|
+
"""
|
|
771
|
+
topValue = SWP.HWND_Topmost if isTopmost else SWP.HWND_NoTopmost
|
|
772
|
+
return SetWindowPos(handle, topValue, 0, 0, 0, 0, SWP.SWP_NoSize | SWP.SWP_NoMove)
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def GetWindowText(handle: int) -> str:
|
|
776
|
+
"""
|
|
777
|
+
GetWindowText from Win32.
|
|
778
|
+
handle: int, the handle of a native window.
|
|
779
|
+
Return str.
|
|
780
|
+
"""
|
|
781
|
+
arrayType = ctypes.c_wchar * MAX_PATH
|
|
782
|
+
values = arrayType()
|
|
783
|
+
ctypes.windll.user32.GetWindowTextW(ctypes.c_void_p(handle), values, ctypes.c_int(MAX_PATH))
|
|
784
|
+
return values.value
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def SetWindowText(handle: int, text: str) -> bool:
|
|
788
|
+
"""
|
|
789
|
+
SetWindowText from Win32.
|
|
790
|
+
handle: int, the handle of a native window.
|
|
791
|
+
text: str.
|
|
792
|
+
Return bool, True if succeed otherwise False.
|
|
793
|
+
"""
|
|
794
|
+
return bool(ctypes.windll.user32.SetWindowTextW(ctypes.c_void_p(handle), ctypes.c_wchar_p(text)))
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def GetEditText(handle: int) -> str:
|
|
798
|
+
"""
|
|
799
|
+
Get text of a native Win32 Edit.
|
|
800
|
+
handle: int, the handle of a native window.
|
|
801
|
+
Return str.
|
|
802
|
+
"""
|
|
803
|
+
textLen = SendMessage(handle, 0x000E, 0, 0) + 1 # WM_GETTEXTLENGTH
|
|
804
|
+
arrayType = ctypes.c_wchar * textLen
|
|
805
|
+
values = arrayType()
|
|
806
|
+
SendMessage(handle, 0x000D, textLen, ctypes.addressof(values)) # WM_GETTEXT
|
|
807
|
+
return values.value
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
def GetConsoleOriginalTitle() -> str:
|
|
811
|
+
"""
|
|
812
|
+
GetConsoleOriginalTitle from Win32.
|
|
813
|
+
Return str.
|
|
814
|
+
Only available on Windows Vista or higher.
|
|
815
|
+
"""
|
|
816
|
+
if IsNT6orHigher:
|
|
817
|
+
arrayType = ctypes.c_wchar * MAX_PATH
|
|
818
|
+
values = arrayType()
|
|
819
|
+
ctypes.windll.kernel32.GetConsoleOriginalTitleW(values, ctypes.c_uint(MAX_PATH))
|
|
820
|
+
return values.value
|
|
821
|
+
else:
|
|
822
|
+
raise RuntimeError('GetConsoleOriginalTitle is not supported on Windows XP or lower.')
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def GetConsoleTitle() -> str:
|
|
826
|
+
"""
|
|
827
|
+
GetConsoleTitle from Win32.
|
|
828
|
+
Return str.
|
|
829
|
+
"""
|
|
830
|
+
arrayType = ctypes.c_wchar * MAX_PATH
|
|
831
|
+
values = arrayType()
|
|
832
|
+
ctypes.windll.kernel32.GetConsoleTitleW(values, ctypes.c_uint(MAX_PATH))
|
|
833
|
+
return values.value
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
def SetConsoleTitle(text: str) -> bool:
|
|
837
|
+
"""
|
|
838
|
+
SetConsoleTitle from Win32.
|
|
839
|
+
text: str.
|
|
840
|
+
Return bool, True if succeed otherwise False.
|
|
841
|
+
"""
|
|
842
|
+
return bool(ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(text)))
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def GetForegroundWindow() -> int:
|
|
846
|
+
"""
|
|
847
|
+
GetForegroundWindow from Win32.
|
|
848
|
+
Return int, the native handle of the foreground window.
|
|
849
|
+
"""
|
|
850
|
+
return ctypes.windll.user32.GetForegroundWindow()
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
def DwmIsCompositionEnabled() -> bool:
|
|
854
|
+
"""
|
|
855
|
+
DwmIsCompositionEnabled from dwmapi.
|
|
856
|
+
Return bool.
|
|
857
|
+
"""
|
|
858
|
+
try:
|
|
859
|
+
dwmapi = ctypes.WinDLL('dwmapi')
|
|
860
|
+
dwmapi.DwmIsCompositionEnabled.restype = ctypes.HRESULT
|
|
861
|
+
isEnabled = ctypes.wintypes.BOOL()
|
|
862
|
+
hr = dwmapi.DwmIsCompositionEnabled(ctypes.byref(isEnabled))
|
|
863
|
+
if hr == S_OK:
|
|
864
|
+
return bool(isEnabled.value)
|
|
865
|
+
else:
|
|
866
|
+
return False
|
|
867
|
+
except:
|
|
868
|
+
return False
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def DwmGetWindowExtendFrameBounds(handle: int) -> Optional[Rect]:
|
|
872
|
+
"""
|
|
873
|
+
Get Native Window Rect without invisible resize borders.
|
|
874
|
+
Return Rect or None. If handle is not top level, return None.
|
|
875
|
+
"""
|
|
876
|
+
try:
|
|
877
|
+
DWMWA_EXTENDED_FRAME_BOUNDS = 9
|
|
878
|
+
dwmapi = ctypes.WinDLL('dwmapi')
|
|
879
|
+
dwmapi.DwmGetWindowAttribute.restype = ctypes.HRESULT
|
|
880
|
+
rect = ctypes.wintypes.RECT()
|
|
881
|
+
hr = dwmapi.DwmGetWindowAttribute(
|
|
882
|
+
ctypes.c_void_p(handle),
|
|
883
|
+
DWMWA_EXTENDED_FRAME_BOUNDS,
|
|
884
|
+
ctypes.byref(rect),
|
|
885
|
+
ctypes.sizeof(ctypes.wintypes.RECT)
|
|
886
|
+
)
|
|
887
|
+
if hr == S_OK:
|
|
888
|
+
return Rect(rect.left, rect.top, rect.right, rect.bottom)
|
|
889
|
+
return None
|
|
890
|
+
except:
|
|
891
|
+
return None
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
def GetWindowRect(handle: int) -> Optional[Rect]:
|
|
895
|
+
"""
|
|
896
|
+
GetWindowRect from user32.
|
|
897
|
+
Return RECT.
|
|
898
|
+
"""
|
|
899
|
+
user32 = ctypes.windll.user32
|
|
900
|
+
rect = ctypes.wintypes.RECT()
|
|
901
|
+
success = user32.GetWindowRect(ctypes.c_void_p(handle), ctypes.byref(rect))
|
|
902
|
+
if success:
|
|
903
|
+
return Rect(rect.left, rect.top, rect.right, rect.bottom)
|
|
904
|
+
return None
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def IsDesktopLocked() -> bool:
|
|
908
|
+
"""
|
|
909
|
+
Check if desktop is locked.
|
|
910
|
+
Return bool.
|
|
911
|
+
Desktop is locked if press Win+L, Ctrl+Alt+Del or in remote desktop mode.
|
|
912
|
+
"""
|
|
913
|
+
isLocked = False
|
|
914
|
+
desk = ctypes.windll.user32.OpenDesktopW(ctypes.c_wchar_p('Default'), ctypes.c_uint(0), ctypes.c_int(0), ctypes.c_uint(0x0100)) # DESKTOP_SWITCHDESKTOP = 0x0100
|
|
915
|
+
if desk:
|
|
916
|
+
isLocked = not ctypes.windll.user32.SwitchDesktop(ctypes.c_void_p(desk))
|
|
917
|
+
ctypes.windll.user32.CloseDesktop(ctypes.c_void_p(desk))
|
|
918
|
+
return isLocked
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def PlayWaveFile(filePath: str = r'C:\Windows\Media\notify.wav', isAsync: bool = False, isLoop: bool = False) -> bool:
|
|
922
|
+
"""
|
|
923
|
+
Call PlaySound from Win32.
|
|
924
|
+
filePath: str, if emtpy, stop playing the current sound.
|
|
925
|
+
isAsync: bool, if True, the sound is played asynchronously and returns immediately.
|
|
926
|
+
isLoop: bool, if True, the sound plays repeatedly until PlayWaveFile(None) is called again, must also set isAsync to True.
|
|
927
|
+
Return bool, True if succeed otherwise False.
|
|
928
|
+
"""
|
|
929
|
+
if filePath:
|
|
930
|
+
SND_ASYNC = 0x0001
|
|
931
|
+
SND_NODEFAULT = 0x0002
|
|
932
|
+
SND_LOOP = 0x0008
|
|
933
|
+
SND_FILENAME = 0x20000
|
|
934
|
+
flags = SND_NODEFAULT | SND_FILENAME
|
|
935
|
+
if isAsync:
|
|
936
|
+
flags |= SND_ASYNC
|
|
937
|
+
if isLoop:
|
|
938
|
+
flags |= SND_LOOP
|
|
939
|
+
flags |= SND_ASYNC
|
|
940
|
+
return bool(ctypes.windll.winmm.PlaySoundW(ctypes.c_wchar_p(filePath), ctypes.c_void_p(0), ctypes.c_uint(flags)))
|
|
941
|
+
else:
|
|
942
|
+
return bool(ctypes.windll.winmm.PlaySoundW(ctypes.c_wchar_p(0), ctypes.c_void_p(0), ctypes.c_uint(0)))
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def IsProcess64Bit(processId: int) -> Optional[bool]:
|
|
946
|
+
"""
|
|
947
|
+
Return True if process is 64 bit.
|
|
948
|
+
Return False if process is 32 bit.
|
|
949
|
+
Return None if unknown, maybe caused by having no access right to the process.
|
|
950
|
+
"""
|
|
951
|
+
try:
|
|
952
|
+
IsWow64Process = ctypes.windll.kernel32.IsWow64Process
|
|
953
|
+
except Exception as ex:
|
|
954
|
+
return False
|
|
955
|
+
hProcess = ctypes.windll.kernel32.OpenProcess(0x1000, 0, processId) # PROCESS_QUERY_INFORMATION=0x0400,PROCESS_QUERY_LIMITED_INFORMATION=0x1000
|
|
956
|
+
if hProcess:
|
|
957
|
+
hProcess = ctypes.c_void_p(hProcess)
|
|
958
|
+
isWow64 = ctypes.wintypes.BOOL()
|
|
959
|
+
if IsWow64Process(hProcess, ctypes.byref(isWow64)):
|
|
960
|
+
ctypes.windll.kernel32.CloseHandle(hProcess)
|
|
961
|
+
return not isWow64
|
|
962
|
+
else:
|
|
963
|
+
ctypes.windll.kernel32.CloseHandle(hProcess)
|
|
964
|
+
return None
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
def IsUserAnAdmin() -> bool:
|
|
968
|
+
"""
|
|
969
|
+
IsUserAnAdmin from Win32.
|
|
970
|
+
Return bool.
|
|
971
|
+
Minimum supported OS: Windows XP, Windows Server 2003
|
|
972
|
+
"""
|
|
973
|
+
return bool(ctypes.windll.shell32.IsUserAnAdmin()) if IsNT6orHigher else True
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def RunScriptAsAdmin(argv: List[str], workingDirectory: str = None, showFlag: int = SW.ShowNormal) -> bool:
|
|
977
|
+
"""
|
|
978
|
+
Run a python script as administrator.
|
|
979
|
+
System will show a popup dialog askes you whether to elevate as administrator if UAC is enabled.
|
|
980
|
+
argv: List[str], a str list like sys.argv, argv[0] is the script file, argv[1:] are other arguments.
|
|
981
|
+
workingDirectory: str, the working directory for the script file.
|
|
982
|
+
showFlag: int, a value in class `SW`.
|
|
983
|
+
Return bool, True if succeed.
|
|
984
|
+
"""
|
|
985
|
+
args = shlex.join(argv)
|
|
986
|
+
return ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, args, workingDirectory, showFlag) > 32
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
def SendKey(key: int, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
990
|
+
"""
|
|
991
|
+
Simulate typing a key.
|
|
992
|
+
key: int, a value in class `Keys`.
|
|
993
|
+
"""
|
|
994
|
+
keybd_event(key, 0, KeyboardEventFlag.KeyDown | KeyboardEventFlag.ExtendedKey, 0)
|
|
995
|
+
keybd_event(key, 0, KeyboardEventFlag.KeyUp | KeyboardEventFlag.ExtendedKey, 0)
|
|
996
|
+
time.sleep(waitTime)
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
def PressKey(key: int, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
1000
|
+
"""
|
|
1001
|
+
Simulate a key down for key.
|
|
1002
|
+
key: int, a value in class `Keys`.
|
|
1003
|
+
waitTime: float.
|
|
1004
|
+
"""
|
|
1005
|
+
keybd_event(key, 0, KeyboardEventFlag.KeyDown | KeyboardEventFlag.ExtendedKey, 0)
|
|
1006
|
+
time.sleep(waitTime)
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
def ReleaseKey(key: int, waitTime: float = OPERATION_WAIT_TIME) -> None:
|
|
1010
|
+
"""
|
|
1011
|
+
Simulate a key up for key.
|
|
1012
|
+
key: int, a value in class `Keys`.
|
|
1013
|
+
waitTime: float.
|
|
1014
|
+
"""
|
|
1015
|
+
keybd_event(key, 0, KeyboardEventFlag.KeyUp | KeyboardEventFlag.ExtendedKey, 0)
|
|
1016
|
+
time.sleep(waitTime)
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def IsKeyPressed(key: int) -> bool:
|
|
1020
|
+
"""
|
|
1021
|
+
key: int, a value in class `Keys`.
|
|
1022
|
+
Return bool.
|
|
1023
|
+
"""
|
|
1024
|
+
state = ctypes.windll.user32.GetAsyncKeyState(key)
|
|
1025
|
+
return bool(state & 0x8000)
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
def _CreateInput(structure) -> INPUT:
|
|
1029
|
+
"""
|
|
1030
|
+
Create Win32 struct `INPUT` for `SendInput`.
|
|
1031
|
+
Return `INPUT`.
|
|
1032
|
+
"""
|
|
1033
|
+
if isinstance(structure, MOUSEINPUT):
|
|
1034
|
+
return INPUT(InputType.Mouse, _INPUTUnion(mi=structure))
|
|
1035
|
+
if isinstance(structure, KEYBDINPUT):
|
|
1036
|
+
return INPUT(InputType.Keyboard, _INPUTUnion(ki=structure))
|
|
1037
|
+
if isinstance(structure, HARDWAREINPUT):
|
|
1038
|
+
return INPUT(InputType.Hardware, _INPUTUnion(hi=structure))
|
|
1039
|
+
raise TypeError('Cannot create INPUT structure!')
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
def MouseInput(dx: int, dy: int, mouseData: int = 0, dwFlags: int = MouseEventFlag.LeftDown, time_: int = 0) -> INPUT:
|
|
1043
|
+
"""
|
|
1044
|
+
Create Win32 struct `MOUSEINPUT` for `SendInput`.
|
|
1045
|
+
Return `INPUT`.
|
|
1046
|
+
"""
|
|
1047
|
+
return _CreateInput(MOUSEINPUT(dx, dy, mouseData, dwFlags, time_, None))
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
def KeyboardInput(wVk: int, wScan: int, dwFlags: int = KeyboardEventFlag.KeyDown, time_: int = 0) -> INPUT:
|
|
1051
|
+
"""Create Win32 struct `KEYBDINPUT` for `SendInput`."""
|
|
1052
|
+
return _CreateInput(KEYBDINPUT(wVk, wScan, dwFlags, time_, None))
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
def HardwareInput(uMsg: int, param: int = 0) -> INPUT:
|
|
1056
|
+
"""Create Win32 struct `HARDWAREINPUT` for `SendInput`."""
|
|
1057
|
+
return _CreateInput(HARDWAREINPUT(uMsg, param & 0xFFFF, param >> 16 & 0xFFFF))
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
def SendInput(*inputs) -> int:
|
|
1061
|
+
"""
|
|
1062
|
+
SendInput from Win32.
|
|
1063
|
+
input: `INPUT`.
|
|
1064
|
+
Return int, the number of events that it successfully inserted into the keyboard or mouse input stream.
|
|
1065
|
+
If the function returns zero, the input was already blocked by another thread.
|
|
1066
|
+
"""
|
|
1067
|
+
cbSize = ctypes.c_int(ctypes.sizeof(INPUT))
|
|
1068
|
+
for ip in inputs:
|
|
1069
|
+
ret = ctypes.windll.user32.SendInput(1, ctypes.byref(ip), cbSize)
|
|
1070
|
+
return ret
|
|
1071
|
+
# or one call
|
|
1072
|
+
#nInputs = len(inputs)
|
|
1073
|
+
#LPINPUT = INPUT * nInputs
|
|
1074
|
+
#pInputs = LPINPUT(*inputs)
|
|
1075
|
+
#cbSize = ctypes.c_int(ctypes.sizeof(INPUT))
|
|
1076
|
+
# return ctypes.windll.user32.SendInput(nInputs, ctypes.byref(pInputs), cbSize)
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
def SendUnicodeChar(char: str, charMode: bool = True) -> int:
|
|
1080
|
+
"""
|
|
1081
|
+
Type a single unicode char.
|
|
1082
|
+
char: str, len(char) must equal to 1.
|
|
1083
|
+
charMode: bool, if False, the char typied is depend on the input method if a input method is on.
|
|
1084
|
+
Return int, the number of events that it successfully inserted into the keyboard or mouse input stream.
|
|
1085
|
+
If the function returns zero, the input was already blocked by another thread.
|
|
1086
|
+
"""
|
|
1087
|
+
if charMode:
|
|
1088
|
+
vk = 0
|
|
1089
|
+
scan = ord(char)
|
|
1090
|
+
flag = KeyboardEventFlag.KeyUnicode
|
|
1091
|
+
else:
|
|
1092
|
+
res = ctypes.windll.user32.VkKeyScanW(ctypes.wintypes.WCHAR(char))
|
|
1093
|
+
if (res >> 8) & 0xFF == 0:
|
|
1094
|
+
vk = res & 0xFF
|
|
1095
|
+
scan = 0
|
|
1096
|
+
flag = 0
|
|
1097
|
+
else:
|
|
1098
|
+
vk = 0
|
|
1099
|
+
scan = ord(char)
|
|
1100
|
+
flag = KeyboardEventFlag.KeyUnicode
|
|
1101
|
+
return SendInput(KeyboardInput(vk, scan, flag | KeyboardEventFlag.KeyDown),
|
|
1102
|
+
KeyboardInput(vk, scan, flag | KeyboardEventFlag.KeyUp))
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
_SCKeys = {
|
|
1106
|
+
Keys.VK_LSHIFT: 0x02A,
|
|
1107
|
+
Keys.VK_RSHIFT: 0x136,
|
|
1108
|
+
Keys.VK_LCONTROL: 0x01D,
|
|
1109
|
+
Keys.VK_RCONTROL: 0x11D,
|
|
1110
|
+
Keys.VK_LMENU: 0x038,
|
|
1111
|
+
Keys.VK_RMENU: 0x138,
|
|
1112
|
+
Keys.VK_LWIN: 0x15B,
|
|
1113
|
+
Keys.VK_RWIN: 0x15C,
|
|
1114
|
+
Keys.VK_NUMPAD0: 0x52,
|
|
1115
|
+
Keys.VK_NUMPAD1: 0x4F,
|
|
1116
|
+
Keys.VK_NUMPAD2: 0x50,
|
|
1117
|
+
Keys.VK_NUMPAD3: 0x51,
|
|
1118
|
+
Keys.VK_NUMPAD4: 0x4B,
|
|
1119
|
+
Keys.VK_NUMPAD5: 0x4C,
|
|
1120
|
+
Keys.VK_NUMPAD6: 0x4D,
|
|
1121
|
+
Keys.VK_NUMPAD7: 0x47,
|
|
1122
|
+
Keys.VK_NUMPAD8: 0x48,
|
|
1123
|
+
Keys.VK_NUMPAD9: 0x49,
|
|
1124
|
+
Keys.VK_DECIMAL: 0x53,
|
|
1125
|
+
Keys.VK_NUMLOCK: 0x145,
|
|
1126
|
+
Keys.VK_DIVIDE: 0x135,
|
|
1127
|
+
Keys.VK_MULTIPLY: 0x037,
|
|
1128
|
+
Keys.VK_SUBTRACT: 0x04A,
|
|
1129
|
+
Keys.VK_ADD: 0x04E,
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
def _VKtoSC(key: int) -> int:
|
|
1134
|
+
"""
|
|
1135
|
+
This function is only for internal use in SendKeys.
|
|
1136
|
+
key: int, a value in class `Keys`.
|
|
1137
|
+
Return int.
|
|
1138
|
+
"""
|
|
1139
|
+
if key in _SCKeys:
|
|
1140
|
+
return _SCKeys[key]
|
|
1141
|
+
scanCode = ctypes.windll.user32.MapVirtualKeyA(key, 0)
|
|
1142
|
+
if not scanCode:
|
|
1143
|
+
return 0
|
|
1144
|
+
keyList = [Keys.VK_APPS, Keys.VK_CANCEL, Keys.VK_SNAPSHOT, Keys.VK_DIVIDE, Keys.VK_NUMLOCK]
|
|
1145
|
+
if key in keyList:
|
|
1146
|
+
scanCode |= 0x0100
|
|
1147
|
+
return scanCode
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
def SendKeys(text: str, interval: float = 0.01, waitTime: float = OPERATION_WAIT_TIME, charMode: bool = True, debug: bool = False) -> None:
|
|
1151
|
+
"""
|
|
1152
|
+
Simulate typing keys on keyboard.
|
|
1153
|
+
text: str, keys to type.
|
|
1154
|
+
interval: float, seconds between keys.
|
|
1155
|
+
waitTime: float.
|
|
1156
|
+
charMode: bool, if False, the text typed is depend on the input method if a input method is on.
|
|
1157
|
+
debug: bool, if True, print the keys.
|
|
1158
|
+
Examples:
|
|
1159
|
+
{Ctrl}, {Delete} ... are special keys' name in SpecialKeyNames.
|
|
1160
|
+
SendKeys('{Ctrl}a{Delete}{Ctrl}v{Ctrl}s{Ctrl}{Shift}s{Win}e{PageDown}') #press Ctrl+a, Delete, Ctrl+v, Ctrl+s, Ctrl+Shift+s, Win+e, PageDown
|
|
1161
|
+
SendKeys('{Ctrl}(AB)({Shift}(123))') #press Ctrl+A+B, type '(', press Shift+1+2+3, type ')', if '()' follows a hold key, hold key won't release until ')'
|
|
1162
|
+
SendKeys('{Ctrl}{a 3}') #press Ctrl+a at the same time, release Ctrl+a, then type 'a' 2 times
|
|
1163
|
+
SendKeys('{a 3}{B 5}') #type 'a' 3 times, type 'B' 5 times
|
|
1164
|
+
SendKeys('{{}Hello{}}abc {a}{b}{c} test{} 3}{!}{a} (){(}{)}') #type: '{Hello}abc abc test}}}!a ()()'
|
|
1165
|
+
SendKeys('0123456789{Enter}')
|
|
1166
|
+
SendKeys('ABCDEFGHIJKLMNOPQRSTUVWXYZ{Enter}')
|
|
1167
|
+
SendKeys('abcdefghijklmnopqrstuvwxyz{Enter}')
|
|
1168
|
+
SendKeys('`~!@#$%^&*()-_=+{Enter}')
|
|
1169
|
+
SendKeys('[]{{}{}}\\|;:\'\",<.>/?{Enter}')
|
|
1170
|
+
"""
|
|
1171
|
+
holdKeys = ('WIN', 'LWIN', 'RWIN', 'SHIFT', 'LSHIFT', 'RSHIFT', 'CTRL', 'CONTROL', 'LCTRL', 'RCTRL', 'LCONTROL', 'LCONTROL', 'ALT', 'LALT', 'RALT')
|
|
1172
|
+
keys = []
|
|
1173
|
+
printKeys = []
|
|
1174
|
+
i = 0
|
|
1175
|
+
insertIndex = 0
|
|
1176
|
+
length = len(text)
|
|
1177
|
+
hold = False
|
|
1178
|
+
include = False
|
|
1179
|
+
lastKeyValue = None
|
|
1180
|
+
while True:
|
|
1181
|
+
if text[i] == '{':
|
|
1182
|
+
rindex = text.find('}', i)
|
|
1183
|
+
if rindex == i + 1: # {}}
|
|
1184
|
+
rindex = text.find('}', i + 2)
|
|
1185
|
+
if rindex == -1:
|
|
1186
|
+
raise ValueError('"{" or "{}" is not valid, use "{{}" for "{", use "{}}" for "}"')
|
|
1187
|
+
keyStr = text[i + 1:rindex]
|
|
1188
|
+
key = [it for it in keyStr.split(' ') if it]
|
|
1189
|
+
if not key:
|
|
1190
|
+
raise ValueError('"{}" is not valid, use "{{Space}}" or " " for " "'.format(text[i:rindex + 1]))
|
|
1191
|
+
if (len(key) == 2 and not key[1].isdigit()) or len(key) > 2:
|
|
1192
|
+
raise ValueError('"{}" is not valid'.format(text[i:rindex + 1]))
|
|
1193
|
+
upperKey = key[0].upper()
|
|
1194
|
+
count = 1
|
|
1195
|
+
if len(key) > 1:
|
|
1196
|
+
count = int(key[1])
|
|
1197
|
+
for _j in range(count):
|
|
1198
|
+
if hold:
|
|
1199
|
+
if upperKey in SpecialKeyNames:
|
|
1200
|
+
keyValue = SpecialKeyNames[upperKey]
|
|
1201
|
+
if type(lastKeyValue) == type(keyValue) and lastKeyValue == keyValue:
|
|
1202
|
+
insertIndex += 1
|
|
1203
|
+
printKeys.insert(insertIndex, (key[0], 'KeyDown | ExtendedKey'))
|
|
1204
|
+
printKeys.insert(insertIndex + 1, (key[0], 'KeyUp | ExtendedKey'))
|
|
1205
|
+
keys.insert(insertIndex, (keyValue, KeyboardEventFlag.KeyDown | KeyboardEventFlag.ExtendedKey))
|
|
1206
|
+
keys.insert(insertIndex + 1, (keyValue, KeyboardEventFlag.KeyUp | KeyboardEventFlag.ExtendedKey))
|
|
1207
|
+
lastKeyValue = keyValue
|
|
1208
|
+
elif key[0] in CharacterCodes:
|
|
1209
|
+
keyValue = CharacterCodes[key[0]]
|
|
1210
|
+
if type(lastKeyValue) == type(keyValue) and lastKeyValue == keyValue:
|
|
1211
|
+
insertIndex += 1
|
|
1212
|
+
printKeys.insert(insertIndex, (key[0], 'KeyDown | ExtendedKey'))
|
|
1213
|
+
printKeys.insert(insertIndex + 1, (key[0], 'KeyUp | ExtendedKey'))
|
|
1214
|
+
keys.insert(insertIndex, (keyValue, KeyboardEventFlag.KeyDown | KeyboardEventFlag.ExtendedKey))
|
|
1215
|
+
keys.insert(insertIndex + 1, (keyValue, KeyboardEventFlag.KeyUp | KeyboardEventFlag.ExtendedKey))
|
|
1216
|
+
lastKeyValue = keyValue
|
|
1217
|
+
else:
|
|
1218
|
+
printKeys.insert(insertIndex, (key[0], 'UnicodeChar'))
|
|
1219
|
+
keys.insert(insertIndex, (key[0], 'UnicodeChar'))
|
|
1220
|
+
lastKeyValue = key[0]
|
|
1221
|
+
if include:
|
|
1222
|
+
insertIndex += 1
|
|
1223
|
+
else:
|
|
1224
|
+
if upperKey in holdKeys:
|
|
1225
|
+
insertIndex += 1
|
|
1226
|
+
else:
|
|
1227
|
+
hold = False
|
|
1228
|
+
else:
|
|
1229
|
+
if upperKey in SpecialKeyNames:
|
|
1230
|
+
keyValue = SpecialKeyNames[upperKey]
|
|
1231
|
+
printKeys.append((key[0], 'KeyDown | ExtendedKey'))
|
|
1232
|
+
printKeys.append((key[0], 'KeyUp | ExtendedKey'))
|
|
1233
|
+
keys.append((keyValue, KeyboardEventFlag.KeyDown | KeyboardEventFlag.ExtendedKey))
|
|
1234
|
+
keys.append((keyValue, KeyboardEventFlag.KeyUp | KeyboardEventFlag.ExtendedKey))
|
|
1235
|
+
lastKeyValue = keyValue
|
|
1236
|
+
if upperKey in holdKeys:
|
|
1237
|
+
hold = True
|
|
1238
|
+
insertIndex = len(keys) - 1
|
|
1239
|
+
else:
|
|
1240
|
+
hold = False
|
|
1241
|
+
else:
|
|
1242
|
+
printKeys.append((key[0], 'UnicodeChar'))
|
|
1243
|
+
keys.append((key[0], 'UnicodeChar'))
|
|
1244
|
+
lastKeyValue = key[0]
|
|
1245
|
+
i = rindex + 1
|
|
1246
|
+
elif text[i] == '(':
|
|
1247
|
+
if hold:
|
|
1248
|
+
include = True
|
|
1249
|
+
else:
|
|
1250
|
+
printKeys.append((text[i], 'UnicodeChar'))
|
|
1251
|
+
keys.append((text[i], 'UnicodeChar'))
|
|
1252
|
+
lastKeyValue = text[i]
|
|
1253
|
+
i += 1
|
|
1254
|
+
elif text[i] == ')':
|
|
1255
|
+
if hold:
|
|
1256
|
+
include = False
|
|
1257
|
+
hold = False
|
|
1258
|
+
else:
|
|
1259
|
+
printKeys.append((text[i], 'UnicodeChar'))
|
|
1260
|
+
keys.append((text[i], 'UnicodeChar'))
|
|
1261
|
+
lastKeyValue = text[i]
|
|
1262
|
+
i += 1
|
|
1263
|
+
else:
|
|
1264
|
+
if hold:
|
|
1265
|
+
if text[i] in CharacterCodes:
|
|
1266
|
+
keyValue = CharacterCodes[text[i]]
|
|
1267
|
+
if include and type(lastKeyValue) == type(keyValue) and lastKeyValue == keyValue:
|
|
1268
|
+
insertIndex += 1
|
|
1269
|
+
printKeys.insert(insertIndex, (text[i], 'KeyDown | ExtendedKey'))
|
|
1270
|
+
printKeys.insert(insertIndex + 1, (text[i], 'KeyUp | ExtendedKey'))
|
|
1271
|
+
keys.insert(insertIndex, (keyValue, KeyboardEventFlag.KeyDown | KeyboardEventFlag.ExtendedKey))
|
|
1272
|
+
keys.insert(insertIndex + 1, (keyValue, KeyboardEventFlag.KeyUp | KeyboardEventFlag.ExtendedKey))
|
|
1273
|
+
lastKeyValue = keyValue
|
|
1274
|
+
else:
|
|
1275
|
+
printKeys.append((text[i], 'UnicodeChar'))
|
|
1276
|
+
keys.append((text[i], 'UnicodeChar'))
|
|
1277
|
+
lastKeyValue = text[i]
|
|
1278
|
+
if include:
|
|
1279
|
+
insertIndex += 1
|
|
1280
|
+
else:
|
|
1281
|
+
hold = False
|
|
1282
|
+
else:
|
|
1283
|
+
printKeys.append((text[i], 'UnicodeChar'))
|
|
1284
|
+
keys.append((text[i], 'UnicodeChar'))
|
|
1285
|
+
lastKeyValue = text[i]
|
|
1286
|
+
i += 1
|
|
1287
|
+
if i >= length:
|
|
1288
|
+
break
|
|
1289
|
+
hotkeyInterval = 0.01
|
|
1290
|
+
for i, key in enumerate(keys):
|
|
1291
|
+
if key[1] == 'UnicodeChar':
|
|
1292
|
+
SendUnicodeChar(key[0], charMode)
|
|
1293
|
+
time.sleep(interval)
|
|
1294
|
+
if debug:
|
|
1295
|
+
Logger.ColorfullyWrite('<Color=DarkGreen>{}</Color>, sleep({})\n'.format(printKeys[i], interval), writeToFile=False)
|
|
1296
|
+
else:
|
|
1297
|
+
scanCode = _VKtoSC(key[0])
|
|
1298
|
+
keybd_event(key[0], scanCode, key[1], 0)
|
|
1299
|
+
if debug:
|
|
1300
|
+
Logger.Write(printKeys[i], ConsoleColor.DarkGreen, writeToFile=False)
|
|
1301
|
+
if i + 1 == len(keys):
|
|
1302
|
+
time.sleep(interval)
|
|
1303
|
+
if debug:
|
|
1304
|
+
Logger.Write(', sleep({})\n'.format(interval), writeToFile=False)
|
|
1305
|
+
else:
|
|
1306
|
+
if key[1] & KeyboardEventFlag.KeyUp:
|
|
1307
|
+
if keys[i + 1][1] == 'UnicodeChar' or keys[i + 1][1] & KeyboardEventFlag.KeyUp == 0:
|
|
1308
|
+
time.sleep(interval)
|
|
1309
|
+
if debug:
|
|
1310
|
+
Logger.Write(', sleep({})\n'.format(interval), writeToFile=False)
|
|
1311
|
+
else:
|
|
1312
|
+
time.sleep(hotkeyInterval) # must sleep for a while, otherwise combined keys may not be caught
|
|
1313
|
+
if debug:
|
|
1314
|
+
Logger.Write(', sleep({})\n'.format(hotkeyInterval), writeToFile=False)
|
|
1315
|
+
else: # KeyboardEventFlag.KeyDown
|
|
1316
|
+
time.sleep(hotkeyInterval)
|
|
1317
|
+
if debug:
|
|
1318
|
+
Logger.Write(', sleep({})\n'.format(hotkeyInterval), writeToFile=False)
|
|
1319
|
+
# make sure hold keys are not pressed
|
|
1320
|
+
#win = ctypes.windll.user32.GetAsyncKeyState(Keys.VK_LWIN)
|
|
1321
|
+
#ctrl = ctypes.windll.user32.GetAsyncKeyState(Keys.VK_CONTROL)
|
|
1322
|
+
#alt = ctypes.windll.user32.GetAsyncKeyState(Keys.VK_MENU)
|
|
1323
|
+
#shift = ctypes.windll.user32.GetAsyncKeyState(Keys.VK_SHIFT)
|
|
1324
|
+
# if win & 0x8000:
|
|
1325
|
+
#Logger.WriteLine('ERROR: WIN is pressed, it should not be pressed!', ConsoleColor.Red)
|
|
1326
|
+
#keybd_event(Keys.VK_LWIN, 0, KeyboardEventFlag.KeyUp | KeyboardEventFlag.ExtendedKey, 0)
|
|
1327
|
+
# if ctrl & 0x8000:
|
|
1328
|
+
#Logger.WriteLine('ERROR: CTRL is pressed, it should not be pressed!', ConsoleColor.Red)
|
|
1329
|
+
#keybd_event(Keys.VK_CONTROL, 0, KeyboardEventFlag.KeyUp | KeyboardEventFlag.ExtendedKey, 0)
|
|
1330
|
+
# if alt & 0x8000:
|
|
1331
|
+
#Logger.WriteLine('ERROR: ALT is pressed, it should not be pressed!', ConsoleColor.Red)
|
|
1332
|
+
#keybd_event(Keys.VK_MENU, 0, KeyboardEventFlag.KeyUp | KeyboardEventFlag.ExtendedKey, 0)
|
|
1333
|
+
# if shift & 0x8000:
|
|
1334
|
+
#Logger.WriteLine('ERROR: SHIFT is pressed, it should not be pressed!', ConsoleColor.Red)
|
|
1335
|
+
#keybd_event(Keys.VK_SHIFT, 0, KeyboardEventFlag.KeyUp | KeyboardEventFlag.ExtendedKey, 0)
|
|
1336
|
+
time.sleep(waitTime)
|
|
1337
|
+
|
|
1338
|
+
|
|
1339
|
+
def SetThreadDpiAwarenessContext(dpiAwarenessContext: int):
|
|
1340
|
+
"""
|
|
1341
|
+
SetThreadDpiAwarenessContext from Win32.
|
|
1342
|
+
dpiAwarenessContext: int, a value in class `DpiAwarenessContext`
|
|
1343
|
+
"""
|
|
1344
|
+
try:
|
|
1345
|
+
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setthreaddpiawarenesscontext
|
|
1346
|
+
# Windows 10 1607+
|
|
1347
|
+
ctypes.windll.user32.SetThreadDpiAwarenessContext.restype = ctypes.c_void_p
|
|
1348
|
+
oldContext = ctypes.windll.user32.SetThreadDpiAwarenessContext(ctypes.c_void_p(dpiAwarenessContext))
|
|
1349
|
+
return oldContext
|
|
1350
|
+
except Exception as ex:
|
|
1351
|
+
pass
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
def SetProcessDpiAwareness(dpiAwareness: int):
|
|
1355
|
+
"""
|
|
1356
|
+
ProcessDpiAwareness from Win32.
|
|
1357
|
+
dpiAwareness: int, a value in class `ProcessDpiAwareness`
|
|
1358
|
+
"""
|
|
1359
|
+
try:
|
|
1360
|
+
# https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-setprocessdpiawareness
|
|
1361
|
+
# Once SetProcessDpiAwareness is set for an app, any future calls to SetProcessDpiAwareness will fail.
|
|
1362
|
+
# Windows 8.1+
|
|
1363
|
+
return ctypes.windll.shcore.SetProcessDpiAwareness(dpiAwareness)
|
|
1364
|
+
except Exception as ex:
|
|
1365
|
+
pass
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
SetProcessDpiAwareness(ProcessDpiAwareness.PerMonitorDpiAware)
|
|
1369
|
+
|
|
1370
|
+
|
|
1371
|
+
class tagPROCESSENTRY32(ctypes.Structure):
|
|
1372
|
+
_fields_ = [
|
|
1373
|
+
('dwSize', ctypes.wintypes.DWORD),
|
|
1374
|
+
('cntUsage', ctypes.wintypes.DWORD),
|
|
1375
|
+
('th32ProcessID', ctypes.wintypes.DWORD),
|
|
1376
|
+
('th32DefaultHeapID', ctypes.POINTER(ctypes.wintypes.ULONG)),
|
|
1377
|
+
('th32ModuleID', ctypes.wintypes.DWORD),
|
|
1378
|
+
('cntThreads', ctypes.wintypes.DWORD),
|
|
1379
|
+
('th32ParentProcessID', ctypes.wintypes.DWORD),
|
|
1380
|
+
('pcPriClassBase', ctypes.wintypes.LONG),
|
|
1381
|
+
('dwFlags', ctypes.wintypes.DWORD),
|
|
1382
|
+
('szExeFile', ctypes.c_wchar * MAX_PATH)
|
|
1383
|
+
]
|
|
1384
|
+
|
|
1385
|
+
|
|
1386
|
+
class ProcessInfo:
|
|
1387
|
+
def __init__(self, exeName: str, pid: int, ppid: int = -1, exePath: str = '', cmdLine: str = ''):
|
|
1388
|
+
self.pid = pid
|
|
1389
|
+
self.ppid = ppid # ppid is -1 if failed
|
|
1390
|
+
self.exeName = exeName # such as explorer.exe
|
|
1391
|
+
self.is64Bit = None # True if is 64 bit, False if 32 bit, None if failed
|
|
1392
|
+
self.exePath = exePath # such as C:\Windows\explorer.exe, empty if failed
|
|
1393
|
+
self.cmdLine = cmdLine # empty if failed
|
|
1394
|
+
|
|
1395
|
+
def __str__(self):
|
|
1396
|
+
return "ProcessInfo(pid={}, ppid={}, exeName='{}', is64Bit={}, exePath='{}', cmdLine='{}'".format(
|
|
1397
|
+
self.pid, self.ppid, self.exeName, self.is64Bit, self.exePath, self.cmdLine)
|
|
1398
|
+
|
|
1399
|
+
def __repr__(self):
|
|
1400
|
+
return '<{} object at 0x{:08X} {}>'.format(self.__class__.__name__, id(self),
|
|
1401
|
+
', '.join('{}={}'.format(k, v) for k, v in self.__dict__.items()))
|
|
1402
|
+
|
|
1403
|
+
|
|
1404
|
+
def GetProcesses(detailedInfo: bool = True) -> List[ProcessInfo]:
|
|
1405
|
+
'''
|
|
1406
|
+
Enum process by Win32 API.
|
|
1407
|
+
detailedInfo: bool, only get pid and exeName if False.
|
|
1408
|
+
You should run python as administrator to call this function.
|
|
1409
|
+
Can not get some system processes' info.
|
|
1410
|
+
'''
|
|
1411
|
+
if detailedInfo:
|
|
1412
|
+
try:
|
|
1413
|
+
IsWow64Process = ctypes.windll.kernel32.IsWow64Process
|
|
1414
|
+
except Exception as ex:
|
|
1415
|
+
IsWow64Process = None
|
|
1416
|
+
hSnapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot(15, 0) # TH32CS_SNAPALL = 15
|
|
1417
|
+
processEntry32 = tagPROCESSENTRY32()
|
|
1418
|
+
processEntry32.dwSize = ctypes.sizeof(processEntry32)
|
|
1419
|
+
processList = []
|
|
1420
|
+
processNext = ctypes.windll.kernel32.Process32FirstW(ctypes.c_void_p(hSnapshot), ctypes.byref(processEntry32))
|
|
1421
|
+
cPointerSize = ctypes.sizeof(ctypes.c_void_p)
|
|
1422
|
+
while processNext:
|
|
1423
|
+
pinfo = ProcessInfo(processEntry32.szExeFile, processEntry32.th32ProcessID)
|
|
1424
|
+
if detailedInfo:
|
|
1425
|
+
#PROCESS_QUERY_INFORMATION=0x0400, PROCESS_QUERY_LIMITED_INFORMATION=0x1000, PROCESS_VM_READ=0x0010
|
|
1426
|
+
queryType = (0x1000 if IsNT6orHigher else 0x0400) | 0x0010
|
|
1427
|
+
hProcess = ctypes.windll.kernel32.OpenProcess(queryType, 0, pinfo.pid)
|
|
1428
|
+
if hProcess:
|
|
1429
|
+
hProcess = ctypes.c_void_p(hProcess)
|
|
1430
|
+
processBasicInformationAddr = 0
|
|
1431
|
+
processBasicInformation = (ctypes.c_size_t * 6)()#sizeof PROCESS_BASIC_INFORMATION
|
|
1432
|
+
outLen = ctypes.c_ulong(0)
|
|
1433
|
+
ctypes.windll.ntdll.NtQueryInformationProcess.restype = ctypes.c_uint32
|
|
1434
|
+
if IsWow64Process:
|
|
1435
|
+
isWow64 = ctypes.wintypes.BOOL()
|
|
1436
|
+
if IsWow64Process(hProcess, ctypes.byref(isWow64)):
|
|
1437
|
+
pinfo.is64Bit = not isWow64
|
|
1438
|
+
else:
|
|
1439
|
+
pinfo.is64Bit = False
|
|
1440
|
+
ntStatus = ctypes.windll.ntdll.NtQueryInformationProcess(
|
|
1441
|
+
hProcess, processBasicInformationAddr, processBasicInformation, ctypes.sizeof(processBasicInformation), ctypes.byref(outLen))
|
|
1442
|
+
if ntStatus == 0: #STATUS_SUCCESS=0
|
|
1443
|
+
pinfo.ppid = processBasicInformation[5]
|
|
1444
|
+
pebBaseAddress = processBasicInformation[1]
|
|
1445
|
+
if pebBaseAddress:
|
|
1446
|
+
pebSize = 712 if CurrentProcessIs64Bit else 472 #sizeof PEB
|
|
1447
|
+
peb = (ctypes.c_size_t * (pebSize // cPointerSize))()
|
|
1448
|
+
outLen.value = 0
|
|
1449
|
+
isok = ctypes.windll.kernel32.ReadProcessMemory(hProcess, ctypes.c_void_p(pebBaseAddress), peb, pebSize, ctypes.byref(outLen))
|
|
1450
|
+
if isok:
|
|
1451
|
+
processParametersAddr = ctypes.c_void_p(peb[4])
|
|
1452
|
+
uppSize = 128 if CurrentProcessIs64Bit else 72 #sizeof RTL_USER_PROCESS_PARAMETERS
|
|
1453
|
+
upp = (ctypes.c_ubyte * uppSize)()
|
|
1454
|
+
outLen.value = 0
|
|
1455
|
+
isok = ctypes.windll.kernel32.ReadProcessMemory(hProcess, processParametersAddr, upp, uppSize, ctypes.byref(outLen))
|
|
1456
|
+
if isok:
|
|
1457
|
+
offset = 16 + 10 * cPointerSize
|
|
1458
|
+
imgPathSize, imgPathSizeMax, imgPathAddr, cmdLineSize, cmdLineSizeMax, cmdLineAddr = struct.unpack('@HHNHHN', bytes(upp[offset:]))
|
|
1459
|
+
exePath = (ctypes.c_wchar * imgPathSizeMax)()
|
|
1460
|
+
outLen.value = 0
|
|
1461
|
+
isok = ctypes.windll.kernel32.ReadProcessMemory(hProcess, ctypes.c_void_p(imgPathAddr), exePath, ctypes.sizeof(exePath), ctypes.byref(outLen))
|
|
1462
|
+
if isok:
|
|
1463
|
+
pinfo.exePath = exePath.value
|
|
1464
|
+
cmdLine = (ctypes.c_wchar * cmdLineSizeMax)()
|
|
1465
|
+
outLen.value = 0
|
|
1466
|
+
isok = ctypes.windll.kernel32.ReadProcessMemory(hProcess, ctypes.c_void_p(cmdLineAddr), cmdLine, ctypes.sizeof(cmdLine), ctypes.byref(outLen))
|
|
1467
|
+
if isok:
|
|
1468
|
+
pinfo.cmdLine = cmdLine.value
|
|
1469
|
+
if not pinfo.exePath:
|
|
1470
|
+
exePath = (ctypes.c_wchar * MAX_PATH)()
|
|
1471
|
+
if IsNT6orHigher:
|
|
1472
|
+
win32PathFormat = 0 #nativeSystemPathFormat = 1
|
|
1473
|
+
outLen.value = len(exePath)
|
|
1474
|
+
isok = ctypes.windll.kernel32.QueryFullProcessImageNameW(hProcess, win32PathFormat, exePath, ctypes.byref(outLen))
|
|
1475
|
+
else:
|
|
1476
|
+
hModule = None
|
|
1477
|
+
try:
|
|
1478
|
+
#strlen =
|
|
1479
|
+
ctypes.windll.psapi.GetModuleFileNameExW(hProcess, hModule, exePath, len(exePath))
|
|
1480
|
+
except:
|
|
1481
|
+
#strlen =
|
|
1482
|
+
ctypes.windll.kernel32.GetModuleFileNameExW(hProcess, hModule, exePath, len(exePath))
|
|
1483
|
+
#exePath is nativeSystemPathFormat
|
|
1484
|
+
#strlen = ctypes.windll.psapi.GetProcessImageFileNameW(hProcess, exePath, len(exePath))
|
|
1485
|
+
#if exePath.value:
|
|
1486
|
+
#strlen = ctypes.windll.kernel32.QueryDosDeviceW(ctypes.c_wchar_p(exePath.value), exePath, len(exePath))
|
|
1487
|
+
pinfo.exePath = exePath.value
|
|
1488
|
+
ctypes.windll.kernel32.CloseHandle(hProcess)
|
|
1489
|
+
processList.append(pinfo)
|
|
1490
|
+
processNext = ctypes.windll.kernel32.Process32NextW(ctypes.c_void_p(hSnapshot), ctypes.byref(processEntry32))
|
|
1491
|
+
ctypes.windll.kernel32.CloseHandle(ctypes.c_void_p(hSnapshot))
|
|
1492
|
+
return processList
|
|
1493
|
+
|
|
1494
|
+
|
|
1495
|
+
def EnumProcessByWMI() -> Generator[ProcessInfo, None, None]:
|
|
1496
|
+
'''Maybe slower, but can get system processes' info'''
|
|
1497
|
+
import wmi # pip install wmi
|
|
1498
|
+
wobj = wmi.WMI()
|
|
1499
|
+
fields = ['Name', 'ProcessId', 'ParentProcessId', 'ExecutablePath', 'CommandLine']
|
|
1500
|
+
for it in wobj.Win32_Process(fields): # only query the specified fields, speed up the process
|
|
1501
|
+
pinfo = ProcessInfo(it.Name, it.ProcessId, it.ParentProcessId, it.ExecutablePath, it.CommandLine)
|
|
1502
|
+
yield pinfo
|
|
1503
|
+
|
|
1504
|
+
|
|
1505
|
+
def TerminateProcess(pid: int) -> bool:
|
|
1506
|
+
hProcess = ctypes.windll.kernel32.OpenProcess(0x0001, 0, pid) # PROCESS_TERMINATE=0x0001
|
|
1507
|
+
if hProcess:
|
|
1508
|
+
hProcess = ctypes.c_void_p(hProcess)
|
|
1509
|
+
ret = ctypes.windll.kernel32.TerminateProcess(hProcess, -1)
|
|
1510
|
+
ctypes.windll.kernel32.CloseHandle(hProcess)
|
|
1511
|
+
return bool(ret)
|
|
1512
|
+
return False
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
def TerminateProcessByName(exeName: str, killAll: bool = True) -> int:
|
|
1516
|
+
'''
|
|
1517
|
+
exeName: str, such as notepad.exe
|
|
1518
|
+
return int, process count that was terminated
|
|
1519
|
+
'''
|
|
1520
|
+
count = 0
|
|
1521
|
+
for pinfo in GetProcesses(detailedInfo=False):
|
|
1522
|
+
if pinfo.exeName == exeName:
|
|
1523
|
+
if TerminateProcess(pinfo.pid):
|
|
1524
|
+
count += 1
|
|
1525
|
+
if not killAll:
|
|
1526
|
+
break
|
|
1527
|
+
return count
|
|
1528
|
+
|
|
1529
|
+
|
|
1530
|
+
class Logger:
|
|
1531
|
+
"""
|
|
1532
|
+
Logger for print and log. Support for printing log with different colors on console.
|
|
1533
|
+
"""
|
|
1534
|
+
FilePath = '@AutomationLog.txt'
|
|
1535
|
+
FileObj = None
|
|
1536
|
+
FlushTime = ProcessTime()
|
|
1537
|
+
_SelfFileName = os.path.split(__file__)[1]
|
|
1538
|
+
ColorNames = {
|
|
1539
|
+
"Black": ConsoleColor.Black,
|
|
1540
|
+
"DarkBlue": ConsoleColor.DarkBlue,
|
|
1541
|
+
"DarkGreen": ConsoleColor.DarkGreen,
|
|
1542
|
+
"DarkCyan": ConsoleColor.DarkCyan,
|
|
1543
|
+
"DarkRed": ConsoleColor.DarkRed,
|
|
1544
|
+
"DarkMagenta": ConsoleColor.DarkMagenta,
|
|
1545
|
+
"DarkYellow": ConsoleColor.DarkYellow,
|
|
1546
|
+
"Gray": ConsoleColor.Gray,
|
|
1547
|
+
"DarkGray": ConsoleColor.DarkGray,
|
|
1548
|
+
"Blue": ConsoleColor.Blue,
|
|
1549
|
+
"Green": ConsoleColor.Green,
|
|
1550
|
+
"Cyan": ConsoleColor.Cyan,
|
|
1551
|
+
"Red": ConsoleColor.Red,
|
|
1552
|
+
"Magenta": ConsoleColor.Magenta,
|
|
1553
|
+
"Yellow": ConsoleColor.Yellow,
|
|
1554
|
+
"White": ConsoleColor.White,
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
@staticmethod
|
|
1558
|
+
def SetLogFile(logFile: Union[TextIOWrapper, str]) -> None:
|
|
1559
|
+
"""
|
|
1560
|
+
logFile: file object or str.
|
|
1561
|
+
If logFile is '', no log file will be written.
|
|
1562
|
+
The previous log file will be closed immediately.
|
|
1563
|
+
"""
|
|
1564
|
+
if Logger.FileObj:
|
|
1565
|
+
Logger.FileObj.close()
|
|
1566
|
+
Logger.FileObj = None
|
|
1567
|
+
if isinstance(logFile, str):
|
|
1568
|
+
Logger.FilePath = logFile
|
|
1569
|
+
if logFile:
|
|
1570
|
+
Logger.FileObj = open(logFile, 'a+', encoding='utf-8', newline='\n')
|
|
1571
|
+
else:
|
|
1572
|
+
Logger.FileObj = logFile
|
|
1573
|
+
|
|
1574
|
+
@staticmethod
|
|
1575
|
+
def Write(log: Any, consoleColor: int = ConsoleColor.Default, writeToFile: bool = True, printToStdout: bool = True, logFile: Optional[str] = None, printTruncateLen: int = 0) -> None:
|
|
1576
|
+
"""
|
|
1577
|
+
log: any type.
|
|
1578
|
+
consoleColor: int, a value in class `ConsoleColor`, such as `ConsoleColor.DarkGreen`.
|
|
1579
|
+
writeToFile: bool.
|
|
1580
|
+
printToStdout: bool.
|
|
1581
|
+
logFile: str, log file path.
|
|
1582
|
+
printTruncateLen: int, if <= 0, log is not truncated when print.
|
|
1583
|
+
"""
|
|
1584
|
+
if not isinstance(log, str):
|
|
1585
|
+
log = str(log)
|
|
1586
|
+
if printToStdout and sys.stdout:
|
|
1587
|
+
isValidColor = (consoleColor >= ConsoleColor.Black and consoleColor <= ConsoleColor.White)
|
|
1588
|
+
if isValidColor:
|
|
1589
|
+
SetConsoleColor(consoleColor)
|
|
1590
|
+
try:
|
|
1591
|
+
if printTruncateLen > 0 and len(log) > printTruncateLen:
|
|
1592
|
+
sys.stdout.write(log[:printTruncateLen] + '...')
|
|
1593
|
+
else:
|
|
1594
|
+
sys.stdout.write(log)
|
|
1595
|
+
except Exception as ex:
|
|
1596
|
+
SetConsoleColor(ConsoleColor.Red)
|
|
1597
|
+
isValidColor = True
|
|
1598
|
+
sys.stdout.write(ex.__class__.__name__ + ': can\'t print the log!')
|
|
1599
|
+
if log.endswith('\n'):
|
|
1600
|
+
sys.stdout.write('\n')
|
|
1601
|
+
if isValidColor:
|
|
1602
|
+
ResetConsoleColor()
|
|
1603
|
+
if sys.stdout:
|
|
1604
|
+
sys.stdout.flush()
|
|
1605
|
+
if not writeToFile:
|
|
1606
|
+
return
|
|
1607
|
+
fout = None
|
|
1608
|
+
close = False
|
|
1609
|
+
try:
|
|
1610
|
+
if logFile:
|
|
1611
|
+
fout = open(logFile, 'a+', encoding='utf-8', newline='\n')
|
|
1612
|
+
close = True
|
|
1613
|
+
else:
|
|
1614
|
+
if Logger.FileObj:
|
|
1615
|
+
fout = Logger.FileObj
|
|
1616
|
+
elif Logger.FilePath:
|
|
1617
|
+
fout = open(Logger.FilePath, 'a+', encoding='utf-8', newline='\n')
|
|
1618
|
+
Logger.FileObj = fout
|
|
1619
|
+
if fout:
|
|
1620
|
+
fout.write(log)
|
|
1621
|
+
now = ProcessTime()
|
|
1622
|
+
if now >= Logger.FlushTime + 2:
|
|
1623
|
+
fout.flush()
|
|
1624
|
+
Logger.FlushTime = now
|
|
1625
|
+
except Exception as ex:
|
|
1626
|
+
if sys.stdout:
|
|
1627
|
+
sys.stdout.write(ex.__class__.__name__ + ': can\'t write the log!')
|
|
1628
|
+
finally:
|
|
1629
|
+
if close and fout:
|
|
1630
|
+
fout.close()
|
|
1631
|
+
|
|
1632
|
+
@staticmethod
|
|
1633
|
+
def WriteLine(log: Any, consoleColor: int = ConsoleColor.Default, writeToFile: bool = True, printToStdout: bool = True, logFile: Optional[str] = None) -> None:
|
|
1634
|
+
"""
|
|
1635
|
+
log: any type.
|
|
1636
|
+
consoleColor: int, a value in class `ConsoleColor`, such as `ConsoleColor.DarkGreen`.
|
|
1637
|
+
writeToFile: bool.
|
|
1638
|
+
printToStdout: bool.
|
|
1639
|
+
logFile: str, log file path.
|
|
1640
|
+
"""
|
|
1641
|
+
Logger.Write('{}\n'.format(log), consoleColor, writeToFile, printToStdout, logFile)
|
|
1642
|
+
|
|
1643
|
+
@staticmethod
|
|
1644
|
+
def ColorfullyWrite(log: str, consoleColor: int = ConsoleColor.Default, writeToFile: bool = True, printToStdout: bool = True, logFile: Optional[str] = None) -> None:
|
|
1645
|
+
"""
|
|
1646
|
+
log: str.
|
|
1647
|
+
consoleColor: int, a value in class `ConsoleColor`, such as `ConsoleColor.DarkGreen`.
|
|
1648
|
+
writeToFile: bool.
|
|
1649
|
+
printToStdout: bool.
|
|
1650
|
+
logFile: str, log file path.
|
|
1651
|
+
ColorfullyWrite('Hello <Color=Green>Green Text</Color> !!!'), Color name must be in `Logger.ColorNames` and can't be nested.
|
|
1652
|
+
"""
|
|
1653
|
+
text = []
|
|
1654
|
+
start = 0
|
|
1655
|
+
while True:
|
|
1656
|
+
index1 = log.find('<Color=', start)
|
|
1657
|
+
if index1 < 0:
|
|
1658
|
+
text.append((log[start:], consoleColor))
|
|
1659
|
+
break
|
|
1660
|
+
if index1 > start:
|
|
1661
|
+
text.append((log[start:index1], consoleColor))
|
|
1662
|
+
start = index1
|
|
1663
|
+
index2 = log.find('>', index1 + 7)
|
|
1664
|
+
if index2 < 0:
|
|
1665
|
+
text.append((log[start:], consoleColor))
|
|
1666
|
+
break
|
|
1667
|
+
colorName = log[index1 + 7:index2]
|
|
1668
|
+
if colorName not in Logger.ColorNames:
|
|
1669
|
+
text.append((log[start:index1 + 7], consoleColor))
|
|
1670
|
+
start = index1 + 7
|
|
1671
|
+
continue
|
|
1672
|
+
index3 = log.find('</Color>', index2 + 1)
|
|
1673
|
+
if index3 < 0:
|
|
1674
|
+
text.append((log[start:], consoleColor))
|
|
1675
|
+
break
|
|
1676
|
+
text.append((log[index2 + 1:index3], Logger.ColorNames[colorName]))
|
|
1677
|
+
start = index3 + 8
|
|
1678
|
+
for t, c in text:
|
|
1679
|
+
Logger.Write(t, c, writeToFile, printToStdout, logFile)
|
|
1680
|
+
|
|
1681
|
+
@staticmethod
|
|
1682
|
+
def ColorfullyWriteLine(log: str, consoleColor: int = ConsoleColor.Default, writeToFile: bool = True, printToStdout: bool = True, logFile: Optional[str] = None) -> None:
|
|
1683
|
+
"""
|
|
1684
|
+
log: str.
|
|
1685
|
+
consoleColor: int, a value in class `ConsoleColor`, such as `ConsoleColor.DarkGreen`.
|
|
1686
|
+
writeToFile: bool.
|
|
1687
|
+
printToStdout: bool.
|
|
1688
|
+
logFile: str, log file path.
|
|
1689
|
+
|
|
1690
|
+
ColorfullyWriteLine('Hello <Color=Green>Green Text</Color> !!!'), Color name must be in `Logger.ColorNames` and can't be nested.
|
|
1691
|
+
"""
|
|
1692
|
+
Logger.ColorfullyWrite(log + '\n', consoleColor, writeToFile, printToStdout, logFile)
|
|
1693
|
+
|
|
1694
|
+
@staticmethod
|
|
1695
|
+
def Log(log: Any = '', consoleColor: int = ConsoleColor.Default, writeToFile: bool = True, printToStdout: bool = True, logFile: Optional[str] = None) -> None:
|
|
1696
|
+
"""
|
|
1697
|
+
log: any type.
|
|
1698
|
+
consoleColor: int, a value in class `ConsoleColor`, such as `ConsoleColor.DarkGreen`.
|
|
1699
|
+
writeToFile: bool.
|
|
1700
|
+
printToStdout: bool.
|
|
1701
|
+
logFile: str, log file path.
|
|
1702
|
+
"""
|
|
1703
|
+
frameCount = 1
|
|
1704
|
+
while True:
|
|
1705
|
+
frame = sys._getframe(frameCount)
|
|
1706
|
+
_, scriptFileName = os.path.split(frame.f_code.co_filename)
|
|
1707
|
+
if scriptFileName != Logger._SelfFileName:
|
|
1708
|
+
break
|
|
1709
|
+
frameCount += 1
|
|
1710
|
+
|
|
1711
|
+
t = datetime.datetime.now()
|
|
1712
|
+
log = '{}-{:02}-{:02} {:02}:{:02}:{:02}.{:03} {}[{}] {} -> {}\n'.format(t.year, t.month, t.day,
|
|
1713
|
+
t.hour, t.minute, t.second, t.microsecond // 1000, scriptFileName, frame.f_lineno, frame.f_code.co_name, log)
|
|
1714
|
+
Logger.Write(log, consoleColor, writeToFile, printToStdout, logFile)
|
|
1715
|
+
|
|
1716
|
+
@staticmethod
|
|
1717
|
+
def ColorfullyLog(log: str = '', consoleColor: int = ConsoleColor.Default, writeToFile: bool = True, printToStdout: bool = True, logFile: Optional[str] = None) -> None:
|
|
1718
|
+
"""
|
|
1719
|
+
log: any type.
|
|
1720
|
+
consoleColor: int, a value in class `ConsoleColor`, such as `ConsoleColor.DarkGreen`.
|
|
1721
|
+
writeToFile: bool.
|
|
1722
|
+
printToStdout: bool.
|
|
1723
|
+
logFile: str, log file path.
|
|
1724
|
+
|
|
1725
|
+
ColorfullyLog('Hello <Color=Green>Green Text</Color> !!!'), Color name must be in `Logger.ColorNames` and can't be nested.
|
|
1726
|
+
"""
|
|
1727
|
+
frameCount = 1
|
|
1728
|
+
while True:
|
|
1729
|
+
frame = sys._getframe(frameCount)
|
|
1730
|
+
_, scriptFileName = os.path.split(frame.f_code.co_filename)
|
|
1731
|
+
if scriptFileName != Logger._SelfFileName:
|
|
1732
|
+
break
|
|
1733
|
+
frameCount += 1
|
|
1734
|
+
|
|
1735
|
+
t = datetime.datetime.now()
|
|
1736
|
+
log = '{}-{:02}-{:02} {:02}:{:02}:{:02}.{:03} {}[{}] {} -> {}\n'.format(t.year, t.month, t.day,
|
|
1737
|
+
t.hour, t.minute, t.second, t.microsecond // 1000, scriptFileName, frame.f_lineno, frame.f_code.co_name, log)
|
|
1738
|
+
Logger.ColorfullyWrite(log, consoleColor, writeToFile, printToStdout, logFile)
|
|
1739
|
+
|
|
1740
|
+
@staticmethod
|
|
1741
|
+
def DeleteLog() -> None:
|
|
1742
|
+
"""Delete log file."""
|
|
1743
|
+
if os.path.exists(Logger.FilePath):
|
|
1744
|
+
os.remove(Logger.FilePath)
|
|
1745
|
+
|
|
1746
|
+
LogColorfully = ColorfullyLog
|
|
1747
|
+
WriteColorfully = ColorfullyWrite
|
|
1748
|
+
WriteLineColorfully = ColorfullyWriteLine
|
|
1749
|
+
|
|
1750
|
+
|
|
1751
|
+
def _ExitHandler():
|
|
1752
|
+
if Logger.FileObj:
|
|
1753
|
+
Logger.FileObj.close()
|
|
1754
|
+
|
|
1755
|
+
|
|
1756
|
+
atexit.register(_ExitHandler)
|
|
1757
|
+
|
|
1758
|
+
|
|
1759
|
+
class RotateFlipType:
|
|
1760
|
+
RotateNoneFlipNone = 0
|
|
1761
|
+
Rotate90FlipNone = 1
|
|
1762
|
+
Rotate180FlipNone = 2
|
|
1763
|
+
Rotate270FlipNone = 3
|
|
1764
|
+
RotateNoneFlipX = 4
|
|
1765
|
+
Rotate90FlipX = 5
|
|
1766
|
+
Rotate180FlipX = 6
|
|
1767
|
+
Rotate270FlipX = 7
|
|
1768
|
+
RotateNoneFlipY = Rotate180FlipX
|
|
1769
|
+
Rotate90FlipY = Rotate270FlipX
|
|
1770
|
+
Rotate180FlipY = RotateNoneFlipX
|
|
1771
|
+
Rotate270FlipY = Rotate90FlipX
|
|
1772
|
+
RotateNoneFlipXY = Rotate180FlipNone
|
|
1773
|
+
Rotate90FlipXY = Rotate270FlipNone
|
|
1774
|
+
Rotate180FlipXY = RotateNoneFlipNone
|
|
1775
|
+
Rotate270FlipXY = Rotate90FlipNone
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
class RawFormat:
|
|
1779
|
+
Undefined = 0
|
|
1780
|
+
MemoryBMP = 1
|
|
1781
|
+
BMP = 2
|
|
1782
|
+
EMF = 3
|
|
1783
|
+
WMF = 4
|
|
1784
|
+
JPEG = 5
|
|
1785
|
+
PNG = 6
|
|
1786
|
+
GIF = 7 # MultiFrameBitmap
|
|
1787
|
+
TIFF = 8 # MultiFrameBitmap
|
|
1788
|
+
EXIF = 9
|
|
1789
|
+
Icon = 10
|
|
1790
|
+
|
|
1791
|
+
|
|
1792
|
+
class Bitmap:
|
|
1793
|
+
"""
|
|
1794
|
+
A simple Bitmap class wraps Windows GDI+ Gdiplus::Bitmap, but may not have high efficiency.
|
|
1795
|
+
The color format is Gdiplus::PixelFormat32bppARGB 0xAARRGGBB, byte order is B G R A.
|
|
1796
|
+
"""
|
|
1797
|
+
|
|
1798
|
+
def __init__(self, width: int = 0, height: int = 0):
|
|
1799
|
+
"""
|
|
1800
|
+
Create a black transparent(ARGB=0x00000000) bimap of size(width, height).
|
|
1801
|
+
"""
|
|
1802
|
+
self._width = width
|
|
1803
|
+
self._height = height
|
|
1804
|
+
self._bitmap = 0
|
|
1805
|
+
self._format = RawFormat.Undefined
|
|
1806
|
+
self._formatStr = ''
|
|
1807
|
+
if width > 0 and height > 0:
|
|
1808
|
+
self._bitmap = _DllClient.instance().dll.BitmapCreate(width, height)
|
|
1809
|
+
|
|
1810
|
+
def __del__(self):
|
|
1811
|
+
self.Close()
|
|
1812
|
+
|
|
1813
|
+
def __enter__(self):
|
|
1814
|
+
return self
|
|
1815
|
+
|
|
1816
|
+
def __exit__(self, exceptionType, exceptionValue, exceptionTraceback):
|
|
1817
|
+
self.Close()
|
|
1818
|
+
|
|
1819
|
+
def __bool__(self):
|
|
1820
|
+
return self._bitmap > 0
|
|
1821
|
+
|
|
1822
|
+
def _GetSize(self) -> None:
|
|
1823
|
+
size = _DllClient.instance().dll.BitmapGetWidthAndHeight(ctypes.c_size_t(self._bitmap))
|
|
1824
|
+
self._width = size & 0xFFFFFFFF
|
|
1825
|
+
self._height = size >> 32
|
|
1826
|
+
|
|
1827
|
+
def Close(self) -> None:
|
|
1828
|
+
"""Close the underlying Gdiplus::Bitmap object."""
|
|
1829
|
+
if self._bitmap:
|
|
1830
|
+
_DllClient.instance().dll.BitmapRelease(ctypes.c_size_t(self._bitmap))
|
|
1831
|
+
self._bitmap = 0
|
|
1832
|
+
self._width = 0
|
|
1833
|
+
self._height = 0
|
|
1834
|
+
|
|
1835
|
+
Release = Close
|
|
1836
|
+
|
|
1837
|
+
@property
|
|
1838
|
+
def Width(self) -> int:
|
|
1839
|
+
"""
|
|
1840
|
+
Property Width.
|
|
1841
|
+
Return int.
|
|
1842
|
+
"""
|
|
1843
|
+
return self._width
|
|
1844
|
+
|
|
1845
|
+
@property
|
|
1846
|
+
def Height(self) -> int:
|
|
1847
|
+
"""
|
|
1848
|
+
Property Height.
|
|
1849
|
+
Return int.
|
|
1850
|
+
"""
|
|
1851
|
+
return self._height
|
|
1852
|
+
|
|
1853
|
+
@staticmethod
|
|
1854
|
+
def FromHandle(handle: int, left: int, top: int, right: int, bottom: int,
|
|
1855
|
+
captureCursor: bool = False) -> Optional['MemoryBMP']:
|
|
1856
|
+
"""
|
|
1857
|
+
Create a `Bitmap` from a native window handle.
|
|
1858
|
+
handle: int, the handle of a native window.
|
|
1859
|
+
left: int.
|
|
1860
|
+
top: int.
|
|
1861
|
+
right: int.
|
|
1862
|
+
bottom: int.
|
|
1863
|
+
left, top, right and bottom are control's internal postion(from 0,0).
|
|
1864
|
+
Return `Bitmap` or None.
|
|
1865
|
+
"""
|
|
1866
|
+
rect = None
|
|
1867
|
+
toplevelHandle = GetAncestor(handle, GAFlag.Root)
|
|
1868
|
+
if toplevelHandle and toplevelHandle == handle:
|
|
1869
|
+
if DwmIsCompositionEnabled():
|
|
1870
|
+
rect = DwmGetWindowExtendFrameBounds(handle)
|
|
1871
|
+
if rect is None:
|
|
1872
|
+
rect = GetWindowRect(handle)
|
|
1873
|
+
if rect is None:
|
|
1874
|
+
return None
|
|
1875
|
+
root = GetRootControl()
|
|
1876
|
+
left, top, right, bottom = left + rect.left, top + rect.top, right + rect.left, bottom + rect.top
|
|
1877
|
+
cbmp = _DllClient.instance().dll.BitmapFromWindow(ctypes.c_size_t(root.NativeWindowHandle),
|
|
1878
|
+
left, top, right, bottom,
|
|
1879
|
+
0, 0, int(captureCursor))
|
|
1880
|
+
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
1881
|
+
|
|
1882
|
+
@staticmethod
|
|
1883
|
+
def FromControl(control: 'Control', x: int = 0, y: int = 0, width: int = 0, height: int = 0,
|
|
1884
|
+
captureCursor: bool = False) -> Optional['MemoryBMP']:
|
|
1885
|
+
"""
|
|
1886
|
+
Create a `Bitmap` from a `Control`.
|
|
1887
|
+
control: `Control` or its subclass.
|
|
1888
|
+
x: int.
|
|
1889
|
+
y: int.
|
|
1890
|
+
width: int.
|
|
1891
|
+
height: int.
|
|
1892
|
+
x, y: the point in control's internal position(from 0,0)
|
|
1893
|
+
width, height: image's width and height from x, y, use 0 for entire area,
|
|
1894
|
+
If width(or height) < 0, image size will be control's width(or height) - width(or height).
|
|
1895
|
+
Return `Bitmap` or None.
|
|
1896
|
+
"""
|
|
1897
|
+
rect = control.BoundingRectangle
|
|
1898
|
+
while rect.width() == 0 or rect.height() == 0:
|
|
1899
|
+
# some controls maybe visible but their BoundingRectangle are all 0, capture its parent until valid
|
|
1900
|
+
control = control.GetParentControl()
|
|
1901
|
+
if not control:
|
|
1902
|
+
return None
|
|
1903
|
+
rect = control.BoundingRectangle
|
|
1904
|
+
handle = control.NativeWindowHandle
|
|
1905
|
+
if handle:
|
|
1906
|
+
toplevelHandle = GetAncestor(handle, GAFlag.Root)
|
|
1907
|
+
if toplevelHandle and toplevelHandle == handle:
|
|
1908
|
+
if DwmIsCompositionEnabled():
|
|
1909
|
+
rect = DwmGetWindowExtendFrameBounds(handle) or rect
|
|
1910
|
+
if width <= 0:
|
|
1911
|
+
width = rect.width() + width
|
|
1912
|
+
if height <= 0:
|
|
1913
|
+
height = rect.height() + height
|
|
1914
|
+
root = GetRootControl()
|
|
1915
|
+
left, top = rect.left + x, rect.top + y
|
|
1916
|
+
right, bottom = left + width, top + height
|
|
1917
|
+
cbmp = _DllClient.instance().dll.BitmapFromWindow(ctypes.c_size_t(root.NativeWindowHandle),
|
|
1918
|
+
left, top, right, bottom,
|
|
1919
|
+
0, 0, int(captureCursor))
|
|
1920
|
+
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
1921
|
+
if width <= 0:
|
|
1922
|
+
width = rect.width() + width
|
|
1923
|
+
if height <= 0:
|
|
1924
|
+
height = rect.height() + height
|
|
1925
|
+
while True:
|
|
1926
|
+
control = control.GetParentControl()
|
|
1927
|
+
handle = control.NativeWindowHandle
|
|
1928
|
+
if handle:
|
|
1929
|
+
pRect = control.BoundingRectangle
|
|
1930
|
+
toplevelHandle = GetAncestor(handle, GAFlag.Root)
|
|
1931
|
+
if toplevelHandle and toplevelHandle == handle:
|
|
1932
|
+
if DwmIsCompositionEnabled():
|
|
1933
|
+
pRect = DwmGetWindowExtendFrameBounds(handle) or pRect
|
|
1934
|
+
left = rect.left - pRect.left + x
|
|
1935
|
+
top = rect.top - pRect.top + y
|
|
1936
|
+
right = left + width
|
|
1937
|
+
bottom = top + height
|
|
1938
|
+
break
|
|
1939
|
+
return Bitmap.FromHandle(handle, left, top, right, bottom, captureCursor)
|
|
1940
|
+
|
|
1941
|
+
@staticmethod
|
|
1942
|
+
def _FromGdiplusBitmap(cbmp: int) -> 'Bitmap':
|
|
1943
|
+
"""
|
|
1944
|
+
Return `Bitmap`'s subclass instance or None.
|
|
1945
|
+
"""
|
|
1946
|
+
if not cbmp:
|
|
1947
|
+
return None
|
|
1948
|
+
formatType = ctypes.c_uint()
|
|
1949
|
+
_DllClient.instance().dll.BitmapGetRawFormat(ctypes.c_size_t(cbmp), ctypes.byref(formatType))
|
|
1950
|
+
if formatType.value == RawFormat.JPEG:
|
|
1951
|
+
bitmap = JPEG()
|
|
1952
|
+
elif formatType.value == RawFormat.PNG:
|
|
1953
|
+
bitmap = PNG()
|
|
1954
|
+
elif formatType.value == RawFormat.GIF:
|
|
1955
|
+
bitmap = GIF()
|
|
1956
|
+
bitmap._bitmap = cbmp
|
|
1957
|
+
bitmap._frameCount = _DllClient.instance().dll.MultiBitmapGetFrameCount(ctypes.c_size_t(cbmp))
|
|
1958
|
+
bitmap._GetGifDealy()
|
|
1959
|
+
elif formatType.value == RawFormat.TIFF:
|
|
1960
|
+
bitmap = TIFF()
|
|
1961
|
+
bitmap._frameCount = _DllClient.instance().dll.MultiBitmapGetFrameCount(ctypes.c_size_t(cbmp))
|
|
1962
|
+
elif formatType.value == RawFormat.BMP:
|
|
1963
|
+
bitmap = BMP()
|
|
1964
|
+
elif formatType.value == RawFormat.MemoryBMP:
|
|
1965
|
+
bitmap = MemoryBMP()
|
|
1966
|
+
elif formatType.value == RawFormat.Icon:
|
|
1967
|
+
bitmap = ICON()
|
|
1968
|
+
elif formatType.value == RawFormat.EMF:
|
|
1969
|
+
bitmap = EMF()
|
|
1970
|
+
elif formatType.value == RawFormat.WMF:
|
|
1971
|
+
bitmap = WMF()
|
|
1972
|
+
elif formatType.value == RawFormat.EXIF:
|
|
1973
|
+
bitmap = EXIF()
|
|
1974
|
+
else:
|
|
1975
|
+
bitmap = Bitmap()
|
|
1976
|
+
bitmap._bitmap = cbmp
|
|
1977
|
+
bitmap._format = formatType.value
|
|
1978
|
+
bitmap._GetSize()
|
|
1979
|
+
return bitmap
|
|
1980
|
+
|
|
1981
|
+
@staticmethod
|
|
1982
|
+
def FromBytes(data: Union[bytes, bytearray], format: str = None, width: int = None, height: int = None) -> Optional['Bitmap']:
|
|
1983
|
+
"""
|
|
1984
|
+
Create a `Bitmap` instance from raw BGRA pixel data or image file byte content.
|
|
1985
|
+
data: bytes or bytearray.
|
|
1986
|
+
format (str, optional): Specifies the format of the input data.
|
|
1987
|
+
- Use 'BGRA' for raw pixel data, byte order is B G R A.
|
|
1988
|
+
- Use None for standard image file data (e.g., PNG, JPEG).
|
|
1989
|
+
width (int, optional): The width of the image. Required only when `format` is 'BGRA'.
|
|
1990
|
+
height (int, optional): The height of the image. Required only when `format` is 'BGRA'.
|
|
1991
|
+
Return `Bitmap`'s subclass instance or None.
|
|
1992
|
+
"""
|
|
1993
|
+
if format is not None:
|
|
1994
|
+
assert format == 'bgra' or format == 'BGRA'
|
|
1995
|
+
assert len(data) == width * height * 4
|
|
1996
|
+
if isinstance(data, bytearray):
|
|
1997
|
+
pixelBytes = data
|
|
1998
|
+
else:
|
|
1999
|
+
pixelBytes = bytearray(data)
|
|
2000
|
+
pixelArrayType = (ctypes.c_uint32 * (len(pixelBytes) // 4))
|
|
2001
|
+
pixelArray = pixelArrayType.from_buffer(pixelBytes)
|
|
2002
|
+
bitmap = Bitmap(width, height)
|
|
2003
|
+
bitmap.SetAllPixelColors(pixelArray)
|
|
2004
|
+
return bitmap
|
|
2005
|
+
cbmp = _DllClient.instance().dll.BitmapFromBytes(ctypes.c_char_p(data), len(data), 0)
|
|
2006
|
+
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2007
|
+
|
|
2008
|
+
@staticmethod
|
|
2009
|
+
def FromFile(filePath: str) -> Optional['Bitmap']:
|
|
2010
|
+
"""
|
|
2011
|
+
Create a `Bitmap` from a file path.
|
|
2012
|
+
filePath: str.
|
|
2013
|
+
Return `Bitmap`'s subclass instance or None.
|
|
2014
|
+
"""
|
|
2015
|
+
cbmp = _DllClient.instance().dll.BitmapFromFile(ctypes.c_wchar_p(filePath))
|
|
2016
|
+
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2017
|
+
|
|
2018
|
+
@staticmethod
|
|
2019
|
+
def FromNDArray(image: 'numpy.ndarray') -> 'MemoryBMP':
|
|
2020
|
+
"""
|
|
2021
|
+
Create a `MemoryBMP` from a numpy.ndarray(BGR or BGRA).
|
|
2022
|
+
"""
|
|
2023
|
+
import numpy as np
|
|
2024
|
+
assert len(image.shape) == 3 and (image.shape[2] == 3 or image.shape[2] == 4)
|
|
2025
|
+
height, width = image.shape[:2]
|
|
2026
|
+
if image.shape[2] == 3:
|
|
2027
|
+
bgraArray = np.zeros((height, width, 4), dtype=np.uint8)
|
|
2028
|
+
bgraArray[:, :, :3] = image[:, :, :]
|
|
2029
|
+
bgraArray[:, :, 3] = 0xFF
|
|
2030
|
+
else: # 4
|
|
2031
|
+
bgraArray = image
|
|
2032
|
+
pixelBytes = bytearray(bgraArray.tobytes())
|
|
2033
|
+
pixelArrayType = (ctypes.c_uint32 * (len(pixelBytes) // 4))
|
|
2034
|
+
pixelArray = pixelArrayType.from_buffer(pixelBytes)
|
|
2035
|
+
bitmap = MemoryBMP(width, height)
|
|
2036
|
+
bitmap.SetAllPixelColors(pixelArray)
|
|
2037
|
+
return bitmap
|
|
2038
|
+
|
|
2039
|
+
@staticmethod
|
|
2040
|
+
def FromPILImage(image: 'PIL.Image.Image') -> 'MemoryBMP':
|
|
2041
|
+
"""
|
|
2042
|
+
Create a `MemoryBMP` from a PIL Image.
|
|
2043
|
+
"""
|
|
2044
|
+
assert image.mode == 'RGB' or image.mode == 'RGBA'
|
|
2045
|
+
try:
|
|
2046
|
+
import numpy as np
|
|
2047
|
+
imgArray = np.array(image)
|
|
2048
|
+
if image.mode == 'RGB':
|
|
2049
|
+
imgArray = imgArray[:, :, ::-1]
|
|
2050
|
+
else: # 'RGBA'
|
|
2051
|
+
imgArray = imgArray[:, :, [2, 1, 0, 3]]
|
|
2052
|
+
return Bitmap.FromNDArray(imgArray)
|
|
2053
|
+
except:
|
|
2054
|
+
if image.mode == 'RGB':
|
|
2055
|
+
rgbaImg = image.convert('RGBA')
|
|
2056
|
+
rgbaBytes = rgbaImg.tobytes()
|
|
2057
|
+
else: # 'RGBA'
|
|
2058
|
+
rgbaBytes = image.tobytes()
|
|
2059
|
+
pixelBytes = bytearray(rgbaBytes)
|
|
2060
|
+
for i in range(0, len(pixelBytes), 4):
|
|
2061
|
+
pixel = pixelBytes[i:i+4]
|
|
2062
|
+
pixelBytes[i:i+4] = pixel[2], pixel[1], pixel[0], pixel[3]
|
|
2063
|
+
pixelArrayType = (ctypes.c_uint32 * (len(pixelBytes) // 4))
|
|
2064
|
+
pixelArray = pixelArrayType.from_buffer(pixelBytes)
|
|
2065
|
+
bitmap = MemoryBMP(image.width, image.height)
|
|
2066
|
+
bitmap.SetAllPixelColors(pixelArray)
|
|
2067
|
+
return bitmap
|
|
2068
|
+
|
|
2069
|
+
def ToFile(self, savePath: str, *, quality: int = None) -> bool:
|
|
2070
|
+
"""
|
|
2071
|
+
Save to a file.
|
|
2072
|
+
savePath: str, should end with .bmp, .jpg, .jpeg, .png, .gif, .tif, .tiff.
|
|
2073
|
+
quality: int, 1-100, only used when save to jpeg.
|
|
2074
|
+
Return bool, True if succeed otherwise False.
|
|
2075
|
+
Note: If file extension is .gif or .tiff, it is only saved as a single frame.
|
|
2076
|
+
If you want to save a gif or a tiff file with multiple frames, use `GIF.ToGifFile` or `TIFF.ToTiffFile`.
|
|
2077
|
+
"""
|
|
2078
|
+
name, ext = os.path.splitext(savePath)
|
|
2079
|
+
extMap = {'.bmp': 'image/bmp',
|
|
2080
|
+
'.jpg': 'image/jpeg',
|
|
2081
|
+
'.jpeg': 'image/jpeg',
|
|
2082
|
+
'.gif': 'image/gif',
|
|
2083
|
+
'.tif': 'image/tiff',
|
|
2084
|
+
'.tiff': 'image/tiff',
|
|
2085
|
+
'.png': 'image/png',
|
|
2086
|
+
}
|
|
2087
|
+
gdiplusImageFormat = extMap.get(ext.lower(), 'image/png')
|
|
2088
|
+
gdiStatus = _DllClient.instance().dll.BitmapToFile(ctypes.c_size_t(self._bitmap), ctypes.c_wchar_p(savePath), ctypes.c_wchar_p(gdiplusImageFormat), quality or 80)
|
|
2089
|
+
return gdiStatus == 0
|
|
2090
|
+
|
|
2091
|
+
def ToBytes(self, format: str, *, quality: int = None) -> bytearray:
|
|
2092
|
+
"""
|
|
2093
|
+
Convert to a bytearray in the specified format.
|
|
2094
|
+
format: str, The desired output format. Supported formats include:
|
|
2095
|
+
- 'BGRA': Raw pixel data without any file header, byte order is B G R A.
|
|
2096
|
+
- 'bmp', 'jpg', 'jpeg', 'gif', 'tif', 'tiff', 'png':
|
|
2097
|
+
Standard image file formats with corresponding file headers.
|
|
2098
|
+
quality: int, 1-100, only used when format is jpg and jpeg.
|
|
2099
|
+
Note: If format is gif or tiff, only the active single frame is saved to a bytearray.
|
|
2100
|
+
Currently multiple frames are not supported.
|
|
2101
|
+
"""
|
|
2102
|
+
format = format.lower()
|
|
2103
|
+
if format == 'bgra':
|
|
2104
|
+
return bytearray(memoryview(self.GetAllPixelColors()))
|
|
2105
|
+
|
|
2106
|
+
if format[0] != '.':
|
|
2107
|
+
format = '.' + format
|
|
2108
|
+
extMap = {'.bmp': 'image/bmp',
|
|
2109
|
+
'.jpg': 'image/jpeg',
|
|
2110
|
+
'.jpeg': 'image/jpeg',
|
|
2111
|
+
'.gif': 'image/gif',
|
|
2112
|
+
'.tif': 'image/tiff',
|
|
2113
|
+
'.tiff': 'image/tiff',
|
|
2114
|
+
'.png': 'image/png',
|
|
2115
|
+
}
|
|
2116
|
+
gdiplusImageFormat = extMap.get(format.lower(), 'image/png')
|
|
2117
|
+
stream = ctypes.c_size_t()
|
|
2118
|
+
gdiStatus = _DllClient.instance().dll.BitmapToStream(
|
|
2119
|
+
ctypes.c_size_t(self._bitmap), ctypes.byref(stream), ctypes.c_wchar_p(gdiplusImageFormat), quality or 80)
|
|
2120
|
+
if stream.value == 0:
|
|
2121
|
+
return None
|
|
2122
|
+
streamSize = _DllClient.instance().dll.GetStreamSize(stream)
|
|
2123
|
+
if streamSize == 0:
|
|
2124
|
+
return None
|
|
2125
|
+
data = bytearray(streamSize)
|
|
2126
|
+
cdata = (ctypes.c_ubyte * streamSize).from_buffer(data)
|
|
2127
|
+
streamSize = _DllClient.instance().dll.CopyStreamData(stream, cdata, streamSize)
|
|
2128
|
+
_DllClient.instance().dll.ReleaseStream(stream)
|
|
2129
|
+
return data
|
|
2130
|
+
|
|
2131
|
+
def ToPILImage(self) -> 'PIL.Image.Image':
|
|
2132
|
+
"""
|
|
2133
|
+
Convert to a PIL image.
|
|
2134
|
+
usage:
|
|
2135
|
+
image = bitmap.ToPILImage()
|
|
2136
|
+
image.save("pil.png")
|
|
2137
|
+
image.save("pil.png")
|
|
2138
|
+
image.convert('RGB').save('pil.jpg', quality=80, optimize=True)
|
|
2139
|
+
"""
|
|
2140
|
+
from PIL import Image
|
|
2141
|
+
return Image.frombytes('RGBA', (self.Width, self.Height), self.ToBytes('BGRA'), 'raw', 'BGRA')
|
|
2142
|
+
|
|
2143
|
+
def ToNDArray(self) -> 'numpy.ndarray':
|
|
2144
|
+
"""
|
|
2145
|
+
Convert to a numpy.ndarray(shape=(height,width,4), compatible with OpenCV).
|
|
2146
|
+
usage:
|
|
2147
|
+
bgraImage = bitmap.ToNDArray()
|
|
2148
|
+
bgrImage = cv2.cvtColor(bgraImage, cv2.COLOR_BGRA2BGR)
|
|
2149
|
+
"""
|
|
2150
|
+
import numpy as np
|
|
2151
|
+
npArray = np.frombuffer(self.ToBytes('BGRA'), dtype=np.uint8)
|
|
2152
|
+
return npArray.reshape((self.Height, self.Width, 4))
|
|
2153
|
+
|
|
2154
|
+
def GetRawFormat(self) -> int:
|
|
2155
|
+
"""
|
|
2156
|
+
return a int value in class `RawFormat`
|
|
2157
|
+
"""
|
|
2158
|
+
if self._format == RawFormat.Undefined:
|
|
2159
|
+
formatType = ctypes.c_uint()
|
|
2160
|
+
_DllClient.instance().dll.BitmapGetRawFormat(ctypes.c_size_t(self._bitmap), ctypes.byref(formatType))
|
|
2161
|
+
self._format = formatType.value
|
|
2162
|
+
return self._format
|
|
2163
|
+
|
|
2164
|
+
def GetRawFormatStr(self) -> str:
|
|
2165
|
+
"""
|
|
2166
|
+
return the string description of `RawFormat` value
|
|
2167
|
+
"""
|
|
2168
|
+
if not self._formatStr:
|
|
2169
|
+
self._formatStr = _GetDictKeyName(RawFormat.__dict__, self.GetRawFormat())
|
|
2170
|
+
return self._formatStr
|
|
2171
|
+
|
|
2172
|
+
def GetPixelColor(self, x: int, y: int) -> int:
|
|
2173
|
+
"""
|
|
2174
|
+
Get color value of a pixel.
|
|
2175
|
+
x: int.
|
|
2176
|
+
y: int.
|
|
2177
|
+
Return int, ARGB(0xAARRGGBB) color format.
|
|
2178
|
+
b = argb & 0x0000_00FF
|
|
2179
|
+
g = (argb & 0x0000_FF00) >> 8
|
|
2180
|
+
r = (argb & 0x00FF_0000) >> 16
|
|
2181
|
+
a = (argb & 0xFF00_0000) >> 24
|
|
2182
|
+
"""
|
|
2183
|
+
return _DllClient.instance().dll.BitmapGetPixel(ctypes.c_size_t(self._bitmap), x, y)
|
|
2184
|
+
|
|
2185
|
+
def SetPixelColor(self, x: int, y: int, argb: int) -> bool:
|
|
2186
|
+
"""
|
|
2187
|
+
Set color value of a pixel.
|
|
2188
|
+
x: int.
|
|
2189
|
+
y: int.
|
|
2190
|
+
argb: int, ARGB(0xAARRGGBB) color format.
|
|
2191
|
+
Return bool, True if succeed otherwise False.
|
|
2192
|
+
"""
|
|
2193
|
+
gdiStatus = _DllClient.instance().dll.BitmapSetPixel(ctypes.c_size_t(self._bitmap), x, y, argb)
|
|
2194
|
+
return gdiStatus == 0
|
|
2195
|
+
|
|
2196
|
+
def GetPixelColorsHorizontally(self, x: int, y: int, count: int) -> ctypes.Array:
|
|
2197
|
+
"""
|
|
2198
|
+
x: int.
|
|
2199
|
+
y: int.
|
|
2200
|
+
count: int.
|
|
2201
|
+
Return `ctypes.Array`, an iterable array of int values in ARGB(0xAARRGGBB) color format form point x,y horizontally.
|
|
2202
|
+
"""
|
|
2203
|
+
#assert count <= self.Width * (self.Height - y) - x, 'count > max available from x,y'
|
|
2204
|
+
arrayType = ctypes.c_uint32 * count
|
|
2205
|
+
values = arrayType()
|
|
2206
|
+
gdiStatus = _DllClient.instance().dll.BitmapGetPixelsHorizontally(ctypes.c_size_t(self._bitmap), x, y, values, count)
|
|
2207
|
+
return values
|
|
2208
|
+
|
|
2209
|
+
def SetPixelColorsHorizontally(self, x: int, y: int, colors: Sequence[int]) -> bool:
|
|
2210
|
+
"""
|
|
2211
|
+
Set pixel colors form x,y horizontally.
|
|
2212
|
+
x: int.
|
|
2213
|
+
y: int.
|
|
2214
|
+
colors: Sequence[int], an iterable list of int color values in ARGB(0xAARRGGBB) color format,
|
|
2215
|
+
use ctypes.Array for better performance, such as `ctypes.c_uint32 * length`.
|
|
2216
|
+
Return bool, True if succeed otherwise False.
|
|
2217
|
+
"""
|
|
2218
|
+
count = len(colors)
|
|
2219
|
+
#assert count <= self.Width * (self.Height - y) - x, 'len(colors) > max available from x,y'
|
|
2220
|
+
if not isinstance(colors, ctypes.Array):
|
|
2221
|
+
arrayType = ctypes.c_uint32 * count
|
|
2222
|
+
colors = arrayType(*colors)
|
|
2223
|
+
gdiStatus = _DllClient.instance().dll.BitmapSetPixelsHorizontally(ctypes.c_size_t(self._bitmap), x, y, colors, count)
|
|
2224
|
+
return gdiStatus == 0
|
|
2225
|
+
|
|
2226
|
+
def GetPixelColorsVertically(self, x: int, y: int, count: int) -> ctypes.Array:
|
|
2227
|
+
"""
|
|
2228
|
+
x: int.
|
|
2229
|
+
y: int.
|
|
2230
|
+
count: int.
|
|
2231
|
+
Return `ctypes.Array`, an iterable array of int values in ARGB(0xAARRGGBB) color format form point x,y vertically.
|
|
2232
|
+
"""
|
|
2233
|
+
#assert count <= self.Height * (self.Width - x) - y, 'count > max available from x,y'
|
|
2234
|
+
arrayType = ctypes.c_uint32 * count
|
|
2235
|
+
values = arrayType()
|
|
2236
|
+
gdiStatus = _DllClient.instance().dll.BitmapGetPixelsVertically(ctypes.c_size_t(self._bitmap), x, y, values, count)
|
|
2237
|
+
return values
|
|
2238
|
+
|
|
2239
|
+
def SetPixelColorsVertically(self, x: int, y: int, colors: Sequence[int]) -> bool:
|
|
2240
|
+
"""
|
|
2241
|
+
Set pixel colors form x,y vertically.
|
|
2242
|
+
x: int.
|
|
2243
|
+
y: int.
|
|
2244
|
+
colors: Sequence[int], an iterable list of int color values in ARGB(0xAARRGGBB) color format,
|
|
2245
|
+
use ctypes.Array for better performance, such as `ctypes.c_uint32 * length`.
|
|
2246
|
+
Return bool, True if succeed otherwise False.
|
|
2247
|
+
"""
|
|
2248
|
+
count = len(colors)
|
|
2249
|
+
#assert count <= self.Height * (self.Width - x) - y, 'len(colors) > max available from x,y'
|
|
2250
|
+
if not isinstance(colors, ctypes.Array):
|
|
2251
|
+
arrayType = ctypes.c_uint32 * count
|
|
2252
|
+
colors = arrayType(*colors)
|
|
2253
|
+
gdiStatus = _DllClient.instance().dll.BitmapSetPixelsVertically(ctypes.c_size_t(self._bitmap), x, y, colors, count)
|
|
2254
|
+
return gdiStatus == 0
|
|
2255
|
+
|
|
2256
|
+
def GetPixelColorsOfRow(self, y: int) -> ctypes.Array:
|
|
2257
|
+
"""
|
|
2258
|
+
y: int, row index.
|
|
2259
|
+
Return `ctypes.Array`, an iterable array of int values in ARGB(0xAARRGGBB) color format of y row.
|
|
2260
|
+
"""
|
|
2261
|
+
return self.GetPixelColorsOfRect(0, y, self.Width, 1)
|
|
2262
|
+
|
|
2263
|
+
def GetPixelColorsOfColumn(self, x: int) -> ctypes.Array:
|
|
2264
|
+
"""
|
|
2265
|
+
x: int, column index.
|
|
2266
|
+
Return `ctypes.Array`, an iterable array of int values in ARGB(0xAARRGGBB) color format of x column.
|
|
2267
|
+
"""
|
|
2268
|
+
return self.GetPixelColorsOfRect(x, 0, 1, self.Height)
|
|
2269
|
+
|
|
2270
|
+
def GetPixelColorsOfRect(self, x: int, y: int, width: int, height: int) -> ctypes.Array:
|
|
2271
|
+
"""
|
|
2272
|
+
x: int.
|
|
2273
|
+
y: int.
|
|
2274
|
+
width: int.
|
|
2275
|
+
height: int.
|
|
2276
|
+
Return `ctypes.Array`, an iterable array of int values in ARGB(0xAARRGGBB) color format of the input rect.
|
|
2277
|
+
"""
|
|
2278
|
+
arrayType = ctypes.c_uint32 * (width * height)
|
|
2279
|
+
values = arrayType()
|
|
2280
|
+
gdiStatus = _DllClient.instance().dll.BitmapGetPixelsOfRect(ctypes.c_size_t(self._bitmap), x, y, width, height, values)
|
|
2281
|
+
return values
|
|
2282
|
+
|
|
2283
|
+
def SetPixelColorsOfRect(self, x: int, y: int, width: int, height: int, colors: Sequence[int]) -> bool:
|
|
2284
|
+
"""
|
|
2285
|
+
x: int.
|
|
2286
|
+
y: int.
|
|
2287
|
+
width: int.
|
|
2288
|
+
height: int.
|
|
2289
|
+
colors: Sequence[int], a sequence of int values in ARGB(0xAARRGGBB) color format, it's length must equal to width*height,
|
|
2290
|
+
use ctypes.Array for better performance, such as `ctypes.c_uint32 * (width*height)`.
|
|
2291
|
+
Return bool.
|
|
2292
|
+
"""
|
|
2293
|
+
#assert len(colors) == width * height, 'len(colors) != width * height'
|
|
2294
|
+
if not isinstance(colors, ctypes.Array):
|
|
2295
|
+
arrayType = ctypes.c_uint32 * (width * height)
|
|
2296
|
+
colors = arrayType(*colors)
|
|
2297
|
+
gdiStatus = _DllClient.instance().dll.BitmapSetPixelsOfRect(ctypes.c_size_t(self._bitmap), x, y, width, height, colors)
|
|
2298
|
+
return gdiStatus == 0
|
|
2299
|
+
|
|
2300
|
+
def GetPixelColorsOfRects(self, rects: List[Tuple[int, int, int, int]]) -> List[ctypes.Array]:
|
|
2301
|
+
"""
|
|
2302
|
+
rects: List[Tuple[int, int, int, int]], such as [(0,0,10,10), (10,10,20,20), (x,y,width,height)].
|
|
2303
|
+
Return List[ctypes.Array], a list whose elements are ctypes.Array which is an iterable array of
|
|
2304
|
+
int values in ARGB(0xAARRGGBB) color format.
|
|
2305
|
+
"""
|
|
2306
|
+
return [self.GetPixelColorsOfRect(x, y, width, height) for x, y, width, height in rects]
|
|
2307
|
+
|
|
2308
|
+
def GetAllPixelColors(self) -> ctypes.Array:
|
|
2309
|
+
"""
|
|
2310
|
+
Return `ctypes.Array`, an iterable array of int values in ARGB(0xAARRGGBB) color format.
|
|
2311
|
+
"""
|
|
2312
|
+
return self.GetPixelColorsOfRect(0, 0, self.Width, self.Height)
|
|
2313
|
+
|
|
2314
|
+
def SetAllPixelColors(self, colors: Sequence[int]) -> bool:
|
|
2315
|
+
"""
|
|
2316
|
+
colors: Sequence[int], a sequence of int values in ARGB(0xAARRGGBB) color format, it's length must equal to width*height,
|
|
2317
|
+
use ctypes.Array for better performance, such as `ctypes.c_uint32 * (width*height)`.
|
|
2318
|
+
Return bool.
|
|
2319
|
+
"""
|
|
2320
|
+
return self.SetPixelColorsOfRect(0, 0, self.Width, self.Height, colors)
|
|
2321
|
+
|
|
2322
|
+
def Clear(self, color: int = 0xFFFFFFFF, x: int = 0, y: int = 0, width: int = 0, height: int = 0) -> bool:
|
|
2323
|
+
"""
|
|
2324
|
+
Set the color of rect(x,y,width,height).
|
|
2325
|
+
color: int, ARGB(0xAARRGGBB) color format.
|
|
2326
|
+
x: int.
|
|
2327
|
+
y: int.
|
|
2328
|
+
width: int, if == 0, the width will be self.Width-x
|
|
2329
|
+
height: int, if == 0, the height will be self.Height-y
|
|
2330
|
+
Return bool.
|
|
2331
|
+
"""
|
|
2332
|
+
if width == 0:
|
|
2333
|
+
width = self.Width - x
|
|
2334
|
+
if height == 0:
|
|
2335
|
+
height = self.Height - y
|
|
2336
|
+
arrayType = ctypes.c_uint * (width * height)
|
|
2337
|
+
nativeArray = arrayType(*[color]*(width * height))
|
|
2338
|
+
return self.SetPixelColorsOfRect(x, y, width, height, nativeArray)
|
|
2339
|
+
|
|
2340
|
+
def Clone(self) -> 'MemoryBMP':
|
|
2341
|
+
"""
|
|
2342
|
+
Return `Bitmap`'s subclass instance.
|
|
2343
|
+
The cloned Bitmap's RawFormat is same as original Bitmap.
|
|
2344
|
+
If Bitmap is multiple frames, the cloned Bitmap is also multiple frames.
|
|
2345
|
+
"""
|
|
2346
|
+
cbmp = _DllClient.instance().dll.BitmapClone(ctypes.c_size_t(self._bitmap), 0, 0, self._width, self._height)
|
|
2347
|
+
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2348
|
+
|
|
2349
|
+
def Copy(self, x: int = 0, y: int = 0, width: int = 0, height: int = 0) -> 'MemoryBMP':
|
|
2350
|
+
"""
|
|
2351
|
+
x: int, must >= 0.
|
|
2352
|
+
y: int, must >= 0.
|
|
2353
|
+
width: int, must <= self.Width-x.
|
|
2354
|
+
height: int, must <= self.Height-y.
|
|
2355
|
+
Return `MemoryBMP`, a new Bitmap copied from (x,y,width,height).
|
|
2356
|
+
If Bitmap is multiple frames, the cloned Bitmap is only single frame of the active frame.
|
|
2357
|
+
"""
|
|
2358
|
+
if width == 0:
|
|
2359
|
+
width = self.Width - x
|
|
2360
|
+
if height == 0:
|
|
2361
|
+
height = self.Height - y
|
|
2362
|
+
nativeArray = self.GetPixelColorsOfRect(x, y, width, height)
|
|
2363
|
+
bitmap = MemoryBMP(width, height)
|
|
2364
|
+
bitmap.SetPixelColorsOfRect(0, 0, width, height, nativeArray)
|
|
2365
|
+
return bitmap
|
|
2366
|
+
# cbmp = _DllClient.instance().dll.BitmapClone(ctypes.c_size_t(self._bitmap), x, y, width, height)
|
|
2367
|
+
# return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2368
|
+
|
|
2369
|
+
def Paste(self, x: int, y: int, bitmap: 'Bitmap') -> bool:
|
|
2370
|
+
"""
|
|
2371
|
+
Paste bitmap to (x,y) of self, modify the original Bitmap,
|
|
2372
|
+
if x < 0 or x+bitmap.Width > self.Width, only the intersection part of bitmap is pasted,
|
|
2373
|
+
if y < 0 or y+bitmap.Height > self.Height, only the intersection part of bitmap is pasted.
|
|
2374
|
+
x: int, can < 0.
|
|
2375
|
+
y: int, can < 0.
|
|
2376
|
+
bitmap: `Bitmap`.
|
|
2377
|
+
Return bool, True if bitmap or a part of bitmap is pasted.
|
|
2378
|
+
"""
|
|
2379
|
+
left, top, right, bottom = max(0, x), max(0, y), min(self.Width, x + bitmap.Width), min(self.Height, y + bitmap.Height)
|
|
2380
|
+
width, height = right - left, bottom - top
|
|
2381
|
+
if width <= 0 or height <= 0:
|
|
2382
|
+
return False
|
|
2383
|
+
srcX = 0 if x >= 0 else -x
|
|
2384
|
+
srcY = 0 if y >= 0 else -y
|
|
2385
|
+
nativeArray = bitmap.GetPixelColorsOfRect(srcX, srcY, width, height)
|
|
2386
|
+
return self.SetPixelColorsOfRect(left, top, width, height, nativeArray)
|
|
2387
|
+
|
|
2388
|
+
def PastePart(self, dstX: int, dstY: int, srcBitmap: 'Bitmap', srcX: int = 0, srcY: int = 0, srcWidth: int = 0, srcHeight: int = 0) -> bool:
|
|
2389
|
+
"""
|
|
2390
|
+
Paste (srcX, srcY, srcWidth, srcHeight) of bitmap to (dstX, dstY) of self, modify the original Bitmap,
|
|
2391
|
+
only the intersection part of the bitmap is pasted.
|
|
2392
|
+
dstX: int, must >= 0.
|
|
2393
|
+
dstY: int, must >= 0.
|
|
2394
|
+
srcBitmap: `Bitmap`.
|
|
2395
|
+
srcX: int, must >= 0.
|
|
2396
|
+
srcY: int, must >= 0.
|
|
2397
|
+
srcWidth: int, must >= 0 and <= srcBitmap.Width - srcX.
|
|
2398
|
+
srcHeight: int, must >= 0 and <= srcBitmap.Height - srcY.
|
|
2399
|
+
Return bool, True if a part of srcBitmap is pasted.
|
|
2400
|
+
"""
|
|
2401
|
+
if srcWidth == 0:
|
|
2402
|
+
srcWidth = srcBitmap.Width - srcX
|
|
2403
|
+
if srcHeight == 0:
|
|
2404
|
+
srcHeight = srcBitmap.Height - srcY
|
|
2405
|
+
left, top, right, bottom = max(0, dstX), max(0, dstY), min(self.Width, dstX + srcWidth), min(self.Height, dstY + srcHeight)
|
|
2406
|
+
width, height = right - left, bottom - top
|
|
2407
|
+
if width <= 0 or height <= 0:
|
|
2408
|
+
return False
|
|
2409
|
+
nativeArray = srcBitmap.GetPixelColorsOfRect(srcX, srcY, width, height)
|
|
2410
|
+
return self.SetPixelColorsOfRect(dstX, dstY, width, height, nativeArray)
|
|
2411
|
+
|
|
2412
|
+
def Resize(self, width: int, height: int) -> 'MemoryBMP':
|
|
2413
|
+
"""
|
|
2414
|
+
Resize a copy of the original to size (width, height), the original Bitmap is not modified.
|
|
2415
|
+
width: int.
|
|
2416
|
+
height: int.
|
|
2417
|
+
Return a new `MemoryBMP`, the original is not modified.
|
|
2418
|
+
"""
|
|
2419
|
+
cbmp = _DllClient.instance().dll.BitmapResize(ctypes.c_size_t(self._bitmap), width, height)
|
|
2420
|
+
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2421
|
+
|
|
2422
|
+
def Rotate(self, angle: float, backgroundColor: int = 0xFFFFFFFF) -> 'MemoryBMP':
|
|
2423
|
+
"""
|
|
2424
|
+
Rotate angle degrees clockwise around the center point.
|
|
2425
|
+
Return a copy of the original with angle, the original Bitmap is not modified.
|
|
2426
|
+
angle: float, closewise.
|
|
2427
|
+
backgroundColor: int, ARGB(0xAARRGGBB) color format.
|
|
2428
|
+
Return a new `MemoryBMP`, the new Bitmap size may not be the same as the original.
|
|
2429
|
+
"""
|
|
2430
|
+
cbmp = _DllClient.instance().dll.BitmapRotate(ctypes.c_size_t(self._bitmap), ctypes.c_float(angle), backgroundColor)
|
|
2431
|
+
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2432
|
+
|
|
2433
|
+
def RotateFlip(self, rotateFlip: int) -> 'MemoryBMP':
|
|
2434
|
+
"""
|
|
2435
|
+
Rotate 90*n or Filp a copy of the original, the original Bitmap is not modified.
|
|
2436
|
+
rotateFlip: int, a value in class `RotateFlipType`.
|
|
2437
|
+
Return a new `MemoryBMP`, the original is not modified.
|
|
2438
|
+
"""
|
|
2439
|
+
bitmap = self.Copy()
|
|
2440
|
+
gdiStatus = _DllClient.instance().dll.BitmapRotateFlip(ctypes.c_size_t(bitmap._bitmap), rotateFlip)
|
|
2441
|
+
bitmap._GetSize()
|
|
2442
|
+
return bitmap
|
|
2443
|
+
|
|
2444
|
+
def RotateWithSameSize(self, dx: float, dy: float, angle: float, backgroundColor: int = 0xFFFFFFFF) -> 'MemoryBMP':
|
|
2445
|
+
"""
|
|
2446
|
+
Rotate angle degrees clockwise around the point (dx, dy) from a copy, the original Bitmap is not modified.
|
|
2447
|
+
angle: float, closewise.
|
|
2448
|
+
backgroundColor: int, ARGB(0xAARRGGBB) color format.
|
|
2449
|
+
Return a new `MemoryBMP`, the original is not modified.
|
|
2450
|
+
"""
|
|
2451
|
+
cbmp = _DllClient.instance().dll.BitmapRotateWithSameSize(ctypes.c_size_t(self._bitmap),
|
|
2452
|
+
ctypes.c_float(dx), ctypes.c_float(dy), ctypes.c_float(angle), backgroundColor)
|
|
2453
|
+
return Bitmap._FromGdiplusBitmap(cbmp)
|
|
2454
|
+
|
|
2455
|
+
def __str__(self) -> str:
|
|
2456
|
+
return '{}(Width={}, Height={})'.format(self.__class__.__name__, self._width, self._height)
|
|
2457
|
+
|
|
2458
|
+
def __repr__(self) -> str:
|
|
2459
|
+
return '<{}(Width={}, Height={}) at 0x{:X}>'.format(self.__class__.__name__, self._width, self._height, id(self))
|
|
2460
|
+
|
|
2461
|
+
|
|
2462
|
+
class MultiFrameBitmap(Bitmap):
|
|
2463
|
+
def __init__(self, width: int = 0, height: int = 0):
|
|
2464
|
+
super().__init__(width, height)
|
|
2465
|
+
self._frameCount = 0
|
|
2466
|
+
self._index = 0
|
|
2467
|
+
|
|
2468
|
+
def SelectActiveFrame(self, index: int) -> bool:
|
|
2469
|
+
gdiStatus = _DllClient.instance().dll.MultiBitmapSelectActiveFrame(ctypes.c_size_t(self._bitmap), index)
|
|
2470
|
+
return gdiStatus == 0
|
|
2471
|
+
|
|
2472
|
+
def GetFrameCount(self) -> int:
|
|
2473
|
+
return self._frameCount
|
|
2474
|
+
|
|
2475
|
+
def __iter__(self):
|
|
2476
|
+
self._index = 0
|
|
2477
|
+
return self
|
|
2478
|
+
|
|
2479
|
+
def __next__(self) -> Bitmap:
|
|
2480
|
+
if self._index < self._frameCount:
|
|
2481
|
+
self.SelectActiveFrame(self._index)
|
|
2482
|
+
self._GetSize()
|
|
2483
|
+
self._index += 1
|
|
2484
|
+
return self.Copy()
|
|
2485
|
+
else:
|
|
2486
|
+
raise StopIteration
|
|
2487
|
+
|
|
2488
|
+
def __str__(self) -> str:
|
|
2489
|
+
return '{}(Width={}, Height={}, FrameCount={})'.format(self.__class__.__name__, self._width, self._height, self._frameCount)
|
|
2490
|
+
|
|
2491
|
+
def __repr__(self) -> str:
|
|
2492
|
+
return '<{}(Width={}, Height={}, FrameCount={}) at 0x{:X}>'.format(self.__class__.__name__, self._width, self._height, self._frameCount, id(self))
|
|
2493
|
+
|
|
2494
|
+
|
|
2495
|
+
class MemoryBMP(Bitmap):
|
|
2496
|
+
pass
|
|
2497
|
+
|
|
2498
|
+
|
|
2499
|
+
class BMP(Bitmap):
|
|
2500
|
+
pass
|
|
2501
|
+
|
|
2502
|
+
|
|
2503
|
+
class JPEG(Bitmap):
|
|
2504
|
+
pass
|
|
2505
|
+
|
|
2506
|
+
|
|
2507
|
+
class PNG(Bitmap):
|
|
2508
|
+
pass
|
|
2509
|
+
|
|
2510
|
+
|
|
2511
|
+
class EMF(Bitmap):
|
|
2512
|
+
pass
|
|
2513
|
+
|
|
2514
|
+
|
|
2515
|
+
class WMF(Bitmap):
|
|
2516
|
+
pass
|
|
2517
|
+
|
|
2518
|
+
|
|
2519
|
+
class ICON(Bitmap):
|
|
2520
|
+
pass
|
|
2521
|
+
|
|
2522
|
+
|
|
2523
|
+
class EXIF(Bitmap):
|
|
2524
|
+
pass
|
|
2525
|
+
|
|
2526
|
+
|
|
2527
|
+
class TIFF(MultiFrameBitmap):
|
|
2528
|
+
@staticmethod
|
|
2529
|
+
def ToTiffFile(path: str, bitmaps: List['Bitmap']) -> bool:
|
|
2530
|
+
'''
|
|
2531
|
+
Save a list of bitmaps to a multi frame tiff file.
|
|
2532
|
+
path: str, file path.
|
|
2533
|
+
bitmaps: List[Bitmap].
|
|
2534
|
+
'''
|
|
2535
|
+
cbitmaps = (ctypes.c_size_t * len(bitmaps))()
|
|
2536
|
+
for i, bmp in enumerate(bitmaps):
|
|
2537
|
+
cbitmaps[i] = bmp._bitmap
|
|
2538
|
+
gdiStatus = _DllClient.instance().dll.MultiBitmapToFile(cbitmaps, None, len(bitmaps),
|
|
2539
|
+
ctypes.c_wchar_p(path), "image/tiff")
|
|
2540
|
+
return gdiStatus == 0
|
|
2541
|
+
|
|
2542
|
+
|
|
2543
|
+
class GIF(MultiFrameBitmap):
|
|
2544
|
+
def __init__(self, width: int = 0, height: int = 0):
|
|
2545
|
+
super().__init__(width, height)
|
|
2546
|
+
self._frameDelay = ()
|
|
2547
|
+
|
|
2548
|
+
def _GetGifDealy(self):
|
|
2549
|
+
if self._bitmap:
|
|
2550
|
+
delayDataSize = _DllClient.instance().dll.MultiBitmapGetFrameDelaySize(ctypes.c_size_t(self._bitmap))
|
|
2551
|
+
delayData = (ctypes.c_byte * delayDataSize)()
|
|
2552
|
+
valueOffset = ctypes.c_int()
|
|
2553
|
+
gdiStatus = _DllClient.instance().dll.MultiBitmapGetFrameDelay(ctypes.c_size_t(self._bitmap), delayData, delayDataSize, ctypes.byref(valueOffset))
|
|
2554
|
+
if gdiStatus == 0:
|
|
2555
|
+
valueOffset = valueOffset.value
|
|
2556
|
+
# the uint of frame delay is 1/100 second
|
|
2557
|
+
self._frameDelay = tuple(10*int.from_bytes(delayData[i:i+4], byteorder='little') for i in range(valueOffset, valueOffset+4*self._frameCount, 4))
|
|
2558
|
+
|
|
2559
|
+
def GetFrameDelay(self, index: int) -> int:
|
|
2560
|
+
'''
|
|
2561
|
+
return frame delay in milliseconds
|
|
2562
|
+
'''
|
|
2563
|
+
return self._frameDelay[index]
|
|
2564
|
+
|
|
2565
|
+
def GetFrameDelays(self) -> List[int]:
|
|
2566
|
+
'''
|
|
2567
|
+
return a list of frame delays in milliseconds
|
|
2568
|
+
'''
|
|
2569
|
+
return list(self._frameDelay)
|
|
2570
|
+
|
|
2571
|
+
@staticmethod
|
|
2572
|
+
def ToGifFile(path: str, bitmaps: List[Bitmap], delays: List[int] = None) -> bool:
|
|
2573
|
+
'''
|
|
2574
|
+
Save a list of bitmaps to a multi frame gif file.
|
|
2575
|
+
path: str, file path.
|
|
2576
|
+
bitmaps: List[Bitmap].
|
|
2577
|
+
delays: List[int], frame delay time in milliseconds.
|
|
2578
|
+
Note: every frame delay must be > 10 ms.
|
|
2579
|
+
'''
|
|
2580
|
+
assert len(bitmaps) == len(delays)
|
|
2581
|
+
blen = len(bitmaps)
|
|
2582
|
+
cbitmaps = (ctypes.c_size_t * blen)()
|
|
2583
|
+
cdelays = (ctypes.c_uint32 * blen)()
|
|
2584
|
+
for i, bmp in enumerate(bitmaps):
|
|
2585
|
+
cbitmaps[i] = bmp._bitmap
|
|
2586
|
+
cdelays[i] = delays[i] // 10
|
|
2587
|
+
gdiStatus = _DllClient.instance().dll.SaveGif(cbitmaps, cdelays, blen, ctypes.c_wchar_p(path))
|
|
2588
|
+
return gdiStatus == 0
|
|
2589
|
+
|
|
2590
|
+
|
|
2591
|
+
_ClipboardLock = threading.Lock()
|
|
2592
|
+
|
|
2593
|
+
|
|
2594
|
+
def _OpenClipboard(value):
|
|
2595
|
+
end = ProcessTime() + 0.2
|
|
2596
|
+
while ProcessTime() < end:
|
|
2597
|
+
ret = ctypes.windll.user32.OpenClipboard(value)
|
|
2598
|
+
if ret:
|
|
2599
|
+
return ret
|
|
2600
|
+
time.sleep(0.005)
|
|
2601
|
+
|
|
2602
|
+
|
|
2603
|
+
def GetClipboardFormats() -> Dict[int, str]:
|
|
2604
|
+
'''
|
|
2605
|
+
Get clipboard formats that system clipboard has currently.
|
|
2606
|
+
Return Dict[int, str].
|
|
2607
|
+
The key is a int value in class `ClipboardFormat` or othes values that apps registered by ctypes.windll.user32.RegisterClipboardFormatW
|
|
2608
|
+
'''
|
|
2609
|
+
formats = {}
|
|
2610
|
+
with _ClipboardLock:
|
|
2611
|
+
if _OpenClipboard(0):
|
|
2612
|
+
formatType = 0
|
|
2613
|
+
arrayType = ctypes.c_wchar * 64
|
|
2614
|
+
while True:
|
|
2615
|
+
formatType = ctypes.windll.user32.EnumClipboardFormats(formatType)
|
|
2616
|
+
if formatType == 0:
|
|
2617
|
+
break
|
|
2618
|
+
values = arrayType()
|
|
2619
|
+
ctypes.windll.user32.GetClipboardFormatNameW(formatType, values, len(values))
|
|
2620
|
+
formatName = values.value
|
|
2621
|
+
if not formatName:
|
|
2622
|
+
formatName = _GetDictKeyName(ClipboardFormat.__dict__, formatType, lambda key: key.startswith('CF_'))
|
|
2623
|
+
formats[formatType] = formatName
|
|
2624
|
+
ctypes.windll.user32.CloseClipboard()
|
|
2625
|
+
return formats
|
|
2626
|
+
|
|
2627
|
+
|
|
2628
|
+
def GetClipboardText() -> str:
|
|
2629
|
+
with _ClipboardLock:
|
|
2630
|
+
if _OpenClipboard(0):
|
|
2631
|
+
if ctypes.windll.user32.IsClipboardFormatAvailable(ClipboardFormat.CF_UNICODETEXT):
|
|
2632
|
+
hClipboardData = ctypes.windll.user32.GetClipboardData(ClipboardFormat.CF_UNICODETEXT)
|
|
2633
|
+
hText = ctypes.windll.kernel32.GlobalLock(ctypes.c_void_p(hClipboardData))
|
|
2634
|
+
text = ctypes.c_wchar_p(hText).value
|
|
2635
|
+
ctypes.windll.kernel32.GlobalUnlock(ctypes.c_void_p(hClipboardData))
|
|
2636
|
+
ctypes.windll.user32.CloseClipboard()
|
|
2637
|
+
if text is None:
|
|
2638
|
+
return ''
|
|
2639
|
+
return text
|
|
2640
|
+
return ''
|
|
2641
|
+
|
|
2642
|
+
|
|
2643
|
+
def SetClipboardText(text: str) -> bool:
|
|
2644
|
+
"""
|
|
2645
|
+
Return bool, True if succeed otherwise False.
|
|
2646
|
+
"""
|
|
2647
|
+
ret = False
|
|
2648
|
+
with _ClipboardLock:
|
|
2649
|
+
if _OpenClipboard(0):
|
|
2650
|
+
ctypes.windll.user32.EmptyClipboard()
|
|
2651
|
+
textByteLen = (len(text) + 1) * 2
|
|
2652
|
+
hClipboardData = ctypes.windll.kernel32.GlobalAlloc(0x2, textByteLen) # GMEM_MOVEABLE
|
|
2653
|
+
hDestText = ctypes.windll.kernel32.GlobalLock(ctypes.c_void_p(hClipboardData))
|
|
2654
|
+
ctypes.cdll.msvcrt.wcsncpy(ctypes.c_wchar_p(hDestText), ctypes.c_wchar_p(text), ctypes.c_size_t(textByteLen // 2))
|
|
2655
|
+
ctypes.windll.kernel32.GlobalUnlock(ctypes.c_void_p(hClipboardData))
|
|
2656
|
+
# system owns hClipboardData after calling SetClipboardData,
|
|
2657
|
+
# application can not write to or free the data once ownership has been transferred to the system
|
|
2658
|
+
if ctypes.windll.user32.SetClipboardData(ctypes.c_uint(ClipboardFormat.CF_UNICODETEXT), ctypes.c_void_p(hClipboardData)):
|
|
2659
|
+
ret = True
|
|
2660
|
+
else:
|
|
2661
|
+
ctypes.windll.kernel32.GlobalFree(ctypes.c_void_p(hClipboardData))
|
|
2662
|
+
ctypes.windll.user32.CloseClipboard()
|
|
2663
|
+
return ret
|
|
2664
|
+
|
|
2665
|
+
|
|
2666
|
+
def GetClipboardHtml() -> str:
|
|
2667
|
+
"""
|
|
2668
|
+
Return str.
|
|
2669
|
+
Note: the positions(StartHTML, EndHTML ...) are valid for utf-8 encoding html text,
|
|
2670
|
+
when the utf-8 encoding html text is decoded to Python unicode str,
|
|
2671
|
+
the positions may not correspond to the actual positions in the returned str.
|
|
2672
|
+
"""
|
|
2673
|
+
with _ClipboardLock:
|
|
2674
|
+
if _OpenClipboard(0):
|
|
2675
|
+
if ctypes.windll.user32.IsClipboardFormatAvailable(ClipboardFormat.CF_HTML):
|
|
2676
|
+
hClipboardData = ctypes.windll.user32.GetClipboardData(ClipboardFormat.CF_HTML)
|
|
2677
|
+
hText = ctypes.windll.kernel32.GlobalLock(ctypes.c_void_p(hClipboardData))
|
|
2678
|
+
v = ctypes.c_char_p(hText).value
|
|
2679
|
+
ctypes.windll.kernel32.GlobalUnlock(ctypes.c_void_p(hClipboardData))
|
|
2680
|
+
ctypes.windll.user32.CloseClipboard()
|
|
2681
|
+
if v is None:
|
|
2682
|
+
return ''
|
|
2683
|
+
return v.decode('utf-8')
|
|
2684
|
+
return ''
|
|
2685
|
+
|
|
2686
|
+
|
|
2687
|
+
def SetClipboardHtml(htmlText: str) -> bool:
|
|
2688
|
+
"""
|
|
2689
|
+
htmlText: str, such as '<h1>Title</h1><h3>Hello</h3><p>hello world</p>'
|
|
2690
|
+
Return bool, True if succeed otherwise False.
|
|
2691
|
+
Refer: https://docs.microsoft.com/en-us/troubleshoot/cpp/add-html-code-clipboard
|
|
2692
|
+
"""
|
|
2693
|
+
u8Html = htmlText.encode('utf-8')
|
|
2694
|
+
formatBytes = b'Version:0.9\r\nStartHTML:00000000\r\nEndHTML:00000000\r\nStartFragment:00000000\r\nEndFragment:00000000\r\n<html>\r\n<body>\r\n<!--StartFragment-->{}<!--EndFragment-->\r\n</body>\r\n</html>'
|
|
2695
|
+
startHtml = formatBytes.find(b'<html>')
|
|
2696
|
+
endHtml = len(formatBytes) + len(u8Html) - 2
|
|
2697
|
+
startFragment = formatBytes.find(b'{}')
|
|
2698
|
+
endFragment = formatBytes.find(b'<!--EndFragment-->') + len(u8Html) - 2
|
|
2699
|
+
formatBytes = formatBytes.replace(b'StartHTML:00000000', 'StartHTML:{:08}'.format(startHtml).encode('utf-8'))
|
|
2700
|
+
formatBytes = formatBytes.replace(b'EndHTML:00000000', 'EndHTML:{:08}'.format(endHtml).encode('utf-8'))
|
|
2701
|
+
formatBytes = formatBytes.replace(b'StartFragment:00000000', 'StartFragment:{:08}'.format(startFragment).encode('utf-8'))
|
|
2702
|
+
formatBytes = formatBytes.replace(b'EndFragment:00000000', 'EndFragment:{:08}'.format(endFragment).encode('utf-8'))
|
|
2703
|
+
u8Result = formatBytes.replace(b'{}', u8Html)
|
|
2704
|
+
ret = False
|
|
2705
|
+
with _ClipboardLock:
|
|
2706
|
+
if _OpenClipboard(0):
|
|
2707
|
+
ctypes.windll.user32.EmptyClipboard()
|
|
2708
|
+
hClipboardData = ctypes.windll.kernel32.GlobalAlloc(0x2002, len(u8Result) + 4) # GMEM_MOVEABLE |GMEM_DDESHARE
|
|
2709
|
+
hDestText = ctypes.windll.kernel32.GlobalLock(ctypes.c_void_p(hClipboardData))
|
|
2710
|
+
ctypes.cdll.msvcrt.strncpy(ctypes.c_char_p(hDestText), ctypes.c_char_p(u8Result), len(u8Result))
|
|
2711
|
+
ctypes.windll.kernel32.GlobalUnlock(ctypes.c_void_p(hClipboardData))
|
|
2712
|
+
# system owns hClipboardData after calling SetClipboardData,
|
|
2713
|
+
# application can not write to or free the data once ownership has been transferred to the system
|
|
2714
|
+
if ctypes.windll.user32.SetClipboardData(ctypes.c_uint(ClipboardFormat.CF_HTML), ctypes.c_void_p(hClipboardData)):
|
|
2715
|
+
ret = True
|
|
2716
|
+
else:
|
|
2717
|
+
ctypes.windll.kernel32.GlobalFree(ctypes.c_void_p(hClipboardData))
|
|
2718
|
+
ctypes.windll.user32.CloseClipboard()
|
|
2719
|
+
return ret
|
|
2720
|
+
|
|
2721
|
+
|
|
2722
|
+
def GetClipboardBitmap() -> Optional[Bitmap]:
|
|
2723
|
+
with _ClipboardLock:
|
|
2724
|
+
if _OpenClipboard(0):
|
|
2725
|
+
if ctypes.windll.user32.IsClipboardFormatAvailable(ClipboardFormat.CF_BITMAP):
|
|
2726
|
+
hClipboardData = ctypes.windll.user32.GetClipboardData(ClipboardFormat.CF_BITMAP)
|
|
2727
|
+
cbmp = _DllClient.instance().dll.BitmapFromHBITMAP(ctypes.c_size_t(hClipboardData), 0, 0, 0, 0)
|
|
2728
|
+
bitmap = Bitmap._FromGdiplusBitmap(cbmp)
|
|
2729
|
+
ctypes.windll.user32.CloseClipboard()
|
|
2730
|
+
return bitmap
|
|
2731
|
+
return None
|
|
2732
|
+
|
|
2733
|
+
|
|
2734
|
+
def SetClipboardBitmap(bitmap: Bitmap) -> bool:
|
|
2735
|
+
"""
|
|
2736
|
+
Return bool, True if succeed otherwise False.
|
|
2737
|
+
"""
|
|
2738
|
+
ret = False
|
|
2739
|
+
with _ClipboardLock:
|
|
2740
|
+
if bitmap._bitmap and _OpenClipboard(0):
|
|
2741
|
+
ctypes.windll.user32.EmptyClipboard()
|
|
2742
|
+
hBitmap = _DllClient.instance().dll.BitmapToHBITMAP(ctypes.c_size_t(bitmap._bitmap), 0xFFFFFFFF)
|
|
2743
|
+
hBitmap2 = ctypes.windll.gdi32.CreateBitmap(bitmap.Width, bitmap.Height, 1, 32, 0)
|
|
2744
|
+
hdc = ctypes.windll.user32.GetDC(0)
|
|
2745
|
+
hdc1 = ctypes.windll.gdi32.CreateCompatibleDC(ctypes.c_void_p(hdc))
|
|
2746
|
+
hdc2 = ctypes.windll.gdi32.CreateCompatibleDC(ctypes.c_void_p(hdc))
|
|
2747
|
+
ctypes.windll.user32.ReleaseDC(0, ctypes.c_void_p(hdc))
|
|
2748
|
+
hOldBmp1 = ctypes.windll.gdi32.SelectObject(ctypes.c_void_p(hdc1), ctypes.c_void_p(hBitmap))
|
|
2749
|
+
hOldBmp2 = ctypes.windll.gdi32.SelectObject(ctypes.c_void_p(hdc2), ctypes.c_void_p(hBitmap2))
|
|
2750
|
+
ctypes.windll.gdi32.BitBlt(ctypes.c_void_p(hdc2), 0, 0, bitmap.Width, bitmap.Height, ctypes.c_void_p(hdc1), 0, 0, 0x00CC0020) # SRCCOPY
|
|
2751
|
+
ctypes.windll.gdi32.SelectObject(ctypes.c_void_p(hdc1), ctypes.c_void_p(hOldBmp1))
|
|
2752
|
+
ctypes.windll.gdi32.SelectObject(ctypes.c_void_p(hdc2), ctypes.c_void_p(hOldBmp2))
|
|
2753
|
+
ctypes.windll.gdi32.DeleteDC(ctypes.c_void_p(hdc1))
|
|
2754
|
+
ctypes.windll.gdi32.DeleteDC(ctypes.c_void_p(hdc2))
|
|
2755
|
+
ctypes.windll.gdi32.DeleteObject(ctypes.c_void_p(hBitmap))
|
|
2756
|
+
# system owns hClipboardData after calling SetClipboardData,
|
|
2757
|
+
# application can not write to or free the data once ownership has been transferred to the system
|
|
2758
|
+
if ctypes.windll.user32.SetClipboardData(ctypes.c_uint(ClipboardFormat.CF_BITMAP), ctypes.c_void_p(hBitmap2)):
|
|
2759
|
+
ret = True
|
|
2760
|
+
else:
|
|
2761
|
+
ctypes.windll.gdi32.DeleteObject(ctypes.c_void_p(hBitmap2))
|
|
2762
|
+
ctypes.windll.user32.CloseClipboard()
|
|
2763
|
+
return ret
|
|
2764
|
+
|
|
2765
|
+
|
|
2766
|
+
def Input(prompt: str, consoleColor: int = ConsoleColor.Default) -> str:
|
|
2767
|
+
Logger.Write(prompt, consoleColor, writeToFile=False)
|
|
2768
|
+
return input()
|
|
2769
|
+
|
|
2770
|
+
|
|
2771
|
+
def InputColorfully(prompt: str, consoleColor: int = ConsoleColor.Default) -> str:
|
|
2772
|
+
Logger.ColorfullyWrite(prompt, consoleColor, writeToFile=False)
|
|
2773
|
+
return input()
|
|
2774
|
+
class Rect():
|
|
2775
|
+
"""
|
|
2776
|
+
class Rect, like `ctypes.wintypes.RECT`.
|
|
2777
|
+
"""
|
|
2778
|
+
|
|
2779
|
+
def __init__(self, left: int = 0, top: int = 0, right: int = 0, bottom: int = 0):
|
|
2780
|
+
self.left = left
|
|
2781
|
+
self.top = top
|
|
2782
|
+
self.right = right
|
|
2783
|
+
self.bottom = bottom
|
|
2784
|
+
|
|
2785
|
+
def width(self) -> int:
|
|
2786
|
+
return self.right - self.left
|
|
2787
|
+
|
|
2788
|
+
def height(self) -> int:
|
|
2789
|
+
return self.bottom - self.top
|
|
2790
|
+
|
|
2791
|
+
def xcenter(self) -> int:
|
|
2792
|
+
return self.left + self.width() // 2
|
|
2793
|
+
|
|
2794
|
+
def ycenter(self) -> int:
|
|
2795
|
+
return self.top + self.height() // 2
|
|
2796
|
+
|
|
2797
|
+
def isempty(self) -> int:
|
|
2798
|
+
return self.width() == 0 or self.height() == 0
|
|
2799
|
+
|
|
2800
|
+
def contains(self, x: int, y: int) -> bool:
|
|
2801
|
+
return self.left <= x < self.right and self.top <= y < self.bottom
|
|
2802
|
+
|
|
2803
|
+
def intersect(self, rect: 'Rect') -> 'Rect':
|
|
2804
|
+
left, top, right, bottom = max(self.left, rect.left), max(self.top, rect.top), min(self.right, rect.right), min(self.bottom, rect.bottom)
|
|
2805
|
+
return Rect(left, top, right, bottom)
|
|
2806
|
+
|
|
2807
|
+
def offset(self, x: int, y: int) -> None:
|
|
2808
|
+
self.left += x
|
|
2809
|
+
self.right += x
|
|
2810
|
+
self.top += y
|
|
2811
|
+
self.bottom += y
|
|
2812
|
+
|
|
2813
|
+
def __eq__(self, rect):
|
|
2814
|
+
return self.left == rect.left and self.top == rect.top and self.right == rect.right and self.bottom == rect.bottom
|
|
2815
|
+
|
|
2816
|
+
def __str__(self) -> str:
|
|
2817
|
+
return '({},{},{},{})[{}x{}]'.format(self.left, self.top, self.right, self.bottom, self.width(), self.height())
|
|
2818
|
+
|
|
2819
|
+
def __repr__(self) -> str:
|
|
2820
|
+
return '{}({},{},{},{})[{}x{}]'.format(self.__class__.__name__, self.left, self.top, self.right, self.bottom, self.width(), self.height())
|
|
2821
|
+
|
|
2822
|
+
|
|
2823
|
+
class ClipboardFormat:
|
|
2824
|
+
CF_TEXT = 1
|
|
2825
|
+
CF_BITMAP = 2
|
|
2826
|
+
CF_METAFILEPICT = 3
|
|
2827
|
+
CF_SYLK = 4
|
|
2828
|
+
CF_DIF = 5
|
|
2829
|
+
CF_TIFF = 6
|
|
2830
|
+
CF_OEMTEXT = 7
|
|
2831
|
+
CF_DIB = 8
|
|
2832
|
+
CF_PALETTE = 9
|
|
2833
|
+
CF_PENDATA = 10
|
|
2834
|
+
CF_RIFF = 11
|
|
2835
|
+
CF_WAVE = 12
|
|
2836
|
+
CF_UNICODETEXT = 13
|
|
2837
|
+
CF_ENHMETAFILE = 14
|
|
2838
|
+
CF_HDROP = 15
|
|
2839
|
+
CF_LOCALE = 16
|
|
2840
|
+
CF_DIBV5 = 17
|
|
2841
|
+
CF_MAX = 18
|
|
2842
|
+
CF_HTML = ctypes.windll.user32.RegisterClipboardFormatW("HTML Format")
|
|
2843
|
+
|
|
2844
|
+
|
|
2845
|
+
# TODO: Failed to parse structure ACCESSTIMEOUT
|
|
2846
|
+
class ExtendedProperty(ctypes.Structure):
|
|
2847
|
+
_fields_ = [
|
|
2848
|
+
('PropertyName', ctypes.c_wchar_p),
|
|
2849
|
+
('PropertyValue', ctypes.c_wchar_p),
|
|
2850
|
+
]
|
|
2851
|
+
|
|
2852
|
+
# TODO: Failed to parse structure FILTERKEYS
|
|
2853
|
+
# TODO: Failed to parse structure HIGHCONTRASTA
|
|
2854
|
+
# TODO: Failed to parse structure HIGHCONTRASTW
|
|
2855
|
+
# TODO: Failed to parse structure MOUSEKEYS
|
|
2856
|
+
# TODO: Failed to parse structure MSAAMENUINFO
|
|
2857
|
+
# TODO: Failed to parse structure SERIALKEYSA
|
|
2858
|
+
# TODO: Failed to parse structure SERIALKEYSW
|
|
2859
|
+
# TODO: Failed to parse structure SOUNDSENTRYA
|
|
2860
|
+
# TODO: Failed to parse structure SOUNDSENTRYW
|
|
2861
|
+
# TODO: Failed to parse structure STICKYKEYS
|
|
2862
|
+
# TODO: Failed to parse structure TOGGLEKEYS
|
|
2863
|
+
class UIAutomationEventInfo(ctypes.Structure):
|
|
2864
|
+
_fields_ = [
|
|
2865
|
+
('guid', ctypes.c_void_p),
|
|
2866
|
+
('pProgrammaticName', ctypes.wintypes.LPCWSTR),
|
|
2867
|
+
]
|
|
2868
|
+
|
|
2869
|
+
class UIAutomationMethodInfo(ctypes.Structure):
|
|
2870
|
+
_fields_ = [
|
|
2871
|
+
('pProgrammaticName', ctypes.wintypes.LPCWSTR),
|
|
2872
|
+
('doSetFocus', ctypes.wintypes.BOOL),
|
|
2873
|
+
('cInParameters', ctypes.wintypes.UINT),
|
|
2874
|
+
('cOutParameters', ctypes.wintypes.UINT),
|
|
2875
|
+
('pParameterTypes', ctypes.c_void_p),
|
|
2876
|
+
('pParameterNames', ctypes.c_void_p),
|
|
2877
|
+
]
|
|
2878
|
+
|
|
2879
|
+
class UIAutomationParameter(ctypes.Structure):
|
|
2880
|
+
_fields_ = [
|
|
2881
|
+
('type', ctypes.c_void_p),
|
|
2882
|
+
('pData', ctypes.c_void_p),
|
|
2883
|
+
]
|
|
2884
|
+
|
|
2885
|
+
class UIAutomationPatternInfo(ctypes.Structure):
|
|
2886
|
+
_fields_ = [
|
|
2887
|
+
('guid', ctypes.c_void_p),
|
|
2888
|
+
('pProgrammaticName', ctypes.wintypes.LPCWSTR),
|
|
2889
|
+
('providerInterfaceId', ctypes.c_void_p),
|
|
2890
|
+
('clientInterfaceId', ctypes.c_void_p),
|
|
2891
|
+
('cProperties', ctypes.wintypes.UINT),
|
|
2892
|
+
('UIAutomationPropertyInfo', ctypes.c_void_p),
|
|
2893
|
+
('cMethods', ctypes.wintypes.UINT),
|
|
2894
|
+
('UIAutomationMethodInfo', ctypes.c_void_p),
|
|
2895
|
+
('cEvents', ctypes.wintypes.UINT),
|
|
2896
|
+
('UIAutomationEventInfo', ctypes.c_void_p),
|
|
2897
|
+
('pPatternHandler', ctypes.c_void_p),
|
|
2898
|
+
]
|
|
2899
|
+
|
|
2900
|
+
class UIAutomationPropertyInfo(ctypes.Structure):
|
|
2901
|
+
_fields_ = [
|
|
2902
|
+
('guid', ctypes.c_void_p),
|
|
2903
|
+
('pProgrammaticName', ctypes.wintypes.LPCWSTR),
|
|
2904
|
+
('type', ctypes.c_void_p),
|
|
2905
|
+
]
|
|
2906
|
+
|
|
2907
|
+
class UiaAndOrCondition(ctypes.Structure):
|
|
2908
|
+
_fields_ = [
|
|
2909
|
+
('ConditionType', ctypes.c_void_p),
|
|
2910
|
+
('ppConditions', ctypes.c_void_p),
|
|
2911
|
+
('UiaCondition', ctypes.c_void_p),
|
|
2912
|
+
('cConditions', ctypes.c_int),
|
|
2913
|
+
]
|
|
2914
|
+
|
|
2915
|
+
|
|
2916
|
+
class UiaAsyncContentLoadedEventArgs(ctypes.Structure):
|
|
2917
|
+
_fields_ = [
|
|
2918
|
+
('Type', ctypes.c_void_p),
|
|
2919
|
+
('EventId', ctypes.c_int),
|
|
2920
|
+
('AsyncContentLoadedState', ctypes.c_void_p),
|
|
2921
|
+
('PercentComplete', ctypes.c_double),
|
|
2922
|
+
]
|
|
2923
|
+
|
|
2924
|
+
class UiaCacheRequest(ctypes.Structure):
|
|
2925
|
+
_fields_ = [
|
|
2926
|
+
('UiaCondition', ctypes.c_void_p),
|
|
2927
|
+
('Scope', ctypes.c_int),
|
|
2928
|
+
('pProperties', ctypes.c_void_p),
|
|
2929
|
+
('cProperties', ctypes.c_int),
|
|
2930
|
+
('pPatterns', ctypes.c_void_p),
|
|
2931
|
+
('cPatterns', ctypes.c_int),
|
|
2932
|
+
('automationElementMode', ctypes.c_int),
|
|
2933
|
+
]
|
|
2934
|
+
|
|
2935
|
+
class UiaChangeInfo(ctypes.Structure):
|
|
2936
|
+
_fields_ = [
|
|
2937
|
+
('uiaId', ctypes.c_int),
|
|
2938
|
+
('payload', ctypes.c_void_p),
|
|
2939
|
+
('extraInfo', ctypes.c_void_p),
|
|
2940
|
+
]
|
|
2941
|
+
|
|
2942
|
+
class UiaCondition(ctypes.Structure):
|
|
2943
|
+
_fields_ = [
|
|
2944
|
+
('ConditionType', ctypes.c_void_p),
|
|
2945
|
+
]
|
|
2946
|
+
|
|
2947
|
+
class UiaEventArgs(ctypes.Structure):
|
|
2948
|
+
_fields_ = [
|
|
2949
|
+
('Type', ctypes.c_void_p),
|
|
2950
|
+
('EventId', ctypes.c_int),
|
|
2951
|
+
]
|
|
2952
|
+
|
|
2953
|
+
class UiaFindParams(ctypes.Structure):
|
|
2954
|
+
_fields_ = [
|
|
2955
|
+
('MaxDepth', ctypes.c_int),
|
|
2956
|
+
('FindFirst', ctypes.wintypes.BOOL),
|
|
2957
|
+
('ExcludeRoot', ctypes.wintypes.BOOL),
|
|
2958
|
+
('UiaCondition', ctypes.c_void_p),
|
|
2959
|
+
]
|
|
2960
|
+
|
|
2961
|
+
class UiaNotCondition(ctypes.Structure):
|
|
2962
|
+
_fields_ = [
|
|
2963
|
+
('ConditionType', ctypes.c_void_p),
|
|
2964
|
+
('UiaCondition', ctypes.c_void_p),
|
|
2965
|
+
]
|
|
2966
|
+
|
|
2967
|
+
class UiaPoint(ctypes.Structure):
|
|
2968
|
+
_fields_ = [
|
|
2969
|
+
('x', ctypes.c_double),
|
|
2970
|
+
('y', ctypes.c_double),
|
|
2971
|
+
]
|
|
2972
|
+
|
|
2973
|
+
class UiaPropertyChangedEventArgs(ctypes.Structure):
|
|
2974
|
+
_fields_ = [
|
|
2975
|
+
('Type', ctypes.c_void_p),
|
|
2976
|
+
('EventId', ctypes.c_int),
|
|
2977
|
+
('PropertyId', ctypes.c_int),
|
|
2978
|
+
('OldValue', ctypes.c_void_p),
|
|
2979
|
+
('NewValue', ctypes.c_void_p),
|
|
2980
|
+
]
|
|
2981
|
+
|
|
2982
|
+
class UiaPropertyCondition(ctypes.Structure):
|
|
2983
|
+
_fields_ = [
|
|
2984
|
+
('ConditionType', ctypes.c_void_p),
|
|
2985
|
+
('PropertyId', ctypes.c_int),
|
|
2986
|
+
('Value', ctypes.c_void_p),
|
|
2987
|
+
('Flags', ctypes.c_void_p),
|
|
2988
|
+
]
|
|
2989
|
+
|
|
2990
|
+
class UiaRect(ctypes.Structure):
|
|
2991
|
+
_fields_ = [
|
|
2992
|
+
('left', ctypes.c_double),
|
|
2993
|
+
('top', ctypes.c_double),
|
|
2994
|
+
('width', ctypes.c_double),
|
|
2995
|
+
('height', ctypes.c_double),
|
|
2996
|
+
]
|
|
2997
|
+
|
|
2998
|
+
class UiaStructureChangedEventArgs(ctypes.Structure):
|
|
2999
|
+
_fields_ = [
|
|
3000
|
+
('Type', ctypes.c_void_p),
|
|
3001
|
+
('EventId', ctypes.c_int),
|
|
3002
|
+
('StructureChangeType', ctypes.c_void_p),
|
|
3003
|
+
('pRuntimeId', ctypes.c_void_p),
|
|
3004
|
+
('cRuntimeIdLen', ctypes.c_int),
|
|
3005
|
+
]
|
|
3006
|
+
|
|
3007
|
+
class UiaWindowClosedEventArgs(ctypes.Structure):
|
|
3008
|
+
_fields_ = [
|
|
3009
|
+
('Type', ctypes.c_void_p),
|
|
3010
|
+
('EventId', ctypes.c_int),
|
|
3011
|
+
('pRuntimeId', ctypes.c_void_p),
|
|
3012
|
+
('cRuntimeIdLen', ctypes.c_int),
|
|
3013
|
+
]
|
|
3014
|
+
|
|
3015
|
+
class ExtendedProperty(ctypes.Structure):
|
|
3016
|
+
_fields_ = [
|
|
3017
|
+
('PropertyName', ctypes.c_wchar_p),
|
|
3018
|
+
('PropertyValue', ctypes.c_wchar_p),
|
|
3019
|
+
]
|
|
3020
|
+
|
|
3021
|
+
class UIAutomationEventInfo(ctypes.Structure):
|
|
3022
|
+
_fields_ = [
|
|
3023
|
+
('guid', ctypes.c_void_p),
|
|
3024
|
+
('pProgrammaticName', ctypes.wintypes.LPCWSTR),
|
|
3025
|
+
]
|
|
3026
|
+
|
|
3027
|
+
class UIAutomationMethodInfo(ctypes.Structure):
|
|
3028
|
+
_fields_ = [
|
|
3029
|
+
('pProgrammaticName', ctypes.wintypes.LPCWSTR),
|
|
3030
|
+
('doSetFocus', ctypes.wintypes.BOOL),
|
|
3031
|
+
('cInParameters', ctypes.wintypes.UINT),
|
|
3032
|
+
('cOutParameters', ctypes.wintypes.UINT),
|
|
3033
|
+
('pParameterTypes', ctypes.c_void_p),
|
|
3034
|
+
('pParameterNames', ctypes.c_void_p),
|
|
3035
|
+
]
|
|
3036
|
+
|
|
3037
|
+
class UIAutomationParameter(ctypes.Structure):
|
|
3038
|
+
_fields_ = [
|
|
3039
|
+
('type', ctypes.c_void_p),
|
|
3040
|
+
('pData', ctypes.c_void_p),
|
|
3041
|
+
]
|
|
3042
|
+
|
|
3043
|
+
class UIAutomationPatternInfo(ctypes.Structure):
|
|
3044
|
+
_fields_ = [
|
|
3045
|
+
('guid', ctypes.c_void_p),
|
|
3046
|
+
('pProgrammaticName', ctypes.wintypes.LPCWSTR),
|
|
3047
|
+
('providerInterfaceId', ctypes.c_void_p),
|
|
3048
|
+
('clientInterfaceId', ctypes.c_void_p),
|
|
3049
|
+
('cProperties', ctypes.wintypes.UINT),
|
|
3050
|
+
('UIAutomationPropertyInfo', ctypes.c_void_p),
|
|
3051
|
+
('cMethods', ctypes.wintypes.UINT),
|
|
3052
|
+
('UIAutomationMethodInfo', ctypes.c_void_p),
|
|
3053
|
+
('cEvents', ctypes.wintypes.UINT),
|
|
3054
|
+
('UIAutomationEventInfo', ctypes.c_void_p),
|
|
3055
|
+
('pPatternHandler', ctypes.c_void_p),
|
|
3056
|
+
]
|
|
3057
|
+
|
|
3058
|
+
class UIAutomationPropertyInfo(ctypes.Structure):
|
|
3059
|
+
_fields_ = [
|
|
3060
|
+
('guid', ctypes.c_void_p),
|
|
3061
|
+
('pProgrammaticName', ctypes.wintypes.LPCWSTR),
|
|
3062
|
+
('type', ctypes.c_void_p),
|
|
3063
|
+
]
|
|
3064
|
+
|
|
3065
|
+
class UiaAndOrCondition(ctypes.Structure):
|
|
3066
|
+
_fields_ = [
|
|
3067
|
+
('ConditionType', ctypes.c_void_p),
|
|
3068
|
+
('ppConditions', ctypes.c_void_p),
|
|
3069
|
+
('UiaCondition', ctypes.c_void_p),
|
|
3070
|
+
('cConditions', ctypes.c_int),
|
|
3071
|
+
]
|
|
3072
|
+
|
|
3073
|
+
class CacheRequest:
|
|
3074
|
+
"""
|
|
3075
|
+
Wrapper for IUIAutomationCacheRequest.
|
|
3076
|
+
"""
|
|
3077
|
+
def __init__(self, cache_request=None):
|
|
3078
|
+
if cache_request:
|
|
3079
|
+
self.check_request = cache_request
|
|
3080
|
+
else:
|
|
3081
|
+
self.check_request = _AutomationClient.instance().IUIAutomation.CreateCacheRequest()
|
|
3082
|
+
|
|
3083
|
+
@property
|
|
3084
|
+
def TreeScope(self) -> int:
|
|
3085
|
+
return self.check_request.TreeScope
|
|
3086
|
+
|
|
3087
|
+
@TreeScope.setter
|
|
3088
|
+
def TreeScope(self, scope: int):
|
|
3089
|
+
self.check_request.TreeScope = scope
|
|
3090
|
+
|
|
3091
|
+
@property
|
|
3092
|
+
def AutomationElementMode(self) -> int:
|
|
3093
|
+
return self.check_request.AutomationElementMode
|
|
3094
|
+
|
|
3095
|
+
@AutomationElementMode.setter
|
|
3096
|
+
def AutomationElementMode(self, mode: int):
|
|
3097
|
+
self.check_request.AutomationElementMode = mode
|
|
3098
|
+
|
|
3099
|
+
@property
|
|
3100
|
+
def TreeFilter(self):
|
|
3101
|
+
return self.check_request.TreeFilter
|
|
3102
|
+
|
|
3103
|
+
@TreeFilter.setter
|
|
3104
|
+
def TreeFilter(self, filter):
|
|
3105
|
+
self.check_request.TreeFilter = filter
|
|
3106
|
+
|
|
3107
|
+
def AddProperty(self, propertyId: int):
|
|
3108
|
+
"""
|
|
3109
|
+
Adds a property to the cache request.
|
|
3110
|
+
propertyId: int, PropertyId.
|
|
3111
|
+
"""
|
|
3112
|
+
self.check_request.AddProperty(propertyId)
|
|
3113
|
+
|
|
3114
|
+
def AddPattern(self, patternId: int):
|
|
3115
|
+
"""
|
|
3116
|
+
Adds a pattern to the cache request.
|
|
3117
|
+
patternId: int, PatternId.
|
|
3118
|
+
"""
|
|
3119
|
+
self.check_request.AddPattern(patternId)
|
|
3120
|
+
|
|
3121
|
+
def Clone(self) -> 'CacheRequest':
|
|
3122
|
+
"""
|
|
3123
|
+
Clones the cache request.
|
|
3124
|
+
"""
|
|
3125
|
+
cloned = self.check_request.Clone()
|
|
3126
|
+
return CacheRequest(cloned)
|
|
3127
|
+
|
|
3128
|
+
def CreateCacheRequest() -> CacheRequest:
|
|
3129
|
+
"""
|
|
3130
|
+
Creates a new CacheRequest.
|
|
3131
|
+
"""
|
|
3132
|
+
return CacheRequest()
|
|
3133
|
+
|
|
3134
|
+
# Event Handling Implementations for core.py
|
|
3135
|
+
|
|
3136
|
+
def AddAutomationEventHandler(eventId: int, element, scope: int, cacheRequest, handler) -> None:
|
|
3137
|
+
"""
|
|
3138
|
+
Registers a method that handles Microsoft UI Automation events.
|
|
3139
|
+
"""
|
|
3140
|
+
_AutomationClient.instance().IUIAutomation.AddAutomationEventHandler(eventId, element, scope, cacheRequest, handler)
|
|
3141
|
+
|
|
3142
|
+
def RemoveAutomationEventHandler(eventId: int, element, handler) -> None:
|
|
3143
|
+
"""
|
|
3144
|
+
Removes the specified Microsoft UI Automation event handler.
|
|
3145
|
+
"""
|
|
3146
|
+
_AutomationClient.instance().IUIAutomation.RemoveAutomationEventHandler(eventId, element, handler)
|
|
3147
|
+
|
|
3148
|
+
def AddPropertyChangedEventHandler(element, scope: int, cacheRequest, handler, propertyArray: List[int]) -> None:
|
|
3149
|
+
"""
|
|
3150
|
+
Registers a method that handles UI Automation property-changed events.
|
|
3151
|
+
"""
|
|
3152
|
+
# Convert propertyArray to a ctypes array if needed, but comtypes usually handles lists
|
|
3153
|
+
# However, AddPropertyChangedEventHandler expects a pointer to an array of property IDs
|
|
3154
|
+
# Let's see how generic we can be.
|
|
3155
|
+
# The signature in IUIAutomation is: HRESULT AddPropertyChangedEventHandler(ptr_element, scope, ptr_cacheRequest, ptr_handler, ptr_propertyArray)
|
|
3156
|
+
# The last arg is SAFEARRAY(int)
|
|
3157
|
+
|
|
3158
|
+
# We might need to manually convert list to SAFEARRAY or rely on comtypes.
|
|
3159
|
+
# For now, let's pass a tuple/list and see if comtypes marshals it.
|
|
3160
|
+
_AutomationClient.instance().IUIAutomation.AddPropertyChangedEventHandler(element, scope, cacheRequest, handler, propertyArray)
|
|
3161
|
+
|
|
3162
|
+
def RemovePropertyChangedEventHandler(element, handler) -> None:
|
|
3163
|
+
"""
|
|
3164
|
+
Removes the specified property-changed event handler.
|
|
3165
|
+
"""
|
|
3166
|
+
_AutomationClient.instance().IUIAutomation.RemovePropertyChangedEventHandler(element, handler)
|
|
3167
|
+
|
|
3168
|
+
def AddStructureChangedEventHandler(element, scope: int, cacheRequest, handler) -> None:
|
|
3169
|
+
"""
|
|
3170
|
+
Registers a method that handles UI Automation structure-changed events.
|
|
3171
|
+
"""
|
|
3172
|
+
_AutomationClient.instance().IUIAutomation.AddStructureChangedEventHandler(element, scope, cacheRequest, handler)
|
|
3173
|
+
|
|
3174
|
+
def RemoveStructureChangedEventHandler(element, handler) -> None:
|
|
3175
|
+
"""
|
|
3176
|
+
Removes the specified structure-changed event handler.
|
|
3177
|
+
"""
|
|
3178
|
+
_AutomationClient.instance().IUIAutomation.RemoveStructureChangedEventHandler(element, handler)
|
|
3179
|
+
|
|
3180
|
+
def AddFocusChangedEventHandler(cacheRequest, handler) -> None:
|
|
3181
|
+
"""
|
|
3182
|
+
Registers a method that handles UI Automation focus-changed events.
|
|
3183
|
+
"""
|
|
3184
|
+
_AutomationClient.instance().IUIAutomation.AddFocusChangedEventHandler(cacheRequest, handler)
|
|
3185
|
+
|
|
3186
|
+
def RemoveFocusChangedEventHandler(handler) -> None:
|
|
3187
|
+
"""
|
|
3188
|
+
Removes the specified focus-changed event handler.
|
|
3189
|
+
"""
|
|
3190
|
+
_AutomationClient.instance().IUIAutomation.RemoveFocusChangedEventHandler(handler)
|
|
3191
|
+
|
|
3192
|
+
def RemoveAllEventHandlers() -> None:
|
|
3193
|
+
"""
|
|
3194
|
+
Removes all registered Microsoft UI Automation event handlers.
|
|
3195
|
+
"""
|
|
3196
|
+
_AutomationClient.instance().IUIAutomation.RemoveAllEventHandlers()
|
|
3197
|
+
|
|
3198
|
+
|
|
3199
|
+
# Condition creation helper functions
|
|
3200
|
+
|
|
3201
|
+
def CreateTrueCondition():
|
|
3202
|
+
"""
|
|
3203
|
+
Create a condition that is always true. This matches all elements.
|
|
3204
|
+
|
|
3205
|
+
Return: A condition object that can be used with FindAll, FindFirst, etc.
|
|
3206
|
+
Refer https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomation-createtruecondition
|
|
3207
|
+
"""
|
|
3208
|
+
return _AutomationClient.instance().IUIAutomation.CreateTrueCondition()
|
|
3209
|
+
|
|
3210
|
+
|
|
3211
|
+
def CreateFalseCondition():
|
|
3212
|
+
"""
|
|
3213
|
+
Create a condition that is always false. This matches no elements.
|
|
3214
|
+
|
|
3215
|
+
Return: A condition object that can be used with FindAll, FindFirst, etc.
|
|
3216
|
+
Refer https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomation-createfalsecondition
|
|
3217
|
+
"""
|
|
3218
|
+
return _AutomationClient.instance().IUIAutomation.CreateFalseCondition()
|
|
3219
|
+
|
|
3220
|
+
|
|
3221
|
+
def CreatePropertyCondition(propertyId: int, value):
|
|
3222
|
+
"""
|
|
3223
|
+
Create a condition that matches elements with a specific property value.
|
|
3224
|
+
|
|
3225
|
+
propertyId: int, a value in class `PropertyId`.
|
|
3226
|
+
value: The value to match for the property.
|
|
3227
|
+
Return: A condition object that can be used with FindAll, FindFirst, etc.
|
|
3228
|
+
|
|
3229
|
+
Refer https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomation-createpropertycondition
|
|
3230
|
+
"""
|
|
3231
|
+
return _AutomationClient.instance().IUIAutomation.CreatePropertyCondition(propertyId, value)
|
|
3232
|
+
|
|
3233
|
+
|
|
3234
|
+
def CreateAndCondition(condition1, condition2):
|
|
3235
|
+
"""
|
|
3236
|
+
Create a condition that is the logical AND of two conditions.
|
|
3237
|
+
|
|
3238
|
+
condition1: First condition.
|
|
3239
|
+
condition2: Second condition.
|
|
3240
|
+
Return: A condition object that can be used with FindAll, FindFirst, etc.
|
|
3241
|
+
|
|
3242
|
+
Refer https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomation-createandcondition
|
|
3243
|
+
"""
|
|
3244
|
+
return _AutomationClient.instance().IUIAutomation.CreateAndCondition(condition1, condition2)
|
|
3245
|
+
|
|
3246
|
+
|
|
3247
|
+
def CreateOrCondition(condition1, condition2):
|
|
3248
|
+
"""
|
|
3249
|
+
Create a condition that is the logical OR of two conditions.
|
|
3250
|
+
|
|
3251
|
+
condition1: First condition.
|
|
3252
|
+
condition2: Second condition.
|
|
3253
|
+
Return: A condition object that can be used with FindAll, FindFirst, etc.
|
|
3254
|
+
|
|
3255
|
+
Refer https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomation-createorcondition
|
|
3256
|
+
"""
|
|
3257
|
+
return _AutomationClient.instance().IUIAutomation.CreateOrCondition(condition1, condition2)
|
|
3258
|
+
|
|
3259
|
+
|
|
3260
|
+
def CreateNotCondition(condition):
|
|
3261
|
+
"""
|
|
3262
|
+
Create a condition that is the logical NOT of another condition.
|
|
3263
|
+
|
|
3264
|
+
condition: The condition to negate.
|
|
3265
|
+
Return: A condition object that can be used with FindAll, FindFirst, etc.
|
|
3266
|
+
|
|
3267
|
+
Refer https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomation-createnotcondition
|
|
3268
|
+
"""
|
|
3269
|
+
return _AutomationClient.instance().IUIAutomation.CreateNotCondition(condition)
|