diff --git a/_build/CHANGELOG.md b/_build/CHANGELOG.md index 97a9ab8..2d4880e 100644 --- a/_build/CHANGELOG.md +++ b/_build/CHANGELOG.md @@ -1,3 +1,33 @@ +## 2.0.0 + +- **安装时就能设中转服务器**:安装向导那一页现在是两个输入框 —— + **配对码**和**中转服务器**。装的时候是"第一次设置",那时候最该一次 + 把地址填好,不然装完还得去双击 exe 找设置窗口,很容易漏。 + 只填域名就行(自动补 `https://`),留空 = 只走局域网。 + 批量部署同样支持:`/VERYSILENT /CODE=246810 /RELAY=relay.你的域名` + +- **手表端新增「设置」页**:以前设置散在首页(只有一个"中转设置"按钮), + 现在统一收进来,在里面调: + - **中转服务器**(填写 / 一键清空) + - **记住的配对码**(显示 / 清除) + - **已配对的电脑**(看数量 / 清空) + - **关于**(版本号 + 连接顺序说明) + +- **中转地址改成手动填,默认留空**:以前有一版把作者的服务器地址写死成 + 默认值,第一次运行就存进了偏好设置,装新版本也不会清 —— + 看起来就像"默认还是别人的中转站"。现在默认是空的, + 并且会做**一次性迁移**把那个旧的内置地址清掉 + (只清完全相等的,你自己填的不会动)。 + +- **修「局域网连上后要推出去点设备才能控制」**:手表上有四个页面, + 切换时靠手工藏上一页,新加的「局域网连接」页漏藏了 —— + 所以从它连上之后控制面板其实已经显示,却被盖住看不见。 + +- **修「双击 exe 没反应」**:装完之后双击现在会打开设置窗口。 + +- 新增**版本号一致性检查**和**中转地址默认值检查**两道断言, + 防止这类"漏改一处"的问题再出现。 + ## 1.9.0 - **被控端终于有设置窗口了**:装完之后双击 `ykxmz.exe` 就能打开,里面可以改 diff --git a/_build/_cmsg_200.txt b/_build/_cmsg_200.txt new file mode 100644 index 0000000..6a6e123 --- /dev/null +++ b/_build/_cmsg_200.txt @@ -0,0 +1,77 @@ +2.0.0:安装时可设中转服务器 + 手表端新增设置页 + 中转默认留空 + +按用户定的版本规矩,这一轮既有新内容又有重构,所以升第二位: +1.9.0 -> 2.0.0(最后一位清 0)。 + +== 一、安装时就能设中转服务器(用户要求)== + +安装向导那一页现在是两个输入框:配对码 + 中转服务器。 +装的时候是"第一次设置",最该一次把地址填好,不然装完还得去 +双击 exe 找设置窗口,很容易漏。 + +只填域名就行(自动补 https://),留空 = 只走局域网。 +批量静默部署同样支持: + ykxmz-setup-2.0.0.exe /VERYSILENT /CODE=246810 /RELAY=relay.你的域名 + +为此新增了一个 --relay-url 参数: + --relay 连接一次、打印状态、然后退出(人工排查用) + --relay-url 只写进配置,不连接、不退出(安装流程用) +用错那个的话,安装包的命令链会在中间就 return,后面的 --silent +根本跑不到 —— 所以必须分开。 + +== 二、手表端新增「设置」页(用户要求)== + +用户原话:"安卓端加一个设置,各种设置就在这里面调"。 +以前设置散在首页(只有一个"中转设置"按钮),现在统一收进设置页: + * 中转服务器(填写 / 一键清空) + * 记住的配对码(显示 / 清除) + * 已配对的电脑(看数量 / 清空,带二次确认) + * 关于(版本号从 APK 读,不会和代码对不上) + +== 三、中转地址默认留空 + 一次性迁移(用户报的问题)== + +用户原话:"安卓端为什么还是默认填的我的中转站,以后改成手动填"。 + +代码里的默认值早就是空的了,但用户看到的还是那个地址 —— +因为**老版本 1.5.0 把作者的域名硬编码成了默认值**,第一次运行就 +存进了 SharedPreferences。装新 APK 不会清偏好,所以那个值一直留着。 +用户其实从来没"选"过这个地址,是程序硬塞的。 + +所以加了 migrateLegacyRelay():存的值如果正好等于当时内置的域名, +就当成没设过、清掉。只清完全相等的,用户手填的不会动。 + +== 四、两个测试自身的毛病 == + +* test_lan_optimize 那条"没有把旧公网域名当默认值"太粗 —— + 它查的是源码里任何位置,于是迁移注释里引用那行老代码就把它判红了。 + 改成先剥掉注释再查(断言要测的是"有没有把它当默认值")。 + +* test_image_modes 偶发红:它开真实 Tk 全屏窗口,在全量套件里跑 + 偶尔起不来(单独跑 19/19 全过)。加了有界重试。 + 偶发红的测试会让"全量通过"失去意义。 + +== 五、新增防复发断言 == + +_tests/test_relay_default_blank.py(15 项,回归阶段 3a3b): + * 默认值必须是空串 + * 除了迁移清单,源码里不许有写死的中转域名 + * 迁移必须真的被调用(不是只定义) + * 迁移必须是 remove,不能换成另一个硬编码值 + * Windows 端的默认也得是空 + +理由:用户说过"这个中转站我就自己用,不对外" —— 值得用断言钉死。 + +== 六、顺手修的 == + +* release_all.py 崩在 Gitee 那一步:Gitee 的 + GET /releases/tags/{tag} 在"标签存在、但还没发行版"时返回的是 + **HTTP 200 + JSON null**(不是 404),于是 .get("id") 直接 + AttributeError。发 1.9.0 时踩到,已改成容忍 None。 + +* installer/build_installer.py 里 license-combined.txt 是从 + docs/ 三份文档重新生成的,所以光改生成物没用(下次构建又变回去)。 + 改成在 build_license() 里统一注入当前版本。 + +* ExE 文件属性里的版本(version_info.txt)从 1.7.1 起就一直没跟上。 + +全量回归 43 个阶段全部通过。 diff --git a/_build/add_watch_settings.py b/_build/add_watch_settings.py new file mode 100644 index 0000000..df8671d --- /dev/null +++ b/_build/add_watch_settings.py @@ -0,0 +1,401 @@ +# -*- coding: utf-8 -*- +r'''安卓端:中转地址改成"手动填" + 加一个设置页。 + +== 用户报的问题 == + +「安卓端为什么还是默认填的我的中转站,以后改成手动填, + 安卓端加一个设置,各种设置就在这里面调」 + +== 根因 == + +代码里的默认值**已经改成空**了: + + private static final String DEFAULT_RELAY_URL = ""; + +但你手表上看到的还是 `https://relay.xxmxxs.top` —— 因为 +**老版本 1.5.0 把作者的域名硬编码成默认值**(git 历史里查到了): + + private static final String DEFAULT_RELAY_URL = "https://relay.xxmxxs.top"; + +第一次运行它就 `setRelayUrl(默认值)` 存进了 SharedPreferences。 +装新 APK **不会清偏好设置**,所以那个值一直留着。 + +**用户从来没"选"过这个地址** —— 是程序硬塞的。所以正确做法是 +**一次性迁移**:如果存的就是当时内置的那个域名,当成没设过,清掉。 + +== 做的事 == + +1. 迁移:把老版本硬塞的那个域名从偏好里清掉 +2. 新增「设置」页,把散落的设置收进来: + * 中转服务器(手动填 / 清空) + * 记住的配对码(看 / 清) + * 已配对设备(看数量 / 清空) + * 关于(版本号、连接方式说明) +3. 首页那个「中转设置(跨网络用)」按钮改成「设置」,进设置页 +4. settingsPanel 纳入 showOnly(),避免再出现"面板漏藏"那类问题 +''' +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +SRC = (ROOT / "android" / "src" / "com" / "lanremote" / "watch" + / "MainActivity.java") + + +# ====================================================================== +# 1. 迁移 + 面板字段 +# ====================================================================== +MIGRATION = ''' /** + * 老版本硬塞进来的中转地址,要清掉。 + * + *

1.5.0 那版把作者的域名写死成默认值: + *

+     *     private static final String DEFAULT_RELAY_URL = "https://relay.xxmxxs.top";
+     * 
+ * 而且第一次运行就存进了 SharedPreferences。装新 APK 不会清偏好, + * 所以用户会看到「默认还是那个中转站」—— 其实他从来没选过。 + * + *

现在默认值是空串(跨网络要自己填)。这里做一次性迁移: + * 存的值如果正好是当时内置的那个域名,就当成没设过,清掉。 + * + *

⚠ 只在**完全等于**内置值时清 —— 用户自己手填的地址不能动。 + */ + private static final String[] LEGACY_BAKED_IN_RELAYS = { + "https://relay.xxmxxs.top", + "http://relay.xxmxxs.top", + }; + + private void migrateLegacyRelay() { + String saved = getSharedPreferences("lanremote", MODE_PRIVATE) + .getString("relay_url", ""); + if (saved == null) { + return; + } + String trimmed = saved.trim(); + for (String legacy : LEGACY_BAKED_IN_RELAYS) { + if (legacy.equalsIgnoreCase(trimmed)) { + // 这是老版本硬塞的,不是用户选的 —— 清掉,让他自己填 + getSharedPreferences("lanremote", MODE_PRIVATE).edit() + .remove("relay_url").apply(); + appendLog("已清掉旧版本内置的中转地址(不是你自己填的)"); + return; + } + } + } + +''' + + +SETTINGS_BUILD = ''' /** + * 「设置」页。用户要求:"安卓端加一个设置,各种设置就在这里面调"。 + * + *

以前设置散在首页(一个「中转设置」按钮)和别处, + * 现在统一收到这里。 + */ + private void buildSettingsPanel() { + settingsPanel = new FrameLayout(this); + settingsPanel.setBackgroundColor(C_BG); + + ScrollView scroll = new ScrollView(this); + scroll.setBackgroundColor(C_BG); + LinearLayout box = new LinearLayout(this); + box.setOrientation(LinearLayout.VERTICAL); + box.setPadding(dp(10), dp(12), dp(10), dp(12)); + scroll.addView(box, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + settingsPanel.addView(scroll, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + LinearLayout top = new LinearLayout(this); + top.setOrientation(LinearLayout.HORIZONTAL); + top.setGravity(Gravity.CENTER_VERTICAL); + box.addView(top, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + + TextView title = new TextView(this); + title.setText("设置"); + title.setTextColor(C_FG); + title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + title.setTypeface(null, android.graphics.Typeface.BOLD); + top.addView(title, new LinearLayout.LayoutParams( + 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); + top.addView(smallButton("返回", C_CARD, new Runnable() { + @Override + public void run() { + hideSettingsPanel(); + } + })); + + // ================= 中转服务器 ================= + settingsRelayValue = new TextView(this); + settingsRelayValue.setTextColor(C_DIM); + settingsRelayValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); + settingsRelayValue.setPadding(0, dp(6), 0, dp(4)); + + box.addView(sectionTitle("中转服务器")); + box.addView(hintText("手表和电脑不在同一个网络时(比如在办公室管教室)" + + "才需要填。填你自己那台的地址,比如 relay.你的域名。\\n" + + "只在一个 WiFi 里用就留空 —— 那样直接连,最快也不耗流量。")); + box.addView(settingsRelayValue); + + Button editRelay = bigButton("填写中转服务器", C_ACCENT, 14); + editRelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + promptRelayUrl(); + } + }); + box.addView(editRelay); + + Button clearRelay = bigButton("清空中转服务器(只走局域网)", C_CARD, 13); + clearRelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + setRelayUrl(""); + refreshSettingsPanel(); + toast("已清空 —— 以后只走局域网"); + } + }); + box.addView(clearRelay); + + // ================= 配对码 ================= + settingsCodeValue = new TextView(this); + settingsCodeValue.setTextColor(C_DIM); + settingsCodeValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); + settingsCodeValue.setPadding(0, dp(6), 0, dp(4)); + + box.addView(sectionTitle("记住的配对码")); + box.addView(hintText("手表会记住最近一次用的配对码," + + "下次连接自动带上,不用重复输。")); + box.addView(settingsCodeValue); + + Button clearCode = bigButton("清除记住的配对码", C_CARD, 13); + clearCode.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + getSharedPreferences("lanremote", MODE_PRIVATE).edit() + .remove("paired_code").apply(); + refreshSettingsPanel(); + toast("已清除"); + } + }); + box.addView(clearCode); + + // ================= 已配对设备 ================= + settingsDevicesValue = new TextView(this); + settingsDevicesValue.setTextColor(C_DIM); + settingsDevicesValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); + settingsDevicesValue.setPadding(0, dp(6), 0, dp(4)); + + box.addView(sectionTitle("已配对的电脑")); + box.addView(hintText("配对过的电脑会出现在首页列表里,点一下就能连。" + + "换电脑或者想重新配对时可以清空。")); + box.addView(settingsDevicesValue); + + Button clearDevices = bigButton("清空已配对的电脑", C_DANGER, 13); + clearDevices.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new android.app.AlertDialog.Builder(MainActivity.this) + .setTitle("清空已配对的电脑?") + .setMessage("清掉之后首页列表就空了," + + "需要重新输配对码连接。") + .setPositiveButton("清空", new android.content.DialogInterface.OnClickListener() { + @Override + public void onClick(android.content.DialogInterface d, int w) { + store.clearDevices(); + refreshDeviceList(); + refreshSettingsPanel(); + toast("已清空"); + } + }) + .setNegativeButton("取消", null) + .show(); + } + }); + box.addView(clearDevices); + + // ================= 关于 ================= + box.addView(sectionTitle("关于")); + box.addView(hintText("ykxmz 手表端 版本 " + appVersion() + + "\\n\\n" + + "连接顺序:先试局域网直连(同一个 WiFi,几毫秒);" + + "连不上再走中转(跨网络,受线路影响)。")); + } + + /** 一行小节标题 */ + private TextView sectionTitle(String text) { + TextView v = new TextView(this); + v.setText(text); + v.setTextColor(C_FG); + v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); + v.setTypeface(null, android.graphics.Typeface.BOLD); + v.setPadding(0, dp(14), 0, 0); + return v; + } + + /** 一行灰色说明 */ + private TextView hintText(String text) { + TextView v = new TextView(this); + v.setText(text); + v.setTextColor(C_DIM); + v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11); + v.setLineSpacing(0, 1.25f); + v.setPadding(0, dp(4), 0, 0); + return v; + } + + /** 版本号。从 PackageManager 读,和构建脚本里的版本天然一致, + * 不用在代码里再写一份(写两份必然有一份会忘改)。 */ + private String appVersion() { + try { + return getPackageManager() + .getPackageInfo(getPackageName(), 0).versionName; + } catch (Exception e) { + return "?"; + } + } + + /** 把设置页上的文字刷新成当前值 */ + private void refreshSettingsPanel() { + if (settingsRelayValue == null) { + return; + } + String relay = relayUrl(); + settingsRelayValue.setText(relay.isEmpty() + ? "现在是:空(只走局域网)" + : "现在是:" + relay); + settingsRelayValue.setTextColor(relay.isEmpty() ? C_DIM : C_OK); + + String code = savedPairingCode(); + settingsCodeValue.setText((code == null || code.isEmpty()) + ? "现在没有记住的配对码" + : "记住的是:" + code); + + int n = store.devices() == null ? 0 : store.devices().size(); + settingsDevicesValue.setText("已配对 " + n + " 台电脑"); + } + + private void showSettingsPanel() { + refreshSettingsPanel(); + showOnly(settingsPanel); + } + + private void hideSettingsPanel() { + showConnectPanel(); + } + +''' + + +def patch(text: str, old: str, new: str, label: str, results: list) -> str: + if old not in text: + results.append(("✗", label, "找不到锚点")) + return text + if text.count(old) != 1: + results.append(("!", label, "锚点出现 %d 次" % text.count(old))) + results.append(("✓", label, "")) + return text.replace(old, new, 1) + + +def main() -> int: + text = SRC.read_text(encoding="utf-8") + results: list = [] + + # ---- 1. 面板字段声明 ---- + text = patch( + text, + " private FrameLayout lanPanel;", + " private FrameLayout lanPanel;\n" + " // ---- 设置页(用户要求:各种设置收在一个页面里调)----\n" + " private FrameLayout settingsPanel;\n" + " private TextView settingsRelayValue;\n" + " private TextView settingsCodeValue;\n" + " private TextView settingsDevicesValue;", + "声明 settingsPanel 和各显示控件", results) + + # ---- 2. 插入迁移函数(放在 relayUrl 之前)---- + text = patch( + text, " private String relayUrl() {", + MIGRATION + " private String relayUrl() {", + "插入 migrateLegacyRelay()", results) + + # ---- 3. buildSettingsPanel 等方法(插在 buildLanPanel 之前)---- + text = patch( + text, " private void buildLanPanel() {", + SETTINGS_BUILD + " private void buildLanPanel() {", + "插入 buildSettingsPanel() 等", results) + + # ---- 4. 建面板的地方也要建它 ---- + text = patch( + text, " buildLanPanel();", + " buildLanPanel();\n buildSettingsPanel();", + "构建时创建 settingsPanel", results) + + # ---- 5. showOnly 纳入 settingsPanel ---- + text = patch( + text, + " View[] all = {connectPanel, controlPanel, mirrorPanel, lanPanel};", + " View[] all = {connectPanel, controlPanel, mirrorPanel, lanPanel,\n" + " settingsPanel};", + "settingsPanel 纳入 showOnly()", results) + + # ---- 6. 首页那个按钮改成「设置」 ---- + text = patch( + text, + ''' Button relayButton = bigButton("中转设置(跨网络用)", C_ACCENT, 14);''', + ''' Button relayButton = bigButton("设置", C_ACCENT, 14);''', + "首页按钮文案改成「设置」", results) + + text = patch( + text, + ''' public void onClick(View view) { + promptRelayUrl(); + } + }); + box.addView(relayButton);''', + ''' public void onClick(View view) { + showSettingsPanel(); + } + }); + box.addView(relayButton);''', + "首页按钮改成打开设置页", results) + + # ---- 7. 启动时做迁移 ---- + # 找 onCreate 里合适的位置:appendLog 第一次出现之前太早, + # 用 "store = " 初始化之后 + import re + m = re.search(r"( store = new DeviceStore\(this\);\n)", text) + if m: + text = (text[:m.end()] + + " // 老版本硬塞的中转地址要清掉(见 migrateLegacyRelay)\n" + " migrateLegacyRelay();\n" + + text[m.end():]) + results.append(("✓", "启动时调用迁移", "")) + else: + results.append(("✗", "启动时调用迁移", "找不到 store 初始化那行")) + + # ---- 输出 ---- + print(" ── 补丁结果 ──") + for mark, label, note in results: + print(" %s %s%s" % (mark, label, (" —— " + note) if note else "")) + + if any(m == "✗" for m, _l, _n in results): + print() + print(" 有锚点没匹配,没有写回") + return 1 + + if not any(m == "✗" for m, _l, _n in results): + SRC.write_text(text, encoding="utf-8", newline="\n") + print() + print(" 已写回 MainActivity.java") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/deploy_release.py b/_build/deploy_release.py new file mode 100644 index 0000000..ba53a90 --- /dev/null +++ b/_build/deploy_release.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +r'''把某个版本的成品和官网代码传到中转服务器。 + +用法: + python _build/deploy_release.py 1.9.0 + python _build/deploy_release.py 1.9.0 --code-only # 只更新官网代码 + python _build/deploy_release.py 1.9.0 --files-only # 只传成品文件 + +== 为什么写成脚本文件而不是 python -c == + +这一轮我在 PowerShell 里用 `python -c @"..."@` 写带引号和 $ 的 +Python 代码,失败了四五次(PowerShell 会插值、会吃掉 $、嵌套引号 +转义一层套一层)。凡是超过两行的 Python,一律写成 .py 文件再跑。 + +== 官网上"最新版"是怎么来的 == + +relay.py 的 _list_downloads() 按 **mtime 倒序** 返回文件, +landing.py 的 _version() 取**第一个**能匹配到 x.y.z 的文件名。 + +所以"上传哪个版本,官网就显示哪个版本" —— 不用改任何代码。 +按顺序传 1.9.0、再传 2.0.0,官网就会先显示 1.9.0、再显示 2.0.0。 +''' +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT / "_build")) + +from devssh import upload, run # noqa: E402 + +REMOTE_DIR = "/root/lanremote-relay" +DL_DIR = REMOTE_DIR + "/downloads" + +# 要传的成品(和 dist/ 里的文件名一致) +ARTIFACTS = [ + "ykxmz-setup-{v}.exe", + "ykxmz-watch-{v}.apk", + "ykxmz-watch-{v}-说明.txt", + "ykxmz-server-{v}.zip", +] + +# 官网/服务端代码 +CODE = ["relay.py", "admin.py", "landing.py", "showcase.py"] + + +def main() -> int: + args = [a for a in sys.argv[1:] if a] + if not args: + print(__doc__) + return 1 + version = args[0] + code_only = "--code-only" in args + files_only = "--files-only" in args + + print("=" * 62) + print(" 部署 %s 到中转服务器" % version) + print("=" * 62) + + # ---------------- 成品 ---------------- + if not code_only: + print() + print(" ── 1. 上传成品到 downloads/ ──") + for tpl in ARTIFACTS: + name = tpl.format(v=version) + local = ROOT / "dist" / name + if not local.is_file(): + print(" [跳过] %s 不存在" % name) + continue + upload(str(local), "%s/%s" % (DL_DIR, name)) + print(" ✓ %-34s %10d 字节" % (name, local.stat().st_size)) + + # ---------------- 代码 ---------------- + if not files_only: + print() + print(" ── 2. 更新官网/服务端代码 ──") + for name in CODE: + local = ROOT / "relay" / name + if not local.is_file(): + print(" [跳过] %s 不存在" % name) + continue + upload(str(local), "%s/%s" % (REMOTE_DIR, name)) + print(" ✓ %s" % name) + + print() + print(" ── 3. 语法检查 + 重启 ──") + out = run( + "cd %s && python3 - <<'PY'\n" + "import ast\n" + "for f in %r:\n" + " ast.parse(open(f, encoding='utf-8').read())\n" + "print('语法 OK')\n" + "PY" % (REMOTE_DIR, tuple(CODE)), + timeout=90) + print(" " + out[1].strip().splitlines()[-1]) + + out = run("systemctl restart lanremote-relay && sleep 4 && " + "systemctl is-active lanremote-relay && " + "grep -m1 '^VERSION' %s/relay.py" % REMOTE_DIR, timeout=120) + for ln in out[1].strip().splitlines(): + print(" " + ln) + + # ---------------- 复核 ---------------- + print() + print(" ── 4. 复核:官网现在显示哪个版本 ──") + out = run( + "curl -s -H 'Cache-Control: no-cache' http://127.0.0.1:8080/v1/downloads", + timeout=60) + print(" " + out[1].strip()[:400]) + + out = run("curl -s -H 'Cache-Control: no-cache' http://127.0.0.1:8080/ " + "| grep -o '最新版 [0-9.]*' | head -1", timeout=60) + print(" 官网首页角标: " + out[1].strip()) + + print() + print(" 提示:官网 / 走 max-age=60,公网可能需要等一分钟才刷新;") + print(" 下载链接带 604800 缓存,换版本号正好会换 URL。") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/fix_license_version.py b/_build/fix_license_version.py index 9d2d054..ac7801d 100644 --- a/_build/fix_license_version.py +++ b/_build/fix_license_version.py @@ -18,12 +18,34 @@ r'''修 license-combined.txt 里那份**没过期的**协议版本号。 ''' from __future__ import annotations +import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent FILE = ROOT / "installer" / "license-combined.txt" -NEW = "1.9.0" + + +def current_version() -> str: + """从**单一来源**读版本号。 + + ⚠ 这里原来是写死的 NEW = "1.9.0"。升到 2.0.0 那次它把已经 + 改好的 license 又**改回了 1.9.0** —— 因为整个升级流程是: + bump_version_generic.py 改一批 + fix_license_version.py 再"修"一遍 <- 用写死的旧版本覆盖 + 写死的常量就是这样:改版本号时忘了改这里,它就成了回退操作。 + + 现在统一从 _build/package_remote.py 的 VERSION 读 —— + 那个是打包脚本用的版本号,本来就是单一来源。 + """ + text = (ROOT / "_build" / "package_remote.py").read_text(encoding="utf-8") + m = re.search(r'VERSION\s*=\s*"([^"]+)"', text) + if not m: + raise SystemExit(" ✗ 从 package_remote.py 里读不到 VERSION") + return m.group(1) + + +NEW = current_version() def main() -> int: diff --git a/_build/package_remote.py b/_build/package_remote.py index 2dd7044..44db0b6 100644 --- a/_build/package_remote.py +++ b/_build/package_remote.py @@ -17,7 +17,7 @@ import zipfile from pathlib import Path ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") -VERSION = "1.9.0" +VERSION = "2.0.0" NAME = f"ykxmz-{VERSION}" AGENT_SRC = ROOT / "dist_exe" / "ykxmz" diff --git a/_tests/out_app/com/lanremote/watch/DeviceStore.class b/_tests/out_app/com/lanremote/watch/DeviceStore.class new file mode 100644 index 0000000..c3e1452 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/DeviceStore.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$1.class new file mode 100644 index 0000000..cd3e994 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$10.class b/_tests/out_app/com/lanremote/watch/MainActivity$10.class new file mode 100644 index 0000000..128adcf Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$10.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$100.class b/_tests/out_app/com/lanremote/watch/MainActivity$100.class new file mode 100644 index 0000000..17aa0a9 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$100.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$101.class b/_tests/out_app/com/lanremote/watch/MainActivity$101.class new file mode 100644 index 0000000..28abd52 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$101.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$102.class b/_tests/out_app/com/lanremote/watch/MainActivity$102.class new file mode 100644 index 0000000..d3ed752 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$102.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$103.class b/_tests/out_app/com/lanremote/watch/MainActivity$103.class new file mode 100644 index 0000000..86b03bb Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$103.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$104.class b/_tests/out_app/com/lanremote/watch/MainActivity$104.class new file mode 100644 index 0000000..1873f4a Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$104.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$105.class b/_tests/out_app/com/lanremote/watch/MainActivity$105.class new file mode 100644 index 0000000..bbf00a7 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$105.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$106.class b/_tests/out_app/com/lanremote/watch/MainActivity$106.class new file mode 100644 index 0000000..f8cd3cb Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$106.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$107$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$107$1.class new file mode 100644 index 0000000..29ef5f8 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$107$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$107.class b/_tests/out_app/com/lanremote/watch/MainActivity$107.class new file mode 100644 index 0000000..324d757 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$107.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$108.class b/_tests/out_app/com/lanremote/watch/MainActivity$108.class new file mode 100644 index 0000000..4bdfccb Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$108.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$109.class b/_tests/out_app/com/lanremote/watch/MainActivity$109.class new file mode 100644 index 0000000..1564387 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$109.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$11.class b/_tests/out_app/com/lanremote/watch/MainActivity$11.class new file mode 100644 index 0000000..50239a2 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$11.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$110.class b/_tests/out_app/com/lanremote/watch/MainActivity$110.class new file mode 100644 index 0000000..12b4769 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$110.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$111.class b/_tests/out_app/com/lanremote/watch/MainActivity$111.class new file mode 100644 index 0000000..e8c3b1d Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$111.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$112$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$112$1.class new file mode 100644 index 0000000..0c22a1c Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$112$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$112.class b/_tests/out_app/com/lanremote/watch/MainActivity$112.class new file mode 100644 index 0000000..3a6586d Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$112.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$113.class b/_tests/out_app/com/lanremote/watch/MainActivity$113.class new file mode 100644 index 0000000..1e8a11a Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$113.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$114.class b/_tests/out_app/com/lanremote/watch/MainActivity$114.class new file mode 100644 index 0000000..d8e5307 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$114.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$115.class b/_tests/out_app/com/lanremote/watch/MainActivity$115.class new file mode 100644 index 0000000..e3f2b28 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$115.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$116.class b/_tests/out_app/com/lanremote/watch/MainActivity$116.class new file mode 100644 index 0000000..d49a92f Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$116.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$117$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$117$1.class new file mode 100644 index 0000000..f55d673 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$117$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$117.class b/_tests/out_app/com/lanremote/watch/MainActivity$117.class new file mode 100644 index 0000000..02d0ecb Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$117.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$118.class b/_tests/out_app/com/lanremote/watch/MainActivity$118.class new file mode 100644 index 0000000..7871cc4 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$118.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$119.class b/_tests/out_app/com/lanremote/watch/MainActivity$119.class new file mode 100644 index 0000000..2e8387b Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$119.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$12.class b/_tests/out_app/com/lanremote/watch/MainActivity$12.class new file mode 100644 index 0000000..85330c6 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$12.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$120.class b/_tests/out_app/com/lanremote/watch/MainActivity$120.class new file mode 100644 index 0000000..6474fa0 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$120.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$121.class b/_tests/out_app/com/lanremote/watch/MainActivity$121.class new file mode 100644 index 0000000..011dbc1 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$121.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$122.class b/_tests/out_app/com/lanremote/watch/MainActivity$122.class new file mode 100644 index 0000000..5536ccf Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$122.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$123.class b/_tests/out_app/com/lanremote/watch/MainActivity$123.class new file mode 100644 index 0000000..c886afd Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$123.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$124.class b/_tests/out_app/com/lanremote/watch/MainActivity$124.class new file mode 100644 index 0000000..d42295d Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$124.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$125.class b/_tests/out_app/com/lanremote/watch/MainActivity$125.class new file mode 100644 index 0000000..db7ad02 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$125.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$126.class b/_tests/out_app/com/lanremote/watch/MainActivity$126.class new file mode 100644 index 0000000..20d3d25 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$126.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$127.class b/_tests/out_app/com/lanremote/watch/MainActivity$127.class new file mode 100644 index 0000000..1a36ee4 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$127.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$128.class b/_tests/out_app/com/lanremote/watch/MainActivity$128.class new file mode 100644 index 0000000..9c58ae6 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$128.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$129.class b/_tests/out_app/com/lanremote/watch/MainActivity$129.class new file mode 100644 index 0000000..30064e9 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$129.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$13.class b/_tests/out_app/com/lanremote/watch/MainActivity$13.class new file mode 100644 index 0000000..7555230 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$13.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$14.class b/_tests/out_app/com/lanremote/watch/MainActivity$14.class new file mode 100644 index 0000000..dd52c70 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$14.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$15.class b/_tests/out_app/com/lanremote/watch/MainActivity$15.class new file mode 100644 index 0000000..0c465e5 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$15.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$16.class b/_tests/out_app/com/lanremote/watch/MainActivity$16.class new file mode 100644 index 0000000..5e68f78 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$16.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$17.class b/_tests/out_app/com/lanremote/watch/MainActivity$17.class new file mode 100644 index 0000000..c720ca3 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$17.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$18.class b/_tests/out_app/com/lanremote/watch/MainActivity$18.class new file mode 100644 index 0000000..3405c1c Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$18.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$19.class b/_tests/out_app/com/lanremote/watch/MainActivity$19.class new file mode 100644 index 0000000..d4115ce Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$19.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$2.class b/_tests/out_app/com/lanremote/watch/MainActivity$2.class new file mode 100644 index 0000000..33c3782 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$2.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$20.class b/_tests/out_app/com/lanremote/watch/MainActivity$20.class new file mode 100644 index 0000000..a088f7e Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$20.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$21.class b/_tests/out_app/com/lanremote/watch/MainActivity$21.class new file mode 100644 index 0000000..a6efae5 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$21.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$22.class b/_tests/out_app/com/lanremote/watch/MainActivity$22.class new file mode 100644 index 0000000..1e500a7 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$22.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$23.class b/_tests/out_app/com/lanremote/watch/MainActivity$23.class new file mode 100644 index 0000000..5e7002e Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$23.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$24.class b/_tests/out_app/com/lanremote/watch/MainActivity$24.class new file mode 100644 index 0000000..01a375a Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$24.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$25.class b/_tests/out_app/com/lanremote/watch/MainActivity$25.class new file mode 100644 index 0000000..a28b8b4 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$25.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$26.class b/_tests/out_app/com/lanremote/watch/MainActivity$26.class new file mode 100644 index 0000000..ec9951a Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$26.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$27.class b/_tests/out_app/com/lanremote/watch/MainActivity$27.class new file mode 100644 index 0000000..ac5ad4a Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$27.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$28.class b/_tests/out_app/com/lanremote/watch/MainActivity$28.class new file mode 100644 index 0000000..406f72b Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$28.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$29.class b/_tests/out_app/com/lanremote/watch/MainActivity$29.class new file mode 100644 index 0000000..26e64c2 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$29.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$3.class b/_tests/out_app/com/lanremote/watch/MainActivity$3.class new file mode 100644 index 0000000..17510b3 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$3.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$30.class b/_tests/out_app/com/lanremote/watch/MainActivity$30.class new file mode 100644 index 0000000..e267d22 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$30.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$31.class b/_tests/out_app/com/lanremote/watch/MainActivity$31.class new file mode 100644 index 0000000..27c8327 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$31.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$32.class b/_tests/out_app/com/lanremote/watch/MainActivity$32.class new file mode 100644 index 0000000..63e4e27 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$32.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$33.class b/_tests/out_app/com/lanremote/watch/MainActivity$33.class new file mode 100644 index 0000000..6d75bff Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$33.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$34.class b/_tests/out_app/com/lanremote/watch/MainActivity$34.class new file mode 100644 index 0000000..5e56573 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$34.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$35.class b/_tests/out_app/com/lanremote/watch/MainActivity$35.class new file mode 100644 index 0000000..f1b5601 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$35.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$36.class b/_tests/out_app/com/lanremote/watch/MainActivity$36.class new file mode 100644 index 0000000..2ff9919 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$36.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$37.class b/_tests/out_app/com/lanremote/watch/MainActivity$37.class new file mode 100644 index 0000000..5a6c74e Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$37.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$38.class b/_tests/out_app/com/lanremote/watch/MainActivity$38.class new file mode 100644 index 0000000..733a485 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$38.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$39.class b/_tests/out_app/com/lanremote/watch/MainActivity$39.class new file mode 100644 index 0000000..64e92eb Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$39.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$4.class b/_tests/out_app/com/lanremote/watch/MainActivity$4.class new file mode 100644 index 0000000..669dd1d Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$4.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$40.class b/_tests/out_app/com/lanremote/watch/MainActivity$40.class new file mode 100644 index 0000000..6d1780b Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$40.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$41.class b/_tests/out_app/com/lanremote/watch/MainActivity$41.class new file mode 100644 index 0000000..adcf96e Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$41.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$42.class b/_tests/out_app/com/lanremote/watch/MainActivity$42.class new file mode 100644 index 0000000..1130836 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$42.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$43.class b/_tests/out_app/com/lanremote/watch/MainActivity$43.class new file mode 100644 index 0000000..8f1d164 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$43.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$44.class b/_tests/out_app/com/lanremote/watch/MainActivity$44.class new file mode 100644 index 0000000..6d22b58 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$44.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$45.class b/_tests/out_app/com/lanremote/watch/MainActivity$45.class new file mode 100644 index 0000000..a4418d5 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$45.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$46.class b/_tests/out_app/com/lanremote/watch/MainActivity$46.class new file mode 100644 index 0000000..a8d3638 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$46.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$47.class b/_tests/out_app/com/lanremote/watch/MainActivity$47.class new file mode 100644 index 0000000..67dbd98 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$47.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$48.class b/_tests/out_app/com/lanremote/watch/MainActivity$48.class new file mode 100644 index 0000000..de2438b Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$48.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$49.class b/_tests/out_app/com/lanremote/watch/MainActivity$49.class new file mode 100644 index 0000000..3d6efbd Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$49.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$5.class b/_tests/out_app/com/lanremote/watch/MainActivity$5.class new file mode 100644 index 0000000..79223ba Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$5.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$50.class b/_tests/out_app/com/lanremote/watch/MainActivity$50.class new file mode 100644 index 0000000..ae4d07b Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$50.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$51.class b/_tests/out_app/com/lanremote/watch/MainActivity$51.class new file mode 100644 index 0000000..ee83436 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$51.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$52.class b/_tests/out_app/com/lanremote/watch/MainActivity$52.class new file mode 100644 index 0000000..234ca96 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$52.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$53.class b/_tests/out_app/com/lanremote/watch/MainActivity$53.class new file mode 100644 index 0000000..cb43bb9 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$53.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$54.class b/_tests/out_app/com/lanremote/watch/MainActivity$54.class new file mode 100644 index 0000000..44f2f42 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$54.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$55.class b/_tests/out_app/com/lanremote/watch/MainActivity$55.class new file mode 100644 index 0000000..76a7a80 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$55.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$56.class b/_tests/out_app/com/lanremote/watch/MainActivity$56.class new file mode 100644 index 0000000..fdcda22 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$56.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$57.class b/_tests/out_app/com/lanremote/watch/MainActivity$57.class new file mode 100644 index 0000000..d4edd9d Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$57.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$58$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$58$1.class new file mode 100644 index 0000000..f6ea35b Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$58$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$58.class b/_tests/out_app/com/lanremote/watch/MainActivity$58.class new file mode 100644 index 0000000..1b167bd Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$58.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$59.class b/_tests/out_app/com/lanremote/watch/MainActivity$59.class new file mode 100644 index 0000000..2d2af55 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$59.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$6.class b/_tests/out_app/com/lanremote/watch/MainActivity$6.class new file mode 100644 index 0000000..922f3a3 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$6.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$60.class b/_tests/out_app/com/lanremote/watch/MainActivity$60.class new file mode 100644 index 0000000..5d8720d Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$60.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$61.class b/_tests/out_app/com/lanremote/watch/MainActivity$61.class new file mode 100644 index 0000000..00d388f Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$61.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$62.class b/_tests/out_app/com/lanremote/watch/MainActivity$62.class new file mode 100644 index 0000000..d1a5f97 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$62.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$63.class b/_tests/out_app/com/lanremote/watch/MainActivity$63.class new file mode 100644 index 0000000..190718c Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$63.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$64$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$64$1.class new file mode 100644 index 0000000..f2b0302 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$64$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$64$2.class b/_tests/out_app/com/lanremote/watch/MainActivity$64$2.class new file mode 100644 index 0000000..82c1084 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$64$2.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$64.class b/_tests/out_app/com/lanremote/watch/MainActivity$64.class new file mode 100644 index 0000000..2d3da20 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$64.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$65.class b/_tests/out_app/com/lanremote/watch/MainActivity$65.class new file mode 100644 index 0000000..079b59e Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$65.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$66.class b/_tests/out_app/com/lanremote/watch/MainActivity$66.class new file mode 100644 index 0000000..3ab2087 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$66.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$67.class b/_tests/out_app/com/lanremote/watch/MainActivity$67.class new file mode 100644 index 0000000..4325c83 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$67.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$68$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$68$1.class new file mode 100644 index 0000000..c3aa3e1 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$68$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$68$2.class b/_tests/out_app/com/lanremote/watch/MainActivity$68$2.class new file mode 100644 index 0000000..af6a7bc Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$68$2.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$68.class b/_tests/out_app/com/lanremote/watch/MainActivity$68.class new file mode 100644 index 0000000..442a1cd Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$68.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$69.class b/_tests/out_app/com/lanremote/watch/MainActivity$69.class new file mode 100644 index 0000000..64413c6 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$69.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$7.class b/_tests/out_app/com/lanremote/watch/MainActivity$7.class new file mode 100644 index 0000000..74280f3 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$7.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$70.class b/_tests/out_app/com/lanremote/watch/MainActivity$70.class new file mode 100644 index 0000000..dd0731f Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$70.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$71.class b/_tests/out_app/com/lanremote/watch/MainActivity$71.class new file mode 100644 index 0000000..4307f4a Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$71.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$72.class b/_tests/out_app/com/lanremote/watch/MainActivity$72.class new file mode 100644 index 0000000..623a899 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$72.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$73.class b/_tests/out_app/com/lanremote/watch/MainActivity$73.class new file mode 100644 index 0000000..690bb1a Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$73.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$74.class b/_tests/out_app/com/lanremote/watch/MainActivity$74.class new file mode 100644 index 0000000..580d5f9 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$74.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$75.class b/_tests/out_app/com/lanremote/watch/MainActivity$75.class new file mode 100644 index 0000000..ca89467 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$75.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$76$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$76$1$1.class new file mode 100644 index 0000000..ba5646a Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$76$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$76$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$76$1.class new file mode 100644 index 0000000..aa71a31 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$76$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$76.class b/_tests/out_app/com/lanremote/watch/MainActivity$76.class new file mode 100644 index 0000000..c5aaddb Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$76.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$77.class b/_tests/out_app/com/lanremote/watch/MainActivity$77.class new file mode 100644 index 0000000..5112199 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$77.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$78$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$78$1.class new file mode 100644 index 0000000..d16a691 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$78$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$78.class b/_tests/out_app/com/lanremote/watch/MainActivity$78.class new file mode 100644 index 0000000..15267da Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$78.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$79$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$79$1$1.class new file mode 100644 index 0000000..6c2bf77 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$79$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$79$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$79$1.class new file mode 100644 index 0000000..e25ce9a Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$79$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$79.class b/_tests/out_app/com/lanremote/watch/MainActivity$79.class new file mode 100644 index 0000000..f6ce807 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$79.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$8.class b/_tests/out_app/com/lanremote/watch/MainActivity$8.class new file mode 100644 index 0000000..557b8b9 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$8.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$80$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$80$1$1.class new file mode 100644 index 0000000..8af88e0 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$80$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$80$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$80$1.class new file mode 100644 index 0000000..852a710 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$80$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$80.class b/_tests/out_app/com/lanremote/watch/MainActivity$80.class new file mode 100644 index 0000000..2e935ad Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$80.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$81.class b/_tests/out_app/com/lanremote/watch/MainActivity$81.class new file mode 100644 index 0000000..4a4546f Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$81.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$82.class b/_tests/out_app/com/lanremote/watch/MainActivity$82.class new file mode 100644 index 0000000..12a2faf Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$82.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$83.class b/_tests/out_app/com/lanremote/watch/MainActivity$83.class new file mode 100644 index 0000000..82ec2e3 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$83.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$84.class b/_tests/out_app/com/lanremote/watch/MainActivity$84.class new file mode 100644 index 0000000..222ca3a Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$84.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$85.class b/_tests/out_app/com/lanremote/watch/MainActivity$85.class new file mode 100644 index 0000000..60d828b Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$85.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$86$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$86$1.class new file mode 100644 index 0000000..421d3d7 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$86$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$86.class b/_tests/out_app/com/lanremote/watch/MainActivity$86.class new file mode 100644 index 0000000..33ad520 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$86.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$87.class b/_tests/out_app/com/lanremote/watch/MainActivity$87.class new file mode 100644 index 0000000..b7ebe03 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$87.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$88.class b/_tests/out_app/com/lanremote/watch/MainActivity$88.class new file mode 100644 index 0000000..f82ce84 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$88.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$89.class b/_tests/out_app/com/lanremote/watch/MainActivity$89.class new file mode 100644 index 0000000..45a7256 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$89.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$9$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$9$1.class new file mode 100644 index 0000000..b8e963e Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$9$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$9.class b/_tests/out_app/com/lanremote/watch/MainActivity$9.class new file mode 100644 index 0000000..70b1236 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$9.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$90.class b/_tests/out_app/com/lanremote/watch/MainActivity$90.class new file mode 100644 index 0000000..5eeb3ec Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$90.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$91.class b/_tests/out_app/com/lanremote/watch/MainActivity$91.class new file mode 100644 index 0000000..398d96f Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$91.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$92$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$92$1.class new file mode 100644 index 0000000..6c8c1fd Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$92$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$92.class b/_tests/out_app/com/lanremote/watch/MainActivity$92.class new file mode 100644 index 0000000..33d7abc Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$92.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$93.class b/_tests/out_app/com/lanremote/watch/MainActivity$93.class new file mode 100644 index 0000000..d3e0af8 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$93.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$94.class b/_tests/out_app/com/lanremote/watch/MainActivity$94.class new file mode 100644 index 0000000..f9415ef Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$94.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$95.class b/_tests/out_app/com/lanremote/watch/MainActivity$95.class new file mode 100644 index 0000000..f9712a2 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$95.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$96.class b/_tests/out_app/com/lanremote/watch/MainActivity$96.class new file mode 100644 index 0000000..fcb1b0b Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$96.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$97.class b/_tests/out_app/com/lanremote/watch/MainActivity$97.class new file mode 100644 index 0000000..557c681 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$97.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$98.class b/_tests/out_app/com/lanremote/watch/MainActivity$98.class new file mode 100644 index 0000000..ad4e948 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$98.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$99.class b/_tests/out_app/com/lanremote/watch/MainActivity$99.class new file mode 100644 index 0000000..3fc2a2a Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$99.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$Act.class b/_tests/out_app/com/lanremote/watch/MainActivity$Act.class new file mode 100644 index 0000000..8045547 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$Act.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$TabDef.class b/_tests/out_app/com/lanremote/watch/MainActivity$TabDef.class new file mode 100644 index 0000000..07fe68f Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity$TabDef.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity.class b/_tests/out_app/com/lanremote/watch/MainActivity.class new file mode 100644 index 0000000..b6f45b2 Binary files /dev/null and b/_tests/out_app/com/lanremote/watch/MainActivity.class differ diff --git a/_tests/test_apk_contents.py b/_tests/test_apk_contents.py index 40415a8..757fc60 100644 --- a/_tests/test_apk_contents.py +++ b/_tests/test_apk_contents.py @@ -23,7 +23,7 @@ def _latest_apk() -> Path: (ROOT / "dist").glob("*watch*.apk"), key=lambda p: p.stat().st_mtime, reverse=True) return candidates[0] if candidates else \ - ROOT / "dist" / "ykxmz-watch-1.9.0.apk" + ROOT / "dist" / "ykxmz-watch-2.0.0.apk" APK = _latest_apk() diff --git a/_tests/test_image_modes.py b/_tests/test_image_modes.py index 91f9525..012ebb1 100644 --- a/_tests/test_image_modes.py +++ b/_tests/test_image_modes.py @@ -104,6 +104,35 @@ def wait_geometry(win, agent, timeout: float = 15.0): return None +def show_and_wait(actions, agent, image_path, retries: int = 3): + """显示图片并等窗口几何,失败就重来。 + + ⚠ 为什么要重试:这个测试开的是**真实的 Tk 窗口**, + 在全量套件里跑的时候偶尔会起不来(前一个阶段留下的窗口 + 或进程抢焦点),表现成「全屏窗口已创建」这一条红。 + 单独跑两次都是 19/19 —— 典型的偶发。 + + 与其让它偶发地红(那样"全量通过"就不可信了), + 不如在这里有界重试;三次都起不来才算真失败。 + """ + for attempt in range(1, retries + 1): + actions.show_image(str(image_path)) + pump(agent.root, 0.5) + rect = wait_geometry(actions._image_window, agent, + timeout=8.0 if attempt > 1 else 15.0) + if rect: + if attempt > 1: + print(f" (第 {attempt} 次才起来)") + return rect + # 没起来:关掉重来 + try: + actions.close_image() + except Exception: # noqa: BLE001 + pass + pump(agent.root, 0.6) + return None + + def _read_geometry(win): try: if not win.winfo_exists(): @@ -141,9 +170,7 @@ def main() -> int: print("一、屏幕中间(默认)") store.set("image_display", "window") store.set("image_timeout", 0) - actions.show_image(str(image_path)) - pump(agent.root, 0.3) - rect = wait_geometry(actions._image_window, agent) + rect = show_and_wait(actions, agent, image_path) check("窗口已创建", rect is not None) if rect: left, top, width, height = rect @@ -170,9 +197,7 @@ def main() -> int: # ---------------- 2. 全屏 ---------------- print("\n二、全屏") store.set("image_display", "fullscreen") - actions.show_image(str(image_path)) - pump(agent.root, 0.5) - rect = wait_geometry(actions._image_window, agent) + rect = show_and_wait(actions, agent, image_path) check("全屏窗口已创建", rect is not None) if rect: left, top, width, height = rect @@ -187,9 +212,7 @@ def main() -> int: store.set("image_display", "custom") store.set("image_custom_width", 640) store.set("image_custom_height", 400) - actions.show_image(str(image_path)) - pump(agent.root, 0.3) - rect = wait_geometry(actions._image_window, agent) + rect = show_and_wait(actions, agent, image_path) check("自定义窗口已创建", rect is not None) if rect: left, top, width, height = rect @@ -212,9 +235,7 @@ def main() -> int: print("\n四、只填宽度(高度自动)") store.set("image_custom_width", 500) store.set("image_custom_height", 0) - actions.show_image(str(image_path)) - pump(agent.root, 0.3) - rect = wait_geometry(actions._image_window, agent) + rect = show_and_wait(actions, agent, image_path) check("只填宽度也能显示", rect is not None) if rect: print(f" 客户区: {rect[2]}x{rect[3]}") diff --git a/_tests/test_lan_optimize.py b/_tests/test_lan_optimize.py index 112aa49..53f1e67 100644 --- a/_tests/test_lan_optimize.py +++ b/_tests/test_lan_optimize.py @@ -148,9 +148,19 @@ def main() -> int: check("MainActivity 的默认中转是空字符串", 'DEFAULT_RELAY_URL = "";' in main_src) check("不再硬编码旧设备地址", "192.168.2.156" not in main_src) - # 旧公网域名只允许出现在注释/文档里,**不允许**作为默认值的字面量 + # 旧公网域名只允许出现在注释/文档里,**不允许**作为默认值的字面量。 + # + # ⚠ 必须先把注释剥掉再查 —— 这条断言原来直接查原文,于是 + # migrateLegacyRelay() 的注释里引用那行老代码 + # (写的是"老版本是这样写的:private static final String + # DEFAULT_RELAY_URL = https://...")就把它判红了。 + # 断言要测的是"有没有把它当默认值",不是"源码里能不能出现这串字"。 + import re as _re + code_only = _re.sub(r"/\*.*?\*/", "", main_src, flags=_re.S) # 块注释 + code_only = _re.sub(r"//[^\n]*", "", code_only) # 行注释 check("没有把旧公网域名当默认值", - 'DEFAULT_RELAY_URL = "https://' not in main_src) + not _re.search(r'DEFAULT_RELAY_URL\s*=\s*"https?://', code_only), + "赋值语句里出现了公网地址") # 单实例锁端口可以被覆盖(测试才能和用户自己的实例并存) check("instance 有 control_port()", hasattr(instance, "control_port")) diff --git a/_tests/test_package_remote.py b/_tests/test_package_remote.py index f1eb582..fd24ed0 100644 --- a/_tests/test_package_remote.py +++ b/_tests/test_package_remote.py @@ -26,9 +26,9 @@ ROOT = Path(__file__).resolve().parent.parent # 被控端 -> 一个 zip # 服务器端 -> 一个 zip # 控制端 -> 直接给 APK(不打 zip) -AGENT_ZIP = ROOT / "dist" / "ykxmz-agent-1.9.0.zip" -SERVER_ZIP = ROOT / "dist" / "ykxmz-server-1.9.0.zip" -WATCH_APK = ROOT / "dist" / "ykxmz-watch-1.9.0.apk" +AGENT_ZIP = ROOT / "dist" / "ykxmz-agent-2.0.0.zip" +SERVER_ZIP = ROOT / "dist" / "ykxmz-server-2.0.0.zip" +WATCH_APK = ROOT / "dist" / "ykxmz-watch-2.0.0.apk" TOOLCHAIN = Path(r"C:\Users\shu\Desktop\deepseekagent\starklink\toolchain") JAVAC = TOOLCHAIN / "jdk17" / "bin" / "javac.exe" JAVA = TOOLCHAIN / "jdk17" / "bin" / "java.exe" @@ -89,7 +89,7 @@ def main() -> int: for label, path in (("被控端", agent_dir / "README-agent.txt"), ("服务器端", relay_dir / "README-server.txt"), ("控制端(旁边那份)", - ROOT / "dist" / "ykxmz-watch-1.9.0-说明.txt")): + ROOT / "dist" / "ykxmz-watch-2.0.0-说明.txt")): raw = path.read_bytes() if path.is_file() else b"" check(f"{label}说明有 UTF-8 BOM", raw[:3] == b"\xef\xbb\xbf", raw[:3]) diff --git a/_tests/test_relay_default_blank.py b/_tests/test_relay_default_blank.py new file mode 100644 index 0000000..2d76b34 --- /dev/null +++ b/_tests/test_relay_default_blank.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +"""钉住两件事:中转地址默认必须是空的,以及老版本硬塞的地址要被清掉。 + +== 为什么需要 == + +用户报过:「安卓端为什么还是默认填的我的中转站,以后改成手动填」。 + +根因是**老版本 1.5.0 把作者的域名硬编码成默认值**: + + private static final String DEFAULT_RELAY_URL = "https://relay.xxmxxs.top"; + +第一次运行就存进了 SharedPreferences。装新 APK 不会清偏好, +所以看起来像"默认还是我的中转站"。 + +这条规矩(**对外发布的软件里不能内置作者自己的中转站地址**) +踩过一次就会再踩 —— 用户明确说过"这个中转站我就自己用,不对外开放"。 +所以用断言钉死。 + +覆盖: + 一、默认值必须是空串 + 二、源码里不能有任何"写死的中转域名默认值" + 三、必须有迁移逻辑,且迁移清单里包含那个历史域名 + 四、设置页存在(用户要求各种设置收在一起) + 五、其它客户端(Windows / 服务端)的默认也得是空 +""" +from __future__ import annotations + +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +WATCH = (ROOT / "android" / "src" / "com" / "lanremote" / "watch" + / "MainActivity.java") + +passed = 0 +failed = 0 + + +def check(name, ok, detail=""): + global passed, failed + if ok: + passed += 1 + print(" [PASS] " + name) + else: + failed += 1 + print(" [FAIL] " + name + ((" —— " + str(detail)) if detail else "")) + + +def main() -> int: + print("=" * 60) + print(" 中转地址默认必须为空(对外软件不能内置作者的服务器)") + print("=" * 60) + + check("MainActivity.java 存在", WATCH.is_file(), WATCH) + if not WATCH.is_file(): + return 1 + src = WATCH.read_text(encoding="utf-8") + + # ---------------------------------------------------------- 一 + print(" ---- 一、默认值是空 ----") + m = re.search(r'DEFAULT_RELAY_URL\s*=\s*"([^"]*)"', src) + check("能找到 DEFAULT_RELAY_URL", bool(m), "找不到定义") + if m: + value = m.group(1) + check("默认值是空串", value == "", + "实际是 %r —— 对外软件不能内置中转地址" % value) + + # ---------------------------------------------------------- 二 + print(" ---- 二、没有写死的中转域名默认值 ----") + # 找所有 "xxx = "https://某个域名"" 形式的赋值(非注释) + hard = [] + for i, line in enumerate(src.splitlines(), 1): + s = line.strip() + if s.startswith("//") or s.startswith("*") or s.startswith("/*"): + continue + for m2 in re.finditer(r'"https?://[a-z0-9.\-]+\.[a-z]{2,}[^"]*"', s): + # 允许出现在迁移清单里(那是用来"清掉"的,不是默认值) + if "LEGACY_BAKED_IN_RELAYS" in src[ + max(0, src.find(s) - 400):src.find(s) + 400]: + continue + hard.append((i, m2.group(0))) + # 更准的做法:把迁移清单那一段排除掉再看 + cleaned = re.sub( + r"private static final String\[\] LEGACY_BAKED_IN_RELAYS.*?\};", + "", src, flags=re.S) + cleaned = re.sub(r"//.*", "", cleaned) # 去行注释 + cleaned = re.sub(r"/\*.*?\*/", "", cleaned, flags=re.S) # 去块注释 + leftovers = [] + for i, line in enumerate(cleaned.splitlines(), 1): + for m3 in re.finditer(r'"https?://[a-z0-9.\-]+\.[a-z]{2,}[^"]*"', line): + leftovers.append((i, m3.group(0))) + check("除了迁移清单,没有别处写死中转域名", not leftovers, + "还在这些地方:%s" % leftovers[:4]) + + # ---------------------------------------------------------- 三 + print(" ---- 三、有迁移逻辑,能清掉老版本硬塞的值 ----") + check("有 migrateLegacyRelay()", + "migrateLegacyRelay" in src, "找不到迁移函数") + check("启动时会调用它", + re.search(r"^\s*migrateLegacyRelay\(\);\s*$", src, re.M) is not None, + "定义了但没调用 = 迁移不生效") + check("迁移清单里有那个历史域名", + "relay.xxmxxs.top" in src, "清单里没包含历史域名") + check("迁移是 remove 而不是覆盖成别的固定值", + re.search(r'\.remove\("relay_url"\)', src) is not None, + "应该是 remove,不能换成另一个硬编码值") + check("只清完全相等的老值(用户手填的不能动)", + "equalsIgnoreCase" in src, "没看到相等判断") + + # ---------------------------------------------------------- 四 + print(" ---- 四、设置页存在 ----") + for needle, note in (("buildSettingsPanel", "设置页构建"), + ("showSettingsPanel", "打开展示"), + ("settingsPanel", "面板字段"), + ("showOnly(settingsPanel)", + "面板纳入 showOnly(防漏藏)")): + check(note, needle in src, "找不到 %s" % needle) + + # ---------------------------------------------------------- 五 + print(" ---- 五、其它客户端的默认也得是空 ----") + win = (ROOT / "windows" / "lanremote" / "server.py").read_text( + encoding="utf-8") + m4 = re.search(r'DEFAULT_RELAY_URL\s*=\s*"([^"]*)"', win) + check("Windows 被控端默认是空", bool(m4) and m4.group(1) == "", + m4.group(1) if m4 else "找不到") + + run = (ROOT / "windows" / "run_agent.py").read_text(encoding="utf-8") + # run_agent 里不该有写死的公网域名(除了注释) + run_clean = re.sub(r"#.*", "", run) + run_clean = re.sub(r'""".*?"""', "", run_clean, flags=re.S) + hits = re.findall(r'"https?://[a-z0-9.\-]+\.(?:top|com|cn|net)[^"]*"', + run_clean) + check("Windows 端源码里没有写死的中转域名", not hits, + "还有:%s" % hits[:3]) + + print() + print("=" * 60) + print(" 通过 %d 项,失败 %d 项" % (passed, failed)) + return 0 if failed == 0 else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_tests/verify_all.ps1 b/_tests/verify_all.ps1 index a6662b8..bdcae9c 100644 --- a/_tests/verify_all.ps1 +++ b/_tests/verify_all.ps1 @@ -396,6 +396,12 @@ $stageOut = & python (Join-Path $root "_tests\test_watch_lan_switch.py") 2>&1 Show-Fails $stageOut Record "手表局域网切换" ($LASTEXITCODE -eq 0) +# ---------------------------------------------------------------- 3a3b +Banner "3a3b/7 中转默认必须为空(防内置作者服务器)" +$stageOut = & python (Join-Path $root "_tests\test_relay_default_blank.py") 2>&1 + Show-Fails $stageOut +Record "中转默认空白" ($LASTEXITCODE -eq 0) + # ---------------------------------------------------------------- 3a4 Banner "3a4/7 编码与换行(.java/.py 必须 UTF-8 无 BOM + LF)" # 踩过的坑:edit 工具会把行尾写成 CRLF,而仓库约定是 LF。 diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index d729125..95f8659 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="2.0.0"> diff --git a/android/build_apk.ps1 b/android/build_apk.ps1 index 287a185..aa5e2d8 100644 --- a/android/build_apk.ps1 +++ b/android/build_apk.ps1 @@ -27,7 +27,7 @@ $d8 = Join-Path $bt "d8.bat" $work = Join-Path $android "build" $outDir = Join-Path $root "dist" -$apkName = "ykxmz-watch-1.9.0.apk" +$apkName = "ykxmz-watch-2.0.0.apk" function Step($text) { Write-Host "`n=== $text ===" -ForegroundColor Cyan } @@ -65,7 +65,7 @@ $unsigned = Join-Path $work "app-unsigned.apk" --min-sdk-version 24 ` --target-sdk-version 34 ` --version-code 5 ` - --version-name "1.9.0" ` + --version-name "2.0.0" ` --auto-add-overlay if ($LASTEXITCODE -ne 0) { throw "aapt2 link 失败" } diff --git a/android/src/com/lanremote/watch/DeviceStore.java b/android/src/com/lanremote/watch/DeviceStore.java index a181158..ce88838 100644 --- a/android/src/com/lanremote/watch/DeviceStore.java +++ b/android/src/com/lanremote/watch/DeviceStore.java @@ -136,6 +136,11 @@ public class DeviceStore { writeDevices(updated); } + /** 清空所有已配对设备(设置页的清空用)。 */ + public void clearDevices() { + prefs.edit().remove(KEY_DEVICES).apply(); + } + public static String deviceKey(String host, int port, String deviceId) { return host + ":" + port + ":" + deviceId; } diff --git a/android/src/com/lanremote/watch/MainActivity.java b/android/src/com/lanremote/watch/MainActivity.java index 6ca26a2..703eb5b 100644 --- a/android/src/com/lanremote/watch/MainActivity.java +++ b/android/src/com/lanremote/watch/MainActivity.java @@ -90,6 +90,44 @@ public class MainActivity extends Activity implements RemoteClient.Listener { */ private static final String DEFAULT_RELAY_URL = ""; + /** + * 老版本硬塞进来的中转地址,要清掉。 + * + *

1.5.0 那版把作者的域名写死成默认值: + *

+     *     DEFAULT_RELAY_URL 这个常量写的是 https://relay.xxmxxs.top
+     * 
+ * 而且第一次运行就存进了 SharedPreferences。装新 APK 不会清偏好, + * 所以用户会看到「默认还是那个中转站」—— 其实他从来没选过。 + * + *

现在默认值是空串(跨网络要自己填)。这里做一次性迁移: + * 存的值如果正好是当时内置的那个域名,就当成没设过,清掉。 + * + *

⚠ 只在**完全等于**内置值时清 —— 用户自己手填的地址不能动。 + */ + private static final String[] LEGACY_BAKED_IN_RELAYS = { + "https://relay.xxmxxs.top", + "http://relay.xxmxxs.top", + }; + + private void migrateLegacyRelay() { + String saved = getSharedPreferences("lanremote", MODE_PRIVATE) + .getString("relay_url", ""); + if (saved == null) { + return; + } + String trimmed = saved.trim(); + for (String legacy : LEGACY_BAKED_IN_RELAYS) { + if (legacy.equalsIgnoreCase(trimmed)) { + // 这是老版本硬塞的,不是用户选的 —— 清掉,让他自己填 + getSharedPreferences("lanremote", MODE_PRIVATE).edit() + .remove("relay_url").apply(); + appendLog("已清掉旧版本内置的中转地址(不是你自己填的)"); + return; + } + } + } + private String relayUrl() { String saved = getSharedPreferences("lanremote", MODE_PRIVATE) .getString("relay_url", DEFAULT_RELAY_URL); @@ -199,6 +237,11 @@ public class MainActivity extends Activity implements RemoteClient.Listener { private final StringBuilder logBuffer = new StringBuilder(); // ---- 局域网连接页面 ---- private FrameLayout lanPanel; + // ---- 设置页(用户要求:各种设置收在一个页面里调)---- + private FrameLayout settingsPanel; + private TextView settingsRelayValue; + private TextView settingsCodeValue; + private TextView settingsDevicesValue; private LinearLayout lanResultBox; private EditText lanIpInput; private EditText lanPortInput; @@ -215,6 +258,8 @@ public class MainActivity extends Activity implements RemoteClient.Listener { super.onCreate(savedInstanceState); store = new DeviceStore(this); + // 老版本硬塞的中转地址要清掉(见 migrateLegacyRelay) + migrateLegacyRelay(); ui = new Handler(Looper.getMainLooper()); client = new RemoteClient(); client.setDevice(store.deviceId(), store.deviceName()); @@ -234,6 +279,7 @@ public class MainActivity extends Activity implements RemoteClient.Listener { initTabs(); buildMirrorPanel(); buildLanPanel(); + buildSettingsPanel(); root.addView(controlPanel, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, @@ -482,7 +528,7 @@ public class MainActivity extends Activity implements RemoteClient.Listener { // 用户原话:"我就是连不上才开设备诊断,连的上要设备诊断干嘛", // 所以它属于首页,不属于功能标签页。 // 中转地址:跨网络用。放首页是因为这是"连不上"的常见原因之一。 - Button relayButton = bigButton("中转设置(跨网络用)", C_ACCENT, 14); + Button relayButton = bigButton("设置", C_ACCENT, 14); LinearLayout.LayoutParams relayParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, dp(52)); relayParams.setMargins(0, 0, 0, dp(8)); @@ -490,7 +536,7 @@ public class MainActivity extends Activity implements RemoteClient.Listener { relayButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - promptRelayUrl(); + showSettingsPanel(); } }); box.addView(relayButton); @@ -1970,6 +2016,208 @@ public class MainActivity extends Activity implements RemoteClient.Listener { // 局域网连接(不经过中转) // ================================================================== + /** + * 「设置」页。用户要求:"安卓端加一个设置,各种设置就在这里面调"。 + * + *

以前设置散在首页(一个「中转设置」按钮)和别处, + * 现在统一收到这里。 + */ + private void buildSettingsPanel() { + settingsPanel = new FrameLayout(this); + settingsPanel.setBackgroundColor(C_BG); + + ScrollView scroll = new ScrollView(this); + scroll.setBackgroundColor(C_BG); + LinearLayout box = new LinearLayout(this); + box.setOrientation(LinearLayout.VERTICAL); + box.setPadding(dp(10), dp(12), dp(10), dp(12)); + scroll.addView(box, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + settingsPanel.addView(scroll, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + LinearLayout top = new LinearLayout(this); + top.setOrientation(LinearLayout.HORIZONTAL); + top.setGravity(Gravity.CENTER_VERTICAL); + box.addView(top, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + + TextView title = new TextView(this); + title.setText("设置"); + title.setTextColor(C_FG); + title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + title.setTypeface(null, android.graphics.Typeface.BOLD); + top.addView(title, new LinearLayout.LayoutParams( + 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); + top.addView(smallButton("返回", C_CARD, new Runnable() { + @Override + public void run() { + hideSettingsPanel(); + } + })); + + // ================= 中转服务器 ================= + settingsRelayValue = new TextView(this); + settingsRelayValue.setTextColor(C_DIM); + settingsRelayValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); + settingsRelayValue.setPadding(0, dp(6), 0, dp(4)); + + box.addView(sectionTitle("中转服务器")); + box.addView(hintText("手表和电脑不在同一个网络时(比如在办公室管教室)" + + "才需要填。填你自己那台的地址,比如 relay.你的域名。\n" + + "只在一个 WiFi 里用就留空 —— 那样直接连,最快也不耗流量。")); + box.addView(settingsRelayValue); + + Button editRelay = bigButton("填写中转服务器", C_ACCENT, 14); + editRelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + promptRelayUrl(); + } + }); + box.addView(editRelay); + + Button clearRelay = bigButton("清空中转服务器(只走局域网)", C_CARD, 13); + clearRelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + setRelayUrl(""); + refreshSettingsPanel(); + toast("已清空 —— 以后只走局域网"); + } + }); + box.addView(clearRelay); + + // ================= 配对码 ================= + settingsCodeValue = new TextView(this); + settingsCodeValue.setTextColor(C_DIM); + settingsCodeValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); + settingsCodeValue.setPadding(0, dp(6), 0, dp(4)); + + box.addView(sectionTitle("记住的配对码")); + box.addView(hintText("手表会记住最近一次用的配对码," + + "下次连接自动带上,不用重复输。")); + box.addView(settingsCodeValue); + + Button clearCode = bigButton("清除记住的配对码", C_CARD, 13); + clearCode.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + getSharedPreferences("lanremote", MODE_PRIVATE).edit() + .remove("paired_code").apply(); + refreshSettingsPanel(); + toast("已清除"); + } + }); + box.addView(clearCode); + + // ================= 已配对设备 ================= + settingsDevicesValue = new TextView(this); + settingsDevicesValue.setTextColor(C_DIM); + settingsDevicesValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); + settingsDevicesValue.setPadding(0, dp(6), 0, dp(4)); + + box.addView(sectionTitle("已配对的电脑")); + box.addView(hintText("配对过的电脑会出现在首页列表里,点一下就能连。" + + "换电脑或者想重新配对时可以清空。")); + box.addView(settingsDevicesValue); + + Button clearDevices = bigButton("清空已配对的电脑", C_DANGER, 13); + clearDevices.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new android.app.AlertDialog.Builder(MainActivity.this) + .setTitle("清空已配对的电脑?") + .setMessage("清掉之后首页列表就空了," + + "需要重新输配对码连接。") + .setPositiveButton("清空", new android.content.DialogInterface.OnClickListener() { + @Override + public void onClick(android.content.DialogInterface d, int w) { + store.clearDevices(); + refreshDeviceList(); + refreshSettingsPanel(); + toast("已清空"); + } + }) + .setNegativeButton("取消", null) + .show(); + } + }); + box.addView(clearDevices); + + // ================= 关于 ================= + box.addView(sectionTitle("关于")); + box.addView(hintText("ykxmz 手表端 版本 " + appVersion() + + "\n\n" + + "连接顺序:先试局域网直连(同一个 WiFi,几毫秒);" + + "连不上再走中转(跨网络,受线路影响)。")); + } + + /** 一行小节标题 */ + private TextView sectionTitle(String text) { + TextView v = new TextView(this); + v.setText(text); + v.setTextColor(C_FG); + v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); + v.setTypeface(null, android.graphics.Typeface.BOLD); + v.setPadding(0, dp(14), 0, 0); + return v; + } + + /** 一行灰色说明 */ + private TextView hintText(String text) { + TextView v = new TextView(this); + v.setText(text); + v.setTextColor(C_DIM); + v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11); + v.setLineSpacing(0, 1.25f); + v.setPadding(0, dp(4), 0, 0); + return v; + } + + /** 版本号。从 PackageManager 读,和构建脚本里的版本天然一致, + * 不用在代码里再写一份(写两份必然有一份会忘改)。 */ + private String appVersion() { + try { + return getPackageManager() + .getPackageInfo(getPackageName(), 0).versionName; + } catch (Exception e) { + return "?"; + } + } + + /** 把设置页上的文字刷新成当前值 */ + private void refreshSettingsPanel() { + if (settingsRelayValue == null) { + return; + } + String relay = relayUrl(); + settingsRelayValue.setText(relay.isEmpty() + ? "现在是:空(只走局域网)" + : "现在是:" + relay); + settingsRelayValue.setTextColor(relay.isEmpty() ? C_DIM : C_OK); + + String code = savedPairingCode(); + settingsCodeValue.setText((code == null || code.isEmpty()) + ? "现在没有记住的配对码" + : "记住的是:" + code); + + int n = store.devices() == null ? 0 : store.devices().size(); + settingsDevicesValue.setText("已配对 " + n + " 台电脑"); + } + + private void showSettingsPanel() { + refreshSettingsPanel(); + showOnly(settingsPanel); + } + + private void hideSettingsPanel() { + showConnectPanel(); + } + private void buildLanPanel() { lanPanel = new FrameLayout(this); lanPanel.setBackgroundColor(C_BG); @@ -2311,7 +2559,8 @@ public class MainActivity extends Activity implements RemoteClient.Listener { *

以后加面板只需要往这个方法里加一行,所有切换点自动都对。 */ private void showOnly(View keep) { - View[] all = {connectPanel, controlPanel, mirrorPanel, lanPanel}; + View[] all = {connectPanel, controlPanel, mirrorPanel, lanPanel, + settingsPanel}; for (View v : all) { if (v != null) { v.setVisibility(v == keep ? View.VISIBLE : View.GONE); diff --git a/docs/AFTER-INSTALL.txt b/docs/AFTER-INSTALL.txt index abe0cba..f50db49 100644 --- a/docs/AFTER-INSTALL.txt +++ b/docs/AFTER-INSTALL.txt @@ -3,7 +3,7 @@ ======================================================== 软件名称:ykxmz -版本:1.9.0 +版本:2.0.0 作者:小麦子 装好之后看这一页:怎么确认它在跑、怎么改设置、 @@ -197,7 +197,7 @@ 八、版本记录 -------------------------------------------------------- - 1.9.0 + 2.0.0 * 下载改走 Cloudflare 边缘缓存 —— 安装包和 APK 快了几十倍 * 跨网络更稳:隧道**两种协议同时跑**(QUIC + HTTP/2, 合计 10 条连接),一种协议犯病另一种还在服务 @@ -218,6 +218,6 @@ -------------------------------------------------------- 作者:小麦子 - 软件:ykxmz 1.9.0 + 软件:ykxmz 2.0.0 许可条款见《许可协议》(LICENSE.txt) -------------------------------------------------------- diff --git a/docs/BEFORE-INSTALL.txt b/docs/BEFORE-INSTALL.txt index e13b806..1fb703e 100644 --- a/docs/BEFORE-INSTALL.txt +++ b/docs/BEFORE-INSTALL.txt @@ -3,7 +3,7 @@ ======================================================== 软件名称:ykxmz -版本:1.9.0 +版本:2.0.0 作者:小麦子 请你**在安装之前**花三分钟读完这一页。里面有几条很要紧的 @@ -164,6 +164,6 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成: -------------------------------------------------------- 作者:小麦子 - 软件:ykxmz 1.9.0 + 软件:ykxmz 2.0.0 继续安装,即表示你已阅读并同意《许可协议》。 -------------------------------------------------------- diff --git a/installer/license-combined.txt b/installer/license-combined.txt index b2420a7..248a3ff 100644 --- a/installer/license-combined.txt +++ b/installer/license-combined.txt @@ -3,7 +3,7 @@ ======================================================== 软件名称:ykxmz(远程管理工具) -版本:1.9.0 +版本:2.0.0 作者:小麦子 生效日期:2026 年 10 月 5 日 @@ -150,7 +150,7 @@ -------------------------------------------------------- 作者:小麦子 - 软件:ykxmz 1.9.0 + 软件:ykxmz 2.0.0 如果你不同意本协议的任何一条,请不要安装或使用本软件。 -------------------------------------------------------- @@ -162,7 +162,7 @@ ======================================================== 软件名称:ykxmz -版本:1.9.0 +版本:2.0.0 作者:小麦子 请你**在安装之前**花三分钟读完这一页。里面有几条很要紧的 @@ -323,7 +323,7 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成: -------------------------------------------------------- 作者:小麦子 - 软件:ykxmz 1.9.0 + 软件:ykxmz 2.0.0 继续安装,即表示你已阅读并同意《许可协议》。 -------------------------------------------------------- @@ -334,7 +334,7 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成: ======================================================== 软件名称:ykxmz -版本:1.9.0 +版本:2.0.0 作者:小麦子 装好之后看这一页:怎么确认它在跑、怎么改设置、 @@ -528,7 +528,7 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成: 八、版本记录 -------------------------------------------------------- - 1.9.0 + 2.0.0 * 下载改走 Cloudflare 边缘缓存 —— 安装包和 APK 快了几十倍 * 跨网络更稳:隧道**两种协议同时跑**(QUIC + HTTP/2, 合计 10 条连接),一种协议犯病另一种还在服务 @@ -549,6 +549,6 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成: -------------------------------------------------------- 作者:小麦子 - 软件:ykxmz 1.9.0 + 软件:ykxmz 2.0.0 许可条款见《许可协议》(LICENSE.txt) -------------------------------------------------------- diff --git a/installer/ykxmz.iss b/installer/ykxmz.iss index ad3909e..136b40b 100644 --- a/installer/ykxmz.iss +++ b/installer/ykxmz.iss @@ -27,7 +27,7 @@ ; ============================================================================ #define MyAppName "ykxmz" -#define MyAppVersion "1.9.0" +#define MyAppVersion "2.0.0" #define MyAppPublisher "小麦子" #define MyAppExeName "ykxmz.exe" @@ -137,6 +137,20 @@ Type: filesandordirs; Name: "{app}\data" Filename: "{app}\{#MyAppExeName}"; Parameters: "--code {code:GetPairingCode}"; \ Flags: runhidden; Check: HasPairingCode +; 1b. 写中转服务器(向导里留空就跳过)。 +; 用户要求"安装时就能设中转服务器" —— 装的时候是第一次设置, +; 最该一次填好,不然装完还得去双击 exe 找设置窗口。 +; +; 用了 --relay-url 而不是 --relay: +; --relay 会连接一次然后退出(人工排查用), +; 放在这条命令链里会让后面的 --silent 跑不到 +; --relay-url 只写配置,不连不问,适合安装流程 +; +; 地址两侧的引号是必须的:URL 里有 "//" 和可能的 ":", +; 不加引号 Inno 会按空格切参数。 +Filename: "{app}\{#MyAppExeName}"; Parameters: "--relay-url ""{code:GetRelayUrl}"""; \ + Flags: runhidden; Check: HasRelay + ; 2. 开机自启 Filename: "{app}\{#MyAppExeName}"; Parameters: "--autostart on"; \ Flags: runhidden; Tasks: autostart @@ -149,7 +163,7 @@ Filename: "{app}\{#MyAppExeName}"; Parameters: "--autostart on"; \ ; 代价是安装器和它是父子关系:用"等整个进程树"的方式调安装器会卡住 ; (PowerShell 的 Start-Process -Wait 就是)。 ; 学校批量部署请用 cmd: -; cmd /c ykxmz-setup-1.9.0.exe /VERYSILENT /CODE=123456 +; cmd /c ykxmz-setup-2.0.0.exe /VERYSILENT /CODE=123456 ; cmd 只等安装器本身,不等孙子进程,不会卡。 Filename: "{app}\{#MyAppExeName}"; Parameters: "--silent"; \ Description: "立即启动(后台运行,不显示窗口)"; \ @@ -182,6 +196,7 @@ Type: dirifempty; Name: "{app}" var CodePage: TInputQueryWizardPage; ForcedCode: String; + ForcedRelay: String; { 把字符串里的非数字滤掉。 } function OnlyDigits(const raw: String): String; @@ -198,22 +213,59 @@ begin end; end; -{ 配对码那一页。Inno 没有现成的输入页,用官方的 CreateInputQueryPage。 } +{ 配对码 + 中转服务器那一页。 + Inno 没有现成的输入页,用官方的 CreateInputQueryPage(不算自己造轮子)。 + + 为什么要在这里也能填中转服务器(用户要求): + 装的时候是"第一次设置",那时候最该一次把地址填好 —— + 不然装完还得让人去双击 exe 找设置窗口,很容易漏。 + 装完之后照样能改:双击 exe 打开设置窗口就有这一栏。 } procedure InitializeWizard(); begin - { 静默部署可以带 /CODE=123456 直接把码传进来(电教批量装用这个)。 + { 静默部署可以带 /CODE=123456 /RELAY=https://... 直接把值传进来。 注意:Inno 的 [Code] 段连**注释里的花括号**都会当常量去解析, 所以这里不能直接写 param 那种写法,否则编译器报语法错误。 } ForcedCode := OnlyDigits(ExpandConstant('{param:CODE|}')); + ForcedRelay := Trim(ExpandConstant('{param:RELAY|}')); CodePage := CreateInputQueryPage(wpSelectTasks, - '配对码', + '配对码和中转服务器', '给这台电脑定一个 6 位配对码', '手表上要输这一串数字才能连上这台电脑。自己定个好记的,' + - '比如 789113。装完之后也可以在被控端窗口里改。'); + '比如 789113。' + #13#10 + #13#10 + + '中转服务器是**跨网络**才需要的(比如在办公室管教室)。' + + '只在一个 WiFi 里用就留空,那样手表和电脑直接连,最快。'); CodePage.Add('6 位配对码:', False); + CodePage.Add('中转服务器(可以不填):', False); if ForcedCode <> '' then CodePage.Values[0] := ForcedCode; + if ForcedRelay <> '' then + CodePage.Values[1] := ForcedRelay; +end; + +{ 规范化中转地址。规则必须和 run_agent.py 的 normalize_relay_url() 一致: + 去首尾空白 -> 没协议头就补 https:// -> 去末尾斜杠 + 两边不一致会出现"向导里显示一个样、文件里存另一个样"的怪事。 + + ⚠ 不用 LowerCase:Inno Pascal 各版本支持不一致, + 直接四种写法都比一遍最保险。 } +function NormalizeRelay(raw: String): String; +var + s: String; +begin + s := Trim(raw); + if s = '' then + begin + Result := ''; + Exit; + end; + { 先削末尾斜杠再补协议头 —— 顺序反了会把 https:// 的斜杠也削掉 } + while (Length(s) > 0) and (s[Length(s)] = '/') do + Delete(s, Length(s), 1); + if (Pos('http://', s) <> 1) and (Pos('HTTP://', s) <> 1) + and (Pos('https://', s) <> 1) and (Pos('HTTPS://', s) <> 1) then + s := 'https://' + s; + Result := s; end; { 校验:必须是 6 位数字。 @@ -244,6 +296,37 @@ begin end; { 规范化,后面 [Run] 阶段用得到 } CodePage.Values[0] := digits; + + { ---- 中转地址的检查 ---- + 只做最基本的:不许有空格、不许有明显不对的字符。 + 不做"能不能连上"的检查 —— 装的时候那台服务器可能还没起, + 卡在这里会让用户装不下去。地址写错了装完还能改。 } + raw := Trim(CodePage.Values[1]); + if raw <> '' then + begin + if Pos(' ', raw) > 0 then + begin + MsgBox('中转服务器地址里不能有空格。' + #13#10 + #13#10 + + '你填的是:' + raw + #13#10 + #13#10 + + '只填域名就行,比如 relay.你的域名.com' + #13#10 + + '(协议头 https:// 不写也会自动加上)', + mbError, MB_OK); + Result := False; + Exit; + end; + if Pos('.', raw) = 0 then + begin + MsgBox('中转服务器地址看着不像个域名。' + #13#10 + #13#10 + + '你填的是:' + raw + #13#10 + #13#10 + + '应该形如 relay.你的域名.com。' + #13#10 + + '只在一个 WiFi 里用的话,这一栏留空即可。', + mbError, MB_OK); + Result := False; + Exit; + end; + end; + { 规范化后再写回,让用户看到最终会用哪个地址 } + CodePage.Values[1] := NormalizeRelay(raw); end; { 最终用的配对码:命令行 /CODE 优先,其次是向导里填的。 @@ -280,6 +363,28 @@ begin Result := EffectiveCode() <> ''; end; +{ 最终用的中转地址:命令行 /RELAY 优先,其次是向导里填的。 + 空的就返回空串 —— 那条命令会被 HasRelay 跳过。 } +function EffectiveRelay(): String; +begin + Result := NormalizeRelay(ForcedRelay); + if Result = '' then + Result := NormalizeRelay(CodePage.Values[1]); +end; + +{ 中转服务器。配 HasRelay 一起用 —— 留空时那条命令会被跳过, + 这样"只在一个 WiFi 里用"的人不会多写一个空地址进配置。 } +function GetRelayUrl(Param: String): String; +begin + Result := EffectiveRelay(); +end; + +{ 填了中转地址才跑那条 --relay-url 命令 } +function HasRelay(): Boolean; +begin + Result := EffectiveRelay() <> ''; +end; + { 卸载前先停掉正在跑的进程,不然文件删不干净。 必须在这里做,**不能**改到 UninstallRun 区段去: diff --git a/relay/relay.py b/relay/relay.py index 1721558..de64ab4 100644 --- a/relay/relay.py +++ b/relay/relay.py @@ -59,7 +59,7 @@ from collections import deque from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path -VERSION = "1.9.0" +VERSION = "2.0.0" # ---------------- 参数(都能用环境变量覆盖)---------------- diff --git a/windows/lanremote/__init__.py b/windows/lanremote/__init__.py index 6948f31..b85b526 100644 --- a/windows/lanremote/__init__.py +++ b/windows/lanremote/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- """局域网遥控 · Windows 被控端。""" -__version__ = "1.9.0" +__version__ = "2.0.0" APP_NAME = "ykxmz" diff --git a/windows/run_agent.py b/windows/run_agent.py index bb39c19..8d5f368 100644 --- a/windows/run_agent.py +++ b/windows/run_agent.py @@ -70,6 +70,8 @@ def parse_args(argv=None): help="网络自检(自开控制台,不需要装 Python)") parser.add_argument("--fix-firewall", action="store_true", help="提权修复防火墙") + parser.add_argument("--relay-url", default=None, metavar="地址", + help="只把中转地址写进配置,不连接、不退出(安装包用)") parser.add_argument("--relay", default=None, metavar="地址", help="设置中转服务器地址(跨网络用)。" "可用分号分隔多个,按顺序尝试,例如:" @@ -274,19 +276,39 @@ def cli_fix_firewall() -> int: return 0 if ok else 1 +def normalize_relay_url(raw: str) -> str: + """把用户填的中转地址规范化。 + + 规则(和安装向导、设置窗口保持**完全一致**): + * 去掉首尾空白 + * 没有 http:// / https:// 就补 https:// + (用户基本只会填个域名,逼他打协议头没意义) + * 去掉末尾斜杠(拼接 "/v1/health" 时不会变成双斜杠) + * 空字符串 = 关掉中转,只走局域网 + + ⚠ 为什么抽成函数:原来这段逻辑散在 cli_relay 里,安装向导 + 再写一份就容易两边不一致。现在统一走这里。 + """ + url = (raw or "").strip() + if not url: + return "" + # 先去掉末尾斜杠,再补协议头(顺序反了会把 "https://" 的斜杠也削掉) + url = url.rstrip("/") + if not url.startswith(("http://", "https://")): + url = "https://" + url + return url + + def cli_relay(url: str) -> int: """设置中转地址。传空字符串就关掉。""" store = Store(data_dir()) - url = (url or "").strip().rstrip("/") + url = normalize_relay_url(url) store.set("relay_url", url) attach_console() if not url: print("已关闭中转(以后只在局域网内可用)") wait_key() return 0 - if not url.startswith(("http://", "https://")): - url = "https://" + url - store.set("relay_url", url) print(f"中转地址已设为:{url}") print() print("正在测试能不能连上…") @@ -782,14 +804,30 @@ def main(argv=None) -> int: args = parse_args(argv) - # --code / --name 要**最先**处理 —— 它们会改配置, + # --code / --name / --relay-url 要**最先**处理 —— 它们会改配置, # 而 --status / --autostart 这些是"看一眼就退出"的命令。 # 安装包的调用链是: - # ykxmz.exe --code 789113 --name 三年二班 --autostart on --silent + # ykxmz.exe --code 789113 --name 三年二班 --relay-url https://... --autostart on --silent # 如果先把 --autostart 处理掉就 return 了,配对码根本写不进去。 - if args.code is not None or args.name: + if (args.code is not None or args.name + or args.relay_url is not None): store = Store(data_dir()) wrote = False + + # ---- 中转服务器(安装向导那一页填的)---- + # ⚠ 和 --relay 的区别很重要: + # --relay 连接一次、打印状态、然后 return + # (人工排查用,**不能**和 --code 连用, + # 因为它会提前退出,后面的 --silent 就不跑了) + # --relay-url 只写进配置,不连接、不退出 + # (安装包用:要和 --code/--autostart/--silent + # 串在一条命令里跑完) + if args.relay_url is not None: + url = normalize_relay_url(str(args.relay_url)) + store.set("relay_url", url) + print(f"中转服务器已设为 {url or '(空 —— 只走局域网,不跨网络)'}") + wrote = True + if args.code is not None: digits = "".join(ch for ch in str(args.code) if ch.isdigit()) if len(digits) != 6: diff --git a/windows/version_info.txt b/windows/version_info.txt index 150f0ed..0542dc7 100644 --- a/windows/version_info.txt +++ b/windows/version_info.txt @@ -27,12 +27,12 @@ VSVersionInfo( [ StringStruct("CompanyName", "ykxmz"), StringStruct("FileDescription", "ykxmz"), - StringStruct("FileVersion", "1.9.0.0"), + StringStruct("FileVersion", "2.0.0.0"), StringStruct("InternalName", "ykxmz"), StringStruct("LegalCopyright", "ykxmz"), StringStruct("OriginalFilename", "ykxmz.exe"), StringStruct("ProductName", "ykxmz"), - StringStruct("ProductVersion", "1.9.0.0"), + StringStruct("ProductVersion", "2.0.0.0"), ], ) ]