ascript 1.3.27__py3-none-win_amd64.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.
Files changed (177) hide show
  1. ascript/__init__.py +8 -0
  2. ascript/hooks/__init__.py +0 -0
  3. ascript/hooks/hook-ascript.py +2 -0
  4. ascript/windows/__init__.py +29 -0
  5. ascript/windows/daos/__init__.py +0 -0
  6. ascript/windows/daos/screen.py +143 -0
  7. ascript/windows/hardware/__init__.py +5 -0
  8. ascript/windows/hardware/keyboard.py +279 -0
  9. ascript/windows/hardware/logitech_driver.py +179 -0
  10. ascript/windows/hardware/mouse.py +308 -0
  11. ascript/windows/license/__init__.py +16 -0
  12. ascript/windows/license/achttp.cp310-win_amd64.pyd +0 -0
  13. ascript/windows/license/achttp.cp311-win_amd64.pyd +0 -0
  14. ascript/windows/license/achttp.cp312-win_amd64.pyd +0 -0
  15. ascript/windows/license/achttp.cp313-win_amd64.pyd +0 -0
  16. ascript/windows/license/achttp.cp314-win_amd64.pyd +0 -0
  17. ascript/windows/license/achttp.cp38-win_amd64.pyd +0 -0
  18. ascript/windows/license/achttp.cp39-win_amd64.pyd +0 -0
  19. ascript/windows/media/__init__.py +0 -0
  20. ascript/windows/resources/__init__.py +0 -0
  21. ascript/windows/resources/libs/logitech/x64/LogitechGkeyEnginesWrapper.dll +0 -0
  22. ascript/windows/resources/libs/logitech/x86/LogitechGkeyEnginesWrapper.dll +0 -0
  23. ascript/windows/resources/templates/__init__.py +0 -0
  24. ascript/windows/resources/templates/__init__py.win.template +51 -0
  25. ascript/windows/resources/templates/build.as.win.template +14 -0
  26. ascript/windows/resources/templates/build.py +71 -0
  27. ascript/windows/resources/templates/logo_windows.ico +0 -0
  28. ascript/windows/resources/templates/windows/main.py +50 -0
  29. ascript/windows/resources/templates/windows/pyproject.toml +44 -0
  30. ascript/windows/resources/templates/windows/res/img/logo.ico +0 -0
  31. ascript/windows/screen/__init__.py +13 -0
  32. ascript/windows/screen/as_cv.py +382 -0
  33. ascript/windows/screen/canny.py +128 -0
  34. ascript/windows/screen/code_scanner.py +72 -0
  35. ascript/windows/screen/color_algorithms.py +277 -0
  36. ascript/windows/screen/find_colors.py +251 -0
  37. ascript/windows/screen/find_images.py +217 -0
  38. ascript/windows/screen/ocr/__init__.py +4 -0
  39. ascript/windows/screen/ocr/ocr.py +223 -0
  40. ascript/windows/screen/ocr/rapidocr_hook.py +301 -0
  41. ascript/windows/screen/ocrx.py +169 -0
  42. ascript/windows/screen/screen_core.py +139 -0
  43. ascript/windows/store/__init__.py +21 -0
  44. ascript/windows/system/__init__.py +8 -0
  45. ascript/windows/system/device.py +355 -0
  46. ascript/windows/system/easy_print.py +115 -0
  47. ascript/windows/system/env.py +492 -0
  48. ascript/windows/system/exception/__init__.py +1 -0
  49. ascript/windows/system/key_value.py +145 -0
  50. ascript/windows/system/pip.py +175 -0
  51. ascript/windows/system/r.py +165 -0
  52. ascript/windows/system/runtime.py +62 -0
  53. ascript/windows/tools/__init__.py +27 -0
  54. ascript/windows/tools/__main__.py +21 -0
  55. ascript/windows/tools/action.json +33 -0
  56. ascript/windows/tools/action.py +168 -0
  57. ascript/windows/tools/colors_tool.py +391 -0
  58. ascript/windows/tools/config.py +26 -0
  59. ascript/windows/tools/dumptool.py +18 -0
  60. ascript/windows/tools/eel_api.py +495 -0
  61. ascript/windows/tools/gpplug/__init__.py +0 -0
  62. ascript/windows/tools/gpplug/plug.py +70 -0
  63. ascript/windows/tools/mouse_catch.py +168 -0
  64. ascript/windows/tools/plug_manager.py +38 -0
  65. ascript/windows/tools/tool_api.py +906 -0
  66. ascript/windows/tools/tool_window.py +56 -0
  67. ascript/windows/tools/uia_worker.py +365 -0
  68. ascript/windows/tools/web/app_store_window.html +481 -0
  69. ascript/windows/tools/web/colors.html +532 -0
  70. ascript/windows/tools/web/css/screen.css +1342 -0
  71. ascript/windows/tools/web/css/viewtree.css +357 -0
  72. ascript/windows/tools/web/flot_control.html +195 -0
  73. ascript/windows/tools/web/img/ico/favicon.png +0 -0
  74. ascript/windows/tools/web/img/ico/paintpalette.png +0 -0
  75. ascript/windows/tools/web/img/svg/find_colors.svg +1 -0
  76. ascript/windows/tools/web/img/svg/find_images.svg +1 -0
  77. ascript/windows/tools/web/img/svg/illustration-6.svg +89 -0
  78. ascript/windows/tools/web/img/svg/img_default.svg +1 -0
  79. ascript/windows/tools/web/img/svg/ocr.svg +1 -0
  80. ascript/windows/tools/web/js/screen/colors.js +466 -0
  81. ascript/windows/tools/web/js/screen/gp_plug.js +108 -0
  82. ascript/windows/tools/web/js/screen/image_mouse.js +464 -0
  83. ascript/windows/tools/web/js/screen/img_view.js +558 -0
  84. ascript/windows/tools/web/js/screen/plug.js +246 -0
  85. ascript/windows/tools/web/js/screen/screen.js +690 -0
  86. ascript/windows/tools/web/js/screen/screen_api.js +778 -0
  87. ascript/windows/tools/web/js/screen/screen_maker.js +10 -0
  88. ascript/windows/tools/web/js/utils/map.js +73 -0
  89. ascript/windows/tools/web/js/utils/upload.js +157 -0
  90. ascript/windows/tools/web/js/viewtree/niv_js.js +27 -0
  91. ascript/windows/tools/web/js/viewtree/vtree.js +1510 -0
  92. ascript/windows/tools/web/libs/bootstrap/css/bootstrap.min.css +6 -0
  93. ascript/windows/tools/web/libs/bootstrap/js/bootstrap.bundle.min.js +7 -0
  94. ascript/windows/tools/web/libs/bootstrap-icons/0-circle.svg +4 -0
  95. ascript/windows/tools/web/libs/bootstrap-icons/0-square.svg +4 -0
  96. ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.css +2078 -0
  97. ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.json +2052 -0
  98. ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.min.css +5 -0
  99. ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.scss +2090 -0
  100. ascript/windows/tools/web/libs/bootstrap-icons/font/fonts/bootstrap-icons.woff +0 -0
  101. ascript/windows/tools/web/libs/bootstrap-icons/font/fonts/bootstrap-icons.woff2 +0 -0
  102. ascript/windows/tools/web/libs/feather-icons/dist/feather.min.js +13 -0
  103. ascript/windows/tools/web/libs/feather-icons/dist/icons.json +1 -0
  104. ascript/windows/tools/web/libs/jquery/core.js +399 -0
  105. ascript/windows/tools/web/libs/jquery/jquery.js +10598 -0
  106. ascript/windows/tools/web/libs/jquery/jquery.min.js +4 -0
  107. ascript/windows/tools/web/libs/jquery/jquery.min.js.zip +0 -0
  108. ascript/windows/tools/web/libs/jquery/jquery.slim.js +8495 -0
  109. ascript/windows/tools/web/libs/jquery/jquery.slim.min.js +2 -0
  110. ascript/windows/tools/web/libs/jquery-menu/jquery.contextMenu.js +2134 -0
  111. ascript/windows/tools/web/libs/jquery-menu/jquery.contextMenu.min.css +16 -0
  112. ascript/windows/tools/web/libs/jquery-zTree/css/zTreeStyle.css +97 -0
  113. ascript/windows/tools/web/libs/jquery-zTree/img/diy/1_close.png +0 -0
  114. ascript/windows/tools/web/libs/jquery-zTree/img/diy/1_open.png +0 -0
  115. ascript/windows/tools/web/libs/jquery-zTree/img/diy/2.png +0 -0
  116. ascript/windows/tools/web/libs/jquery-zTree/img/diy/3.png +0 -0
  117. ascript/windows/tools/web/libs/jquery-zTree/img/diy/4.png +0 -0
  118. ascript/windows/tools/web/libs/jquery-zTree/img/diy/5.png +0 -0
  119. ascript/windows/tools/web/libs/jquery-zTree/img/diy/6.png +0 -0
  120. ascript/windows/tools/web/libs/jquery-zTree/img/diy/7.png +0 -0
  121. ascript/windows/tools/web/libs/jquery-zTree/img/diy/8.png +0 -0
  122. ascript/windows/tools/web/libs/jquery-zTree/img/diy/9.png +0 -0
  123. ascript/windows/tools/web/libs/jquery-zTree/img/line_conn.gif +0 -0
  124. ascript/windows/tools/web/libs/jquery-zTree/img/loading.gif +0 -0
  125. ascript/windows/tools/web/libs/jquery-zTree/img/tree-node-btn.png +0 -0
  126. ascript/windows/tools/web/libs/jquery-zTree/img/zTreeStandard.gif +0 -0
  127. ascript/windows/tools/web/libs/jquery-zTree/img/zTreeStandard.png +0 -0
  128. ascript/windows/tools/web/libs/jquery-zTree/js/jquery.ztree.all.min.js +3 -0
  129. ascript/windows/tools/web/libs/treeview/css/jquery.treeview.css +79 -0
  130. ascript/windows/tools/web/libs/treeview/img/file.png +0 -0
  131. ascript/windows/tools/web/libs/treeview/img/ico_loading.png +0 -0
  132. ascript/windows/tools/web/libs/treeview/img/icon4.png +0 -0
  133. ascript/windows/tools/web/libs/treeview/img/icon5.png +0 -0
  134. ascript/windows/tools/web/libs/treeview/img/icon6.png +0 -0
  135. ascript/windows/tools/web/libs/treeview/img/py.png +0 -0
  136. ascript/windows/tools/web/libs/treeview/img/treeview-black-line.gif +0 -0
  137. ascript/windows/tools/web/libs/treeview/img/treeview-black.gif +0 -0
  138. ascript/windows/tools/web/libs/treeview/img/treeview-default-line.gif +0 -0
  139. ascript/windows/tools/web/libs/treeview/img/treeview-default.gif +0 -0
  140. ascript/windows/tools/web/libs/treeview/img/treeview-famfamfam-line.gif +0 -0
  141. ascript/windows/tools/web/libs/treeview/img/treeview-famfamfam.gif +0 -0
  142. ascript/windows/tools/web/libs/treeview/img/treeview-gray-line.gif +0 -0
  143. ascript/windows/tools/web/libs/treeview/img/treeview-gray.gif +0 -0
  144. ascript/windows/tools/web/libs/treeview/img/treeview-red-line.gif +0 -0
  145. ascript/windows/tools/web/libs/treeview/img/treeview-red.gif +0 -0
  146. ascript/windows/tools/web/libs/treeview/js/jquery.treeview.edit.js +37 -0
  147. ascript/windows/tools/web/libs/treeview/js/jquery.treeview.js +250 -0
  148. ascript/windows/tools/web/license_window.html +255 -0
  149. ascript/windows/tools/web/plugs/a.html +653 -0
  150. ascript/windows/tools/web/plugs/demo.html +52 -0
  151. ascript/windows/tools/web/plugs/find_colors.html +629 -0
  152. ascript/windows/tools/web/plugs/find_images.html +699 -0
  153. ascript/windows/tools/web/plugs/rapidocr.html +954 -0
  154. ascript/windows/tools/web/vtree.html +496 -0
  155. ascript/windows/tools/webview/assets/index-HymBPl-I.css +1 -0
  156. ascript/windows/tools/webview/assets/index-PqA_m8tL.js +17 -0
  157. ascript/windows/tools/webview/index.html +13 -0
  158. ascript/windows/ui/__init__.py +6 -0
  159. ascript/windows/ui/app_store_window.py +272 -0
  160. ascript/windows/ui/draw_window.py +231 -0
  161. ascript/windows/ui/flot_test.py +297 -0
  162. ascript/windows/ui/flot_window.py +488 -0
  163. ascript/windows/ui/license_window.py +111 -0
  164. ascript/windows/ui/web_window.py +466 -0
  165. ascript/windows/utils/__init__.py +0 -0
  166. ascript/windows/utils/node_utils.py +20 -0
  167. ascript/windows/utils/screen_utils.py +280 -0
  168. ascript/windows/window/Selector.py +1015 -0
  169. ascript/windows/window/Window.py +747 -0
  170. ascript/windows/window/__init__.py +5 -0
  171. ascript/windows/window/launcher.py +213 -0
  172. ascript/windows/window/uia_runtime.py +26 -0
  173. ascript-1.3.27.dist-info/METADATA +41 -0
  174. ascript-1.3.27.dist-info/RECORD +177 -0
  175. ascript-1.3.27.dist-info/WHEEL +5 -0
  176. ascript-1.3.27.dist-info/entry_points.txt +2 -0
  177. ascript-1.3.27.dist-info/top_level.txt +1 -0
ascript/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ import sys
2
+
3
+ # 1. 操作系统检查
4
+ if sys.platform != "win32":
5
+ raise ImportError(f"Package 'your_package' is Windows-only. Current: {sys.platform}")
6
+
7
+ # 2. 导出版本号(方便用户查看)
8
+ __version__ = "1.3.27"
File without changes
@@ -0,0 +1,2 @@
1
+ from PyInstaller.utils.hooks import collect_data_files
2
+ datas = collect_data_files('ascript', include_py_files=False)
@@ -0,0 +1,29 @@
1
+ """ascript.windows 模块 - Windows 平台自动化核心包。
2
+
3
+ 负责初始化 Windows 平台相关的路径配置和 RapidOCR Hook 注入。
4
+ """
5
+ import os
6
+ import sys
7
+
8
+ # 获取当前 windows 目录的路径
9
+ _current_dir = os.path.dirname(os.path.abspath(__file__))
10
+
11
+ # 关键:将 windows 目录加入搜索路径
12
+ # 这样其下的子模块 license 就能直接 import 旁边的 pyarmor_runtime_000000
13
+ if _current_dir not in sys.path:
14
+ sys.path.insert(0, _current_dir)
15
+
16
+
17
+ try:
18
+ from ascript.windows.screen.ocr import rapidocr_hook
19
+ import rapidocr
20
+
21
+ # 检查是否已经挂钩
22
+ if not hasattr(rapidocr, 'find'):
23
+ rapidocr_hook.install()
24
+ # print("RapidOCR Hook 注入成功")
25
+ except Exception as e:
26
+ # 捕获所有异常,确保主程序继续运行
27
+ # 你可以选择静默失败,或者只在日志里记录一下
28
+ # print(f"RapidOCR Hook 注入失败(非致命): {e}")
29
+ pass
File without changes
@@ -0,0 +1,143 @@
1
+ """屏幕数据模型模块。
2
+
3
+ 定义屏幕操作中使用的基础数据结构,包括坐标点、颜色、尺寸和 OCR 识别结果。
4
+ """
5
+
6
+ import json
7
+
8
+
9
+ class Point:
10
+ """屏幕坐标点。
11
+
12
+ 表示屏幕上的一个二维坐标点。
13
+ """
14
+
15
+ def __init__(self, x: int = 0, y: int = 0):
16
+ """初始化坐标点。
17
+
18
+ Args:
19
+ x: 横坐标,默认为 0。
20
+ y: 纵坐标,默认为 0。
21
+ """
22
+ self.x = x
23
+ self.y = y
24
+
25
+ def __str__(self):
26
+ """返回坐标点的 JSON 字符串表示。
27
+
28
+ Returns:
29
+ str: 包含 x 和 y 的 JSON 字符串,例如 '{"x": 100, "y": 200}'。
30
+ """
31
+ return json.dumps({'x': self.x, 'y': self.y})
32
+
33
+ def __repr__(self):
34
+ return f"Point(x={self.x}, y={self.y})"
35
+
36
+ def __iter__(self):
37
+ yield self.x
38
+ yield self.y
39
+
40
+ def __len__(self):
41
+ return 2
42
+
43
+ def __getitem__(self, index):
44
+ return (self.x, self.y)[index]
45
+
46
+ def __eq__(self, other):
47
+ if isinstance(other, Point):
48
+ return self.x == other.x and self.y == other.y
49
+ if isinstance(other, (tuple, list)) and len(other) == 2:
50
+ return self.x == other[0] and self.y == other[1]
51
+ return NotImplemented
52
+
53
+ def to_dict(self):
54
+ """将坐标点转换为字典。
55
+
56
+ Returns:
57
+ dict: 包含 'x' 和 'y' 键的字典。
58
+ """
59
+ return {'x': self.x, 'y': self.y}
60
+
61
+
62
+ class Color:
63
+ """颜色值。
64
+
65
+ 将十六进制颜色字符串解析为 RGB 分量。
66
+ """
67
+
68
+ def __init__(self, color: str):
69
+ """初始化颜色对象。
70
+
71
+ 从十六进制颜色字符串(如 '#FF00AA')中解析出 R、G、B 分量。
72
+
73
+ Args:
74
+ color: 十六进制颜色字符串,格式为 '#RRGGBB'。
75
+ """
76
+ self.r = int(color[1:3], 16)
77
+ self.g = int(color[3:5], 16)
78
+ self.b = int(color[5:], 16)
79
+
80
+ def to_rgb(self):
81
+ """将颜色转换为 RGB 列表。
82
+
83
+ Returns:
84
+ list: 包含 [R, G, B] 三个整数值的列表,每个值范围为 0-255。
85
+ """
86
+ return [self.r, self.g, self.b]
87
+
88
+
89
+ class Size:
90
+ """尺寸大小。
91
+
92
+ 表示宽度和高度的二维尺寸。
93
+ """
94
+
95
+ def __init__(self, width, height):
96
+ """初始化尺寸对象。
97
+
98
+ Args:
99
+ width: 宽度值。
100
+ height: 高度值。
101
+ """
102
+ self.width = width
103
+ self.height = height
104
+
105
+
106
+ class OcrRes:
107
+ """OCR 识别结果。
108
+
109
+ 封装单条 OCR 文字识别的结果,包括识别文本、置信度和区域位置信息。
110
+ """
111
+
112
+ def __init__(self, text: str = None, confidence: float = None, region_position: list = None):
113
+ """初始化 OCR 识别结果。
114
+
115
+ Args:
116
+ text: 识别出的文本内容。
117
+ confidence: 识别置信度,取值范围为 0.0 到 1.0。
118
+ region_position: 文本区域的四个顶点坐标列表,
119
+ 格式为 [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]。
120
+ """
121
+ self.text = text
122
+ self.confidence = float(confidence)
123
+ self.region_position = region_position
124
+ if self.region_position:
125
+ self.center_x = region_position[2][0] - region_position[0][0]
126
+ self.center_y = region_position[2][1] - region_position[0][1]
127
+
128
+ def to_dict(self):
129
+ """将 OCR 结果转换为字典。
130
+
131
+ Returns:
132
+ dict: 包含 'text'、'confidence' 和 'region_position' 键的字典。
133
+ """
134
+ return {'text': self.text, 'confidence': self.confidence, 'region_position': self.region_position}
135
+
136
+ def __str__(self):
137
+ """返回 OCR 结果的字符串表示。
138
+
139
+ Returns:
140
+ str: OCR 结果字典的字符串形式。
141
+ """
142
+ return self.to_dict().__str__()
143
+
@@ -0,0 +1,5 @@
1
+
2
+ from .keyboard import Keyboard
3
+ from .mouse import Mouse, MouseFailSafeError
4
+
5
+ __all__ = ["Keyboard", "Mouse", "MouseFailSafeError"]
@@ -0,0 +1,279 @@
1
+ """Native Windows keyboard input based on the standard-library ctypes module."""
2
+
3
+ import ctypes
4
+ import math
5
+ import threading
6
+ import time
7
+ from ctypes import wintypes
8
+ from typing import List, Sequence, Tuple, Union
9
+
10
+ from .mouse import Mouse
11
+
12
+ _INPUT_KEYBOARD = 1
13
+ _KEYEVENTF_EXTENDEDKEY = 0x0001
14
+ _KEYEVENTF_KEYUP = 0x0002
15
+ _KEYEVENTF_UNICODE = 0x0004
16
+
17
+
18
+ class _MOUSEINPUT(ctypes.Structure):
19
+ _fields_ = [("dx", wintypes.LONG), ("dy", wintypes.LONG),
20
+ ("mouseData", wintypes.DWORD), ("dwFlags", wintypes.DWORD),
21
+ ("time", wintypes.DWORD), ("dwExtraInfo", ctypes.c_size_t)]
22
+
23
+
24
+ class _KEYBDINPUT(ctypes.Structure):
25
+ _fields_ = [("wVk", wintypes.WORD), ("wScan", wintypes.WORD),
26
+ ("dwFlags", wintypes.DWORD), ("time", wintypes.DWORD),
27
+ ("dwExtraInfo", ctypes.c_size_t)]
28
+
29
+
30
+ class _HARDWAREINPUT(ctypes.Structure):
31
+ _fields_ = [("uMsg", wintypes.DWORD), ("wParamL", wintypes.WORD),
32
+ ("wParamH", wintypes.WORD)]
33
+
34
+
35
+ class _INPUTUNION(ctypes.Union):
36
+ _fields_ = [("mi", _MOUSEINPUT), ("ki", _KEYBDINPUT), ("hi", _HARDWAREINPUT)]
37
+
38
+
39
+ class _INPUT(ctypes.Structure):
40
+ _anonymous_ = ("union",)
41
+ _fields_ = [("type", wintypes.DWORD), ("union", _INPUTUNION)]
42
+
43
+
44
+ _user32 = ctypes.windll.user32
45
+ _send_input = _user32.SendInput
46
+ _send_input.argtypes = (wintypes.UINT, ctypes.c_void_p, ctypes.c_int)
47
+ _send_input.restype = wintypes.UINT
48
+ _vk_key_scan = _user32.VkKeyScanW
49
+ _vk_key_scan.argtypes = (wintypes.WCHAR,)
50
+ _vk_key_scan.restype = ctypes.c_short
51
+
52
+
53
+ _KEYS = {
54
+ "backspace": 0x08, "tab": 0x09, "clear": 0x0C, "enter": 0x0D,
55
+ "shift": 0x10, "ctrl": 0x11, "control": 0x11, "alt": 0x12,
56
+ "pause": 0x13, "capslock": 0x14, "esc": 0x1B, "escape": 0x1B,
57
+ "space": 0x20, "pgup": 0x21, "pageup": 0x21, "pgdn": 0x22,
58
+ "pagedown": 0x22, "end": 0x23, "home": 0x24, "left": 0x25,
59
+ "up": 0x26, "right": 0x27, "down": 0x28, "select": 0x29,
60
+ "print": 0x2A, "execute": 0x2B, "prtsc": 0x2C, "prtscr": 0x2C,
61
+ "prntscrn": 0x2C, "printscreen": 0x2C, "insert": 0x2D,
62
+ "ins": 0x2D, "delete": 0x2E, "del": 0x2E, "help": 0x2F,
63
+ "win": 0x5B, "super": 0x5B, "winleft": 0x5B, "winright": 0x5C,
64
+ "apps": 0x5D, "sleep": 0x5F, "multiply": 0x6A, "add": 0x6B,
65
+ "separator": 0x6C, "subtract": 0x6D, "decimal": 0x6E, "divide": 0x6F,
66
+ "numlock": 0x90, "scrolllock": 0x91, "shiftleft": 0xA0,
67
+ "shiftright": 0xA1, "ctrlleft": 0xA2, "ctrlright": 0xA3,
68
+ "altleft": 0xA4, "altright": 0xA5, "browserback": 0xA6,
69
+ "browserforward": 0xA7, "browserrefresh": 0xA8, "browserstop": 0xA9,
70
+ "browsersearch": 0xAA, "browserfavorites": 0xAB, "browserhome": 0xAC,
71
+ "volumemute": 0xAD, "volumedown": 0xAE, "volumeup": 0xAF,
72
+ "nexttrack": 0xB0, "prevtrack": 0xB1, "stop": 0xB2,
73
+ "playpause": 0xB3, "launchmail": 0xB4, "launchmediaselect": 0xB5,
74
+ "launchapp1": 0xB6, "launchapp2": 0xB7,
75
+ }
76
+ _KEYS.update({f"f{number}": 0x6F + number for number in range(1, 25)})
77
+ _KEYS.update({f"num{number}": 0x60 + number for number in range(10)})
78
+
79
+ _EXTENDED_KEYS = {
80
+ "right", "left", "up", "down", "home", "end", "pageup", "pgup",
81
+ "pagedown", "pgdn", "insert", "ins", "delete", "del", "printscreen",
82
+ "prtsc", "prtscr", "prntscrn", "win", "super", "winleft", "winright",
83
+ "apps", "ctrlright", "altright", "divide", "numlock",
84
+ }
85
+ _MODIFIER_VKS = ((0x01, 0x10), (0x02, 0x11), (0x04, 0x12))
86
+
87
+
88
+ class Keyboard:
89
+ """Chainable native keyboard controller for Windows."""
90
+
91
+ FAILSAFE = True
92
+ _lock = threading.RLock()
93
+ _held = {}
94
+
95
+ @staticmethod
96
+ def _duration(value: float, name: str) -> float:
97
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
98
+ raise TypeError(f"{name} must be a number")
99
+ value = float(value)
100
+ if value < 0 or not math.isfinite(value):
101
+ raise ValueError(f"{name} must be finite and non-negative")
102
+ return value
103
+
104
+ @staticmethod
105
+ def _normalize_key(key: str) -> str:
106
+ if not isinstance(key, str):
107
+ raise TypeError("key must be a string")
108
+ if not key:
109
+ raise ValueError("key must not be empty")
110
+ aliases = {"\n": "enter", "\r": "enter", "\t": "tab", "\b": "backspace"}
111
+ return aliases.get(key, key.lower() if len(key) > 1 else key)
112
+
113
+ @classmethod
114
+ def _fail_safe_check(cls) -> None:
115
+ if cls.FAILSAFE:
116
+ Mouse._fail_safe_check()
117
+
118
+ @classmethod
119
+ def _emit(cls, vk: int = 0, scan: int = 0, flags: int = 0, check: bool = True) -> None:
120
+ if check:
121
+ cls._fail_safe_check()
122
+ event = _INPUT(type=_INPUT_KEYBOARD, ki=_KEYBDINPUT(
123
+ wVk=vk, wScan=scan, dwFlags=flags, time=0, dwExtraInfo=0))
124
+ if _send_input(1, ctypes.byref(event), ctypes.sizeof(_INPUT)) != 1:
125
+ raise ctypes.WinError(ctypes.get_last_error())
126
+
127
+ @classmethod
128
+ def _resolve(cls, key: str) -> Tuple[str, List[Tuple[int, int]]]:
129
+ token = cls._normalize_key(key)
130
+ if token in _KEYS:
131
+ flags = _KEYEVENTF_EXTENDEDKEY if token in _EXTENDED_KEYS else 0
132
+ return token, [(_KEYS[token], flags)]
133
+ if len(token) != 1:
134
+ raise ValueError(f"unsupported key: {key}")
135
+ code = int(_vk_key_scan(token))
136
+ if code == -1:
137
+ raise ValueError(f"the current keyboard layout cannot type: {key!r}")
138
+ vk, modifiers = code & 0xFF, (code >> 8) & 0xFF
139
+ events = [(modifier_vk, 0) for mask, modifier_vk in _MODIFIER_VKS if modifiers & mask]
140
+ events.append((vk, 0))
141
+ return token, events
142
+
143
+ @classmethod
144
+ def _press_events(cls, events: Sequence[Tuple[int, int]], check: bool = True) -> None:
145
+ pressed = []
146
+ try:
147
+ for vk, flags in events:
148
+ cls._emit(vk=vk, flags=flags, check=check)
149
+ pressed.append((vk, flags))
150
+ except Exception:
151
+ cls._release_events(pressed)
152
+ raise
153
+
154
+ @classmethod
155
+ def _release_events(cls, events: Sequence[Tuple[int, int]]) -> None:
156
+ for vk, flags in reversed(events):
157
+ cls._emit(vk=vk, flags=flags | _KEYEVENTF_KEYUP, check=False)
158
+
159
+ @classmethod
160
+ def _unicode_units(cls, text: str) -> List[int]:
161
+ encoded = text.encode("utf-16-le", errors="surrogatepass")
162
+ return [int.from_bytes(encoded[index:index + 2], "little")
163
+ for index in range(0, len(encoded), 2)]
164
+
165
+ @classmethod
166
+ def _type_character(cls, character: str) -> None:
167
+ if character in ("\r", "\n", "\t", "\b"):
168
+ cls._press_and_release(cls._normalize_key(character))
169
+ return
170
+ for unit in cls._unicode_units(character):
171
+ cls._emit(scan=unit, flags=_KEYEVENTF_UNICODE)
172
+ cls._emit(scan=unit, flags=_KEYEVENTF_UNICODE | _KEYEVENTF_KEYUP, check=False)
173
+
174
+ @classmethod
175
+ def _press_and_release(cls, key: str) -> None:
176
+ _, events = cls._resolve(key)
177
+ cls._press_events(events)
178
+ try:
179
+ pass
180
+ finally:
181
+ cls._release_events(events)
182
+
183
+ @classmethod
184
+ def input(cls, msg: str, interval: float = 0.0):
185
+ """Type Unicode text without using the clipboard or active input method."""
186
+ if not isinstance(msg, str):
187
+ raise TypeError("msg must be a string")
188
+ interval = cls._duration(interval, "interval")
189
+ with cls._lock:
190
+ for index, character in enumerate(msg):
191
+ cls._type_character(character)
192
+ if index + 1 < len(msg) and interval:
193
+ time.sleep(interval)
194
+ return cls
195
+
196
+ @classmethod
197
+ def key(cls, keys: Union[str, List[str]], presses: int = 1, interval: float = 0.1):
198
+ """Press and release one key or a sequence of keys."""
199
+ if isinstance(keys, str):
200
+ sequence = [keys]
201
+ elif isinstance(keys, (list, tuple)) and all(isinstance(key, str) for key in keys):
202
+ sequence = list(keys)
203
+ else:
204
+ raise TypeError("keys must be a string or a list of strings")
205
+ if not sequence:
206
+ raise ValueError("keys must not be empty")
207
+ if not isinstance(presses, int) or isinstance(presses, bool):
208
+ raise TypeError("presses must be an integer")
209
+ if presses < 1:
210
+ raise ValueError("presses must be at least one")
211
+ interval = cls._duration(interval, "interval")
212
+ with cls._lock:
213
+ count = presses * len(sequence)
214
+ current = 0
215
+ for _ in range(presses):
216
+ for key in sequence:
217
+ cls._press_and_release(key)
218
+ current += 1
219
+ if current < count and interval:
220
+ time.sleep(interval)
221
+ return cls
222
+
223
+ @classmethod
224
+ def key_hot(cls, *args: str, interval: float = 0.1):
225
+ """Hold keys in order and release them in reverse order."""
226
+ if not args:
227
+ raise ValueError("at least one key is required")
228
+ interval = cls._duration(interval, "interval")
229
+ pressed = []
230
+ with cls._lock:
231
+ try:
232
+ for index, key in enumerate(args):
233
+ _, events = cls._resolve(key)
234
+ cls._press_events(events)
235
+ pressed.append(events)
236
+ if index + 1 < len(args) and interval:
237
+ time.sleep(interval)
238
+ finally:
239
+ for index, events in enumerate(reversed(pressed)):
240
+ cls._release_events(events)
241
+ if index + 1 < len(pressed) and interval:
242
+ time.sleep(interval)
243
+ return cls
244
+
245
+ @classmethod
246
+ def key_down(cls, key_str: str):
247
+ """Press a key and keep it held until key_up() or release_all()."""
248
+ with cls._lock:
249
+ token, events = cls._resolve(key_str)
250
+ if token not in cls._held:
251
+ cls._press_events(events)
252
+ cls._held[token] = events
253
+ return cls
254
+
255
+ @classmethod
256
+ def key_up(cls, key_str: str):
257
+ """Release a key previously held by key_down()."""
258
+ with cls._lock:
259
+ token = cls._normalize_key(key_str)
260
+ events = cls._held.pop(token, None)
261
+ if events is None:
262
+ _, events = cls._resolve(key_str)
263
+ cls._release_events(events)
264
+ return cls
265
+
266
+ @classmethod
267
+ def release_all(cls):
268
+ """Release every key held through key_down(), even during fail-safe recovery."""
269
+ with cls._lock:
270
+ held = list(cls._held.values())
271
+ cls._held.clear()
272
+ for events in reversed(held):
273
+ cls._release_events(events)
274
+ return cls
275
+
276
+ @classmethod
277
+ def wait(cls, seconds: float):
278
+ time.sleep(cls._duration(seconds, "seconds"))
279
+ return cls
@@ -0,0 +1,179 @@
1
+ import ctypes
2
+ import time
3
+ import sys
4
+ import os
5
+ from ..system import R
6
+
7
+
8
+ class LogitechDriver:
9
+ """罗技驱动级硬件操作类。
10
+
11
+ 通过加载罗技 G HUB SDK 的 DLL,实现驱动级别的鼠标点击和移动操作。
12
+ 需要系统已安装并运行罗技 G HUB 驱动程序。
13
+ 支持 x86 和 x64 架构自动适配。
14
+
15
+ Attributes:
16
+ DLL_NAME: 罗技 SDK DLL 文件名。
17
+ _dll: 已加载的 DLL 对象实例。
18
+ _initialized: 驱动是否已完成初始化。
19
+ """
20
+
21
+ DLL_NAME = "LogitechGkeyEnginesWrapper.dll"
22
+
23
+ _dll = None
24
+ _initialized = False
25
+
26
+ @classmethod
27
+ def _get_dll_path(cls):
28
+ """根据当前 Python 解释器的位数定位对应架构的 DLL 文件路径。
29
+
30
+ Returns:
31
+ str: DLL 文件的完整路径。
32
+ """
33
+ arch = "x64" if sys.maxsize > 2 ** 32 else "x86"
34
+ return R.internal("windows", "resources", "libs", "logitech", arch, cls.DLL_NAME)
35
+
36
+ @classmethod
37
+ def _is_ghub_running(cls) -> bool:
38
+ """检测罗技 G HUB 进程是否正在运行。
39
+
40
+ 通过 tasklist 命令检查 lghub.exe、lghub_agent.exe、lghub_updater.exe
41
+ 等进程是否存在。
42
+
43
+ Returns:
44
+ bool: G HUB 正在运行返回 True,否则返回 False。
45
+ """
46
+ try:
47
+ # 罗技 G HUB 可能存在于这三个进程中
48
+ targets = {"lghub.exe", "lghub_agent.exe", "lghub_updater.exe"}
49
+ with os.popen('tasklist /NH /FI "STATUS eq running"') as f:
50
+ output = f.read().lower()
51
+ return any(t in output for t in targets)
52
+ except:
53
+ return False
54
+
55
+ @classmethod
56
+ def init(cls) -> bool:
57
+ """初始化罗技驱动。
58
+
59
+ 执行环境检查(G HUB 进程、DLL 文件),加载 DLL 并尝试调用
60
+ SDK 初始化函数。初始化成功后会等待 0.5 秒确保驱动就绪。
61
+ 重复调用时若已初始化会直接返回 True。
62
+
63
+ Returns:
64
+ bool: 初始化成功返回 True,失败返回 False。
65
+ """
66
+ if cls._initialized:
67
+ return True
68
+
69
+ # 1. 环境检查
70
+ if not cls._is_ghub_running():
71
+ print("[LogitechDriver] 错误: 未检测到 G HUB 进程,请确保罗技驱动已启动。")
72
+ return False
73
+
74
+ dll_path = cls._get_dll_path()
75
+ if not os.path.exists(dll_path):
76
+ print(f"[LogitechDriver] 错误: 找不到 DLL -> {dll_path}")
77
+ return False
78
+
79
+ try:
80
+ # 2. 加载 DLL 并强制使用 Windows 路径标准
81
+ cls._dll = ctypes.CDLL(dll_path)
82
+
83
+ # 3. 尝试所有可能的初始化导出函数
84
+ # 罗技 SDK 随版本变化,函数名可能为以下其一
85
+ init_functions = [
86
+ "LogiGkeyInitWithoutContext",
87
+ "LogiGkeyInit",
88
+ "LogiGkeyInitWithoutScene"
89
+ ]
90
+
91
+ success = False
92
+ for func_name in init_functions:
93
+ if hasattr(cls._dll, func_name):
94
+ if getattr(cls._dll, func_name)():
95
+ success = True
96
+ break
97
+
98
+ if success:
99
+ # [关键] 驱动初始化后必须等待,否则紧随其后的指令会丢失
100
+ time.sleep(0.5)
101
+ cls._initialized = True
102
+ return True
103
+
104
+ return False
105
+ except Exception as e:
106
+ print(f"[LogitechDriver] 加载异常: {e}")
107
+ return False
108
+
109
+ @classmethod
110
+ def advanced_click_guide(cls):
111
+ """高级引导:检查运行权限与驱动环境。
112
+
113
+ 检查当前是否以管理员权限运行,并尝试激活驱动模式。
114
+ 在控制台输出检查结果供用户排查问题。
115
+
116
+ Returns:
117
+ bool: 驱动激活成功返回 True,失败返回 False。
118
+ """
119
+ print("--- [Logitech] 底层引擎自检 ---")
120
+
121
+ # 管理员权限检查
122
+ try:
123
+ is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
124
+ except:
125
+ is_admin = False
126
+
127
+ if not is_admin:
128
+ print("⚠️ 注意: 当前非管理员权限,这可能是点击无效的主因!")
129
+
130
+ if cls.init():
131
+ print("✅ 驱动模式已激活。")
132
+ return True
133
+ else:
134
+ print("❌ 驱动激活失败,请检查 G HUB 是否正常运行。")
135
+ return False
136
+
137
+ @classmethod
138
+ def click(cls, button: int = 1, duration: float = 0.05):
139
+ """执行驱动级鼠标点击。
140
+
141
+ 通过罗技 SDK 直接发送鼠标按键事件,可绕过部分反作弊检测。
142
+
143
+ Args:
144
+ button: 鼠标按键编号,1=左键,2=中键,4=右键(SDK 定义)。
145
+ duration: 按下与释放之间的间隔时间(秒),默认为 0.05。
146
+
147
+ Returns:
148
+ bool: 点击成功返回 True,失败(驱动未初始化或异常)返回 False。
149
+ """
150
+ if not cls.init():
151
+ return False
152
+ try:
153
+ cls._dll.LogiMouseButtonPressed(button)
154
+ time.sleep(duration)
155
+ cls._dll.LogiMouseButtonReleased(button)
156
+ return True
157
+ except:
158
+ return False
159
+
160
+ @classmethod
161
+ def move_rel(cls, x: int, y: int):
162
+ """驱动级鼠标相对位移。
163
+
164
+ 通过罗技 SDK 发送鼠标相对移动事件。
165
+
166
+ Args:
167
+ x: 水平方向的相对位移量(像素)。
168
+ y: 垂直方向的相对位移量(像素)。
169
+
170
+ Returns:
171
+ bool: 移动成功返回对应的 SDK 返回值,失败返回 False。
172
+ """
173
+ if not cls.init():
174
+ return False
175
+ try:
176
+ # 罗技相对移动
177
+ return cls._dll.LogiMouseRelativeMove(int(x), int(y))
178
+ except:
179
+ return False