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.
@@ -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)