# -*- coding: utf-8 -*- """统一打包:被控端 + 手表端(已内置诊断)+ 文档。 注意:遥控诊断原来是一个单独的 App,现在已合并进手表主程序 (主界面 ->「设备诊断」按钮),所以不再单独出 APK。 产物(都在 dist/): ykxmz-Windows.zip 被控端(解压即用,内含工具箱) LanRemote-Watch-1.3.0.apk 手表控制端(含设备诊断) LanRemote-1.3.0-完整包.zip 以上全部 + 预览图 + 文档 被控端目录结构(解压后): LanRemote-Agent/ ├── (已移除) <- 双击这个,所有功能都在菜单里 ├── ykxmz.exe ├── _internal/ ├── xffhq.bat ├── wlzj.bat / .py ├── 使用说明.txt └── 先看这个.txt """ from __future__ import annotations import shutil import sys import zipfile from pathlib import Path VERSION = "1.4.0" ROOT = Path(__file__).resolve().parent.parent DIST = ROOT / "dist" EXE_DIR = ROOT / "dist_exe" / "ykxmz" TOOLS = ROOT / "tools" PREVIEW = ROOT / "dist_preview" README = ROOT / "README.md" TEST_REPORT = DIST / "测试报告.txt" APK = DIST / f"LanRemote-Watch-{VERSION}.apk" AGENT_ZIP = DIST / "ykxmz-Windows.zip" BUNDLE = DIST / f"LanRemote-{VERSION}-完整包.zip" # 会被放进被控端目录的工具(工具箱必须排第一) TOOL_FILES = ( "fxfhq.bat", "wlzj.bat", "xffhq.bat", ) QUICKSTART = f"""======================================================== 局域网遥控 · 被控端(Windows) 版本 {VERSION} ======================================================== *** 不需要装 Python *** 这个目录里的 ykxmz.exe 是编译好的独立程序, 拷到任何一台 Windows 电脑上双击就能跑。 【装完之后怎么用】 1) 双击 ykxmz.exe 2) 弹出一个小窗口,让你输入 6 位数字(配对码) 自己定一个记得住的,比如 789113 下面有两个勾: [x] 设置开机自启(开机后自动在后台运行) [x] 顺便放行防火墙(需要管理员确认一次) 3) 点「确定,开始使用」 → 会弹一次 Windows 的管理员确认(UAC),点「是」 → 黑色终端窗口一闪,它在一键修复防火墙 → 窗口全部消失,程序转入后台静默运行 4) 手表上打开 App → 点「自动搜索电脑」→ 点搜到的电脑 → 输入你设的 6 位数字 5) 以后开机就直接在后台跑,屏幕上什么都没有; 手表打开 App 会自动连上,数字都不用再输。 【目录里这几个文件】 ykxmz.exe 主程序。双击就是上面第 1 步。 wlzj.bat 网络自检。连不上时跑它,看手表的包到底有没有到电脑。 xffhq.bat 一键修复防火墙。右键 →「以管理员身份运行」。 fxfhq.bat 同上,是程序自动生成的放行脚本,也可以右键提权运行。 先看这个.txt / 使用说明.txt 就是本文。 三个 .bat 都不需要装 Python。 【连不上?按这个顺序来】 1) 确认被控端在跑 任务管理器里能看到 ykxmz.exe 没有的话:ykxmz.exe --setup 2) 右键 xffhq.bat →「以管理员身份运行」,点「是」 3) 还不行 → 双击 wlzj.bat(网络自检) 它会监听 90 秒,看手表的请求有没有到达这台电脑: 收到连接 -> 网络是通的,问题在电脑这边 一直没动静 -> 包没到电脑,多半路由器开了「AP 隔离」 4) 手表上:首页「连不上?点这里诊断」 【命令行参数】(一般用不到) ykxmz.exe --setup 重新打开设置窗口 ykxmz.exe --silent 直接静默运行 ykxmz.exe --add-device 10 静默运行 + 开放 10 分钟配对 ykxmz.exe --autostart on|off 开关开机自启 ykxmz.exe --status 看当前状态 ykxmz.exe --netcheck 90 网络自检(同 wlzj.bat) ykxmz.exe --fix-firewall 提权修复防火墙 ykxmz.exe --reset-devices 清空已配对设备 【关于安全】 配对码是你自己设的,只存在这台电脑的 data\\settings.json 里。 一台设备都没配对过时配对是开着的(否则你设完码会连不上自己); 第一台配对成功后自动关闭。 配错 10 次锁 5 分钟(按来源 IP 分别计,别人锁不住你)。 屏幕共享时右下角会出现一条红色小提示 —— 这是故意的:有人在看你的屏幕时,屏幕前面的人应该知道。 """ def clean_runtime_state() -> None: """打包前清掉运行时产生的东西,保持交付干净。""" for name in ("data", "received"): target = EXE_DIR / name if target.exists(): shutil.rmtree(target, ignore_errors=True) print(f" 已清理 {target.relative_to(ROOT)}") for stale in ("fxfhq.bat",): path = EXE_DIR / stale if path.exists(): path.unlink() print(f" 已清理 {stale}") def install_tools() -> list[str]: installed = [] for name in TOOL_FILES: source = TOOLS / name if source.is_file(): shutil.copy2(source, EXE_DIR / name) installed.append(name) else: print(f" [警告] 缺少工具: {name}") return installed def zip_dir(source: Path, target: Path, arc_prefix: str = "") -> int: files = [p for p in source.rglob("*") if p.is_file()] with zipfile.ZipFile(target, "w", zipfile.ZIP_DEFLATED, compresslevel=6) as zf: for path in files: rel = path.relative_to(source) arc = str(Path(arc_prefix) / rel) if arc_prefix else str(rel) zf.write(path, arc) print(f" {target.name} ({target.stat().st_size / 1048576:.1f} MB, {len(files)} 个文件)") return len(files) def main() -> int: missing = [] if not EXE_DIR.is_dir(): missing.append(f"被控端产物目录 {EXE_DIR}(先运行 windows\\build_exe.ps1)") if not APK.is_file(): missing.append(f"{APK.name}(先运行 android\\build_apk.ps1)") if missing: print("缺少产物:") for item in missing: print(f" - {item}") return 1 DIST.mkdir(parents=True, exist_ok=True) print("清理测试残留:") clean_runtime_state() print("\n安装工具到被控端目录:") installed = install_tools() for name in installed: print(f" + {name}") (EXE_DIR / "使用说明.txt").write_text(QUICKSTART, encoding="utf-8") (EXE_DIR / "先看这个.txt").write_text(QUICKSTART, encoding="utf-8") print(" + 使用说明.txt / 先看这个.txt") print("\n打包被控端:") count = zip_dir(EXE_DIR, AGENT_ZIP, arc_prefix="ykxmz") print("\n打包完整交付:") with zipfile.ZipFile(BUNDLE, "w", zipfile.ZIP_DEFLATED, compresslevel=6) as zf: zf.writestr("先看这个.txt", QUICKSTART) zf.write(APK, f"1-手表控制端/{APK.name}") zf.write(AGENT_ZIP, f"2-电脑被控端/{AGENT_ZIP.name}") if PREVIEW.is_dir(): for shot in sorted(PREVIEW.glob("*.png")): zf.write(shot, f"3-界面预览/{shot.name}") if README.is_file(): zf.write(README, "使用说明.md") if TEST_REPORT.is_file(): zf.write(TEST_REPORT, "测试报告.txt") print(f" {BUNDLE.name} ({BUNDLE.stat().st_size / 1048576:.1f} MB)") print("\n" + "=" * 62) print(f"交付内容(版本 {VERSION}):") for path in (APK, AGENT_ZIP, BUNDLE): print(f" {path.stat().st_size / 1048576:7.2f} MB {path.name}") print(f"\n被控端包内含 {count} 个文件,工具箱等 {len(installed)} 个工具已就位。") print("遥控诊断已合并进手表主程序,不再单独出包。") return 0 if __name__ == "__main__": sys.exit(main())