Files
ykxmz/README.md
T
xmz7891 b62ac865ea ykxmz 1.5.0:修复手表端 TLS 证书校验 + MTU 修复网络稳定性
被控端
- 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%)
- 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败)
- 每分钟回头找局域网,自动切回快 400 倍的路径
- 画面心跳帧:静止时省流量,客户端仍知道流是活的
- 锁屏时给出可操作的提示

手表端
- 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException
- 连接错误翻译成人话(证书/时间/解析/超时分别提示)
- 经中转连上后用服务器报的地址刷新已配对记录
- 新增局域网连接页面、服务器延迟检测

服务器端
- 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502)
- 网络调优脚本 net-tune.sh(开机自动应用)
- 管理页:批量删除、网络体检、下载区
- 隧道改 HTTP/2 + 双连接

文档
- 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
2026-10-06 19:01:05 +08:00

656 lines
31 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 局域网遥控 · LAN Remote
用手表(安卓)遥控自己的电脑。同一个 WiFi 下即可,不经过任何服务器。
```
┌─────────────────┐ 局域网 TCP ┌──────────────────┐
│ 手表控制端 APK │ ─────────────────────────► │ Windows 被控端 │
│ 490×602 大按钮 │ ◄───────────────────────── │ 可见托盘 + 配对码 │
└─────────────────┘ └──────────────────┘
```
---
## 一、先说清楚这个工具「不是什么」
你最初的描述里,有几条我**明确没有做**,因为它们属于恶意软件特征:
| 你提的要求 | 为什么不做 |
|---|---|
| 注册成系统更新组件、**杀毒软件杀不掉** | 这是免杀对抗。正常远控工具(TeamViewer、向日葵、AnyDesk)从不做这个。 |
| 托盘不显示、**任务管理器里看不到** | 这是对电脑使用者隐藏自身。 |
| 伪装成普通系统文件常驻 | 持久化 + 伪装。 |
**原因很简单**:如果被控的是你自己的电脑,你不需要隐藏——你会希望它可见、能管理、能一键断开。而"杀软杀不掉、任务管理器看不到"只在一种场景下有意义:**用电脑的人不知道你在控制它**。
所以这一版是:**被控端全程可见、可随时断开、权限可随时关闭**。它是个正经工具,不是木马。
---
## 二、快速开始(3 步)
### 第 1 步:电脑端
把 `LanRemote-Agent-Windows.zip` 解压到任意目录,双击 `LanRemote-Agent.exe`。
程序会弹出窗口,**窗口中间有一组 6 位配对码**,例如:
```
在手表上输入这组配对码
4 5 4 5 3 4
```
窗口里同时能看到:
- 当前连接状态
- 防火墙是否放行(没放行会显示橙色提示 + 「一键修复」按钮)
- 「打开接收目录」「退出」
**关掉窗口 = 程序继续在后台跑**(没有托盘图标)。
想再打开窗口,**再双击一次 EXE** 就行,不会重复起进程。
想彻底退出,在窗口上点「退出」。
### 第 2 步:手表端
安装 `LanRemote-Watch-1.3.0.apk`,打开后点那个大按钮 **「自动搜索电脑」**。
App 会往局域网广播一个探测包,被控端收到后回自己的名字和端口,
**所以手表端不用输 IP、不用管网段、也不用管电脑上装了几个虚拟网卡**。
搜到之后点一下那台电脑,输入被控端窗口上的 6 位数字,就连上了。
### 第 3 步:之后就不用再输了
配对成功后令牌保存在手表上,**下次打开 App 会自动搜到并直接连**,
连数字都不用再输。
多台电脑就配对多次,列表里点哪个连哪个。长按可以删除配对。
> 如果搜不到,展开「手动输入地址」可以自己填 IP 和端口 —— 但绝大多数情况
> 下搜不到都不是 IP 的问题,而是**防火墙没放行**(见下面第五节)。
---
### 工具箱(唯一入口)
被控端目录里有个 **`工具箱.bat`**,双击它就能看到所有功能:
```
============================================================
局域网遥控 . 工具箱 版本 1.3.0
============================================================
1. 启动被控端 日常使用就点这个
2. 一键修复防火墙 连不上时先试这个(需要管理员)
3. 网络自检 还连不上就用它排查
4. 查看使用说明
5. 查看运行日志
6. 打开接收文件夹
7. 查看当前防火墙状态
0. 退出
```
排查连接问题的推荐顺序:**1 → 2 → 3**。
| 选项 | 作用 |
|---|---|
| **1 启动被控端** | 确认程序在跑(任务管理器里能看到 `LanRemote-Agent.exe`) |
| **2 一键修复防火墙** | 弹一次 UAC 点「是」。删掉「阻止」规则 + 补一条**程序级**放行规则。后者才是治本 |
| **3 网络自检** | 跑起来后到手表上点「测一下」,看电脑窗口有没有 `>>> 收到 TCP 连接 <<<` |
| **7 查看防火墙状态** | 随时确认四条规则在不在、有没有阻止规则 |
网络自检的两种结果:
- **收到连接** → 包能到电脑,问题在本机(程序没跑 / 端口被占)
- **一个包都没收到** → 包没到,多半是路由器开了「AP 隔离」
---
## 三、功能
手表上全部是 58~64dp 的大按钮,两列排布,整页可滚动。
| 分类 | 功能 |
|---|---|
| **媒体** | 播放/暂停、停止、上一曲、下一曲、音量±、静音 |
| **显示** | 投图到电脑(全屏显示,按 Esc 或点一下关闭) |
| **屏幕** | **实时屏幕镜像**(默认 4 fps,可调帧率/画质/分辨率)、**一键截屏**并保存到手表 |
| **音频** | 放音到电脑(用系统 MCI 播放,不弹播放器窗口) |
| **文件** | 发送文件到电脑 / 接收电脑上的文件 |
| **剪贴板** | 手表文字 → 电脑剪贴板;电脑剪贴板 → 手表(自动复制) |
| **语音** | 让电脑语音播报指定文字(系统自带 TTS) |
| **通知** | 在电脑托盘弹通知 |
| **电源** | 关机、重启、锁屏、睡眠、取消关机(二次确认) |
| **其他** | 自定义命令(回显输出和退出码)、查看电脑系统信息 |
| **被动** | 电脑上复制东西时,手表会收到推送提示 |
---
## 四、安全设计
这是重点,请看完。
1. **首次连接必须配对**:电脑端生成 6 位随机码,只在窗口上显示,手表输入正确才能连。
2. **令牌制**:配对成功后发一个 32 位随机令牌,之后靠令牌免配对码登录。
3. **未鉴权拒绝一切命令**:没通过握手就发命令,服务端直接拒绝。
4. **权限开关实时生效**:窗口上可以随时关掉"自定义命令"、"电源操作"、"屏幕镜像",关掉后远程立刻执行不了。
5. **无托盘但仍有可见性**:按需求去掉了托盘图标;只要有设备连着,
屏幕右上角就会出现一条「已连接」提示条,可一键断开。
6. **屏幕共享有强制提示**(见下)。
7. **不写注册表**:所有数据放在程序目录下的 `data/`,绿色便携。
8. **只在局域网**:不联外网、不上传任何数据。想更安全就在防火墙里只放行内网段。
### 屏幕镜像的强制可见提示
只要有人在看你的屏幕,被控端**屏幕右上角就会出现一条红色提示条**:
```
┌──────────────────────────────────────────────────────────┐
│ ● 屏幕正在被共享 1 台设备正在观看 · 4.0 fps [停止共享] │
└──────────────────────────────────────────────────────────┘
```
这条提示的规则:
- **一定有**:开始推流就出现,没有开关能关掉它。界面里也没有"隐藏提示"这类选项。
- **本机可一键掐断**:点提示条上的「停止共享」,立刻断开所有观看,并通知手表。
- **截屏也会闪一下**:单次截屏虽然短暂,同样会闪 2.5 秒提示。
- **无人观看自动停**:所有手表都断开后,抓屏线程立即结束,提示条消失,不留后台耗电。
- **关掉权限立刻断**:把窗口上的「允许屏幕镜像 / 截屏」取消勾选,正在进行的共享立刻被掐断。
这是本工具和"屏幕监视软件"的分界线:**被控端永远知道、永远能停。**
> ⚠️ 自定义命令 = 远程执行任意命令。这是你自己电脑上你自己开的功能,
> 但请务必:**只在你信任的局域网里用**,用完可以在窗口里关掉它。
### 安卓端权限
手表 App **在第一次启动时一次性把需要的权限全要了**,之后选图片、选音频、发文件都不再弹窗:
| 权限 | 用途 |
|---|---|
| `INTERNET` / `ACCESS_NETWORK_STATE` / `ACCESS_WIFI_STATE` | 连局域网 |
| `READ_MEDIA_IMAGES` / `READ_MEDIA_AUDIO` / `READ_MEDIA_VIDEO`(Android 13+) | 列出手表上的媒体文件 |
| `READ_EXTERNAL_STORAGE`(Android 12 及以下,`maxSdkVersion=32`) | 同上 |
有权限时用**内置选择器**(带缩略图,直接在 App 里列文件);万一权限被拒,会自动回退到系统选择器,
所以功能不会因为拒权限而不可用。
---
## 五、连不上?先看这里
**实测中「连不上」最常见的原因只有一个:Windows 防火墙没放行。**
家用 WiFi 一般会被 Windows 归类成「公用网络(Public)」,
而公用网络的默认入站动作是**阻止**。如果没有显式放行规则,
手表的连接会被**静默丢包** —— 表现就是「配对码明明对,但一直转圈连不上」。
真机上验证过:
```
Get-NetFirewallRule -Direction Inbound -Action Allow -> 0 条规则
Get-NetConnectionProfile -InterfaceAlias WLAN -> Public
```
**解决办法**:被控端窗口会自动检测并显示一条橙色提示
```
防火墙未放行,手表连不上。 [一键修复]
```
点「一键修复」,同意一次管理员请求即可(放行需要管理员权限,这一步躲不掉)。
程序也会在目录下生成 `放行防火墙.bat`,可以右键「以管理员身份运行」。
手动执行等价于:
```powershell
netsh advfirewall firewall add rule name="LAN Remote TCP" dir=in action=allow protocol=TCP localport=8765 profile=any
netsh advfirewall firewall add rule name="LAN Remote UDP" dir=in action=allow protocol=UDP localport=8766 profile=any
```
**如果防火墙已放行还是连不上**,依次检查:
1. 手表和电脑是不是**同一个 WiFi**(不是手机热点、不是客人网络)
2. 路由器有没有开 **「AP 隔离」**(开了的话同一 WiFi 的设备也互相不通)
3. 电脑上是不是有 VPN / 加速器 / 虚拟网卡在抢占路由
(程序会优先选真实网卡地址;本机装 Tailscale 时 `hostname` 会解析到
169.254 链路地址,程序已按网段优先级排除了这种情况)
4. 端口被别的程序占用(程序会提示「端口被占用」)
---
## 六、连不上时:用「设备诊断」
**诊断已经合并进手表主程序了**,不需要再装单独的 App。
手表上:**主界面 ->「设备诊断」** -> 自动开始检测。
它会自动:
1. **采集环境信息** —— 厂商/型号/安卓版本/API、屏幕像素与 density、
屏幕 dp 尺寸、字体缩放、是否被识别为手表、各权限的授予状态
2. **采集网络参数** —— 连接类型、WiFi 名称/信号、本机所有 IPv4 地址与
前缀长度、DHCP 网关/掩码/DNS
3. **真的跑一次局域网自动发现**(UDP 广播 4 秒),列出找到的电脑
4. **对找到的每台电脑做真实 TCP 连接测试**,报告成功/失败与耗时
5. 测完**自动复制到剪贴板**,直接到聊天窗口粘贴发给开发者即可
如果一台都没搜到,可以在诊断界面手动填电脑 IP 点「测一下」——
它会分别做 **UDP 单播探测** 和 **TCP 连接**,能直接区分问题在哪:
| 结果 | 说明 |
|---|---|
| 单播有应答 + TCP 成功 | 网络是通的,问题在广播(路由器多播过滤 / AP 隔离) |
| 单播有应答 + TCP 失败 | 广播被拦,且**电脑防火墙拦了 TCP 8765** → 点被控端的「一键修复」 |
| 单播无应答 + TCP 失败 | 根本没通(不同网段 / 电脑没运行 / 防火墙全拦) |
**它刻意不收集什么:**
- IMEI、序列号、手机号、运营商
- 联系人、短信、通话记录
- 相册、文件列表、剪贴板内容
- 位置坐标(定位权限只用于读取 WiFi 名称,不读经纬度)
- 账号、已安装应用列表
报告内容全部显示在屏幕上,复制由使用者主动触发。
诊断与主程序在**同一个 APK** 里,共用同一份自动发现实现
(`DiscoveryCore`),所以不会出现「诊断说能通、主程序连不上」的假象。
底部还有一行「手动测电脑 IP」+「测一下」按钮——广播被拦时,
它会分别做 UDP 单播和 TCP 连接,直接告诉你问题在哪一段。
---
## 六点五、电脑端全部图形化(不依赖任何 .bat)
被控端窗口底部一行短按钮:
```
[ 接收 ] [ 自检 ] [ 说明 ] [ 设置 ] [ 退出 ]
```
| 按钮 | 作用 |
|---|---|
| **接收** | 打开收到的文件目录 |
| **自检** | 图形化网络自检(见下) |
| **说明** | 窗口里直接看使用说明 |
| **设置** | 投图标题 / 播报音色 / 语速 / 音乐目录 |
| **退出** | 真正结束进程并释放端口 |
### 图形化自检
**不再需要点任何 .bat。** 自检直接读被控端自己的计数器,
所以被控端在跑的时候也能诊断(旧的控制台版本要抢端口,跑不了):
```
● 正常 一切正常,手表应该能连上。
--- 本机网络 ---
192.168.2.64 [默认路由]
>>> 手表上填这个 IP:192.168.2.64
WiFi: 1602
--- 监听 ---
控制端口 TCP 8765: 监听中
发现端口 UDP 8766: 监听中
--- 收到过多少(关键)---
自动发现请求: 12 个
自检请求: 1 个
TCP 连接: 3 次
有数字说明手表的包能到达这台电脑
--- 防火墙 ---
端口放行 TCP: 是
程序级放行: 是 (LanRemote-Agent.exe)
阻止规则: 0 条(针对本程序)
--- 结论 ---
[OK] 一切正常,手表应该能连上。
```
窗口里还有「复制」和「一键修复防火墙」按钮。
**手表上点「连不上?」**,这台电脑会自动把自检窗口弹出来
(走 UDP,所以 TCP 被防火墙拦掉时也能到达)。
---
## 六点六、手表端:分页 + 37 个功能
**连接后**的界面是 6 个分页标签,每页最多 7 个按钮 —— 一屏放得下,**完全不用滚动**。
| 分页 | 功能 |
|---|---|
| **媒体** | 播放/暂停、停止、上一曲、下一曲、音量 +、音量 −、静音 |
| **屏幕** | 屏幕镜像、截屏、投图到电脑、放音到电脑、关显示器、设为壁纸 |
| **文件** | 发送文件、接收文件、剪贴板→电脑、取电脑剪贴板、剪贴板历史、打开文件夹 |
| **语音** | 语音播报、音色、朗读剪贴板、电脑音乐、弹通知 |
| **电脑** | 快捷命令、打开网址、进程管理、性能、磁盘、电量 |
| **系统** | 锁屏、睡眠、关机、重启、取消定时关机、定时关机、设备诊断 |
几个值得说的:
- **电脑音乐** —— 列出电脑音乐目录里的音频,选一个直接在电脑上播
- **音色** —— 列出电脑上所有 TTS 音色(中文 HuiHui / 英文 Zira 等),选一个
- **进程管理** —— 按内存排序列出电脑进程,可以结束
- **剪贴板历史** —— 电脑端保留最近 20 条剪贴板,手表上点一条就取回来
- **设为壁纸** —— 把刚投过去的那张图设成电脑桌面壁纸
- **快捷命令** —— 任务管理器 / 资源管理器 / CMD / 计算器 / 记事本 / 画图 / 控制面板 / 设备管理器
- **定时关机** —— 15 / 30 / 60 / 120 分钟后关机,可取消
**安全边界**(都有测试):
- 打开网址只允许 `http` / `https`,拒绝 `file://` 之类
- 播放本机音频只允许音乐目录内的文件
- 快捷命令和文件夹都是白名单,不接受任意路径
- 结束进程必须由用户在手表上确认
---
## 关于杀毒软件误报
### 先说结论
**这个工具没有做任何免杀。** 它会被某些杀软启发式报毒,这是真的,
我不打算靠混淆/加壳/加密去绕,因为那正是恶意软件的做法 ——
绕过去之后,你也分不清自己装的是不是真有毒。
真正能解决误报的路只有一条:**代码签名**(要买证书,见下)。
### 为什么会被盯上
| 特征 | 为什么杀软敏感 | 这是谁的需求 |
|---|---|---|
| PyInstaller 打包 | 打包器的引导代码是启发式的重灾区 | 你要「多文件 exe,双击就跑」 |
| 写注册表 Run 键 | 自启动是持久化的典型手法 | 你要「开机自启」 |
| 无窗口后台常驻 | 隐藏进程是木马的常见形态 | 你要「全程静默」 |
| 监听端口 + 收指令 | 远控和被控端在行为上无法区分 | 这就是远控工具 |
| 提权改防火墙 | 改防火墙规则经常被拦 | 你要「黑终端一闪修防火墙」 |
**这五条全是你明确要求的。** 它们组合在一起,行为特征上和远控木马是一回事 ——
区别在于:**配对码是你自己设的、认证是真的、你不会拿它去连别人的电脑。**
### 我做了哪些合法的降误报
1. **`--onedir` 而不是 `--onefile`**
onefile 每次启动都往临时目录解压自己,这是最容易被判毒的行为之一。
onedir 不解压,启动也更快。
2. **`--noupx`**
加壳压缩是杀软重点关注对象,明确关掉。
3. **完整的版本信息**
EXE 里写入了公司名、产品名、文件描述、版本、版权。
未签名二进制有正经的版本资源,比光秃秃的少一点可疑度,
也方便你在杀软里按「厂商 + 产品」加例外。
4. **不做任何偷偷摸摸的事**
没有自我复制、没有进程注入、没有内存加载、没有关闭杀软、
没有偷偷加防火墙例外。自启写注册表是**你勾了勾选框**才做的。
### 你遇到误报时怎么办
**优先做成例外,不要关杀软。**
被控端目录整个加进排除项即可(就是你放 `LanRemote-Agent.exe` 那个文件夹):
```
Windows 安全中心 → 病毒和威胁防护 → 管理设置
→ 排除项 → 添加或删除排除项 → 添加文件夹
→ 选 LanRemote-Agent 那个目录
```
加**文件夹**而不是单个 exe —— 因为 `_internal\` 里的 dll/pyd 也可能被单独报。
### 真正根治:代码签名
有了签名,SmartScreen 和大部分杀软就不会再拦。需要:
1. 买一张代码签名证书(OV 约 ¥1000-2000/年,EV 更贵但立刻获得 SmartScreen 信誉)
2. 用 `signtool` 给 `LanRemote-Agent.exe` 和 `_internal` 里的 dll 签名
3. 加时间戳(否则证书过期后签名失效)
买了证书之后告诉我,我把签名步骤加进构建脚本。
### 我查到的情况(本机)
这台机器上 Windows Defender 的**按需扫描是坏的**:
```
MpSmartConfigInitialize() failed (80070002)
```
Defender 的 PowerShell 模块(`Get-MpComputerStatus` 等)也不存在,
所以**我没法在这台机器上给你一个权威的"报不报毒"结论**。
我不会假装测过。
想自己确认的话,把 EXE 传到 VirusTotal 看一下 —— 那是最快的办法。
---
## 七、后台运行方式
按需求设计成**没有托盘图标**:
- 双击 EXE → 弹出一个小窗口,上面一串 6 位配对码
- **关掉窗口 → 程序继续在后台跑**,仍然占着 8765 / 8766 端口
- 任务管理器里能看到 `LanRemote-Agent.exe`
- **再双击一次 EXE → 把已经在跑的那个窗口叫回来**,不会重复起进程
(这里用 `SO_EXCLUSIVEADDRUSE`:Windows 上 `SO_REUSEADDR` 会允许
第二个进程绑同一端口,导致单实例判定失效 —— 实测踩到过)
- 想彻底退出:窗口上点「退出」
- 运行记录写在 `data\运行日志.txt`
**保留了一条可见性**:只要有设备连着,屏幕右上角会出现一条小提示条
(`● 已连接:<设备名>`,蓝色,带「断开」按钮);屏幕共享时它变红,
并带「停止共享」。没有托盘图标的前提下,这是让「有人正在控制这台电脑」
不被完全隐形的最低限度做法。空闲时它完全不显示。
---
## 八、常见问题
**Q:手表连不上?**
A:先看被控端窗口上有没有橙色「防火墙未放行」提示,有就点「一键修复」。
  没有的话,装附带的「遥控诊断」跑一次,把结果发给开发者即可定位。
**Q:提示"电脑拒绝连接"?**
A:被控端程序没在运行(任务管理器里找 `LanRemote-Agent.exe`),或者端口被改了。
**Q:提示"配对码错误"?**
A:配对码会变——点了"换一组"或者被控端重启过,就用窗口上最新的那组。
**Q:下次打开还要输配对码?**
A:不该要。如果还要,说明上次配对没成功保存。
**Q:托盘图标在哪?**
A:按需求**没有托盘图标**。关窗后程序继续后台运行;再双击一次 EXE 就能找回窗口。
**Q:投图之后电脑上窗口关不掉?**
A:点一下图片,或者按 Esc。也可以把设置里的 `image_timeout` 改成秒数自动关闭。
**Q:手表屏幕显示不全?**
A:界面是按 490×602 自适应做的:宽度小于 200dp 时自动切成单列并加大按钮。
  别的分辨率改 `MainActivity.java` 里 `adaptGridToScreen()` 的阈值即可。
---
## 九、项目结构
```
LanRemote/
├── windows/ # 被控端(Python)
│ ├── lanremote/
│ │ ├── protocol.py # 协议常量 + 分块编解码
│ │ ├── config.py # 数据持久化(设备令牌、设置)
│ │ ├── actions.py # 投图/放音/媒体/剪贴板/电源/命令/TTS
│ │ ├── screen.py # 屏幕捕获 + 帧流(Pillow,JPEG)
│ │ ├── discovery.py # UDP 广播应答(手表不用输 IP)
│ │ ├── firewall.py # 防火墙检测 + 一键提权放行
│ │ ├── instance.py # 单实例锁(再次双击找回窗口)
│ │ ├── server.py # TCP 服务 + 配对鉴权 + 命令分发 + 帧广播
│ │ └── ui.py # 小窗口 + 状态提示条(无托盘)
│ ├── run_agent.py # 启动入口
│ └── build_exe.ps1 # PyInstaller --onedir 打包
│
├── android/ # 手表控制端(纯 Java,无第三方依赖)
│ ├── src/com/lanremote/watch/
│ │ ├── Json.java # 自写 JSON(桌面 JVM 也能跑,便于联调)
│ │ ├── B64.java # 自写 Base64(兼容老 Android)
│ │ ├── RemoteClient.java # 协议客户端(纯 Java)
│ │ ├── DiscoveryCore.java # 自动发现核心(纯 Java,可桌面联调)
│ │ ├── Discovery.java # 安卓包装(MulticastLock)
│ │ ├── DeviceStore.java # 设备令牌持久化
│ │ ├── MediaPicker.java # 内置媒体选择器(免系统弹窗)
│ │ └── MainActivity.java # 490×602 自适应大按钮 + 镜像面板
│ ├── AndroidManifest.xml
│ ├── res/ # 图标 + 字符串
│ └── build_apk.ps1 # aapt2+javac+d8+apksigner 手工构建
│
├── diag/ # 早期独立诊断 App 的源码(已合并进主程序,不再单独出包)
│ ├── src/com/lanremote/diag/
│ │ └── DiagActivity.java # 环境采集 + 真实连通测试 + 一键复制
│ ├── AndroidManifest.xml
│ ├── res/ # 图标 + 字符串
│ └── build_diag_apk.ps1 # 复用主程序的 Json/DiscoveryCore 一起编译
│
├── _tests/ # 全部测试
│ ├── test_agent_protocol.py # 被控端协议回归(69 项)
│ ├── check_layout.py # 490×602 布局数学核算(22 项)
│ ├── test_indicator_visible.py# 屏幕共享强制提示(真实界面,9 项)
│ ├── test_simple_ui.py # 小窗口/无托盘/后台常驻(真实界面,12 项)
│ ├── java/JavaClientTest.java # 跨语言联调(48 项)
│ ├── run_stub_server.py # 无界面桩服务端
│ └── verify_all.ps1 # 一键全量验证(7 个阶段)
│
└── _build/ # 构建辅助
```
---
## 十、协议(想自己扩展可以看)
TCP 长连接,每条消息一行 UTF-8 JSON(`\n` 结尾),二进制用 Base64 分块(256KB/块)。
**握手**
| 方向 | 消息 |
|---|---|
| 手表→电脑 | `{"cmd":"pair","code":"454534","deviceId":"watch-xxx","deviceName":"Watch"}` |
| 电脑→手表 | `{"ok":true,"token":"<32位>","serverName":"DESKTOP-XXX"}` |
| 手表→电脑 | `{"cmd":"auth","deviceId":"...","token":"..."}` |
**命令**(需鉴权)
| cmd | 参数 | 说明 |
|---|---|---|
| `ping` | - | 心跳 |
| `info` | - | 系统信息 |
| `media` | `action` | playpause/stop/prev/next/volup/voldown/mute |
| `image` | `name,seq,last,data` | 投图(分块) |
| `audio` | `name,seq,last,data` | 放音(分块) |
| `clip_set` / `clip_get` | `text` / - | 剪贴板 |
| `say` | `text` | 语音播报 |
| `notify` | `title,text` | 通知 |
| `power` | `action` | shutdown/restart/lock/sleep/abort |
| `run` | `command` | 自定义命令 |
| `file_push` | `name,seq,last,data` | 上传文件 |
| `file_list` | - | 列出接收目录 |
| `file_pull` | `path` | 下载文件(分块回传) |
| `screen_start` | `fps,maxWidth,quality` | 开始实时屏幕镜像(1~15 fps) |
| `screen_stop` | - | 停止屏幕镜像 |
| `screenshot` | `maxWidth,quality` | 单次截屏,回包里直接带 JPEG(base64) |
**自动发现**(UDP 8766,让手表不用输 IP)
| 方向 | 报文 |
|---|---|
| 手表→广播 | `{"lanremote":"LANREMOTE_DISCOVER","proto":1}` |
| 电脑→单播 | `{"lanremote":"LANREMOTE_REPLY","proto":1,"name":"DESKTOP-X","port":8765,"version":"1.1.0"}` |
手表拿到回复后就知道电脑的 IP 和端口,直接连 TCP。与在哪个网段、
电脑有几张网卡都无关。
**事件推送**(电脑→手表,异步)
| event | 说明 |
|---|---|
| `clip` | 电脑剪贴板变化 |
| `file` | `file_pull` 的分块数据 |
| `frame` | 屏幕镜像的一帧(JPEG base64,带 `w`/`h`/`size`/`ts`) |
| `screen_stopped` | 被控端本机掐断了共享 |
| `status` | 状态消息 |
**注意三个容易踩的坑**(都是实测踩过的):
1. 分块上传时,**中间块不回包**,只在最后一块回一次。否则回复洪泛会导致后续命令消息错位。
2. `file_pull` 分块之后还会有一条最终回执,客户端必须读走,不然会污染下一条命令。
3. 镜像帧是**高频事件**,客户端读取线程必须把 `frame` 当事件异步处理,
绝不能让 `frame` 进入请求-应答队列,否则命令会全部超时。
---
## 十一、测试情况
跑 `_tests\verify_all.ps1` 一键全量验证:
| 项目 | 内容 | 结果 |
|---|---|---|
| Python 被控端协议回归 | 配对/鉴权/媒体/剪贴板/投图/放音/文件/命令/电源/屏幕镜像/截屏/多设备 | **69/69 PASS** |
| 手表布局适配核算 | 490×602 在 density 1.0~3.0 下的按钮尺寸与文字容量 | **22/22 PASS** |
| 屏幕共享强制提示 | 真实 tkinter 界面 + Win32 `EnumWindows` 枚举窗口 | **9/9 PASS** |
| 小窗口与后台常驻 | 真实界面 + Win32:窗口尺寸、关窗后进程存活、双启动找回窗口、无托盘 | **12/12 PASS** |
| Java 协议层编译 | javac 目标 Java 8 | **PASS** |
| Java ↔ 桩服务端 | 跨语言联调(含帧流、截屏、**UDP 自动发现**) | **48/48 PASS** |
| Java ↔ 打包后 EXE | 端到端跑真交付物 | **48/48 PASS** |
| 交付 zip 解压重跑 | 解压到全新目录后运行 EXE 再跑一遍 | **PASS** |
**测试中发现并修掉的真实 bug:**
1. **分块上传回复洪泛** —— 服务端给每个分块都回一次 ack,客户端只读一次,导致后续所有命令消息错位(`run` 收到的是 `file_pull` 的回包)。改为只在最后一块回包。
2. **同名文件拉回旧版本** —— 同名文件第二次上传会存成 `name_1.bin`,但 `file_pull("name.bin")` 会拉到**上一轮的旧文件**。改为按名字匹配取最新的一份。(靠三方 SHA-256 比对才定位出来:长度完全一致,只有内容不同。)
3. **损坏图片被当成成功** —— 实测发现传随机字节时 PIL 打开失败并返回错误;确认这是正确行为并加了回归用例。
4. **自定义命令的控制台噪音** —— `shutdown /a` 的报错会打到 stderr,改成全静默。
5. **镜像帧污染命令队列** —— 帧是高频事件,必须走事件通道;一旦混入请求-应答队列,所有命令都会超时。已在客户端读取线程里分流。
**屏幕相关的安全属性,是被单独验证过的:**
`_tests/test_indicator_visible.py` 启动**真实界面**(不是桩),开始共享后用 Win32 `EnumWindows`
在系统窗口列表里查找提示条,确认:开始共享时出现 → 停止后消失 → 再次共享再出现 →
客户端断开后自动消失。这条属性不靠"看代码相信",靠枚举窗口实证。
**未能验证的部分(如实说明):**
- **没有真机跑 APK**。这台机器上没接安卓设备,也没装模拟器。APK 的验证做到了:构建成功、签名校验通过、`aapt dump badging` 结构正确、dex 里类齐全、协议层在桌面 JVM 上连真实服务端 35/35 通过。**但界面在真实手表上的渲染效果没实测过。**
- 装到手表上如果布局有问题,改 `adaptGridToScreen()` 的阈值即可。
---
## 十二、自己重新构建
```powershell
# 被控端 → 多文件 EXE
powershell -ExecutionPolicy Bypass -File windows\build_exe.ps1
# 手表端 → APK
powershell -ExecutionPolicy Bypass -File android\build_apk.ps1
# 全量验证
powershell -ExecutionPolicy Bypass -File _tests\verify_all.ps1
```
APK 用的是工作区里现成的工具链(`starklink\toolchain`:JDK 17 + Android SDK 34 + build-tools 34),
走 `aapt2 → javac → d8 → zipalign → apksigner` 手工链,**不需要 Gradle**。
---
## 十三、法律与责任
本工具是**你自己的设备之间**的局域网遥控工具。
- 只用于你拥有或已获授权控制的电脑。
- 在他人电脑上安装并隐藏运行,属于违法行为(《刑法》285/286 条)。
- 本工具不提供任何隐藏、免杀、伪装能力,就是为了让它不能被用于那种场景。
请合法使用。