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 @@
1.5.0 那版把作者的域名写死成默认值:
+ * 现在默认值是空串(跨网络要自己填)。这里做一次性迁移:
+ * 存的值如果正好是当时内置的那个域名,就当成没设过,清掉。
+ *
+ * ⚠ 只在**完全等于**内置值时清 —— 用户自己手填的地址不能动。
+ */
+ 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"),
],
)
]
+ * DEFAULT_RELAY_URL 这个常量写的是 https://relay.xxmxxs.top
+ *
+ * 而且第一次运行就存进了 SharedPreferences。装新 APK 不会清偏好,
+ * 所以用户会看到「默认还是那个中转站」—— 其实他从来没选过。
+ *
+ *