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.
- ascript/__init__.py +8 -0
- ascript/hooks/__init__.py +0 -0
- ascript/hooks/hook-ascript.py +2 -0
- ascript/windows/__init__.py +29 -0
- ascript/windows/daos/__init__.py +0 -0
- ascript/windows/daos/screen.py +143 -0
- ascript/windows/hardware/__init__.py +5 -0
- ascript/windows/hardware/keyboard.py +279 -0
- ascript/windows/hardware/logitech_driver.py +179 -0
- ascript/windows/hardware/mouse.py +308 -0
- ascript/windows/license/__init__.py +16 -0
- ascript/windows/license/achttp.cp310-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp311-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp312-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp313-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp314-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp38-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp39-win_amd64.pyd +0 -0
- ascript/windows/media/__init__.py +0 -0
- ascript/windows/resources/__init__.py +0 -0
- ascript/windows/resources/libs/logitech/x64/LogitechGkeyEnginesWrapper.dll +0 -0
- ascript/windows/resources/libs/logitech/x86/LogitechGkeyEnginesWrapper.dll +0 -0
- ascript/windows/resources/templates/__init__.py +0 -0
- ascript/windows/resources/templates/__init__py.win.template +51 -0
- ascript/windows/resources/templates/build.as.win.template +14 -0
- ascript/windows/resources/templates/build.py +71 -0
- ascript/windows/resources/templates/logo_windows.ico +0 -0
- ascript/windows/resources/templates/windows/main.py +50 -0
- ascript/windows/resources/templates/windows/pyproject.toml +44 -0
- ascript/windows/resources/templates/windows/res/img/logo.ico +0 -0
- ascript/windows/screen/__init__.py +13 -0
- ascript/windows/screen/as_cv.py +382 -0
- ascript/windows/screen/canny.py +128 -0
- ascript/windows/screen/code_scanner.py +72 -0
- ascript/windows/screen/color_algorithms.py +277 -0
- ascript/windows/screen/find_colors.py +251 -0
- ascript/windows/screen/find_images.py +217 -0
- ascript/windows/screen/ocr/__init__.py +4 -0
- ascript/windows/screen/ocr/ocr.py +223 -0
- ascript/windows/screen/ocr/rapidocr_hook.py +301 -0
- ascript/windows/screen/ocrx.py +169 -0
- ascript/windows/screen/screen_core.py +139 -0
- ascript/windows/store/__init__.py +21 -0
- ascript/windows/system/__init__.py +8 -0
- ascript/windows/system/device.py +355 -0
- ascript/windows/system/easy_print.py +115 -0
- ascript/windows/system/env.py +492 -0
- ascript/windows/system/exception/__init__.py +1 -0
- ascript/windows/system/key_value.py +145 -0
- ascript/windows/system/pip.py +175 -0
- ascript/windows/system/r.py +165 -0
- ascript/windows/system/runtime.py +62 -0
- ascript/windows/tools/__init__.py +27 -0
- ascript/windows/tools/__main__.py +21 -0
- ascript/windows/tools/action.json +33 -0
- ascript/windows/tools/action.py +168 -0
- ascript/windows/tools/colors_tool.py +391 -0
- ascript/windows/tools/config.py +26 -0
- ascript/windows/tools/dumptool.py +18 -0
- ascript/windows/tools/eel_api.py +495 -0
- ascript/windows/tools/gpplug/__init__.py +0 -0
- ascript/windows/tools/gpplug/plug.py +70 -0
- ascript/windows/tools/mouse_catch.py +168 -0
- ascript/windows/tools/plug_manager.py +38 -0
- ascript/windows/tools/tool_api.py +906 -0
- ascript/windows/tools/tool_window.py +56 -0
- ascript/windows/tools/uia_worker.py +365 -0
- ascript/windows/tools/web/app_store_window.html +481 -0
- ascript/windows/tools/web/colors.html +532 -0
- ascript/windows/tools/web/css/screen.css +1342 -0
- ascript/windows/tools/web/css/viewtree.css +357 -0
- ascript/windows/tools/web/flot_control.html +195 -0
- ascript/windows/tools/web/img/ico/favicon.png +0 -0
- ascript/windows/tools/web/img/ico/paintpalette.png +0 -0
- ascript/windows/tools/web/img/svg/find_colors.svg +1 -0
- ascript/windows/tools/web/img/svg/find_images.svg +1 -0
- ascript/windows/tools/web/img/svg/illustration-6.svg +89 -0
- ascript/windows/tools/web/img/svg/img_default.svg +1 -0
- ascript/windows/tools/web/img/svg/ocr.svg +1 -0
- ascript/windows/tools/web/js/screen/colors.js +466 -0
- ascript/windows/tools/web/js/screen/gp_plug.js +108 -0
- ascript/windows/tools/web/js/screen/image_mouse.js +464 -0
- ascript/windows/tools/web/js/screen/img_view.js +558 -0
- ascript/windows/tools/web/js/screen/plug.js +246 -0
- ascript/windows/tools/web/js/screen/screen.js +690 -0
- ascript/windows/tools/web/js/screen/screen_api.js +778 -0
- ascript/windows/tools/web/js/screen/screen_maker.js +10 -0
- ascript/windows/tools/web/js/utils/map.js +73 -0
- ascript/windows/tools/web/js/utils/upload.js +157 -0
- ascript/windows/tools/web/js/viewtree/niv_js.js +27 -0
- ascript/windows/tools/web/js/viewtree/vtree.js +1510 -0
- ascript/windows/tools/web/libs/bootstrap/css/bootstrap.min.css +6 -0
- ascript/windows/tools/web/libs/bootstrap/js/bootstrap.bundle.min.js +7 -0
- ascript/windows/tools/web/libs/bootstrap-icons/0-circle.svg +4 -0
- ascript/windows/tools/web/libs/bootstrap-icons/0-square.svg +4 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.css +2078 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.json +2052 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.min.css +5 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.scss +2090 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/fonts/bootstrap-icons.woff +0 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/fonts/bootstrap-icons.woff2 +0 -0
- ascript/windows/tools/web/libs/feather-icons/dist/feather.min.js +13 -0
- ascript/windows/tools/web/libs/feather-icons/dist/icons.json +1 -0
- ascript/windows/tools/web/libs/jquery/core.js +399 -0
- ascript/windows/tools/web/libs/jquery/jquery.js +10598 -0
- ascript/windows/tools/web/libs/jquery/jquery.min.js +4 -0
- ascript/windows/tools/web/libs/jquery/jquery.min.js.zip +0 -0
- ascript/windows/tools/web/libs/jquery/jquery.slim.js +8495 -0
- ascript/windows/tools/web/libs/jquery/jquery.slim.min.js +2 -0
- ascript/windows/tools/web/libs/jquery-menu/jquery.contextMenu.js +2134 -0
- ascript/windows/tools/web/libs/jquery-menu/jquery.contextMenu.min.css +16 -0
- ascript/windows/tools/web/libs/jquery-zTree/css/zTreeStyle.css +97 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/1_close.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/1_open.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/2.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/3.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/4.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/5.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/6.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/7.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/8.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/9.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/line_conn.gif +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/loading.gif +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/tree-node-btn.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/zTreeStandard.gif +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/zTreeStandard.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/js/jquery.ztree.all.min.js +3 -0
- ascript/windows/tools/web/libs/treeview/css/jquery.treeview.css +79 -0
- ascript/windows/tools/web/libs/treeview/img/file.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/ico_loading.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/icon4.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/icon5.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/icon6.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/py.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-black-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-black.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-default-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-default.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-famfamfam-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-famfamfam.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-gray-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-gray.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-red-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-red.gif +0 -0
- ascript/windows/tools/web/libs/treeview/js/jquery.treeview.edit.js +37 -0
- ascript/windows/tools/web/libs/treeview/js/jquery.treeview.js +250 -0
- ascript/windows/tools/web/license_window.html +255 -0
- ascript/windows/tools/web/plugs/a.html +653 -0
- ascript/windows/tools/web/plugs/demo.html +52 -0
- ascript/windows/tools/web/plugs/find_colors.html +629 -0
- ascript/windows/tools/web/plugs/find_images.html +699 -0
- ascript/windows/tools/web/plugs/rapidocr.html +954 -0
- ascript/windows/tools/web/vtree.html +496 -0
- ascript/windows/tools/webview/assets/index-HymBPl-I.css +1 -0
- ascript/windows/tools/webview/assets/index-PqA_m8tL.js +17 -0
- ascript/windows/tools/webview/index.html +13 -0
- ascript/windows/ui/__init__.py +6 -0
- ascript/windows/ui/app_store_window.py +272 -0
- ascript/windows/ui/draw_window.py +231 -0
- ascript/windows/ui/flot_test.py +297 -0
- ascript/windows/ui/flot_window.py +488 -0
- ascript/windows/ui/license_window.py +111 -0
- ascript/windows/ui/web_window.py +466 -0
- ascript/windows/utils/__init__.py +0 -0
- ascript/windows/utils/node_utils.py +20 -0
- ascript/windows/utils/screen_utils.py +280 -0
- ascript/windows/window/Selector.py +1015 -0
- ascript/windows/window/Window.py +747 -0
- ascript/windows/window/__init__.py +5 -0
- ascript/windows/window/launcher.py +213 -0
- ascript/windows/window/uia_runtime.py +26 -0
- ascript-1.3.27.dist-info/METADATA +41 -0
- ascript-1.3.27.dist-info/RECORD +177 -0
- ascript-1.3.27.dist-info/WHEEL +5 -0
- ascript-1.3.27.dist-info/entry_points.txt +2 -0
- ascript-1.3.27.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
import threading
|
|
6
|
+
import urllib.request
|
|
7
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Pip:
|
|
11
|
+
"""
|
|
12
|
+
Python 包管理工具类,具备自动择源功能。
|
|
13
|
+
"""
|
|
14
|
+
# 备选镜像源列表
|
|
15
|
+
MIRRORS = {
|
|
16
|
+
"tsinghua": "https://pypi.tuna.tsinghua.edu.cn/simple",
|
|
17
|
+
"aliyun": "https://mirrors.aliyun.com/pypi/simple/",
|
|
18
|
+
"ustc": "https://pypi.mirrors.ustc.edu.cn/simple/",
|
|
19
|
+
"pypi": "https://pypi.org/simple",
|
|
20
|
+
}
|
|
21
|
+
# 兜底源:所有探测都失败时使用,绝不拿慢的官方源兜底
|
|
22
|
+
_FALLBACK_INDEX = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
23
|
+
|
|
24
|
+
# 吞吐探测参数:单源最多下 1.5 秒 或 3MB 即停;连接超时 2 秒
|
|
25
|
+
_PROBE_TIME_BUDGET = 1.5
|
|
26
|
+
_PROBE_SIZE_CAP = 3 * 1024 * 1024
|
|
27
|
+
_PROBE_CONNECT_TIMEOUT = 2.0
|
|
28
|
+
|
|
29
|
+
# 进程内缓存:择源结果复用,避免每装一个包都重新测速
|
|
30
|
+
_cached_index = None
|
|
31
|
+
_cache_lock = threading.Lock()
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _probe_throughput(url: str) -> float:
|
|
35
|
+
"""限量下载该镜像的 simple 索引,返回实测吞吐量(字节/秒)。失败返回 0。
|
|
36
|
+
|
|
37
|
+
测的是真实下载速度而非连接延迟 —— 镜像源常见"连得上但限速",
|
|
38
|
+
延迟低不代表下得快。
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
req = urllib.request.Request(url, headers={"User-Agent": "ascript-pip-probe"})
|
|
42
|
+
start = time.time()
|
|
43
|
+
downloaded = 0
|
|
44
|
+
with urllib.request.urlopen(req, timeout=Pip._PROBE_CONNECT_TIMEOUT) as resp:
|
|
45
|
+
if resp.getcode() != 200:
|
|
46
|
+
return 0.0
|
|
47
|
+
while True:
|
|
48
|
+
chunk = resp.read(64 * 1024)
|
|
49
|
+
if not chunk:
|
|
50
|
+
break
|
|
51
|
+
downloaded += len(chunk)
|
|
52
|
+
if (time.time() - start) >= Pip._PROBE_TIME_BUDGET \
|
|
53
|
+
or downloaded >= Pip._PROBE_SIZE_CAP:
|
|
54
|
+
break
|
|
55
|
+
elapsed = time.time() - start
|
|
56
|
+
if elapsed <= 0 or downloaded == 0:
|
|
57
|
+
return 0.0
|
|
58
|
+
return downloaded / elapsed
|
|
59
|
+
except Exception:
|
|
60
|
+
return 0.0
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def get_best_index() -> str:
|
|
64
|
+
"""并行测各镜像的实测下载吞吐量,返回最快的源。结果进程内缓存。"""
|
|
65
|
+
if Pip._cached_index:
|
|
66
|
+
return Pip._cached_index
|
|
67
|
+
|
|
68
|
+
with Pip._cache_lock:
|
|
69
|
+
if Pip._cached_index: # 双重检查,避免并发安装时重复测速
|
|
70
|
+
return Pip._cached_index
|
|
71
|
+
|
|
72
|
+
# 预填所有键:worker 只更新值、不新增键,这样万一 join 超时后掉队
|
|
73
|
+
# 线程仍在写,下面 max() 遍历也不会撞上"字典大小被改"。
|
|
74
|
+
results = {name: 0.0 for name in Pip.MIRRORS}
|
|
75
|
+
|
|
76
|
+
def worker(name, url):
|
|
77
|
+
results[name] = Pip._probe_throughput(url)
|
|
78
|
+
|
|
79
|
+
threads = [
|
|
80
|
+
threading.Thread(target=worker, args=(n, u), daemon=True)
|
|
81
|
+
for n, u in Pip.MIRRORS.items()
|
|
82
|
+
]
|
|
83
|
+
for t in threads:
|
|
84
|
+
t.start()
|
|
85
|
+
# join 超时仅作兜底(探测线程内部已有超时,正常 3.5s 内结束);
|
|
86
|
+
# 必须长于单次探测最坏耗时 = 连接超时 + 下载预算。
|
|
87
|
+
for t in threads:
|
|
88
|
+
t.join(timeout=Pip._PROBE_CONNECT_TIMEOUT + Pip._PROBE_TIME_BUDGET + 1.0)
|
|
89
|
+
|
|
90
|
+
best = max(results, key=results.get)
|
|
91
|
+
Pip._cached_index = Pip.MIRRORS[best] if results[best] > 0 \
|
|
92
|
+
else Pip._FALLBACK_INDEX
|
|
93
|
+
return Pip._cached_index
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def is_installed(package_spec: str) -> bool:
|
|
97
|
+
"""检查指定包是否已安装。
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
package_spec: 包名或带版本约束的包规范,如 "requests" 或 "requests>=2.28"。
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
bool: 已安装返回 True,未安装返回 False。
|
|
104
|
+
"""
|
|
105
|
+
package_name = re.split(r'[<>=!]', package_spec)[0].strip()
|
|
106
|
+
try:
|
|
107
|
+
version(package_name)
|
|
108
|
+
return True
|
|
109
|
+
except PackageNotFoundError:
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def install(package_spec: str, upgrade: bool = False, index_url: str = None) -> bool:
|
|
114
|
+
"""安装 Python 包。
|
|
115
|
+
|
|
116
|
+
如果未指定 index_url,则自动测速选择最快的镜像源进行安装。
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
package_spec: 包名或带版本约束的包规范,如 "requests>=2.28"。
|
|
120
|
+
upgrade: 是否升级到最新版本,默认 False。
|
|
121
|
+
index_url: 指定的 PyPI 镜像源 URL,默认 None(自动择源)。
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
bool: 安装成功返回 True,失败返回 False。
|
|
125
|
+
"""
|
|
126
|
+
# 自动择源逻辑
|
|
127
|
+
target_index = index_url if index_url else Pip.get_best_index()
|
|
128
|
+
|
|
129
|
+
command = [sys.executable, "-m", "pip", "install", package_spec]
|
|
130
|
+
|
|
131
|
+
if target_index:
|
|
132
|
+
command.extend(["-i", target_index])
|
|
133
|
+
if target_index.startswith("http://"):
|
|
134
|
+
host = target_index.split("/")[2].split(":")[0]
|
|
135
|
+
command.extend(["--trusted-host", host])
|
|
136
|
+
|
|
137
|
+
if upgrade:
|
|
138
|
+
command.append("--upgrade")
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
subprocess.check_call(command)
|
|
142
|
+
return True
|
|
143
|
+
except subprocess.CalledProcessError:
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def ensure(package_spec: str, index_url: str = None) -> bool:
|
|
148
|
+
"""确保指定包已安装,未安装则自动安装。
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
package_spec: 包名或带版本约束的包规范。
|
|
152
|
+
index_url: 指定的 PyPI 镜像源 URL,默认 None(自动择源)。
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
bool: 包已存在或安装成功返回 True,安装失败返回 False。
|
|
156
|
+
"""
|
|
157
|
+
if Pip.is_installed(package_spec):
|
|
158
|
+
return True
|
|
159
|
+
return Pip.install(package_spec, index_url=index_url)
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def remove(package_name: str) -> bool:
|
|
163
|
+
"""卸载指定的 Python 包。
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
package_name: 要卸载的包名。
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
bool: 卸载成功返回 True,失败返回 False。
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
subprocess.check_call([sys.executable, "-m", "pip", "uninstall", package_name, "-y"])
|
|
173
|
+
return True
|
|
174
|
+
except subprocess.CalledProcessError:
|
|
175
|
+
return False
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import ascript # 必须导入你的包以定位其物理位置
|
|
5
|
+
import sys
|
|
6
|
+
import ctypes
|
|
7
|
+
|
|
8
|
+
class R:
|
|
9
|
+
"""资源路径解析器。
|
|
10
|
+
|
|
11
|
+
提供统一的资源文件路径解析能力,自动适配开发环境和打包环境
|
|
12
|
+
(支持 PyInstaller 和 Nuitka)。分为两类接口:
|
|
13
|
+
- 开发者接口(ui/img/res):锚定工程根目录下的 res/ 文件夹。
|
|
14
|
+
- 内部接口(internal):锚定 ascript 包的安装目录。
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
_BASE_DIR: 缓存的工程根目录路径。
|
|
18
|
+
_PKG_ROOT: ascript 包的物理安装根目录。
|
|
19
|
+
"""
|
|
20
|
+
_BASE_DIR = None
|
|
21
|
+
# 静态定位 ascript 包的物理根目录 (site-packages/ascript)
|
|
22
|
+
_PKG_ROOT = Path(os.path.dirname(os.path.abspath(ascript.__file__))).resolve()
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def get_root(cls) -> Path:
|
|
26
|
+
"""获取工程根目录或打包后的解压临时目录。
|
|
27
|
+
|
|
28
|
+
自动检测运行环境:
|
|
29
|
+
- Nuitka 打包:使用 NUITKA_ONEFILE_PARENT 环境变量。
|
|
30
|
+
- PyInstaller 打包:使用 sys._MEIPASS。
|
|
31
|
+
- 开发环境:使用 main.py 所在目录。
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Path: 工程根目录的绝对路径。
|
|
35
|
+
"""
|
|
36
|
+
if cls._BASE_DIR is not None:
|
|
37
|
+
return cls._BASE_DIR
|
|
38
|
+
|
|
39
|
+
if getattr(sys, 'frozen', False):
|
|
40
|
+
# Nuitka 优先环境变量
|
|
41
|
+
nuitka_tmp = os.environ.get('NUITKA_ONEFILE_PARENT')
|
|
42
|
+
if nuitka_tmp:
|
|
43
|
+
cls._BASE_DIR = Path(nuitka_tmp).resolve()
|
|
44
|
+
elif hasattr(sys, '_MEIPASS'):
|
|
45
|
+
cls._BASE_DIR = Path(sys._MEIPASS).resolve()
|
|
46
|
+
else:
|
|
47
|
+
cls._BASE_DIR = Path(sys.executable).resolve().parent
|
|
48
|
+
else:
|
|
49
|
+
# 开发环境:锚定 main.py 所在目录
|
|
50
|
+
main_file = sys.argv[0]
|
|
51
|
+
if main_file:
|
|
52
|
+
cls._BASE_DIR = Path(main_file).resolve().parent
|
|
53
|
+
else:
|
|
54
|
+
cls._BASE_DIR = Path.cwd().resolve()
|
|
55
|
+
return cls._BASE_DIR
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def _build_path(cls, base: Path, *sub_paths) -> str:
|
|
59
|
+
"""通用路径构建方法。
|
|
60
|
+
|
|
61
|
+
拼接基准目录和子路径,路径不存在时输出警告。
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
base: 基准目录路径。
|
|
65
|
+
*sub_paths: 子路径片段,依次拼接。
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
str: 完整的文件路径字符串。
|
|
69
|
+
"""
|
|
70
|
+
full_path = base.joinpath(*sub_paths)
|
|
71
|
+
if not full_path.exists():
|
|
72
|
+
# 这里的警告非常有意义,能帮开发者快速定位是缺文件还是路走错了
|
|
73
|
+
print(f"[AScript R] 找不到路径: {full_path}")
|
|
74
|
+
print(f"[AScript R] 当前基准目录为: {base}")
|
|
75
|
+
return str(full_path)
|
|
76
|
+
|
|
77
|
+
# --- 1. 开发者使用的接口 (锚定工程根目录/res) ---
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def ui(cls, path: str) -> str:
|
|
81
|
+
"""获取工程 res/ui/ 目录下的 UI 资源文件路径。
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
path: 相对于 res/ui/ 的文件路径。
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
str: UI 资源文件的绝对路径。
|
|
88
|
+
"""
|
|
89
|
+
return cls._build_path(cls.get_root(), "res", "ui", path)
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def img(cls, path: str) -> str:
|
|
93
|
+
"""获取工程 res/img/ 目录下的图片资源文件路径。
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
path: 相对于 res/img/ 的文件路径。
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
str: 图片资源文件的绝对路径。
|
|
100
|
+
"""
|
|
101
|
+
return cls._build_path(cls.get_root(), "res", "img", path)
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def res(cls, path: str) -> str:
|
|
105
|
+
"""获取工程 res/ 目录下的通用资源文件路径。
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
path: 相对于 res/ 的文件路径。
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
str: 资源文件的绝对路径。
|
|
112
|
+
"""
|
|
113
|
+
return cls._build_path(cls.get_root(), "res", path)
|
|
114
|
+
|
|
115
|
+
# --- 2. ascript 内部使用的接口 (双重查找逻辑) ---
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def internal(cls, *sub_paths) -> str:
|
|
119
|
+
"""查找 ascript 包内部资源文件路径。
|
|
120
|
+
|
|
121
|
+
打包环境下优先在临时解压目录查找,未找到或开发环境下
|
|
122
|
+
回退到包安装目录(site-packages)查找。
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
*sub_paths: 相对于 ascript 包根目录的子路径片段。
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
str: 内部资源文件的绝对路径。
|
|
129
|
+
"""
|
|
130
|
+
# A. 打包环境下的尝试 (寻找临时目录/ascript/xxx)
|
|
131
|
+
if getattr(sys, 'frozen', False):
|
|
132
|
+
# 注意:这里 sub_paths 前面通常要带上 "ascript"
|
|
133
|
+
# 因为 Nuitka 会把整个包结构解压出来
|
|
134
|
+
bundle_path = cls.get_root().joinpath("ascript", *sub_paths)
|
|
135
|
+
if bundle_path.exists():
|
|
136
|
+
return str(bundle_path)
|
|
137
|
+
|
|
138
|
+
# B. 开发环境或兜底 (寻找 site-packages/ascript/xxx)
|
|
139
|
+
return cls._build_path(cls._PKG_ROOT, *sub_paths)
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def is_admin(cls):
|
|
143
|
+
"""判断当前进程是否拥有管理员权限。
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
bool: 拥有管理员权限返回 True,否则返回 False。
|
|
147
|
+
"""
|
|
148
|
+
try:
|
|
149
|
+
return ctypes.windll.shell32.IsUserAnAdmin()
|
|
150
|
+
except:
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def run_as_admin(cls):
|
|
155
|
+
"""以管理员权限重新启动当前程序。
|
|
156
|
+
|
|
157
|
+
如果当前进程不具有管理员权限,将通过 Windows UAC 弹窗
|
|
158
|
+
请求提升权限并重新启动程序,当前进程随即退出。
|
|
159
|
+
"""
|
|
160
|
+
if not cls.is_admin():
|
|
161
|
+
# ShellExecuteEx 会触发系统 UAC 弹窗
|
|
162
|
+
ctypes.windll.shell32.ShellExecuteW(
|
|
163
|
+
None, "runas", sys.executable, " ".join(sys.argv), None, 1
|
|
164
|
+
)
|
|
165
|
+
sys.exit()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import signal
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Runtime:
|
|
7
|
+
"""程序运行时生命周期管理器。
|
|
8
|
+
|
|
9
|
+
提供全局事件循环机制,使主线程保持运行状态直到收到停止信号。
|
|
10
|
+
适用于需要主线程驻留的场景(如 GUI 应用、后台服务等)。
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
_stop_event: 线程事件对象,用于控制运行/停止状态。
|
|
14
|
+
"""
|
|
15
|
+
# 静态变量,用于控制生命周期
|
|
16
|
+
_stop_event = threading.Event()
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def wait():
|
|
20
|
+
"""启动全局事件循环,阻塞主线程直到收到停止信号。
|
|
21
|
+
|
|
22
|
+
主线程会停留在此方法中,直到调用 Runtime.stop() 或
|
|
23
|
+
接收到 SIGINT/SIGTERM 信号(如 Ctrl+C)才会继续向下运行。
|
|
24
|
+
支持优雅的信号处理和键盘中断。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# 优雅处理 Ctrl+C 信号
|
|
28
|
+
def _handle_signal(signum, frame):
|
|
29
|
+
Runtime.stop()
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
signal.signal(signal.SIGINT, _handle_signal)
|
|
33
|
+
signal.signal(signal.SIGTERM, _handle_signal)
|
|
34
|
+
except (ValueError, AttributeError):
|
|
35
|
+
# 某些环境(如非主线程)不支持信号注册
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
print("[System] Runtime 正在等待信号,程序不会退出...")
|
|
39
|
+
|
|
40
|
+
# 核心逻辑:无限等待。
|
|
41
|
+
# wait(timeout) 可以让主线程有机会处理系统中断,比 wait() 更推荐
|
|
42
|
+
try:
|
|
43
|
+
while not Runtime._stop_event.is_set():
|
|
44
|
+
Runtime._stop_event.wait(timeout=0.1)
|
|
45
|
+
except KeyboardInterrupt:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
print("[System] Runtime 已停止。")
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def stop():
|
|
52
|
+
"""停止全局事件循环,解除 Runtime.wait() 的阻塞状态。
|
|
53
|
+
|
|
54
|
+
调用后主线程将从 wait() 方法返回并继续执行后续代码。
|
|
55
|
+
"""
|
|
56
|
+
Runtime._stop_event.set()
|
|
57
|
+
|
|
58
|
+
# --- 调用示范 ---
|
|
59
|
+
# win = WebWindow("a.html")
|
|
60
|
+
# win.show(block=False) # 窗口弹出,主线程不被 block
|
|
61
|
+
# ... 执行其他初始化 ...
|
|
62
|
+
# Runtime.wait() # 这里才是真正的驻留逻辑
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Windows developer tools.
|
|
2
|
+
|
|
3
|
+
Heavy legacy modules are deliberately not imported here. Importing this package is
|
|
4
|
+
now safe in environments that only use the WebView2 tools.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import importlib
|
|
8
|
+
|
|
9
|
+
from .tool_window import SDKToolWindow, start_colors as _start_colors, start_inspector, start_tool
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"SDKToolWindow", "start_tool", "start_inspector", "start_colors",
|
|
13
|
+
"dumptool", "colors_tool", "action", "MouseCatch",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def start_colors(*args, **kwargs):
|
|
18
|
+
return _start_colors(*args, **kwargs)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def __getattr__(name):
|
|
22
|
+
if name in {"dumptool", "colors_tool", "action"}:
|
|
23
|
+
return importlib.import_module(f"{__name__}.{name}")
|
|
24
|
+
if name == "MouseCatch":
|
|
25
|
+
from .mouse_catch import MouseCatch
|
|
26
|
+
return MouseCatch
|
|
27
|
+
raise AttributeError(name)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import multiprocessing
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
parser = argparse.ArgumentParser(description="AScript Windows SDK tools")
|
|
7
|
+
parser.add_argument("tool", choices=("inspector", "colors"))
|
|
8
|
+
parser.add_argument("--hwnd", type=int)
|
|
9
|
+
parser.add_argument("--debug", action="store_true")
|
|
10
|
+
args = parser.parse_args()
|
|
11
|
+
if args.tool == "colors":
|
|
12
|
+
from .tool_window import start_colors
|
|
13
|
+
start_colors(hwnd=args.hwnd)
|
|
14
|
+
else:
|
|
15
|
+
from .tool_window import start_tool
|
|
16
|
+
start_tool(args.tool, hwnd=args.hwnd, debug=args.debug)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
multiprocessing.freeze_support()
|
|
21
|
+
main()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "动态工具",
|
|
4
|
+
"code": null,
|
|
5
|
+
"type": "group",
|
|
6
|
+
"children": [
|
|
7
|
+
{
|
|
8
|
+
"name": "坐标捕获",
|
|
9
|
+
"code": "from ascript.windows.tools import MouseCatch;MouseCatch().run()",
|
|
10
|
+
"type": "action",
|
|
11
|
+
"children": []
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": null,
|
|
15
|
+
"code": null,
|
|
16
|
+
"type": "separator",
|
|
17
|
+
"children": []
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "控件检索",
|
|
21
|
+
"code": "from ascript.windows.tools import dumptool;dumptool.start_vtree()",
|
|
22
|
+
"type": "action",
|
|
23
|
+
"children": []
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "图形处理",
|
|
27
|
+
"code": "from ascript.windows.tools import start_colors;start_colors()",
|
|
28
|
+
"type": "action",
|
|
29
|
+
"children": []
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import webview
|
|
2
|
+
import os
|
|
3
|
+
import threading
|
|
4
|
+
import ctypes
|
|
5
|
+
import time
|
|
6
|
+
import sys
|
|
7
|
+
import win32gui
|
|
8
|
+
import win32con
|
|
9
|
+
import win32clipboard
|
|
10
|
+
from ctypes import windll, byref, sizeof
|
|
11
|
+
from ctypes.wintypes import RECT, MSG
|
|
12
|
+
|
|
13
|
+
# 设置 AppID 以便在任务栏独立显示(虽然在 .py 环境下图标可能仍是 Python)
|
|
14
|
+
try:
|
|
15
|
+
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(u'ascript.coordinate.picker.v6')
|
|
16
|
+
except:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
# DPI 高清适配
|
|
20
|
+
try:
|
|
21
|
+
windll.shcore.SetProcessDpiAwareness(1)
|
|
22
|
+
except:
|
|
23
|
+
windll.user32.SetProcessDPIAware()
|
|
24
|
+
|
|
25
|
+
HTML_CODE = """
|
|
26
|
+
<!DOCTYPE html>
|
|
27
|
+
<html>
|
|
28
|
+
<head>
|
|
29
|
+
<style>
|
|
30
|
+
body { margin: 0; padding: 12px; font-family: 'Segoe UI', sans-serif; background: #000; color: #eee; overflow: hidden; user-select: none; }
|
|
31
|
+
.box { border: 1px solid #333; border-radius: 8px; background: #111; padding: 12px; margin-top: 5px; }
|
|
32
|
+
.row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
|
33
|
+
.coord-val { font-size: 18px; font-weight: bold; font-family: 'Consolas'; color: #00bcd4; }
|
|
34
|
+
#history { height: 280px; overflow-y: auto; margin-top: 10px; border-top: 1px solid #222; padding-top: 5px; }
|
|
35
|
+
.item { background: #1a1a1a; padding: 8px; border-radius: 4px; margin-bottom: 5px; font-size: 12px; cursor: pointer; border-left: 2px solid #00bcd4; }
|
|
36
|
+
.item:hover { background: #222; }
|
|
37
|
+
</style>
|
|
38
|
+
</head>
|
|
39
|
+
<body>
|
|
40
|
+
<div style="font-size:11px; color:#555; margin-bottom:5px;">Shift + A 记录坐标</div>
|
|
41
|
+
<div class="box">
|
|
42
|
+
<div class="row">
|
|
43
|
+
<label><input type="radio" name="m" value="abs" checked> 屏幕</label>
|
|
44
|
+
<span id="a-v" class="coord-val">0, 0</span>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="row">
|
|
47
|
+
<label><input type="radio" name="m" value="rel"> 窗口</label>
|
|
48
|
+
<span id="r-v" class="coord-val" style="color:#4caf50;">0, 0</span>
|
|
49
|
+
</div>
|
|
50
|
+
<div id="w-t" style="font-size:11px; color:#666; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">窗口: -</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div id="history"></div>
|
|
53
|
+
<script>
|
|
54
|
+
function updateUI(a, r, w) {
|
|
55
|
+
document.getElementById('a-v').innerText = a;
|
|
56
|
+
document.getElementById('r-v').innerText = r;
|
|
57
|
+
document.getElementById('w-t').innerText = "窗口: " + w;
|
|
58
|
+
}
|
|
59
|
+
function getMode() { return document.querySelector('input[name="m"]:checked').value; }
|
|
60
|
+
function addHistory(v) {
|
|
61
|
+
const l = document.getElementById('history');
|
|
62
|
+
const d = document.createElement('div');
|
|
63
|
+
d.className = 'item'; d.innerText = "复制: " + v;
|
|
64
|
+
d.onclick = () => window.pywebview.api.copy(v);
|
|
65
|
+
l.insertBefore(d, l.firstChild);
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ProApi:
|
|
74
|
+
"""剪贴板操作代理类,提供将文本复制到系统剪贴板的功能。"""
|
|
75
|
+
|
|
76
|
+
def copy(self, text):
|
|
77
|
+
"""将指定文本复制到系统剪贴板。
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
text: 要复制到剪贴板的文本内容,会自动转换为字符串。
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
win32clipboard.OpenClipboard()
|
|
84
|
+
win32clipboard.EmptyClipboard()
|
|
85
|
+
win32clipboard.SetClipboardText(str(text))
|
|
86
|
+
win32clipboard.CloseClipboard()
|
|
87
|
+
except:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class AScriptApp:
|
|
92
|
+
"""AScript 坐标捕获工具应用程序。
|
|
93
|
+
|
|
94
|
+
提供一个置顶小窗口,实时显示鼠标的屏幕坐标和窗口相对坐标,
|
|
95
|
+
支持通过 Shift+A 热键记录坐标并复制到剪贴板。
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(self):
|
|
99
|
+
"""初始化坐标捕获工具。
|
|
100
|
+
|
|
101
|
+
设置应用图标路径、窗口标题,并初始化运行状态。
|
|
102
|
+
自动适配打包环境(PyInstaller)和开发环境的资源路径。
|
|
103
|
+
"""
|
|
104
|
+
# 路径逻辑 [cite: 2026-01-20]
|
|
105
|
+
base = sys._MEIPASS if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__))
|
|
106
|
+
self.icon_path = os.path.normpath(os.path.join(base, "web", "img", "ico", "favicon.png"))
|
|
107
|
+
self.title = "AScript 坐标捕获工具"
|
|
108
|
+
self.window = None
|
|
109
|
+
self.running = True
|
|
110
|
+
|
|
111
|
+
def _worker(self):
|
|
112
|
+
"""后台工作线程,持续追踪鼠标坐标并监听热键事件。
|
|
113
|
+
|
|
114
|
+
注册 Shift+A 全局热键,循环获取鼠标位置信息(屏幕坐标和窗口相对坐标),
|
|
115
|
+
实时更新前端 UI 显示。当热键触发时,根据当前模式(屏幕/窗口)将坐标
|
|
116
|
+
复制到剪贴板并添加到历史记录。
|
|
117
|
+
"""
|
|
118
|
+
hotkey_id = 1
|
|
119
|
+
# --- 修改处:将 MOD_ALT 改为 MOD_SHIFT ---
|
|
120
|
+
windll.user32.RegisterHotKey(None, hotkey_id, win32con.MOD_SHIFT, ord('A'))
|
|
121
|
+
|
|
122
|
+
while self.running:
|
|
123
|
+
try:
|
|
124
|
+
x, y = win32gui.GetCursorPos()
|
|
125
|
+
raw_hwnd = win32gui.WindowFromPoint((x, y))
|
|
126
|
+
root_hwnd = win32gui.GetAncestor(raw_hwnd, win32con.GA_ROOT)
|
|
127
|
+
title = win32gui.GetWindowText(root_hwnd) or "Unknown"
|
|
128
|
+
rect = RECT()
|
|
129
|
+
windll.dwmapi.DwmGetWindowAttribute(root_hwnd, 9, byref(rect), sizeof(rect))
|
|
130
|
+
rx, ry = x - rect.left, y - rect.top
|
|
131
|
+
|
|
132
|
+
if self.window:
|
|
133
|
+
try:
|
|
134
|
+
self.window.evaluate_js(f"updateUI('{x}, {y}', '{rx}, {ry}', `{title}`)")
|
|
135
|
+
except:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
# 监听热键消息
|
|
139
|
+
msg = MSG()
|
|
140
|
+
if windll.user32.PeekMessageW(byref(msg), 0, 0x0312, 0x0312, 1):
|
|
141
|
+
mode = self.window.evaluate_js("getMode()")
|
|
142
|
+
res = f"{x}, {y}" if mode == "abs" else f"{rx}, {ry}"
|
|
143
|
+
ProApi().copy(res)
|
|
144
|
+
self.window.evaluate_js(f"addHistory('{res}')")
|
|
145
|
+
except:
|
|
146
|
+
pass
|
|
147
|
+
time.sleep(0.05)
|
|
148
|
+
|
|
149
|
+
def run(self):
|
|
150
|
+
"""启动坐标捕获工具应用。
|
|
151
|
+
|
|
152
|
+
创建 webview 窗口并启动后台工作线程,使用 Edge Chromium 内核渲染界面。
|
|
153
|
+
窗口关闭后自动停止后台线程。
|
|
154
|
+
"""
|
|
155
|
+
self.window = webview.create_window(
|
|
156
|
+
self.title, html=HTML_CODE, js_api=ProApi(),
|
|
157
|
+
width=340, height=520, on_top=True
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
threading.Thread(target=self._worker, daemon=True).start()
|
|
161
|
+
|
|
162
|
+
# 启动。注意:此时左上角可能会变,但任务栏在 .py 环境下极难改变
|
|
163
|
+
webview.start(icon=self.icon_path, gui='edgechromium')
|
|
164
|
+
self.running = False
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
AScriptApp().run()
|