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
@@ -0,0 +1,56 @@
1
+ from ..system import R
2
+ from ..ui.web_window import WebWindow
3
+ from .tool_api import ToolApi
4
+
5
+
6
+ TOOL_TITLES = {
7
+ "inspector": "AScript 控件检索",
8
+ "colors": "AScript 图色工具",
9
+ }
10
+
11
+
12
+ class SDKToolWindow(WebWindow):
13
+ def __init__(self, tool: str, hwnd=None):
14
+ if tool not in TOOL_TITLES:
15
+ raise ValueError(f"未知工具: {tool}")
16
+ html_path = R.internal("windows", "tools", "webview", "index.html")
17
+ super().__init__(html_path)
18
+ self.tool = tool
19
+ self.api = ToolApi(tool, hwnd)
20
+ for name in (
21
+ "get_context", "get_windows", "inspect_window", "capture", "load_image", "get_pixel_color", "test_selector",
22
+ "test_selector_spec",
23
+ "find_colors", "compare_colors", "count_colors", "find_blocks", "find_images",
24
+ "list_image_templates", "add_image_template", "import_image_template",
25
+ "rename_image_template", "delete_image_template", "clear_image_templates",
26
+ "save_image_templates", "list_project_images", "add_project_image_template",
27
+ "run_ocr", "run_ocrx", "run_canny", "scan_codes",
28
+ "save_capture", "open_capture_folder",
29
+ "list_capture_cache", "load_cached_capture", "delete_capture_cache", "clear_capture_cache",
30
+ ):
31
+ self.expose(getattr(self.api, name))
32
+
33
+ def show(self, **kwargs):
34
+ defaults = {
35
+ "title": TOOL_TITLES[self.tool],
36
+ "width": 1180,
37
+ "height": 760,
38
+ "min_size": (820, 560),
39
+ "background_color": "#171a1c",
40
+ "bring_to_front": True,
41
+ "exclude_from_capture": self.tool == "colors",
42
+ }
43
+ defaults.update(kwargs)
44
+ return super().show(**defaults)
45
+
46
+
47
+ def start_tool(tool: str, hwnd=None, **kwargs):
48
+ return SDKToolWindow(tool, hwnd=hwnd).show(**kwargs)
49
+
50
+
51
+ def start_inspector(hwnd=None, **kwargs):
52
+ return start_tool("inspector", hwnd=hwnd, **kwargs)
53
+
54
+
55
+ def start_colors(hwnd=None, **kwargs):
56
+ return start_tool("colors", hwnd=hwnd, **kwargs)
@@ -0,0 +1,365 @@
1
+ """Crash- and hang-isolated UI Automation worker for the inspector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import ast
7
+ import json
8
+ import sys
9
+
10
+
11
+ RESULT_PREFIX = "ASCRIPT_UIA_RESULT="
12
+
13
+
14
+ def _count_nodes(node):
15
+ return 1 + sum(_count_nodes(child) for child in node.get("children", []))
16
+
17
+
18
+ def _tag_source_and_offset(
19
+ node, source_hwnd: int, offset_x: int = 0, offset_y: int = 0,
20
+ source_class_name: str = "",
21
+ ):
22
+ """Keep enough origin information for overlays and generated selectors."""
23
+ node["source_hwnd"] = int(source_hwnd)
24
+ node["source_class_name"] = source_class_name
25
+ rect = node.get("rect")
26
+ if rect:
27
+ rect["left"] += offset_x
28
+ rect["right"] += offset_x
29
+ rect["top"] += offset_y
30
+ rect["bottom"] += offset_y
31
+ for child in node.get("children", []):
32
+ _tag_source_and_offset(child, source_hwnd, offset_x, offset_y, source_class_name)
33
+
34
+
35
+ def _renderer_documents(target, data, depth: int):
36
+ """Read Chromium/Electron web content from its renderer child HWND.
37
+
38
+ Chromium does not expose the active document below the top-level browser
39
+ HWND in Raw View. ElementFromHandle on Chrome_RenderWidgetHostHWND does.
40
+ """
41
+ import win32gui
42
+ from ..window import Selector, Window
43
+
44
+ handles = []
45
+ win32gui.EnumChildWindows(target.hwnd, lambda child, output: output.append(child), handles)
46
+ renderer_handles = []
47
+ for child in handles:
48
+ try:
49
+ if win32gui.GetClassName(child) == "Chrome_RenderWidgetHostHWND" and win32gui.IsWindowVisible(child):
50
+ renderer_handles.append(child)
51
+ except Exception:
52
+ continue
53
+ if not renderer_handles:
54
+ return 0
55
+
56
+ main_client = target.client_rect
57
+ existing_documents = set()
58
+
59
+ def collect_documents(node):
60
+ if node.get("type") == "DocumentControl":
61
+ rect = node.get("rect") or {}
62
+ existing_documents.add((
63
+ node.get("name") or "",
64
+ rect.get("left"), rect.get("top"), rect.get("right"), rect.get("bottom"),
65
+ ))
66
+ for child in node.get("children", []):
67
+ collect_documents(child)
68
+
69
+ collect_documents(data)
70
+
71
+ def remove_document(node, key):
72
+ children = node.get("children", [])
73
+ for index, child in enumerate(children):
74
+ rect = child.get("rect") or {}
75
+ child_key = (
76
+ child.get("name") or "",
77
+ rect.get("left"), rect.get("top"), rect.get("right"), rect.get("bottom"),
78
+ )
79
+ if child.get("type") == "DocumentControl" and child_key == key:
80
+ children.pop(index)
81
+ node["child_count"] = len(children)
82
+ return True
83
+ if remove_document(child, key):
84
+ return True
85
+ return False
86
+ added = 0
87
+ for child_hwnd in renderer_handles:
88
+ try:
89
+ renderer_window = Window(child_hwnd)
90
+ renderer_root = Selector(renderer_window, depth=depth).get_uielement_tree()
91
+ if not renderer_root:
92
+ continue
93
+ renderer_data = renderer_root.to_dict()
94
+ documents = [child for child in renderer_data.get("children", [])
95
+ if child.get("type") == "DocumentControl"]
96
+ if not documents:
97
+ continue
98
+ renderer_client = renderer_window.client_rect
99
+ offset_x = renderer_client.left - main_client.left
100
+ offset_y = renderer_client.top - main_client.top
101
+ for document in documents:
102
+ _tag_source_and_offset(
103
+ document, child_hwnd, offset_x, offset_y,
104
+ win32gui.GetClassName(child_hwnd),
105
+ )
106
+ rect = document.get("rect") or {}
107
+ key = (
108
+ document.get("name") or "",
109
+ rect.get("left"), rect.get("top"), rect.get("right"), rect.get("bottom"),
110
+ )
111
+ if key in existing_documents:
112
+ remove_document(data, key)
113
+ existing_documents.add(key)
114
+ document["depth"] = 1
115
+ data.setdefault("children", []).append(document)
116
+ added += _count_nodes(document)
117
+ except Exception:
118
+ continue
119
+ data["child_count"] = len(data.get("children", []))
120
+ return added
121
+
122
+
123
+ def inspect_window(hwnd: int, depth: int):
124
+ from ..window import Selector, Window
125
+ import uiautomation as auto
126
+
127
+ with auto.UIAutomationInitializerInThread():
128
+ target = Window(hwnd)
129
+ selector = Selector(target, depth=depth if depth > 0 else 30)
130
+ root = selector.get_uielement_tree()
131
+ if not root:
132
+ return {"status": "empty", "message": "目标窗口未暴露 UIA 控件"}
133
+ data = root.to_dict()
134
+ _tag_source_and_offset(data, target.hwnd, source_class_name=target.class_name)
135
+ renderer_count = _renderer_documents(target, data, depth)
136
+ count = _count_nodes(data)
137
+ if count <= 1:
138
+ return {"status": "empty", "message": "目标窗口只暴露了 UIA 根节点"}
139
+ return {
140
+ "status": "success",
141
+ "backend": "UI Automation Raw View" + (" + Chromium Renderer" if renderer_count else ""),
142
+ "node_count": count,
143
+ "message": f"已从浏览器渲染进程补充 {renderer_count} 个网页控件" if renderer_count else "",
144
+ "data": data,
145
+ }
146
+
147
+
148
+ FILTER_METHODS = {
149
+ "name", "type", "class_name", "res_id", "value", "description", "handle",
150
+ "enabled", "visible", "clickable", "password", "child_count", "depth",
151
+ "checkable", "checked", "editable", "focusable", "focused", "selected", "node_depth",
152
+ }
153
+ RELATION_METHODS = {"child", "parent", "brother"}
154
+ ACTION_METHODS = {"click", "input"}
155
+ TERMINAL_METHODS = {"find", "find_first", "find_all"}
156
+ WINDOW_FIND_ARGUMENTS = {"title_re", "process_name", "class_name", "visible_only", "timeout"}
157
+ CHILD_FIND_ARGUMENTS = {"title_re", "class_name", "visible_only", "timeout"}
158
+ SELECTOR_APP_ARGUMENTS = {"title_re", "class_name", "content", "timeout", "depth"}
159
+ SELECTOR_INIT_ARGUMENTS = {
160
+ "app", "title_re", "class_name", "content", "timeout", "depth",
161
+ }
162
+
163
+
164
+ def _literal(node):
165
+ if isinstance(node, ast.Constant) and isinstance(node.value, (str, int, float, bool, type(None))):
166
+ return node.value
167
+ raise ValueError("Selector 参数只允许字符串、数字、布尔值或 None")
168
+
169
+
170
+ def _evaluate_legacy_selector(expression: str):
171
+ """Evaluate the old generated syntax without executing arbitrary Python."""
172
+ from ..window import Selector, Window
173
+
174
+ def evaluate(node):
175
+ if not isinstance(node, ast.Call):
176
+ raise ValueError("只支持 Selector 链式调用")
177
+ if isinstance(node.func, ast.Name) and node.func.id == "Window":
178
+ if len(node.args) != 1 or node.keywords:
179
+ raise ValueError("Window 只接受一个 HWND")
180
+ return Window(int(_literal(node.args[0])))
181
+ if isinstance(node.func, ast.Name) and node.func.id == "Selector":
182
+ if node.args:
183
+ raise ValueError("Selector 请使用关键字参数")
184
+ if any(item.arg is None for item in node.keywords):
185
+ raise ValueError("Selector 不支持展开关键字参数")
186
+ names = {item.arg for item in node.keywords}
187
+ allowed = SELECTOR_INIT_ARGUMENTS | {"window"}
188
+ if names - allowed or len(names) != len(node.keywords):
189
+ raise ValueError("Selector 包含不支持或重复的参数")
190
+ if "window" in names and len(names) != 1:
191
+ raise ValueError("window 不能与应用定位参数同时使用")
192
+ values = {
193
+ item.arg: evaluate(item.value) if item.arg == "window" else _literal(item.value)
194
+ for item in node.keywords
195
+ }
196
+ return Selector(**values)
197
+ if not isinstance(node.func, ast.Attribute):
198
+ raise ValueError("非法 Selector 表达式")
199
+ if isinstance(node.func.value, ast.Name) and node.func.value.id == "Selector" and node.func.attr == "app":
200
+ if len(node.args) > 1:
201
+ raise ValueError("Selector.app 最多接受一个位置参数")
202
+ kwargs = {item.arg: _literal(item.value) for item in node.keywords if item.arg}
203
+ if len(kwargs) != len(node.keywords) or set(kwargs) - SELECTOR_APP_ARGUMENTS:
204
+ raise ValueError("Selector.app 包含不支持的参数")
205
+ process_name = _literal(node.args[0]) if node.args else None
206
+ return Selector.app(process_name, **kwargs)
207
+ if isinstance(node.func.value, ast.Name) and node.func.value.id == "Window" and node.func.attr == "find":
208
+ if node.args:
209
+ raise ValueError("Window.find 请使用关键字参数")
210
+ kwargs = {item.arg: _literal(item.value) for item in node.keywords if item.arg}
211
+ if len(kwargs) != len(node.keywords) or set(kwargs) - WINDOW_FIND_ARGUMENTS:
212
+ raise ValueError("Window.find 包含不支持的参数")
213
+ window = Window.find(**kwargs)
214
+ if window is None:
215
+ raise ValueError("未找到目标窗口")
216
+ return window
217
+ target = evaluate(node.func.value)
218
+ method = node.func.attr
219
+ if isinstance(target, Window) and method == "find_child":
220
+ if node.args:
221
+ raise ValueError("find_child 请使用关键字参数")
222
+ kwargs = {item.arg: _literal(item.value) for item in node.keywords if item.arg}
223
+ if len(kwargs) != len(node.keywords) or set(kwargs) - CHILD_FIND_ARGUMENTS:
224
+ raise ValueError("find_child 包含不支持的参数")
225
+ child = target.find_child(**kwargs)
226
+ if child is None:
227
+ raise ValueError("未找到目标内容窗口")
228
+ return child
229
+ if method not in FILTER_METHODS | RELATION_METHODS | ACTION_METHODS | TERMINAL_METHODS:
230
+ raise ValueError(f"不支持的方法: {method}")
231
+ if node.keywords:
232
+ raise ValueError("Selector 表达式暂不支持关键字参数")
233
+ args = [_literal(item) for item in node.args]
234
+ return getattr(target, method)(*args)
235
+
236
+ parsed = ast.parse(expression.strip(), mode="eval")
237
+ return evaluate(parsed.body)
238
+
239
+
240
+ def _serialize_results(values, source_window, display_window):
241
+ data = [item.to_dict() for item in values]
242
+ offset_x = offset_y = 0
243
+ if source_window.hwnd != display_window.hwnd:
244
+ source_rect = source_window.client_rect
245
+ display_rect = display_window.client_rect
246
+ offset_x = source_rect.left - display_rect.left
247
+ offset_y = source_rect.top - display_rect.top
248
+ for item in data:
249
+ _tag_source_and_offset(
250
+ item, source_window.hwnd, offset_x, offset_y,
251
+ source_window.class_name,
252
+ )
253
+ return data
254
+
255
+
256
+ def _resolve_window_locator(locator):
257
+ from ..window import Window
258
+
259
+ if isinstance(locator, int) and not isinstance(locator, bool):
260
+ window = Window(locator)
261
+ return window, window
262
+ if not isinstance(locator, dict):
263
+ raise ValueError("window 必须是窗口定位对象")
264
+ allowed = WINDOW_FIND_ARGUMENTS | {"child_class_name"}
265
+ unknown = set(locator) - allowed
266
+ if unknown:
267
+ raise ValueError(f"不支持的窗口定位属性: {', '.join(sorted(unknown))}")
268
+ find_args = {key: value for key, value in locator.items() if key in WINDOW_FIND_ARGUMENTS}
269
+ if not any(find_args.get(key) for key in ("title_re", "process_name", "class_name")):
270
+ raise ValueError("窗口定位至少需要 title_re、process_name 或 class_name")
271
+ find_args.setdefault("timeout", 10)
272
+ root_window = Window.find(**find_args)
273
+ if root_window is None:
274
+ raise ValueError("未找到目标窗口,请检查进程名、窗口类名或标题")
275
+ source_window = root_window
276
+ child_class_name = locator.get("child_class_name")
277
+ if child_class_name:
278
+ source_window = root_window.find_child(
279
+ class_name=str(child_class_name), visible_only=True,
280
+ timeout=float(find_args.get("timeout") or 0),
281
+ )
282
+ if source_window is None:
283
+ raise ValueError(f"未找到内容窗口: {child_class_name}")
284
+ return root_window, source_window
285
+
286
+
287
+ def test_selector_spec(spec):
288
+ from ..window import Selector
289
+ import uiautomation as auto
290
+
291
+ if not isinstance(spec, dict):
292
+ raise ValueError("Selector spec 必须是对象")
293
+ display_window, source_window = _resolve_window_locator(spec.get("window"))
294
+ steps = spec.get("steps")
295
+ if not isinstance(steps, list) or not steps:
296
+ raise ValueError("Selector 至少需要一个步骤")
297
+ mode = spec.get("mode", "first")
298
+ if mode not in ("first", "all"):
299
+ raise ValueError("mode 只允许 first 或 all")
300
+
301
+ with auto.UIAutomationInitializerInThread():
302
+ selector = Selector(source_window, depth=max(1, min(int(spec.get("depth") or 30), 50)))
303
+ for index, step in enumerate(steps):
304
+ if not isinstance(step, dict):
305
+ raise ValueError(f"第 {index + 1} 个步骤格式错误")
306
+ method = step.get("method")
307
+ filters = step.get("filters", {})
308
+ if not isinstance(filters, dict):
309
+ raise ValueError(f"第 {index + 1} 个步骤 filters 必须是对象")
310
+ unknown = set(filters) - FILTER_METHODS
311
+ if unknown:
312
+ raise ValueError(f"不支持的属性: {', '.join(sorted(unknown))}")
313
+ if method in RELATION_METHODS:
314
+ getattr(selector, method)(step.get("param"))
315
+ elif method == "find":
316
+ if index and selector._current_filters:
317
+ selector._flush_to_step()
318
+ elif method == "action":
319
+ action = step.get("action")
320
+ if action not in ACTION_METHODS:
321
+ raise ValueError(f"不支持的动作: {action}")
322
+ getattr(selector, action)(step.get("param") if action == "input" else bool(step.get("param", False)))
323
+ else:
324
+ raise ValueError(f"不支持的步骤: {method}")
325
+ for name, value in filters.items():
326
+ getattr(selector, name)(value)
327
+
328
+ result = selector.find_all() if mode == "all" else selector.find()
329
+ values = [] if result is None else result if isinstance(result, list) else [result]
330
+ return {"status": "success", "data": _serialize_results(values, source_window, display_window)}
331
+
332
+
333
+ def test_selector(expression: str):
334
+ import uiautomation as auto
335
+
336
+ with auto.UIAutomationInitializerInThread():
337
+ result = _evaluate_legacy_selector(expression)
338
+ if hasattr(result, "find"):
339
+ result = result.find()
340
+ values = [] if result is None else result if isinstance(result, list) else [result]
341
+ source_window = values[0].window if values else None
342
+ data = _serialize_results(values, source_window, source_window) if source_window else []
343
+ return {"status": "success", "data": data}
344
+
345
+
346
+ def main():
347
+ parser = argparse.ArgumentParser()
348
+ parser.add_argument("action", choices=("inspect", "test", "test-spec"))
349
+ parser.add_argument("--hwnd", type=int)
350
+ parser.add_argument("--depth", type=int, default=0)
351
+ args = parser.parse_args()
352
+ try:
353
+ if args.action == "inspect":
354
+ result = inspect_window(args.hwnd, args.depth)
355
+ elif args.action == "test":
356
+ result = test_selector(sys.stdin.read())
357
+ else:
358
+ result = test_selector_spec(json.loads(sys.stdin.read()))
359
+ except Exception as exc:
360
+ result = {"status": "error", "message": str(exc), "data": []}
361
+ print(RESULT_PREFIX + json.dumps(result, ensure_ascii=False, separators=(",", ":")), flush=True)
362
+
363
+
364
+ if __name__ == "__main__":
365
+ main()