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,466 @@
1
+ import threading
2
+ import subprocess
3
+ import tempfile
4
+ import ctypes
5
+ from ctypes import wintypes
6
+ import time
7
+ import webbrowser
8
+ import datetime
9
+ import logging
10
+ import webview
11
+ import os
12
+ from functools import partial
13
+ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
14
+ from typing import Optional, Callable, Dict
15
+ from urllib.parse import quote
16
+
17
+ # WebView2 运行时:官方下载页 + 常青版引导安装器(约 2MB,联网拉取完整运行时)
18
+ _WEBVIEW2_DOWNLOAD_URL = "https://developer.microsoft.com/microsoft-edge/webview2/"
19
+ _WEBVIEW2_BOOTSTRAPPER_URL = "https://go.microsoft.com/fwlink/p/?LinkId=2124703"
20
+
21
+ # 诊断日志:定位握手故障点,写到用户主目录
22
+ _DIAG_LOG = os.path.join(os.path.expanduser("~"), "ascript_diag.log")
23
+
24
+
25
+ class _IgnoreMissingWebViewDataFolder(logging.Filter):
26
+ """Hide pywebview's harmless double-delete warning on Windows shutdown."""
27
+
28
+ def filter(self, record):
29
+ message = record.getMessage()
30
+ return not (
31
+ message.startswith("Failed to delete user data folder:")
32
+ and ("WinError 3" in message or "No such file or directory" in message)
33
+ )
34
+
35
+
36
+ logging.getLogger("pywebview").addFilter(_IgnoreMissingWebViewDataFolder())
37
+
38
+
39
+ class _LocalAssetHandler(SimpleHTTPRequestHandler):
40
+ """Serve one window's local assets without pywebview's global server."""
41
+
42
+ def log_message(self, format, *args):
43
+ pass
44
+
45
+ def end_headers(self):
46
+ self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
47
+ self.send_header("Pragma", "no-cache")
48
+ self.send_header("Expires", "0")
49
+ super().end_headers()
50
+
51
+
52
+ def _diag(msg, reset=False):
53
+ """把诊断信息写入 ~/ascript_diag.log。reset=True 时清空重写。"""
54
+ try:
55
+ mode = "w" if reset else "a"
56
+ ts = datetime.datetime.now().strftime("%H:%M:%S.%f")[:-3]
57
+ with open(_DIAG_LOG, mode, encoding="utf-8") as f:
58
+ f.write(f"[{ts}] {msg}\n")
59
+ except Exception:
60
+ pass
61
+
62
+
63
+ # Win32 MessageBox 标志位与返回值
64
+ _MB_YESNO = 0x04
65
+ _MB_ICONERROR = 0x10
66
+ _MB_ICONWARNING = 0x30
67
+ _MB_ICONINFORMATION = 0x40
68
+ _IDYES = 6
69
+
70
+
71
+ def _msgbox(text: str, title: str = "AScript", style: int = _MB_ICONERROR) -> int:
72
+ """弹出 Windows 原生消息框。"""
73
+ return ctypes.windll.user32.MessageBoxW(0, text, title, style)
74
+
75
+
76
+ def _install_webview2() -> bool:
77
+ """下载并静默安装 WebView2 Runtime。成功返回 True。"""
78
+ try:
79
+ import requests
80
+ bootstrapper = os.path.join(tempfile.gettempdir(), "MicrosoftEdgeWebview2Setup.exe")
81
+ _diag("downloading WebView2 bootstrapper")
82
+ with requests.get(_WEBVIEW2_BOOTSTRAPPER_URL, stream=True, timeout=120) as resp:
83
+ resp.raise_for_status()
84
+ with open(bootstrapper, "wb") as f:
85
+ for chunk in resp.iter_content(chunk_size=1024 * 64):
86
+ f.write(chunk)
87
+ _diag("running WebView2 bootstrapper /silent /install")
88
+ result = subprocess.run([bootstrapper, "/silent", "/install"], timeout=600)
89
+ _diag(f"WebView2 installer returncode={result.returncode}")
90
+ return result.returncode == 0
91
+ except Exception as e:
92
+ _diag(f"WebView2 install failed: {e!r}")
93
+ return False
94
+
95
+
96
+ def _install_webview2_with_ui() -> bool:
97
+ """带进度窗口地安装 WebView2。MS 静默安装器不汇报百分比,
98
+ 故用滚动式(不确定模式)进度条表示"正在进行"。tkinter 不可用时退化为无窗口安装。"""
99
+ state = {"ok": False, "done": False}
100
+
101
+ def worker():
102
+ state["ok"] = _install_webview2()
103
+ state["done"] = True
104
+
105
+ t = threading.Thread(target=worker, daemon=True)
106
+ t.start()
107
+
108
+ try:
109
+ import tkinter as tk
110
+ from tkinter import ttk
111
+
112
+ root = tk.Tk()
113
+ root.title("AScript - 安装运行环境")
114
+ root.resizable(False, False)
115
+ w, h = 400, 140
116
+ sw, sh = root.winfo_screenwidth(), root.winfo_screenheight()
117
+ root.geometry(f"{w}x{h}+{(sw - w) // 2}+{(sh - h) // 2}")
118
+ root.attributes("-topmost", True)
119
+ root.protocol("WM_DELETE_WINDOW", lambda: None) # 安装中禁止关闭
120
+
121
+ tk.Label(root, text="正在下载并安装 Microsoft Edge WebView2 运行时").pack(pady=(26, 4))
122
+ tk.Label(root, text="约需 1-2 分钟,请勿关闭此窗口...", fg="#666").pack()
123
+ bar = ttk.Progressbar(root, mode="indeterminate", length=340)
124
+ bar.pack(pady=18)
125
+ bar.start(15)
126
+
127
+ def poll():
128
+ if state["done"]:
129
+ bar.stop()
130
+ root.destroy()
131
+ else:
132
+ root.after(200, poll)
133
+
134
+ root.after(200, poll)
135
+ root.mainloop()
136
+ except Exception as e:
137
+ _diag(f"progress UI unavailable, installing without it: {e!r}")
138
+ t.join() # 没有 UI 也要等安装完成
139
+
140
+ return state["ok"]
141
+
142
+
143
+ # 注入到页面的桥接代码:定义 callPython 并派发 onPythonReady。
144
+ # 故意使用现代语法(async / ...rest)—— 既是功能需要,也是引擎能力探针:
145
+ # 真正的 WebView2/Chromium 能解析,IE/MSHTML 会抛 SyntaxError。
146
+ # IIFE + callPython 存在性判断 → 多次注入也幂等。
147
+ _INJECTION_CODE = """
148
+ (function () {
149
+ if (!window.callPython) {
150
+ window.callPython = async function (fnName, ...args) {
151
+ return await window.pywebview.api.call_internal(fnName, args);
152
+ };
153
+ try { window.pywebview.api.call_internal('_diag', ['JS: callPython injected']); } catch (e) {}
154
+ }
155
+ if (!window.__ascriptPythonReady) {
156
+ window.__ascriptPythonReady = true;
157
+ window.dispatchEvent(new CustomEvent('onPythonReady'));
158
+ }
159
+ })();
160
+ """
161
+
162
+
163
+ class WebWindow:
164
+ """基于 pywebview 的 Web 窗口基类。"""
165
+
166
+ def __init__(self, html_path: str, project_root: Optional[str] = None, query: Optional[str] = None):
167
+ self.project_root = os.path.abspath(project_root) if project_root else os.getcwd()
168
+ if html_path.startswith(("http://", "https://")):
169
+ self.abs_path = html_path
170
+ else:
171
+ self.abs_path = os.path.abspath(html_path)
172
+ self.query = query.lstrip("?") if query else ""
173
+ self._exposed_functions: Dict[str, Callable] = {}
174
+ self.webview_instance = None
175
+ self._return_value = None
176
+ self._injected = False
177
+ self._engine_error_handled = False
178
+ self._asset_server = None
179
+ self._exclude_from_capture = False
180
+ self._capture_exclusion_active = False
181
+
182
+ def _native_hwnd(self) -> Optional[int]:
183
+ """Return the native top-level HWND after pywebview has created it."""
184
+ if os.name != "nt" or not self.webview_instance:
185
+ return None
186
+ try:
187
+ hwnd = int(self.webview_instance.native.Handle.ToInt64())
188
+ get_ancestor = ctypes.windll.user32.GetAncestor
189
+ get_ancestor.argtypes = (wintypes.HWND, wintypes.UINT)
190
+ get_ancestor.restype = wintypes.HWND
191
+ root_hwnd = get_ancestor(wintypes.HWND(hwnd), 2) # GA_ROOT
192
+ return int(root_hwnd or hwnd)
193
+ except Exception as error:
194
+ _diag(f"native window handle unavailable: {error!r}")
195
+ return None
196
+
197
+ def _retry_capture_exclusion(self) -> None:
198
+ """Retry after WebView2 has finished creating its top-level window."""
199
+ if not self._exclude_from_capture or self._capture_exclusion_active:
200
+ return
201
+
202
+ def apply():
203
+ for delay in (0.05, 0.2, 0.5):
204
+ time.sleep(delay)
205
+ if self.set_capture_excluded(True):
206
+ return
207
+
208
+ threading.Thread(target=apply, daemon=True).start()
209
+
210
+ def set_capture_excluded(self, excluded: bool = True) -> bool:
211
+ """Exclude this SDK-owned window from Windows screen capture.
212
+
213
+ WDA_EXCLUDEFROMCAPTURE leaves the window visible and interactive for the
214
+ user, but removes it from supported desktop-capture APIs. Unlike
215
+ minimizing/hiding, it does not disturb WebView2 focus or composition.
216
+ """
217
+ self._exclude_from_capture = bool(excluded)
218
+ hwnd = self._native_hwnd()
219
+ if hwnd is None:
220
+ return False
221
+ affinity = 0x00000011 if excluded else 0x00000000
222
+ try:
223
+ setter = ctypes.windll.user32.SetWindowDisplayAffinity
224
+ setter.argtypes = (wintypes.HWND, wintypes.DWORD)
225
+ setter.restype = wintypes.BOOL
226
+ success = bool(setter(hwnd, affinity))
227
+ self._capture_exclusion_active = bool(success and excluded)
228
+ _diag(
229
+ f"capture exclusion {'enabled' if excluded else 'disabled'}, "
230
+ f"hwnd={hwnd}, success={success}"
231
+ )
232
+ return success
233
+ except (AttributeError, OSError) as error:
234
+ self._capture_exclusion_active = False
235
+ _diag(f"capture exclusion failed, hwnd={hwnd}: {error!r}")
236
+ return False
237
+
238
+ def _window_url(self) -> str:
239
+ """Return a URL for the configured page, starting a private local server if needed."""
240
+ if self.abs_path.startswith(("http://", "https://")):
241
+ url = self.abs_path
242
+ else:
243
+ if not os.path.isfile(self.abs_path):
244
+ raise FileNotFoundError(f"WebWindow 页面不存在: {self.abs_path}")
245
+
246
+ project_root = os.path.abspath(self.project_root)
247
+ try:
248
+ inside_project = os.path.normcase(os.path.commonpath([project_root, self.abs_path])) == os.path.normcase(project_root)
249
+ except ValueError:
250
+ inside_project = False
251
+ server_root = project_root if inside_project else os.path.dirname(self.abs_path)
252
+ relative_path = os.path.relpath(self.abs_path, server_root).replace(os.sep, "/")
253
+
254
+ handler = partial(_LocalAssetHandler, directory=server_root)
255
+ self._asset_server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
256
+ self._asset_server.daemon_threads = True
257
+ threading.Thread(target=self._asset_server.serve_forever, daemon=True).start()
258
+ host, port = self._asset_server.server_address
259
+ url = f"http://{host}:{port}/{quote(relative_path, safe='/')}"
260
+ _diag(f"local asset server started, root={server_root}, url={url}")
261
+
262
+ if self.query:
263
+ separator = "&" if "?" in url else "?"
264
+ url += separator + self.query
265
+ return url
266
+
267
+ def _stop_asset_server(self):
268
+ server = self._asset_server
269
+ self._asset_server = None
270
+ if server is None:
271
+ return
272
+ try:
273
+ server.shutdown()
274
+ server.server_close()
275
+ _diag("local asset server stopped")
276
+ except Exception as error:
277
+ _diag(f"local asset server stop failed: {error!r}")
278
+
279
+ def expose(self, func: Callable):
280
+ """将 Python 函数暴露给前端 JavaScript 调用。"""
281
+ self._exposed_functions[func.__name__] = func
282
+ return func
283
+
284
+ def _inject(self, source: str):
285
+ """注入 callPython 桥接并派发 onPythonReady。可被多个时机触发,幂等。"""
286
+ if self._engine_error_handled:
287
+ return
288
+ _diag(f"_inject START, source={source}")
289
+ try:
290
+ self.webview_instance.evaluate_js(_INJECTION_CODE)
291
+ self._injected = True
292
+ _diag(f"_inject DONE, source={source}")
293
+ except Exception as e:
294
+ _diag(f"_inject ERROR, source={source}: {e!r}")
295
+ # SyntaxError 是确凿信号:现代引擎绝不会对合法的现代 JS 报 SyntaxError,
296
+ # 只有 IE/MSHTML 才会 → 据此判定引擎不可用。不查注册表,不会误判。
297
+ # 其它异常(多为时机问题)只记录,留给另一个注入时机重试。
298
+ if "SyntaxError" in str(e):
299
+ self._handle_engine_error()
300
+
301
+ def _handle_engine_error(self):
302
+ """检测到当前是 IE/MSHTML 引擎(WebView2 不可用)。
303
+ 现代 JS 在此引擎下无法运行,关闭卡死的窗口并引导用户安装 WebView2。"""
304
+ if self._engine_error_handled:
305
+ return
306
+ self._engine_error_handled = True
307
+ _diag("ENGINE ERROR: modern JS rejected -> WebView2 not in use (MSHTML fallback)")
308
+ # 先隐藏卡死的窗口,但不要 destroy —— destroy 会让 webview.start 返回、
309
+ # 主线程退出,把后面的安装进度窗口一起带走。等安装流程走完再关。
310
+ try:
311
+ self.webview_instance.hide()
312
+ except Exception:
313
+ pass
314
+ choice = _msgbox(
315
+ "AScript 无法启动:界面引擎回退到了过时的 IE 内核,\n"
316
+ "通常是缺少 Microsoft Edge WebView2 运行时。\n\n"
317
+ "是否现在自动下载并安装?(约 1-2 分钟,需联网)",
318
+ "缺少运行环境", _MB_YESNO | _MB_ICONWARNING)
319
+ if choice == _IDYES:
320
+ if _install_webview2_with_ui():
321
+ _msgbox("WebView2 安装完成,请重新启动 AScript。", "AScript", _MB_ICONINFORMATION)
322
+ else:
323
+ _msgbox("自动安装失败,即将打开下载页面,请手动安装后重新启动程序。")
324
+ webbrowser.open(_WEBVIEW2_DOWNLOAD_URL)
325
+ else:
326
+ _msgbox("已取消。请手动安装 WebView2 运行时后重新启动程序:\n" + _WEBVIEW2_DOWNLOAD_URL)
327
+ # 全部处理完,关闭窗口让 webview.start 返回、进程干净退出
328
+ try:
329
+ self.webview_instance.destroy()
330
+ except Exception:
331
+ pass
332
+
333
+ def _bring_to_front(self, keep_topmost: bool = False):
334
+ """Briefly raise the native window so launches from a browser stay visible."""
335
+ if os.name != "nt" or not self.webview_instance:
336
+ return
337
+ hwnd = self._native_hwnd()
338
+ if hwnd is None:
339
+ return
340
+
341
+ user32 = ctypes.windll.user32
342
+ flags = 0x0001 | 0x0002 | 0x0040 # SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW
343
+ try:
344
+ user32.ShowWindow(hwnd, 9) # SW_RESTORE
345
+ user32.SetWindowPos(hwnd, -1, 0, 0, 0, 0, flags) # HWND_TOPMOST
346
+ user32.BringWindowToTop(hwnd)
347
+ user32.SetForegroundWindow(hwnd)
348
+ _diag(f"bring-to-front requested, hwnd={hwnd}")
349
+ except Exception as e:
350
+ _diag(f"bring-to-front failed: {e!r}")
351
+ return
352
+
353
+ if keep_topmost:
354
+ _diag(f"permanent topmost preserved, hwnd={hwnd}")
355
+ return
356
+
357
+ def release_topmost():
358
+ time.sleep(0.8)
359
+ try:
360
+ user32.SetWindowPos(hwnd, -2, 0, 0, 0, 0, flags) # HWND_NOTOPMOST
361
+ _diag(f"temporary topmost released, hwnd={hwnd}")
362
+ except Exception as e:
363
+ _diag(f"release temporary topmost failed: {e!r}")
364
+
365
+ threading.Thread(target=release_topmost, daemon=True).start()
366
+
367
+ def show(
368
+ self,
369
+ title: str = "AScript Window",
370
+ debug=False,
371
+ block: bool = True,
372
+ bring_to_front: bool = True,
373
+ exclude_from_capture: bool = False,
374
+ **kwargs,
375
+ ):
376
+ """创建并显示 Web 窗口。"""
377
+ _diag(f"show() called, html={self.abs_path}, block={block}", reset=True)
378
+ self._exclude_from_capture = bool(exclude_from_capture)
379
+
380
+ class ApiBridge:
381
+ """JavaScript 与 Python 之间的 API 桥接。"""
382
+
383
+ def __init__(self, funcs):
384
+ self._funcs = funcs
385
+
386
+ def call_internal(self, func_name, args):
387
+ _diag(f"call_internal ENTER: {func_name} args={args!r}")
388
+ f = self._funcs.get(func_name)
389
+ if not f:
390
+ _diag(f"call_internal: '{func_name}' NOT FOUND")
391
+ return None
392
+ try:
393
+ result = f(*args)
394
+ _diag(f"call_internal EXIT: {func_name} -> {repr(result)[:200]}")
395
+ return result
396
+ except Exception as e:
397
+ _diag(f"call_internal ERROR: {func_name}: {e!r}")
398
+ print(f"Error calling {func_name}: {e}")
399
+ return None
400
+
401
+ # 诊断用:让 JS 也能往同一份日志里写
402
+ self._exposed_functions.setdefault("_diag", lambda m: _diag(str(m)))
403
+
404
+ window_url = self._window_url()
405
+ self.webview_instance = webview.create_window(
406
+ title=title,
407
+ url=window_url,
408
+ js_api=ApiBridge(self._exposed_functions),
409
+ **kwargs
410
+ )
411
+ _diag("window created")
412
+
413
+ def on_loaded(*args):
414
+ _diag(f"events.loaded fired, args={args!r}")
415
+ if self._exclude_from_capture and not self._capture_exclusion_active:
416
+ self.set_capture_excluded(True)
417
+ self._retry_capture_exclusion()
418
+ self._inject("events.loaded")
419
+
420
+ def on_shown(*args):
421
+ _diag(f"events.shown fired, args={args!r}")
422
+ if self._exclude_from_capture:
423
+ self.set_capture_excluded(True)
424
+ self._retry_capture_exclusion()
425
+ if bring_to_front:
426
+ self._bring_to_front(keep_topmost=bool(kwargs.get("on_top")))
427
+
428
+ def on_start():
429
+ # webview.start 的启动钩子,作为 events.loaded 的兜底注入时机
430
+ _diag("webview.start hook fired")
431
+ self._inject("start-hook")
432
+
433
+ self.webview_instance.events.loaded += on_loaded
434
+ self.webview_instance.events.shown += on_shown
435
+ _diag("events.loaded subscribed")
436
+
437
+ # 强制 edgechromium:装了 WebView2 时确保用它。
438
+ # 但 pywebview 在 WebView2 缺失时会"静默回退"到 MSHTML 且不抛异常,
439
+ # 所以真正的判定靠 _inject 里对真实引擎的运行时探测,而非这里。
440
+ if block:
441
+ try:
442
+ _diag("calling webview.start (block) gui=edgechromium")
443
+ webview.start(on_start, debug=debug, gui='edgechromium')
444
+ _diag("webview.start returned")
445
+ except Exception as e:
446
+ _diag(f"webview.start raised: {e!r}")
447
+ finally:
448
+ self._stop_asset_server()
449
+ return self._return_value
450
+ else:
451
+ def _run():
452
+ try:
453
+ _diag("calling webview.start (thread) gui=edgechromium")
454
+ webview.start(on_start, debug=debug, gui='edgechromium')
455
+ except Exception as e:
456
+ _diag(f"webview.start raised (thread): {e!r}")
457
+ finally:
458
+ self._stop_asset_server()
459
+
460
+ t = threading.Thread(target=_run, daemon=True)
461
+ t.start()
462
+
463
+ def close(self):
464
+ """关闭并销毁 Web 窗口。"""
465
+ if self.webview_instance:
466
+ self.webview_instance.destroy()
File without changes
@@ -0,0 +1,20 @@
1
+ """窗口节点工具模块。
2
+
3
+ 提供与 Windows 窗口枚举和节点操作相关的实用函数。
4
+ """
5
+
6
+ import win32gui
7
+
8
+
9
+ def get_all_window(model: int):
10
+ """获取所有窗口句柄。
11
+
12
+ 根据指定的模式枚举系统中所有顶层窗口。
13
+
14
+ Args:
15
+ model: 枚举模式,用于控制窗口枚举的方式。
16
+
17
+ Returns:
18
+ None: 当前实现未返回值(EnumWindows 缺少回调函数)。
19
+ """
20
+ win32gui.EnumWindows()