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