refactor: separate plugin repositories and release-based catalog

This commit is contained in:
2026-09-11 10:06:30 +08:00
parent 07174e35ab
commit 07cf38b1a4
148 changed files with 377 additions and 19048 deletions
+28 -43
View File
@@ -1,62 +1,47 @@
# Curio 开发与验收
# 官方软件源的维护与发布
此仓库承载 Trove 正式插件与生产软件源。当前包含用于首批验收的简单插件;生产发布以 `source/index.json` 为准,本地测试材料位于 `dist/`。
`trove/curios` 保存官方软件源索引、插件仓库登记和发布工具。底座在 `trove/app`;插件源码与安装包分别位于 `trove-plugins` 下各仓库的代码和 Releases。
正式插件 **Harmony Connector** 的使用、构建、发布和真机验收步骤见 [Harmony Connector](harmony-connector.md)。它采用独立实现和构建命令,不属于下面的 QA 套件。
## 仓库职责
## 底座测试套件
- `source/repositories.json`:登记插件 ID 与发布仓库的对应关系。
- `source/index.json`:底座消费的 Source Index v1,保留版本、兼容范围、平台、Release 附件 URL、大小与 SHA-256。
- `tools/prepare-release.mjs`:手动构建后生成发布目录。
- `tools/publish-release.mjs`:可选的命令行上传工具,上传全部附件后发布 Release。
- `tools/notify-release.mjs`:由插件仓库的 Linux workflow 在 Release 发布后触发源收录。
- `tools/sync-release.mjs`:校验登记关系和附件,合并版本,通过文件 SHA 比较后更新远端索引。
- `.gitea/workflows/sync-release.yml`:在 Linux runner 上执行收录。
15 个插件工程(14 个插件 ID,其中升级插件有两个版本),覆盖 UI、Host 服务、Rust 原生后端、跨插件服务、可选/必需依赖、版本约束、生命周期、故障恢复和升级回滚。另有 22 个故意无效的安装包和 4 个异常软件源。
安装包不进入 Git,所有 `*.curio` 都被忽略。Git 软件源仍指向 `https://git.render.ink/trove/curios.git`。
```sh
pnpm install --frozen-lockfile
## 发布协议
# 已安装 trove 命令时可省略此变量;也可指向本机 app 仓库构建产物。
export TROVE_BIN=/absolute/path/to/app/target/debug/trove
pnpm run pack
插件仓库根目录的 `release.json` 声明仓库、插件 ID 和是否为共享测试套件。普通插件 tag 为 `v<curio.json version>`;测试套件独立打 tag,各测试插件保留自身版本。
# 可选:使用真实底座组件在临时目录自动验证。
export TROVE_APP_DIR=/absolute/path/to/app
pnpm run verify
```
Release 先以草稿创建,上传每个平台的 `.curio` 和 `trove-release.json` 后再发布。描述文件包含仓库、tag、源码 commit、插件 Manifest 摘要,以及附件名、平台、大小和 SHA-256。插件图标以小型图片 Data URL 收录,私有图片也无需额外鉴权。
打包需要 macOS、Rust/Cargo、Node.js 22+、pnpm、Python 3 和 Trove CLI。原生包只面向当前构建机器的架构;安装后的插件无需 Node.js、Python 或 Cargo。SDK 使用仓库内的版本化 `@trove/plugin-sdk@0.1.0` 包,离线复制到 UI 中。
本机手动生成发布目录,上传全部附件并发布 Release 后,插件的 Linux workflow 使用 `CATALOG_TOKEN` 调用源仓库的 `sync-release.yml`,传入 `repository` 与 `tag`。收录只接受登记仓库及其所属插件 ID,并下载附件校验大小和 SHA-256。
生成结果:
源 workflow 使用 `PLUGIN_RELEASE_TOKEN` 读取私有插件 Release,使用本任务的 `GITEA_TOKEN` 更新自身索引。写入以 Gitea Contents API 的当前文件 SHA 为前提,并发冲突后重新读取、重新合并,不覆盖其他发布。
- `dist/packages/`:15 个可安装 `.curio` 包。
- `dist/index.json`:常规测试源,8 个插件 ID / 9 个版本包。
- `dist/faults-index.json`:6 个故障/异常依赖插件,按需安装。
- `dist/invalid/`:22 个无效包、4 个异常源及预期错误码。
- `dist/verification.json`:自动验证的逐项结果、环境和观察项。
相同插件 ID、版本和平台的产物不可变。重复收录没有副作用;相同版本的 URL、大小或 SHA-256 发生变化会报错。
在 Trove 添加**本地软件源**,选择 `dist/index.json` 的绝对路径,即可安装常规测试插件。若要测试 HTTP 下载:
## 新增插件
```sh
pnpm run serve
# 在底座添加 HTTP 软件源:http://127.0.0.1:8765/index.json
# 用 QA_HTTP_PORT 环境变量更换端口,Ctrl-C 停止。
```
1. 在 `trove-plugins` 创建独立仓库,添加源码、`curio.json`、`release.json` 和发布 workflow。
2. 将仓库与 ID 登记到 `source/repositories.json`,授予收录机器人读取该仓库的代码与 Release 的权限。
3. 在对应平台准备构建环境与 Trove CLI,提交源码,运行插件仓库的手动发布脚本并上传 Release。
完整步骤、覆盖矩阵与当前限制见 [测试指南](testing.md)。
现有插件仓库中的发布 workflow 可作为模板。发布工具从源仓库检出,统一维护。
插件各自通过 `curio.json.version` 管理版本;公开服务契约版本与底座兼容范围分别维护。历史发布索引只追加版本、禁止覆盖同版本产物,详见 [版本与发布](versioning.md)。`pnpm run release:prepare` 将安装包与索引准备到 `source/`,提交并推送后即可通过 Git 仓库源安装。`pnpm run test:release` 验证版本约束。
## 失败恢复
## 目录
Release 成功但收录失败时,在插件 Actions 手动运行发布 workflow,填写已有 tag;该 workflow 使用 Linux runner,只重试收录,不重新构建或替换附件。也可在源仓库直接运行收录 workflow。
- `curios/<name>/`:各插件独立 Manifest 与公共服务契约。
- `shared/ui/`、`shared/backend/`:测试面板和原生后端源码;构建时复制到各插件。
- `source/index.json`:生产软件源与历史版本索引。
- `tools/`:构建、打包、测试源和真实底座验证适配器。
- `vendor/`:版本化 SDK 分发包与来源说明。
## 私有源
项目依赖使用 pnpm;插件通过版本化的 `@trove/plugin-sdk` 引用 SDK,不通过 `../app` 引用源文件。仅自动验证适配器通过显式的 `TROVE_APP_DIR` 编译待测底座组件,插件构建与运行不依赖该路径。App 仓库保留协议规范和教学模板。
当前仓库保持私有。底座用本机 Git 凭据读取索引;下载同一 Git 服务器上的私有 Release 附件时,在服务器要求鉴权后查询 Git HTTPS credential helper。账户必须能读取源仓库和对应插件仓库。SSH 密钥仅用于 Git,附件下载还需要本机 HTTPS 凭据。凭据不写进索引或安装包。
## 发布约定
## 自动化身份
1. 各插件独立声明版本、平台、依赖与权限。
2. 构建并执行 trove curio check / pack。macOS 原生后端使用 Mac Runner 构建。
3. 执行 `pnpm run release:prepare`,检查 `source/index.json` 的新增版本与 `source/packages/` 中的不可变版本包。
4. 将索引和安装包在同一提交中推送,再使用 Git 仓库地址完成远程安装与运行验证。私有仓库沿用本机 Git 认证。
官网:https://trove.render.ink
`trove-publisher` 负责跨仓库通信:可以读取源代码、触发源 Actions,并读取已登记插件的 Release;没有底座仓库访问权限,也不能直接改写源代码。源索引写入由源 workflow 自身的任务令牌完成。
-140
View File
@@ -1,140 +0,0 @@
# Harmony Connector
插件 ID:`dev.trove.harmony-connector`。当前版本:`0.5.2`。
macOS 上的鸿蒙设备连接工具。通过 USB 收发文件与完整文件夹;使用官方 HDC 管理应用、保存截图、导出日志和调试应用数据。功能按连接工具、设备实际能力和授权开放,不按 HarmonyOS 大版本或机型名称限制。
## 安装
在 Trove 添加 Git 软件源:仓库 `https://git.render.ink/trove/curios.git`,唯一 ID `trove-official`(对应 `source/index.json` 顶层 `id`),显示名称可自行填写。刷新后安装 Harmony Connector。私有仓库使用本机 Git 凭据。
发布包适用于 Apple Silicon、macOS 13 及以上,内含 libmtp 1.1.23 和 libusb 1.0.30。Intel 需在对应 Mac 构建并单独验证后发布,当前未提供 Intel 包。
## 文件与文件夹
1. 解锁手机,用支持数据传输的 USB 线连接 Mac,在手机上选择「传输文件」并允许访问。
2. 插件启动后自动发现设备,选择手机并连接。也可手动扫描。
3. 选择存储位置,进入目标目录。「发送文件」选择文件,「发送文件夹」选择整个目录;文件和文件夹旁的「接收」均可下载到 Mac。
文件功能不需要 HDC、ADB、Homebrew 或开发者模式。设备每约 2 秒自动检测一次;连接后仅检查 USB 存在性,不重复打开会话。手动断开后不会自行重新连接。
文件夹会保留层级、中文名称、空文件和空目录。整棵目录先检查再传输;总进度汇总全部文件,显示当前文件、已完成文件数、当前/平均速度、耗时和预计剩余时间。速度来自实际字节进度,不是预设数值。
- 不覆盖或合并同名条目。上传还会检查设备剩余容量,每次写入前再次检查目标名称。
- 下载先写入所选目录下的隐藏临时文件夹,全部文件长度校验成功后,原子保存为最终文件夹。失败或正常取消会清理此次临时内容。
- 不跟随符号链接,不传输特殊文件;拒绝路径穿越、循环、重名目录和超过 100,000 条目或 128 层的目录树。
- 上传中断时手机可能保留已创建的目录和文件。不会自动删除这些内容,请在手机上检查后再重试。底层 USB 事务取消需等待设备响应。
- 长度校验不等于端到端内容哈希校验;实际验收应将上传文件下载回来比较 SHA-256。
- 强制结束进程或断电可能留下 `.harmony-*.part` 临时内容。插件不会扫描或删除用户目录中的历史临时文件。
手机只开放 MTP 可见的存储,实际目录、访问权限和图库行为由设备决定;向普通目录发送照片不保证导入图库。本版不提供断点续传、无线 MTP、图库导入或全盘访问。
扫描名称取自 USB 描述符;连接后使用 MTP 友好名称并单独显示设备报告的型号。它可能不同于手机设置中的自定义名称。0.1.0 将 libmtp 匹配表中的 `Nova14` 误用于展示,0.1.1 已修复,不再把驱动匹配名称当作机型。
## 设备与应用
首次进入「设备与应用」,如果没有可用 HDC,页面会显示安装引导:
1. 点击「前往华为官网下载」,在浏览器打开 [Command Line Tools 官方下载页](https://developer.huawei.com/consumer/cn/download/command-line-tools-for-hmos)。Apple Silicon 选择 Mac (ARM64),Intel 选择 Mac (x86),按官网提示登录华为账号并下载。
2. 完整解压 ZIP,点击「选择已解压的工具目录」。可选择包含 `command-line-tools` 的解压目录、`command-line-tools` 本身,或其 `toolchains` 目录;插件定位 HDC、运行版本检查,成功后保存路径。错误选择和取消不会覆盖已有配置。
3. 手机连接 USB,启用开发者模式与 USB 调试,解锁并允许这台 Mac 的调试请求。配置成功后自动检查设备;「刷新连接」也会重新检查工具,支持启动后才安装到 PATH 的 HDC。
已有 DevEco Studio 或 SDK 的用户也可点击「选择 HDC 文件」。插件保留手动选择的路径;未配置时,按 PATH、`~/Library/HarmonyConnector/tools` 中的工具安装、`~/Downloads` 中已解压的 Command Line Tools、`/Applications` 和 `~/Applications` 中的 DevEco Studio 依次查找。工具安装目录按数字版本从新到旧检查,跳过未包含可执行 HDC 的目录;Downloads 只检查名称以 `command-line-tools` 或 `commandline-tools` 开头的直接子目录及 SDK 的标准位置,不递归扫描用户文件、不将 ZIP 视为已安装工具。典型路径:
```text
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc
```
请保留完整的工具目录及同目录依赖库,插件不重打包官方 SDK。选择路径保存在插件自己的数据目录中;工具被移动、删除或无法运行时重新显示安装引导和原因。工具准备完成前隐藏空设备列表及不可用的管理面板,文件传输仍可直接使用。管理页明确选择 HDC 设备,不根据 MTP USB 地址猜测二者对应关系。
| 操作 | 行为与边界 |
| --- | --- |
| 设备连接与信息 | 显示 HDC 目标、USB/TCP 类型、授权/连接状态;检查 `bm`、`aa`、`uitest`、`hilog` 等实际工具帮助。未知格式报错,不伪装成无设备。 |
| 应用列表与详情 | 按包名筛选,读取版本、签名类型和设备公开的可启动组件。不根据包名猜测 `EntryAbility`。 |
| 安装/更新 | 选择已签名 `.hap`;展示包名、版本、大小、SHA-256 并确认。执行前复制到临时快照并重新校验哈希;安装后读取实际版本。签名或权限错误不会触发自动卸载。 |
| 卸载 | 用户确认后操作;不移除系统应用,结束后检查应用是否确实消失。 |
| 启动/强制停止 | 从实际公开的组件选择启动入口;强制停止需要确认,报告设备返回结果。 |
| 截图 | 使用 UITest 捕获,下载并解码验证 PNG 后保存。只清理本次创建的远端临时图片。 |
| 日志 | 导出 `hilog -x` 快照到用户选择的目录。日志正文中的错误信息不会被误判为命令失败。 |
| 诊断报告 | 使用 HDC `bugreport` 保存报告,拒绝把空结果作为成功。 |
| 调试数据导出 | 仅对调试签名且设备允许访问的应用开放;复制 `data/storage/el2/base`,附带路径、大小、SHA-256 清单。应用通常需已启动。 |
调试数据导出是运行中文件的副本,不保证数据库事务一致性,不包含全部应用状态,不提供一键恢复。HDC 对空目录及空子目录的行为也不同于 MTP 目录传输。普通发布应用私有数据、整机备份/恢复和受保护系统数据没有可供该插件普遍使用的公开权限;不能因为 `backup_tool` 退出码为 0 就宣称备份成功。
管理操作使用独立串行任务,支持取消与超时;取消不回滚设备端已经完成的更改。操作开始前重新确认所选设备在线。功能有命令支持仍可能被单个应用、对象或系统权限拒绝,失败会显示设备返回的原因。
## 投屏与控制
在「设备与应用」选择已授权的 HDC 目标,点击「打开投屏窗口」。投屏使用独立的 macOS 窗口,默认可以直接通过画面控制手机。
- 鼠标按下、拖动、松开即时发送触摸动作;按住即可长按,滚轮滑动,右键返回,中键回到主页。
- ⌘F 切换全屏,⌘V 向手机粘贴剪贴板文字,⌘R 切换仅观看,⌘W 关闭投屏。方向键、回车、退格直接作用于手机。
- 窗口标题显示显示帧率和接收码率。只保留最新解码画面,避免积累播放延迟。
- 切换管理页和文件页、隐藏管理窗口不停止投屏。关闭投屏窗口或点击「停止投屏」会释放设备会话;退出插件也会停止。
- 失焦、关闭窗口和画面方向变化时释放当前触摸;移动事件合并,按下和松开保持顺序,不在重连后重放输入。
0.5.0 使用官方 Hypium 26 的 Unix H.264 recorder 和独立控制代理,经 HDC 转发视频及触摸通道。手机端编码,Mac 端 VideoToolbox 解码并立即显示。无需开发者账号、签名、手机应用或完整 SDK;用户只需官方 HDC 和已授权的设备 USB 调试。编码配置目标为 60 fps、8 Mbps、半尺寸,实际流畅度取决于设备,当前版本尚未进行真机性能验收。
0.5.1 在设备代理进程及 Unix socket 就绪后才创建转发;按官方资源版本依次尝试兼容代理。首次连接失败会重新建立本插件的 HDC 转发。窗口收到首张解码画面之前显示连接中,失败区分组件启动、转发和视频连接,不再把网络错误归为 Mac 文件错误。
0.5.2 点击后先打开独立窗口,设备准备在后台进行,并显示连接阶段;准备中可直接关闭窗口取消。收到首张解码画面后,插件在 Mac 上按设备保存成功的代理版本,下次优先使用;原版本失效时仍自动尝试其他官方组件。首次匹配仍可能等待,后续不再从头扫描全部候选。
组件与传输依据见[官方视频与触控调研](research/harmony-screen-performance.md)。旧 JPEG 后端接口仅保留用于诊断;管理页已使用独立视频窗口。
## 重连问题与 debug
0.1.1 真机记录:HUAWEI Pura 70 Ultra,USB `12d1:1101`,USB 名称 `HDC Device`,MTP 友好名称 `HUAWEI Pura 70 Ultra`,型号代码 `HBP-AL00`。设备未通过 MTP 返回自定义名称 `daniel`。连接后可读取存储,断开请求约 0.27 毫秒返回,USB 释放约 61 秒后结束;**这不证明随后能重新打开会话**。用户报告仍需拔线。
0.3.0 真机复现了 CloseSession 等待 60 秒的问题。固定 libmtp 依赖增加一个小补丁,仅把 CloseSession 的超时设为 2 秒,文件传输超时、接口释放、现有 USB reset 与关闭顺序保持不变。macOS 重枚举有短暂离线窗口,诊断程序最多等 10 秒重新发现设备,再实际打开并读取存储和目录。
2026-09-10,在同一 USB 插接周期,开发构建与发布构建各完成 3 轮只读重连,每轮返回 1 个存储和 9 个根目录条目。开发构建关闭约 2.28–2.33 秒;发布构建约 2.13–7.28 秒,包含 USB 重枚举耗时。设备仍不正常响应 CloseSession,恢复由随后的 USB 清理/reset 完成;不能将 2 秒超时宣传为所有设备都固定 2 秒完成断开。该结果覆盖这台手机,不代表全部固件。见[会话恢复调研](research/harmony-mtp-session.md)。
关闭插件窗口,保留一台解锁且处于「传输文件」模式的手机,然后执行:
```sh
# 仅枚举
curios/harmony-connector/backend/harmony-connector --diagnose
# 只读执行三轮打开、存储/目录读取、关闭与重新打开;不要在插件占用 USB 时运行
curios/harmony-connector/backend/harmony-connector --check-reconnect
```
`--check-reconnect` 逐阶段输出 JSON、耗时和实际目录读取结果,不修改手机文件。没有恰好一台设备或任何一轮失败时以非零状态结束;CloseSession 时限为 2 秒,USB 清理和重枚举仍可能增加耗时。不能仅凭扫描发现设备、按钮可用或 reset 返回成功判定验收通过。
只读验证投屏(先完成手机 USB 调试授权):
```sh
curios/harmony-connector/backend/harmony-connector --check-screen /absolute/path/to/hdc
# 多台设备时可指定目标;最后的可选目录用于保存最终画面
curios/harmony-connector/backend/harmony-connector --check-screen /absolute/path/to/hdc DEVICE_ID /absolute/save/directory
```
该命令走 UI 相同的后端接口,采集并解码 3 帧,输出耗时,最后关闭会话;不启用控制、不发送输入。首次 HDC 连接显示 Unauthorized 时,在手机上允许 USB 调试。`hdc -v` 能运行但管理失败时,先用 `hdc list targets -v` 检查授权状态。
0.3.0 自动化验证通过 39 项原生测试、27 项 UI/格式/坐标测试、3 项 USB C 桥接检查及 3 项底座协议检查。浏览器另外验证了持续显示模拟画面、启用控制、中文输入、放大和停止清空;模拟数据不会包含在发布包内。官方 Command Line Tools 26.0.0.821(Mac ARM)的 HDC 返回 `Ver: 3.2.0f`,已识别真机,但当次状态为 Unauthorized,故投屏和输入的真机效果尚未验收,不能将上述替身测试当作真机通过。
0.3.1 的安装引导验证通过 41 项原生测试、30 项 UI/格式/坐标测试和 6 项 USB/协议检查(共 77 项)。新增回归覆盖官网下载入口、选择已解压目录、取消/错误选择保留配置、启动后安装工具、工具丢失后返回引导。浏览器以明确标记的模拟工具检查了引导和配置后的界面切换。使用已下载的官方工具目录调用实际发布后端,成功定位 HDC 并返回 `Ver: 3.2.0f`;设备当时仍为 Unauthorized,后续投屏验收因无已授权设备而停止。
0.3.2 补上工具安装目录和 Downloads 的自动发现。回归在独立 HOME、空 PATH 工具目录、未保存任何设置的进程中运行真实 `Manager::new → hdc.tool`,验证数字版本选择、未完成解压跳过、手动路径优先和官方解压目录布局。实际 Mac 验证通过 Wire 请求 `hdc.tool`,使用空插件数据目录和系统 PATH,未调用 `configure`;0.3.1 返回 `available: false`,0.3.2 自动找到之前解压的工具并返回 `Ver: 3.2.0f`。USB 目标仍为 Unauthorized。该版 42 项原生测试、30 项 UI/格式/坐标测试及 6 项 USB/协议检查全部通过。
0.4.0 的 HDC 持续采集与会话恢复通过 54 项原生、33 项 UI/格式/坐标、6 项 USB/协议检查(共 93 项)。真机连续三轮开始、接收并解码 JPEG、停止和重新开始全部成功,每轮均确认代理和转发清理,无需拔线。新增回归覆盖自身孤儿进程恢复、结束失败不误报其他工具、多个窗口的同设备锁、仅恢复自身转发以及 JPEG 对齐填充的显示/输入坐标。浏览器确认填充裁切与放大布局。该轮没有发送真机输入或测量动态性能,详见[开发验证](harmony-realtime-development.md)。
## 构建与发布
要求 macOS、Xcode Command Line Tools、Rust/Cargo、Node.js、pnpm、make、pkg-config。固定源码归档与哈希位于 `vendor/harmony-connector/`。
```sh
pnpm install --frozen-lockfile
export TROVE_BIN=/absolute/path/to/app/target/debug/trove
pnpm run harmony:test
pnpm run harmony:pack
pnpm run release:prepare --entries dist/harmony-release.json
```
发布创建新的不可变版本产物,保留旧版本、哈希和生产源的已有条目。推送后必须以 Git 仓库为源执行安装验证。生产 README 保持产品介绍;技术约束与验收记录在本文和 `docs/research/`。
代码职责:`native/src/file_transfer.rs` 管理目录计划与落盘,`progress.rs` 统计进度,`connector.rs` 管理 MTP 会话与任务,`mtp.rs` / `mtp_bridge.c` 封装固定依赖,`hdc.rs` / `hdc_data.rs` / `hdc/files.rs` 管理官方工具与输出校验,`hdc/screen.rs` / `screen_input.rs` 管理屏幕会话与输入,`command.rs` 控制进程、输出、取消和时限。UI 的文件与管理工作区分开实现。
验证包括文件夹往返与失败清理、会话状态、工具命令和解析、安装确认及后置条件、页面交互、USB 桥接和底座协议。HDC 测试使用明确的工具替身,不代表商业手机已经完成安装、备份或截图验收。实际文件吞吐量和 USB 往返 SHA-256 仍需真机记录。
参考:[设备管理能力](research/harmony-device-management.md)、[会话恢复](research/harmony-mtp-session.md)、[投屏与控制](research/harmony-screen-control.md)、[第三方声明](../curios/harmony-connector/legal/THIRD-PARTY.md)。
-56
View File
@@ -1,56 +0,0 @@
# Harmony Connector 投屏开发记录
2026-09-10:用户反馈 0.5.1「很流畅」,但打开耗时较长。0.5.2 将窗口创建提前到设备探测之前,后台准备支持进度显示和取消;只在首张画面成功解码显示后保存设备的可用视频代理,下次优先使用,失败时保留自动回退。启动期间暂停管理页的设备轮询,避免争用。首次匹配耗时未承诺降低到固定数值;本轮按用户要求仅构建打包,未跑测试、真机计时或远程安装验证。
2026-09-10:0.5.1 针对用户报告的 `无法读写 Mac 文件:Connection refused (os error 61)` 修改启动流程。代码可确认该提示来自视频 TCP 连接使用了通用文件 I/O 错误转换;此前没有确认设备代理/Unix socket 就绪,且只重试同一端口。现在增加官方代理候选、就绪检查、仅重建自身转发、启动日志和首帧状态。具体设备上的代理兼容性与连接恢复效果仍等待用户反馈;本轮只构建打包,未运行测试或真机验证。
2026-09-10:0.5.0 改为独立 AppKit 窗口、官方 Unix H.264 视频流和默认实时触控。按用户要求,本轮只完成编码与构建,不运行测试、真机跑分或远程源安装验证。60 fps 是编码配置目标,不能作为已达到的性能结果。官方接口依据见[视频性能调研](research/harmony-screen-performance.md)。
以下为旧版本历史记录。
运行路径为:已授权的 HDC USB 连接 → 官方 Hypium 设备代理 → 持续 JPEG 推送 → 插件显示。用户无需开发者账号、AppGallery Connect、HAP 安装、编译或签名。代理由插件自动部署到本次会话的临时文件,停止时删除。
## 当前状态
2026-09-10:0.4.0 改为 HDC 持续 JPEG 推送,修复投屏残留会话误报占用及重连失败。0.3.2 的逐张截图链路为 696、803、802 ms,约 1.3 fps;新版本的动态帧率仍须独立测量。
官方 `xdevice-devicetest` 6.1.0.210 的 `uitest_agent_v1.2.2.so` 已在 HUAWEI Pura 70 Ultra 的商业系统上成功加载。独立探针收到真实 JPEG、屏幕尺寸 1260 × 2844,并成功调用尺寸、旋转和唤醒接口。设备端文件为 600245 字节,未改动签名原件。没有安装 HAP,也没有账号或系统共享确认步骤。
生产后端已在同一台真机上连续通过三轮“开始 → 接收并解码 → 停止 → 再次开始”,全程未拔 USB,每轮均确认 `pidof uitest` 为空、`fport ls` 无残留。每轮收到 2–3 张独立画面,也验证了静止画面返回 unchanged 的行为。独立探针另收到可查看的实际手机内容;这项连通/重连检查没有发送输入,不能作为动态帧率、跟手程度或端到端延迟达标的证据。
本次错误来自插件自身的残留 `trove-screen-*.so` 会话。手机的普通 `kill` 返回 `Permission denied`,旧代码忽略失败并把任意 UITest 进程视作其他工具。官方 xdevice 的清理使用 `kill -9`;当前手机实测接受该命令。修复会检查完整命令行身份,只清理本插件代理并确认进程退出;显式停止时清理失败会返回错误。其他工具的代理和未经本机记录的活动转发保持不动。主机按设备加文件锁,并记录自己的转发供异常退出后恢复。
手机正常画面实测为 640 × 1422,逻辑半尺寸为 630 × 1422,右侧含对齐填充。接收器兼容 16 像素边界的尾部填充,保留逻辑视口和 JPEG 存储尺寸;UI 直接裁去填充,不重新编码 JPEG,输入仍映射到 1260 × 2844。旋转或无法匹配的尺寸仍会关闭控制。
完整自动化检查通过 93 项:54 项原生、33 项 UI/格式/坐标、6 项 USB 桥接与底座协议检查。浏览器另外确认模拟画面的尾部彩色填充被裁去,放大时可见区域保持居中;这些界面测试不等同于真机性能测试。
之前的 HAP 实验源码和签名流程已移除。本机签名材料不属于插件或发行包。原方案的研究过程保留在[历史记录](research/harmony-realtime-stream.md),当前方案及来源见[HDC 持续投屏调研](research/harmony-hdc-only-screen.md)。
## 构建和验证
```sh
node curios/harmony-connector/build.mjs
node tools/test-harmony.mjs
# 手机开启 USB 调试并授权 Mac;多设备时在末尾加目标 ID。
curios/harmony-connector/backend/harmony-connector --check-screen /absolute/path/to/hdc
# 连续 20 秒,通过与 UI 相同的后端接口接收并实际解码 JPEG。
curios/harmony-connector/backend/harmony-connector --check-screen-stream /absolute/path/to/hdc
```
动态测量时保持手机解锁,并连续滑动桌面或滚动页面。`receivedFps` 与 `mbps` 是最近一秒收到的帧数和码率;`consumerFps` 是此验证程序在完整观察期解码的不同帧数除以时长,不是 WebView 呈现帧率。`frameAgeMs` 是收到画面后经过的时间,不是端到端延迟。静止画面不重复计帧。
上述命令默认不保存画面、不启用控制,也不发送输入。基础连通命令的第三个参数可以是用户指定的保存目录。真实交互仍需在插件中检验:启用控制、点击、拖动、返回/主页、文字输入、停止后重新开始,以及拔线后旧画面清空。
## 会话与协议
- 插件只保留最新一张 JPEG,不积压视频队列。UI 用上一帧序号等待变化;静止时返回状态,不重复发送图片。
- 图像与控制使用两个独立 socket,共用本会话的 HDC 转发。按键和手势通过持久 RPC 发送,文字使用已验证的 `uitest uiInput text`。
- 代理回包包括 JSON 行与 JPEG;解析 JPEG 段长度,避免把 EXIF 数据中的字节误认作图像结尾。单帧上限 2 MiB,JSON 上限 64 KiB。
- 静止画面不视为断线;独立控制连接查询尺寸检查存活。UI 停止请求超过 15 秒会结束会话。屏幕几何变化会关闭输入控制。
- 每次部署使用唯一文件名,以完整命令行重新确认进程身份后结束代理,并验证退出。设备锁与转发记录支持异常退出恢复;其他工具的代理和活动转发会阻止启动。停止时删除自己的临时文件和转发。
运行 HDC 不需要完整 SDK:本机独立验证了 `hdc` 与同目录 `libusb_shared.dylib` 的版本命令,总计约 6.4 MiB。完整工具包用于开发构建,不应成为普通投屏用户的依赖。本次尚未新增精简 HDC 发行包;插件继续使用已配置或自动发现的官方工具。
-114
View File
@@ -1,114 +0,0 @@
# Harmony Connector 技术调研
调研日期:2026-09-10。目标是 macOS 与 HarmonyOS 6 设备之间浏览、上传和下载用户文件。本文区分官方说明、源码证据与实现推断;尚无本项目的 HarmonyOS 6 真机传输验收结果。
## 决定:首版使用 USB MTP
采用 **libmtp 1.1.23 + libusb 1.0.30**,由 Curio 原生后端管理 USB 会话,界面提供设备选择、存储与文件夹浏览、双向文件传输及进度。依赖随插件发布,用户无需另装开发 SDK。首版聚焦 USB,不同时实现 HDC、ADB 或华为分享。
选择依据:华为为 HarmonyOS 6.0/6.1 提供“解锁设备 → 数据线连接 → 选择传输文件”的官方使用流程;其 Mac 支持说明也引导使用 MTP 工具。该消费者流程没有要求开启开发者选项。OpenHarmony 的设备端源码实现了标准 MTP 操作,libmtp 当前发布版已加入较新的华为设备标识。这些证据足以支持开发路线,**不等于特定商业机型已验证兼容**。[华为 HarmonyOS 6 USB 文件传输](https://consumer.huawei.com/cn/support/content/zh-cn16090699/)、[华为 Mac MTP 指引入口](https://consumer.huawei.com/cn/support/content/zh-cn16083899/)、[libmtp 1.1.23 发布](https://github.com/libmtp/libmtp/releases/tag/v1.1.23)
## HarmonyOS 6 的用户流程与边界
1. 使用支持数据传输的 USB 线连接 Mac 和手机,解锁手机。
2. 在手机 USB 弹窗或通知卡片选择“传输文件”,在 Harmony Connector 刷新设备并连接。
3. 浏览设备公开的存储,选择文件上传或下载。手机端仍决定哪些内容可见、可写。
华为的 6.0/6.1 说明区分两种模式:“传输文件”可访问文件管理中的内容,图库在此模式下可导出,但不能向图库导入照片或视频;图库双向操作应切换手机的“传输图片”模式。首版不能把图库导入失败当作普通文档上传失败,也不能承诺 PTP 模式已被本插件验收。[华为 HarmonyOS 6 USB 文件传输](https://consumer.huawei.com/cn/support/content/zh-cn16090699/)
MTP 公开的是存储和对象层次,不能据此访问所有应用沙箱或系统目录。实现应直接显示设备返回的对象与存储,而不是硬编码 `/sdcard`、`/data` 等路径。[libmtp 对象、存储与文件接口](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.h.in)
Mac 上如有其他 MTP 应用占用设备,应关闭占用者后重试。libusb 官方说明:未被内核驱动独占的设备通常可直接访问,无需 root;驱动独占是另一种限制,不能通过插件静默提权、卸载驱动来掩盖。[libusb macOS 访问说明](https://github.com/libusb/libusb/wiki/FAQ#how-can-i-run-libusb-applications-under-macos-if-there-is-already-a-kernel-extension-installed-for-the-device-and-claim-exclusive-access)、[libmtp 已知设备占用问题](https://github.com/libmtp/libmtp/blob/v1.1.23/README)
## 系统与协议比较
| 设备/路线 | 第一方证据 | 对本插件的意义 |
| --- | --- | --- |
| HarmonyOS 3/4.x 的 USB 文件传输 | 华为文档明确 MTP,并介绍 Mac 侧文件传输工具。[来源](https://consumer.huawei.com/cn/support/content/zh-cn00407259/) | 后续可按 MTP 实测扩展;不以“鸿蒙”名称判断底层协议。 |
| HarmonyOS 5.0/5.1 | 华为 USB 文档指定手机至少 5.0.0.123;“传输文件”模式不访问图库,图库使用另一模式。[来源](https://consumer.huawei.com/cn/support/content/zh-cn16059258/) | 不能泛称所有早期 NEXT 系统均可用,也不能把 6.x 的图库行为回推至 5.x。 |
| HarmonyOS 6.0/6.1 | 官方描述 USB 文件/图片传输流程。[来源](https://consumer.huawei.com/cn/support/content/zh-cn16090699/) | 本次主要目标,按公开对象浏览与传输设计。 |
| ADB 与 HDC | 华为开发工具的平台说明分别将 ADB 对应 4.0 及以下、HDC 对应 5.0 及以上。[来源](https://developer.huawei.com/consumer/cn/doc/tools-guides/overview-0000001050741459) | ADB 不是 NEXT/5/6 的通用连接方案;消费者文件传输不应强制引入调试工具。 |
| HiSuite | 当前官网明确 HarmonyOS 5 及以上暂不支持在 macOS 上连接使用。[来源](https://consumer.huawei.com/cn/support/hisuite/) | 不作为插件底层,也不建议用户安装它以解决本插件连接。 |
| 华为分享 | 华为支持页列出手机/平板 6.0.0.112+ 与 Mac 10.15+,Mac 需运行“鸿蒙星河互联”,开启 WLAN/蓝牙。[来源](https://consumer.huawei.com/cn/support/content/zh-cn16093657/) | 已有官方无线方案,但本文未找到可嵌入的公开 macOS SDK/协议授权;不冒充官方分享客户端。 |
华为支持页中称为“MTP Tool”的工具,不应未经确认就指向 App Store 某个同名产品。该支持页下游链接在调研时跳转到支持搜索;可确认的是华为推荐 Mac 使用 MTP 工具,无法从此确认某个二进制实现、签名或私有握手。[华为原始指引](https://consumer.huawei.com/cn/support/content/zh-cn16083899/)
## 标准 MTP 与库选择的源码证据
OpenHarmony 第一方 `multimedia_media_library` 的 `media_mtp` 使用 `OpenSession`、`GetObjectHandles`、`GetObject`、`SendObjectInfo`、`SendObject` 等标准操作与对应操作码,存在会话建立、目录对象查询和双向传输的直接实现。所查实现未显示普通文件传输必须先进行 HiSuite 专有认证。**这是对所查开源代码的观察,不是商业 HarmonyOS 固件完全等同该源码的断言。**[操作定义](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/include/mtp_constants.h)、[设备端操作处理](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/mtp_operation.cpp)
libmtp 1.1.23 发布说明包含 Huawei Nova14,设备表为 `VID 0x12d1 / PID 0x1101`,使用已有兼容标志。1.1.22 的设备表尚无该条目;因此首版选择 1.1.23。设备表条目证明上游已收录设备 ID,不能证明本插件在这台设备完成过读写。[1.1.23 发布说明](https://github.com/libmtp/libmtp/releases/tag/v1.1.23)、[1.1.23 设备表](https://github.com/libmtp/libmtp/blob/v1.1.23/src/music-players.h)、[1.1.22 设备表](https://github.com/libmtp/libmtp/blob/v1.1.22/src/music-players.h)
libmtp 是基于 libusb 的 MTP 主机库,有现成 C 接口;libusb 明确支持 macOS 及 Apple Silicon,官方提供双架构构建说明。由项目固定版本编译 arm64/x86_64 产物,避免依赖用户电脑中的 Homebrew 路径。[libmtp 架构](https://github.com/libmtp/libmtp/blob/v1.1.23/README)、[libusb 平台支持](https://github.com/libusb/libusb/blob/v1.0.30/README)、[libusb Apple Silicon](https://github.com/libusb/libusb/wiki/FAQ#does-libusb-support-apple-silicon-based-mac)
OpenMTP 可用作 macOS MTP 产品与集成的参考,但它当前 Kalam 内核使用 `go-mtpx`/`go-mtpfs`,不是直接复用 libmtp。仓库提供 Intel 和 ARM64 版本说明;其项目支持范围不能直接继承为 Harmony Connector 的支持范围。首版用 libmtp 以复用现有原生后端和 C ABI,避免另外加入 Go、Electron 及完整应用依赖。[OpenMTP 源码说明](https://github.com/ganeshrvel/openmtp)、[go-mtpx 模块依赖](https://github.com/ganeshrvel/go-mtpx/blob/18f12db021cc4256396748dbd2996d15853d7033/go.mod)
## 原生后端实现契约
以下是基于库接口的实现建议,不是设备兼容性承诺:
| 能力 | libmtp API / 规则 |
| --- | --- |
| 枚举与连接 | `LIBMTP_Detect_Raw_Devices`;用户选定后 `LIBMTP_Open_Raw_Device_Uncached`,避免连接即扫描全部文件。 |
| 存储与浏览 | `LIBMTP_Get_Storage`;`LIBMTP_Get_Files_And_Folders(device, storageId, parentId)`。根对象参数使用库的 `LIBMTP_FILES_AND_FOLDERS_ROOT`。 |
| 下载 | `LIBMTP_Get_File_To_File_Descriptor`,按 object ID 获取数据。 |
| 上传 | `LIBMTP_Send_File_From_File_Descriptor`,明确文件名、字节长度、目标 storage ID 和 parent ID。 |
| 进度/取消 | 库回调返回累计字节及总字节;返回非零值可请求取消当前传输。 |
| 失败与释放 | 每次操作读取并清理 error stack,断开时 `LIBMTP_Release_Device`。 |
接口依据:[libmtp 1.1.23 公开头文件](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.h.in)。
工程约束:
- 设备会话和对象 ID 有生命周期;重连后重新枚举,不复用旧句柄。每个会话串行执行命令,传输时允许取消请求但不另开同设备读写。
- 界面通过结构化消息接收条目与进度,不解析 `mtp-*` 示例程序的输出。库本身也说明其命令行程序是示例。[libmtp 示例说明](https://github.com/libmtp/libmtp/blob/v1.1.23/README)
- 用本地文件描述符传输;下载先写独立临时文件,只有成功后才提交到用户选定目标,失败清理本次临时文件。默认拒绝同名覆盖或让用户明确选择。
- 设备返回的名字仍是不可信输入,拒绝路径分隔符、`..`、NUL 等会越出本地目标目录的名字;Unicode 和空格按原名处理。
- 取消是协作式的,不承诺设备卡死时立即停止;不要强杀执行 USB 调用的线程。阻塞请求交给有超时和退出边界的原生进程处理。[libusb 同步 I/O 限制](https://github.com/libusb/libusb/wiki/FAQ#what-are-the-differences-between-synchronous-and-asynchronous-device-io)
- 不自动删除手机现有文件,不清空存储,不更改 USB 系统设置,不绕过手机锁屏或授权。
## HDC 备选方案记录
HDC 可在 macOS 运行,通过 USB 或 TCP 调试连接,需设备启用调试并在信任提示中授权。官方获取入口是 DevEco Studio / Command Line Tools,独立运行需保留相邻的 `libusb_shared` 依赖。目标列表可显示未授权状态;TCP 在所查文档中被标注为测试用途。它适合开发者诊断,不作为本次消费者首版依赖。[HDC 官方文档](https://github.com/openharmony/docs/blob/master/zh-cn/application-dev/dfx/hdc.md)
```text
hdc list targets -v
hdc -t <device> shell <command>
hdc -t <device> file send <local-file> <remote-file>
hdc -t <device> file recv <remote-file> <local-file>
hdc tconn <ip>:<port>
```
这些命令来自 [HDC 第一方仓库使用说明](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/README_zh.md)。可访问性仍由设备限制;不能把 `/data/local/tmp` 调试目录当成手机文件管理的下载目录。应用沙箱 `-b` 仅适用于可调试应用;媒体目录也有 API 版本与操作限制。[HDC 文件与沙箱访问](https://github.com/openharmony/docs/blob/master/zh-cn/application-dev/dfx/hdc.md#文件传输)
若将来加入 HDC 浏览,应先探测设备命令能力,用受控脚本、严格参数转义和明确分隔的字段输出,避免按空格拆 `ls -l`。OpenHarmony 的 Toybox `stat` 提供格式化字段,但不同固件需实测。[Toybox stat 实现](https://github.com/openharmony/third_party_toybox/blob/master/toys/other/stat.c)
HDC 开源仓库采用 Apache-2.0;这不自动授权重分发华为下载站的整个 SDK 或任意预编译工具。若采用该路线,优先使用用户已装官方工具,或对确切开源依赖自行构建并保存声明。[HDC LICENSE](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/LICENSE)、[华为 Command Line Tools 下载](https://developer.huawei.com/consumer/cn/download/command-line-tools-for-hmos)
## 依赖与发布许可
libmtp 发布的 `COPYING` 为 LGPL 2.1,部分源文件头部保留 version 2 or later 表述;libusb 明确采用 LGPL 2.1 or later。应保留上游实际文本和版权,不自行统一改写原声明。[libmtp COPYING](https://github.com/libmtp/libmtp/blob/v1.1.23/COPYING)、[libmtp 头文件声明](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.h.in)、[libusb COPYING](https://github.com/libusb/libusb/blob/v1.0.30/COPYING)
对本次发布,选择可审查的具体做法:
1. 动态链接插件内的库,在第三方声明中标出名称、版本、原项目和许可证,随包提供完整 `COPYING`。
2. 与对应二进制一起提供完整上游源码归档、固定校验、实际补丁以及构建脚本。若归档独立发布,接收二进制的用户应能在同一分发位置以等价方式获取;只给上游主页不作为交付方案。
3. 保留用户用兼容修改版库替换、重新构建和运行插件的路径,并说明 macOS 重新签名/本地安装步骤;插件条款不得禁止为调试此类修改而逆向。
4. `.curio` 安装包与依赖源码都保持版本固定。上游源码或编译选项有变化时重新记录来源并发布新插件版本。
第 1—3 项直接对应 LGPL 2.1 第 4 节、第 6 节与第 6(b) 项;动态链接并不免除“已随包分发的库”自身的对应源码交付要求。[LGPL 2.1 随 libmtp 的完整许可](https://github.com/libmtp/libmtp/blob/v1.1.23/COPYING)、[LGPL 2.1 随 libusb 的完整许可](https://github.com/libusb/libusb/blob/v1.0.30/COPYING)
## 验收与尚未证明的部分
本调研完成了官方流程、第一方设备端代码、库 API 与许可核查。没有将真实设备复制记录、吞吐量或机型兼容性伪装成已完成结果。当前开发主机尚未检测到华为 USB 设备(由主任务本机检查提供)。
正式标记某机型可用前,记录 Mac 型号/架构、macOS 版本、手机型号、HarmonyOS 完整版本及 USB 模式,并执行:
- 未连接、锁屏、仅充电、文件传输、拒绝授权、设备被其他工具占用等状态。
- 枚举多个设备与存储,选择后浏览,不访问未选中的设备。
- 单文件上传和下载:空文件、中文/空格/emoji 名称、二进制和大文件,回传后比较 SHA-256。
- 同名目标处理、磁盘空间不足、只读目录、权限拒绝、传输中拔线和取消,确认既有文件未被破坏。
- 连接关闭、重连、插件退出、底座退出后 USB 会话正常释放。
- HarmonyOS 6 图库导入限制有可理解反馈,不用文档目录成功代替图库验收。
通过库级测试、模拟传输、无设备枚举或包安装测试,只能证明对应层的行为,不能替代上述真实 USB 读写。
-268
View File
@@ -1,268 +0,0 @@
# Harmony Connector 手机管理能力调研
调研日期:2026-09-10。承接 [USB 文件传输调研](harmony-connector.md),评估 Harmony Connector 从文件工具扩展为手机助手。以调研时的最新官方文档设计能力,不以 HarmonyOS 6 为硬上限;HUAWEI Pura 70 Ultra 是待验收设备,不能凭系统名称或文档命令表宣称真机已支持。
本文只做资料核查和本机工具定位,没有连接手机、安装应用、读取手机数据或改变设备状态。华为商业 HarmonyOS 文档与 OpenHarmony 开源资料分别标注。后者能证明对应源码/命令契约,不能直接证明商业固件的编译选项、授权策略与运行行为。
## 结论与实现优先级
建议保留 MTP 用户文件通道,并增加可选的本机官方 HDC 通道。HDC 支持 Mac,适合应用管理、调试文件和诊断;华为要求先开启并授权 USB/无线调试。普通文件传输不应因为新增管理能力而强制进入开发者模式。[华为 HDC](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)、[华为开发者选项](https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-developer-mode)、[华为 USB 文件传输](https://consumer.huawei.com/cn/support/content/zh-cn16090699/)
以下优先级是本项目的工程建议,表示可实现范围,尚非真机验收结果:
| 优先级 | 功能 | 启用条件与产品表达 |
| --- | --- | --- |
| P0 | HDC 工具选择、版本和连接状态;设备基本信息 | 找到可运行工具,用户选择目标,授权成功;失败分清工具缺失、未授权、离线、命令不支持。 |
| P0 | 应用列表、应用详情、签名 HAP 安装、单应用卸载 | 包管理命令可用;安装预览包信息和覆盖影响;卸载显示具体包和数据损失。 |
| P0 | 日志快照、系统报告导出 | 用户触发,限时/限量,保存到用户选择的位置。 |
| P1 | 截图、应用启动、强制停止 | 先探测设备工具和真实组件;动作只针对用户选定对象。 |
| P1 | 调试应用数据导出;导出本机已有的安装包 | 沙箱导出逐应用检查调试签名与权限;安装包归档只处理用户拥有的原包,不等同从设备提取所有应用。 |
| P1 | 用户文件备份任务 | 明确选择范围、清单、校验和、失败项;仅称“所选文件备份”。 |
| P2 | 调试应用数据恢复、无线调试、实时日志/进程、UI 控制 | 探测成功后开放;恢复/控制/无线切换属于改变设备状态的动作。 |
| 暂不承诺 | 全机镜像、所有第三方应用数据备份、联系人/短信数据库直接读写、任意已安装应用导出并可恢复 | 未找到面向普通第三方 macOS 助手的官方公共接口;不能由 MTP 或 HDC shell 推导得到。 |
## 工具获取、发现和授权
华为官方获取路径为 DevEco Studio 所带 SDK,或 Command Line Tools 的 SDK;macOS 下典型位置为 `DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc`。独立运行还需保留相邻 `libusb_shared` 依赖。这说明可以使用已安装的官方工具,不能据此推断可以重新打包分发华为整个 SDK。[华为 HDC 环境准备](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)、[官方工具下载入口](https://developer.huawei.com/consumer/cn/download/)
实现建议:先读用户保存的绝对路径,再查应用自身继承的 `PATH`,最后检查 `/Applications`、`~/Applications` 的 DevEco Studio 应用包及用户选定的 Command Line Tools 目录。解析软链接,验证是文件且可执行,显示最终路径。不要全盘扫描、执行下载来的未知工具或自动修改 shell 配置;GUI 的 PATH 不一定与交互终端相同。
本次本机只读发现:`PATH` 中无 `hdc`;在 `/Applications`、`~/Applications` 和存在的 `~/Library/OpenHarmony`、`~/Library/Huawei`、`~/Library/DevEcoStudio`、`~/ohos-sdk` 范围未找到候选工具。这不是“整台电脑绝无 HDC”的结论。没有安装 SDK,也没有执行设备命令。
首次开启开发者选项与随后打开调试开关是不同步骤:华为手机/平板指引要求在设备名称页连续点击软件版本七次,确认并按需输入 PIN,首次开启可能触发重启。之后 USB/无线调试需在手机确认计算机授权。插件应引导用户操作设备,不绕过锁屏、信任弹窗、企业调试管控。[华为开发者选项](https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-developer-mode)
OpenHarmony HDC 仓库采用 Apache-2.0。若未来需要随插件交付 HDC,可评估从确切开源提交自行构建并保留许可证、版权与依赖声明;商业 Huawei 下载包的分发权需核对其实际许可,本次未确认该授权。不能把仓库许可扩展至 SDK 的其他组件或使用华为商标暗示官方产品。[OpenHarmony HDC LICENSE](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/LICENSE)
## CLI 调用契约
以下表格中 `key` 是用户选定的 `connect-key`,`bundle`/`ability`/`module` 来自设备已返回且验证过的数据;`local` 是用户选择的绝对本地路径。数组是传给 `Command::new(hdc).args(...)` 的 argv,省略可执行文件本身。带尖括号的值只作占位,不能原样执行。
宿主不要使用 `sh -c` 拼接 HDC 命令。`hdc shell` 的最后一个参数仍会由设备 shell 解释,必须用固定模板与严格参数验证/单引号转义;宿主 argv 安全不等于远端 shell 安全。文件名、应用名和设备返回内容都不是可信 shell 代码。
### 只读探测与设备信息
| 功能 | argv | 证据与处理 |
| --- | --- | --- |
| 客户端版本 | `["-v"]` | 解析 `Ver:` 字段,保留未知版本原文。 |
| 客户端/服务端版本 | `["checkserver"]` | 记录两端版本,不能认为刚选择的二进制已经替换后台旧服务。 |
| 设备状态 | `["list", "targets", "-v"]` | 识别 `USB/TCP` 和 `Unknown/Ready/Connected/Offline/Unauthorized`;`[Empty]` 表示没有目标。 |
| HDC 帮助 | `["-h", "verbose"]` | 作为本机命令存在性的证据。 |
| 包管理能力 | `["-t", key, "shell", "bm help"]` | 成功识别帮助内容后再开放相关操作。 |
| 组件管理能力 | `["-t", key, "shell", "aa help"]` | 不通过实际启动/停止应用来探测。 |
| 截图工具能力 | `["-t", key, "shell", "uitest help"]` | 要求返回可识别 `screenCap` 帮助。 |
| 日志工具能力 | `["-t", key, "shell", "hilog -h"]` | 确认 `-x` 可用。 |
| 基本设备信息 | `["-t", key, "shell", "hidumper -c base"]` | 华为文档展示 BuildId、ReleaseType、OsVersion、DeviceType 等字段。 |
| 参数读取 | `["-t", key, "shell", "param get <name>"]` | 单项读取,键需要固件实际返回/探测;未知键不应显示成空字符串的正常值。 |
HDC 命令和状态依据 [华为 HDC](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc);设备信息依据 [华为 HiDumper](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/hidumper);`param get` 语法依据 [OpenHarmony param 文档](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/tools/param-tool.md)。`hdc -t key shell hdcd -v` 是守护程序版本探测,不能当作手机完整系统版本。
实现应把“工具不存在”“没有授权”“权限不足”“输出无法识别”“版本不支持”与“数据为空”区分开。每个能力使用 `unknown / available / unavailable` 加原因;仅凭全局 HDC 连接成功不能一次性点亮所有管理功能。连接阶段不要执行 `kill -r`、`reconnect`、`tmode`、重启或任何包管理写操作。
### 应用列表、安装、卸载与运行
| 功能 | argv | 条件 |
| --- | --- | --- |
| 包名列表 | `["-t", key, "shell", "bm dump -a"]` | 华为官方 FAQ 直接采用该命令查询已安装应用。 |
| 包详情 | `["-t", key, "shell", "bm dump -n <bundle>"]` | 华为 HiDumper 指引用详情中的 `appProvisionType` 判断 debug。 |
| 调试包列表 | `["-t", key, "shell", "bm dump -g"]` | OpenHarmony 最新文档支持;需以本机帮助/实际输出确认。 |
| 应用显示名 | `["-t", key, "shell", "bm dump -n <bundle> -l"]` | OpenHarmony 文档标注 API 20 起;失败仍可显示包名。 |
| 安装签名 HAP | `["-t", key, "install", local]` | 本地绝对路径,成功后重新查询包版本。 |
| 单应用卸载 | `["-t", key, "uninstall", bundle]` | 指定包名;拒绝自动级联卸载依赖。 |
| 启动组件 | `["-t", key, "shell", "aa start -b <bundle> -a <ability> -m <module>"]` | 从详情找可启动、已导出的真实组件;不能硬编码所有应用都有 `EntryAbility`。 |
| 强制停止 | `["-t", key, "shell", "aa force-stop <bundle>"]` | 与正常退出不同,可能丢失尚未保存的工作。 |
查询依据:[华为应用市场错误排查](https://developer.huawei.com/consumer/en/doc/harmonyos-references/errorcode-appgallery)、[华为 HiDumper](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/hidumper)。`bm` 详细参数与 user/root 限制依据 [OpenHarmony bm 文档](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/tools/bm-tool.md)。`aa` 的显式启动、exported 限制、强停和响应依据 [OpenHarmony aa 文档](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/tools/aa-tool.md);商业华为 HDC 将 aa 列为配套工具,但本次华为 aa 独立页面未成功抓取,故实际能力仍需设备探测。
安装不是“有 HAP 就能装”:华为要求真实设备使用签名包,自动签名会绑定调试设备信息。debug/release 签名、应用标识、版本和多模块一致性受安装器校验;不能把 OpenHarmony 的公开测试证书当作商业设备通用签名,也不能从 HDC 命令存在推导任意 release 包均可安装。[华为开发入门](https://developer.huawei.com/consumer/cn/develop-novice-guide/)、[华为自动签名](https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-signing-auto)、[华为安装与更新一致性校验](https://developer.huawei.com/consumer/cn/doc/doccenter-getting-started/install-and-update-consistency-verification)
华为 HDC 说明 `install` 默认就是覆盖安装,省略 `-r` 不能防止覆盖;安装路径支持 HAP/应用内 HSP,APP 包从 API 22 起。`-w` 等组合参数有特殊解析约定,初版建议先只用必要参数。用户操作之前展示即将安装的应用与已装版本;签名冲突或系统拒绝时保留错误码,不能自动先卸载再安装。卸载共享库可能因其他应用依赖而被拒绝。[华为 HDC 安装说明](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)、[华为共享包依赖错误 9568312](https://developer.huawei.com/consumer/cn/doc/doccenter-dev-faq/faqs-technology-32)
包名清单是行文本,详情可能有标签/前缀加 JSON 内容,不能将所有 `bm` 命令输出当作同一个稳定 JSON API。建议仅接受明确命令对应的格式:列表验证包名,详情解析完整 JSON 对象并校验预期字段与查询包名;无法解析则显示原始诊断并禁用依赖该字段的操作。显示名不能作为包名、权限或可卸载性的替代。安装和卸载需同时判断进程结果、工具成功/失败信息及操作后的目标状态,不能只看是否含 `AppMod finish`。
### 截图、日志和系统报告
1. 探测 `uitest help` 后,为每次截图生成不可预测且只含安全字符的文件名。
2. 调用 `["-t", key, "shell", "uitest screenCap -p /data/local/tmp/curios-<nonce>.png"]`。
3. 调用 `["-t", key, "file", "recv", "/data/local/tmp/curios-<nonce>.png", localTemp]`。
4. 校验下载文件确实是可解码 PNG,再提交到用户目标位置。只删除本次创建的远端文件;失败时不要清理整个临时目录。
`uitest screenCap -p`、只能存到 `/data/local/tmp/` 的限制,以及 `-d` 多屏参数从 API 20 开始,均来自 [OpenHarmony UITest 官方文档](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/application-test/uitest-guidelines.md)。华为官方也提供 HiSmartPerf 的截图采集/文件导出流程,但本次没有将 `snapshot_display` 的商业固件可用性或任何投屏帧率作为已确认能力。[华为 HiSmartPerf](https://developer.huawei.com/consumer/cn/doc/doccenter-testing/smartperf-guidelines)
| 导出 | argv | 实现建议 |
| --- | --- | --- |
| 日志快照 | `["-t", key, "shell", "hilog -x"]` | `-x` 非阻塞读完退出,宿主将 stdout 写到独立临时文件。 |
| 实时日志 | `["-t", key, "shell", "hilog"]` | 持续读取,提供停止按钮和大小限制;不要视为普通短请求。 |
| 系统报告 | `["-t", key, "bugreport", localTemp]` | FILE 是本地路径;内容可能较多,预留更长但有限的超时。 |
| 已有落盘日志 | `["-t", key, "file", "recv", "/data/log/hilog", localDir]` | 只有权限与路径确认后才能导出,不需先开启新的落盘任务。 |
`hilog -x` 语义依据 [OpenHarmony HiLog](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/dfx/hilog.md);`bugreport` 与现有落盘日志路径依据 [华为 HDC](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)。日志与系统报告可能包含应用活动和设备标识;默认只本地保存,不自动上传。不要为普通日志导出自动执行清日志、关闭隐私过滤或开启持续日志落盘。
## 备份和恢复:需要分别命名的三种能力
### 用户文件副本
MTP 面向设备公开的存储对象。选择文件夹后递归复制、生成大小/校验和清单、记录失败项,是可以实现的“所选文件备份”;没有通过 MTP 暴露的沙箱、系统设置、账号和数据库不在范围内。图库模式限制沿用原调研,不能把普通文件夹可写推导为可任意恢复图库数据库。[libmtp 对象/存储 API](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.h.in)、[华为 USB 文件传输](https://consumer.huawei.com/cn/support/content/zh-cn16090699/)
### 调试应用数据导出与导入
华为明确 `shell -b`、`file send/recv -b` 只用于“调试证书签名且已经在设备上启动”的应用;不是任意应用的沙箱读取入口。`-b` 从 API 15 引入。先查询 `appProvisionType`,再对具体目录进行只读访问探测,不应单凭包名出现在应用列表就启用备份。[华为 HDC 沙箱条件](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)、[华为 HiDumper 调试类型判定](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/hidumper)
```text
查询指定调试应用目录:
["-t", key, "shell", "-b", bundle, "ls data/storage/el2/base/"]
导出用户选择的沙箱文件或目录:
["-t", key, "file", "recv", "-b", bundle, "<verified-remote-path>", "<new-local-directory>"]
向同一调试应用导入已选择的数据:
["-t", key, "file", "send", "-b", bundle, "<local-file>", "<verified-remote-path>"]
```
这些是官方 `-b` 文件操作语法,remote path 应在该应用实际可见的根目录内,不使用 `/data/app/...` 猜测另一个应用的路径,不接受越界 `..`。导出清单应记录设备/系统、包名、版本、签名类型、所选路径、时间、实际复制文件及校验结果。正在运行的应用可能继续写入数据库;原始文件副本不自动获得事务一致性,也不保证跨设备的密钥、登录状态或应用版本可恢复。建议界面名称为“导出调试应用数据”,完整备份成功必须有明确的范围与验证。
恢复是覆盖写入,不应在枚举或探测时尝试。只允许用户选择明确的备份和目标应用,核对包名/版本/范围,展示将修改的文件后执行。未经该应用可恢复性验证,不提供一键“全部应用恢复”。不要通过重新签名现有商业应用或卸载重装来制造沙箱访问权限。
### 官方系统备份
华为应用备份框架允许应用接入 `BackupExtensionAbility` 并声明可备份范围,但普通应用自身不能触发系统备份;触发功能面向系统应用。官方最新验证指引使用数据克隆,由新旧设备配对触发。应用可不接入,也可限制备份内容。因此额外装一个普通手机伴随应用,并不自然获得其他应用全部数据的备份权。[华为备份概述](https://developer.huawei.com/consumer/en/doc/harmonyos-guides-V13/app-file-backup-overview-V13)、[华为应用接入](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/app-file-backup-extension)、[华为数据克隆验证](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/app-file-backup-dataclone)
特别检查了 OpenHarmony `filemanagement_app_file_service` 提交 `589be5d84d3c23967be62ca7f0317690237a0041`:
- `backup_tool/BUILD.gn` 仅在 `build_variant == "root"` 编入真实操作,且 `install_enable = false`。
- 未定义 `BACKUP_TOOL_ENABLE` 时,`main()` 直接返回 0,不执行备份。
- 备份服务检查 BACKUP 权限;HAP 调用还检查系统应用身份,shell 调用亦受 UID 条件限制。
这是开源实现证据,不能证明商业设备使用完全相同实现;却直接说明“找到 backup_tool 且退出码为 0”不是备份成功证据。本轮不将该工具作为商业设备备份后端,也不自动尝试备份/恢复命令。[OpenHarmony 构建约束](https://github.com/openharmony/filemanagement_app_file_service/blob/589be5d84d3c23967be62ca7f0317690237a0041/tools/backup_tool/BUILD.gn)、[无操作入口](https://github.com/openharmony/filemanagement_app_file_service/blob/589be5d84d3c23967be62ca7f0317690237a0041/tools/backup_tool/src/main.cpp)、[服务权限检查](https://github.com/openharmony/filemanagement_app_file_service/blob/589be5d84d3c23967be62ca7f0317690237a0041/services/backup_sa/src/module_ipc/service.cpp)
## 与官方助手的能力对照
| 官方路线 | 已确认范围 | 对本插件的结论 |
| --- | --- | --- |
| HiSuite / 华为手机助手 | 当前官网针对 HarmonyOS 5+ 列出基础数据、部分第三方应用及数据、部分设置备份;受系统/应用兼容性限制。明确 5+ 暂不支持 macOS 连接。 | Windows 官方能力不等于 macOS 第三方插件可调用的协议/API。不能用旧 Android/HDB 教程保证原生鸿蒙兼容。 |
| 手机“备份和恢复 → 外部存储” | 官方 5+ 指引支持第三方应用和部分应用数据等,并需备份密码;范围以实际设备为准。 | 可以提供官方操作指引,或归档用户已经生成的备份文件;尚无公开证据支持插件在 Mac 直接触发/解密其备份。 |
| 数据克隆 | 最新开发者指引通过两台设备触发已接入应用的数据迁移验证。 | 是系统侧迁移流程,不是通用电脑备份 CLI。 |
| 华为分享 + 鸿蒙星河互联 | 官方提供与 Mac 的图片、视频、文档无线互传流程。 | 可链接官方方案;没有据此获得可嵌入分享 SDK、应用安装或全机备份能力。 |
分别依据:[华为 HiSuite 当前支持范围](https://consumer.huawei.com/cn/support/hisuite/)、[华为外部存储备份](https://consumer.huawei.com/cn/support/content/zh-cn16024725/)、[华为数据克隆验证](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/app-file-backup-dataclone)、[华为与 Mac 互传](https://consumer.huawei.com/cn/support/content/zh-cn16093657/)。产品可以覆盖“文件、应用、设备、诊断、备份”模块;不同模块应展示实际通道与数据范围,不把官方系统级功能画成已经可执行的按钮。
## 无线与其他能力
用户先在手机开启无线调试,输入手机显示的 IP 和端口,然后调用 `["tconn", "<ip>:<port>"]`;断开用 `["tconn", "<ip>:<port>", "-remove"]`。官方仍将 TCP 调试标为测试场景。`tmode port` 会重启设备守护进程并断开会话,连接探测不应使用它。端口转发、重启、UI 输入可以作为后续显式操作,不默认启用。[华为 HDC 无线和连接管理](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)
最新文档版本提示用于解释能力差异,不作为 HarmonyOS 大版本硬编码:`-b` 与 API 15/HDC 3.1.0e 配套;API 20/HDC 3.2.0b 增加部分网络能力;APP 安装 API 22;安装自动授权 `-g` API 24;沙箱无命令交互式 shell、reconnect、子服务器等 API 26 新能力仍需两端支持。实现按本机帮助和设备实际结果逐项判定,不自动调用新命令来“修复”连接。[华为 HDC 版本表](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)
## 输出解析、操作风险与验收
建议为 HDC 建立独立执行适配器:固定可执行路径、每次携带目标、有限并发、逐设备串行写入、进程组取消、stdout/stderr 大小上限、操作超时。长日志和文件任务走流式输出,UI 接收结构化事件,不在前端解释 shell 字符串。不要为了隔离无条件另开 USB server,也不要在退出插件时杀掉用户 DevEco Studio 的共享 HDC 服务。
最小验收应覆盖可观察行为:
- 无工具、工具依赖缺失、未连接、拒绝授权、多设备、未知输出格式、版本不匹配,都不得显示假空列表或操作成功。
- 应用列表中中文显示名与包名分离;详情解析失败不影响其他条目;安装拒绝/签名冲突/包格式错误保留真实结果;成功安装/卸载后刷新验证。
- 签名 HAP 安装只能使用用户专门选取的测试包;卸载只能操作明确选择的应用。验证不得卸载用户日常应用来测试失败路径。
- 截图下载后能解码,报告文件非空且包含实际内容,日志导出有限且可取消;中断不会把临时文件报告为完成。
- 调试沙箱必须验证 release 应用被限制、debug 未启动的处理、权限失败、部分传输、中文/空格路径;导出实际文件校验和对应清单。恢复只在专门测试应用验证,并检查恢复后的应用行为。
- 系统备份无法实现时明确展示边界,不执行空壳 `backup_tool`,不以退出码 0、模拟输出、无设备测试或构建通过替代真机验收。
本调研最终产物仅为本文。文档命令来自第一方资料,设备兼容性仍需按实际固件验证;本机未找到 HDC 的范围见上文,未对手机执行任何命令。
## 补充:应用输出解析与真实入口选择
这一节按 2026-09-10 获取的 OpenHarmony 源码追踪输出生产链:Bundle Framework `e0b9be9be36186e040385895f7f764e6e111a809` 与 bm CLI `6b9b9ad316b764f472194a962081c0fb99c32fdf`。下面是**由源码生成规则还原、省略无关字段的代表样例**,不是 HUAWEI Pura 70 Ultra 的实测输出。商业设备返回新结构时应报告格式未知,不能假装已经得到相同数据。
### `bm dump -a` / `bm dump -g`
成功输出是按用户分组的纯文本,不是 JSON;当前 CLI 直接转发服务结果,没有额外 `all bundle names:` 标题。样例中的应用名是占位数据,缩进实际为制表符。服务支持多个用户段;CLI 默认查询当前用户,指定不活跃 `-u` 可能产生前置警告并退回当前用户。[BMS 列表生成](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/services/bundlemgr/src/bundle_mgr_host_impl.cpp#L2999)、[bm CLI 查询和转发](https://github.com/openharmony/bundlemanager_bundle_tool/blob/6b9b9ad316b764f472194a962081c0fb99c32fdf/frameworks/src/bundle_command.cpp#L1153)
```text
ID: 100:
com.example.notes
com.example.reader
```
解析建议:保留 userId 分组;只在已识别 `ID: <integer>:` 段内读取并验证包名;允许空段和空行;未知诊断行不能被当作应用。不要把任意含点字符串提取成包名,也不要把错误文本归一化成零应用。空结果或查询失败时,当前 CLI 会输出下面的错误,且 dump 分支仍可能返回成功退出码。[bm 空结果处理](https://github.com/openharmony/bundlemanager_bundle_tool/blob/6b9b9ad316b764f472194a962081c0fb99c32fdf/frameworks/src/bundle_command.cpp#L1246)、[缺包查询官方系统测试](https://github.com/openharmony/bundlemanager_bundle_tool/blob/6b9b9ad316b764f472194a962081c0fb99c32fdf/test/systemtest/bm/bm_command_dump_system_test.cpp#L106)
```text
error: failed to get information and the parameters may be wrong.
```
### `bm dump -n <bundle>` 的字段
服务先输出包名和冒号,再输出完整 JSON 对象。它显式移除顶层 `abilityInfos`,组件应从 `hapModuleInfos[].abilityInfos[]` 读取;不能把 manifest 的 `abilities` 字段直接当作 dump 格式。[BMS 详情组装](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/services/bundlemgr/src/bundle_mgr_host_impl.cpp#L3094)、[模块序列化](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/interfaces/inner_api/appexecfwk_base/src/hap_module_info.cpp#L927)
```text
com.example.notes:
{
"name": "com.example.notes",
"versionCode": 1000000,
"versionName": "1.0.0",
"entryModuleName": "entry",
"applicationInfo": {
"bundleName": "com.example.notes",
"appProvisionType": "debug"
},
"hapModuleInfos": [
{
"moduleName": "entry",
"mainAbility": "NotesAbility",
"mainElementName": "NotesAbility",
"isStageBasedModel": true,
"abilityInfos": [
{
"bundleName": "com.example.notes",
"moduleName": "entry",
"name": "NotesAbility",
"type": 1,
"visible": true,
"enabled": true,
"isStageBasedModel": true,
"isLauncherAbility": false,
"skills": [
{
"actions": ["ohos.want.action.home"],
"entities": ["entity.system.home"]
}
]
}
]
}
]
}
```
准确映射与限制:
| 需要的信息 | dump 字段与解释 |
| --- | --- |
| 应用身份/版本 | 顶层 `name`、`versionCode`、`versionName`;并校验 `applicationInfo.bundleName` 与查询目标一致。 |
| 调试签名 | `applicationInfo.appProvisionType`;调试值 `debug`,缺失是未知。 |
| 入口模块 | 顶层 `entryModuleName`;模块使用 `moduleName`,不是模块显示用的 `name`。 |
| 模块主组件 | `hapModuleInfos[].mainElementName`、`mainAbility`;当前 Stage 解析都来源于 manifest 的 `mainElement`。 |
| 实际组件 | `hapModuleInfos[].abilityInfos[]`,每项携带 `bundleName`、`moduleName`、`name`。 |
| 可见/导出状态 | dump 使用布尔 `visible`。Stage manifest 的 `exported` 写入内部 `visible`,且比旧 `visible` 配置优先;dump 不使用 `exported` 字段。 |
| UI 组件类型 | `type` 是数字枚举:`UNKNOWN=0`、`PAGE=1`、`SERVICE=2`、`DATA=3`、`FORM=4`、`EXTENSION=5`;Stage UIAbility 解析为 PAGE。 |
| 是否可用 | `enabled` 是布尔;显式 false 时不能作为启动候选。缺字段不应凭空生成 true。 |
| 桌面入口 skill | 同一项 `skills[]` 中,`actions` 含 `ohos.want.action.home` 或 `action.system.home`,且 `entities` 含 `entity.system.home`。 |
| `isLauncherAbility` | 不是普通应用“可从桌面启动”的通用标志。当前实现只在特殊 launcher entity、预装且系统应用等条件下设置。不能要求普通入口它必须为 true。 |
这些映射依据:[BundleInfo 序列化](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/interfaces/inner_api/appexecfwk_base/src/bundle_info.cpp#L898)、[ApplicationInfo 序列化](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/interfaces/inner_api/appexecfwk_base/src/application_info.cpp#L1064)、[组件 JSON 字段](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/interfaces/inner_api/appexecfwk_base/include/common_json_converter.h#L669)、[组件类型枚举](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/interfaces/inner_api/appexecfwk_base/include/ability_info.h#L57)、[exported 解析](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/services/bundlemgr/src/module_profile.cpp#L636)、[Stage 类型赋值](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/services/bundlemgr/src/module_profile.cpp#L2702)、[模块入口赋值](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/services/bundlemgr/src/inner_bundle_info.cpp#L1800)、[HOME skill 与 launcher 判定](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/services/bundlemgr/src/module_profile.cpp#L3005)、[HOME 字符串常量](https://github.com/openharmony/bundlemanager_bundle_framework/blob/e0b9be9be36186e040385895f7f764e6e111a809/interfaces/inner_api/appexecfwk_base/include/bundle_constants.h#L174)。
实现建议:从合法详情中选择 `type == 1 && visible == true`,排除明确禁用项,并校验组件包名/模块名;优先具有 HOME skill 的组件,再用入口模块和主组件字段消除歧义。没有唯一可确定入口时展示实际组件选择,不自动启动任意 exported 页面。`visible`、`type` 缺失时不能直接宣称可启动;旧格式适配应增加明确来源与 fixture。权限仍由系统决定,元数据筛选通过不保证启动一定成功。以上代表样例对应 argv 为 `["-t", key, "shell", "aa start -b com.example.notes -a NotesAbility -m entry"]`。
解析 JSON 时应先识别查询包名的前缀,再对完整对象解析;校验身份和字段类型,限制输入大小/嵌套深度。不要从错误文本中随意截取第一个到最后一个花括号,或在详情中递归搜同名 `name` 来猜应用身份。没有完整有效详情时保留应用条目和错误,禁用依赖组件/调试状态的操作。
## 补充:`hdc file recv -b` 目录层级
OpenHarmony HDC `e93f663afa159d3e601cad1e1869d70326731af6` 的收件端根据**传输开始时本地目标是否存在**决定是否保留源目录 basename;`-b` 在设备端解析沙箱路径,本地主机仍走同一文件接收逻辑。以下以已确认可读的 `data/storage/el2/base` 目录中存在 `files/a.txt` 为例。[HDC 源目录任务生成及沙箱处理](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/src/common/file.cpp#L266)、[接收端目标目录和文件名处理](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/src/common/transfer.cpp#L724)
| 调用前本地状态 | 接收目标 argv 最后一个参数 | 最终文件位置 |
| --- | --- | --- |
| `/exports/job` 已存在且为目录 | `/exports/job` | `/exports/job/base/files/a.txt` |
| `/exports/job` 不存在 | `/exports/job` | `/exports/job/files/a.txt` |
| `/exports/job` 已存在且为普通文件 | `/exports/job` | 目录模式报错,不能作为合法备份目标。 |
源路径加尾部 `/` 不构成“只复制内容”的开关;源 basename 提取前会去除尾部分隔符。目标不存在时接收器先建目标,再从相对任务路径剥掉第一层;目标已经存在则保留第一层。不要用 rsync 的尾斜杠规则推断 HDC。[basename 实现](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/src/common/base.cpp#L1823)、[CheckFilename 首层处理](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/src/common/transfer.cpp#L801)
建议先创建唯一、空的 staging 父目录,例如 `/exports/.curios-job-<nonce>`,再传 `["-t", key, "file", "recv", "-b", bundle, "data/storage/el2/base", "/exports/.curios-job-<nonce>"]`;验收期望位于 `staging/base/...` 的输出,记录 `remoteRoot` 与 `localRelativeRoot: "base"`。完成后按清单提交用户目标。恢复必须依清单映射,不能将整个 `base` 再送入设备已经存在的 `base`,制造 `base/base`。若想把目录内容直接放入目标,可使用尚不存在的目标路径,但实现必须控制初始状态,并在对应本机 HDC 版本做行为验收。
该源码递归任务以文件为单位,纯空源目录直接报 `Operation failed, because the source folder is empty.`;空子目录也不能按文件任务数量证明完整保留。不能把这类报错改写为完整备份成功。若产品承诺保留空目录,应另行枚举并记录空目录、处理权限失败,或将“不保留空目录”列入导出范围。源码不替代当前华为 SDK 二进制的行为测试。[目录遍历实现](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/src/common/transfer.cpp#L658)、[空目录官方测试](https://github.com/openharmony/developtools_hdc/blob/e93f663afa159d3e601cad1e1869d70326731af6/test/scripts/testModule/test_hdc_file.py#L502)
-156
View File
@@ -1,156 +0,0 @@
# Harmony Connector:仅通过 HDC 的连续投屏
调研日期:2026-09-10。产品约束:最终用户只启用 USB 调试并授权 Mac,不注册开发者账号、不安装 HAP、不修改手机安全策略。目标是可交互的连续画面,不限定 H.264;帧率和延迟必须在真机上测量。本篇研究未操作设备。
## 当前结论
找到了一条此前漏查的公开分发路线:**使用华为 Hypium 的公开 PyPI 依赖包中原样分发的 UITest agent,由设备内置 `uitest` 加载,通过 HDC 转发持续 JPEG 与触控 RPC。** 该路线无需用户编译、签名或安装手机应用;它仍会向设备临时推送一个 agent 文件,作用类似 scrcpy 的临时设备端服务。不能把自编 unsigned ELF 或 addon 被 XPM 拒绝,推广为所有官方签名 agent 均无法加载。
Hypium 的公开说明支持 USB/Wi-Fi 调试连接,并明确 Driver 模式可作为 SDK 集成到其他工具。其 `xdevice-devicetest` 包公开包含原生 agent;对应的 `xdevice-ohos` 包提供选择、推送、启动和转发实现。[Hypium 官方发布页](https://pypi.org/project/hypium/6.1.0.210/)、[设备测试资源包](https://pypi.org/project/xdevice-devicetest/6.1.0.210/)、[设备连接包](https://pypi.org/project/xdevice-ohos/6.1.0.210/)
**主任务真机进展:**官方 `v1.2.2.so` 使用唯一 `--extension-name` 已成功加载,Unix socket 能返回 JPEG,`Driver.create`、屏幕尺寸和旋转查询成功;不需要安装 HAP、开发者账号或录屏确认弹窗。初次收到的是 630 × 1422 黑帧,首帧约 195–235 ms,15 秒仅收到 3 帧;当时可能处于锁屏状态,**这些数字只证明链路连通,不能作为动态画面流畅度或端到端延迟的结论**。本文作者未操作手机,真机结果来自主任务当轮报告。
## 精确来源与许可证据
本轮从 PyPI 元数据取得 wheel 下载地址,在内存读取并校验 SHA256,没有安装或执行其中代码。
| 资源 | 版本、路径及 SHA256 |
| --- | --- |
| xdevice-devicetest wheel | `6.1.0.210`;`1c80f0da8d497d4e034f9873e966e8b2d8ef23ecc376c64c839c1493a813980e` |
| 新版 ARM64 agent | `devicetest/res/prototype/native/uitest_agent_v1.2.2.so`;`e1b8e75fad983aa29640784ee4b457fe7ac1c916a15f72c8b899d5a7716da651` |
| 旧版 ARM64 agent | `devicetest/res/prototype/native/uitest_agent_v1.1.10.so`;`1c9286456fb003ee15d86fef04e8c93f004027349e6dd2ef972c792e0a6d4bf8` |
| xdevice-ohos wheel | `6.1.0.210`;`548a09bb6fc9fcfd92a7dbb5b69180de0159ce8959057e133dec34774054bb12` |
资源包的 `METADATA` 明确列出 `License-File: LICENSE`,`xdevice_devicetest-6.1.0.210.dist-info/LICENSE` 是完整 Apache License 2.0;本次清单未发现额外 EULA、NOTICE 或对 agent 的另行许可文件。Apache 2.0 提供商业使用及 object form 再分发条件;打包时保留该许可证、来源版本和校验值,保留现有版权声明,修改自有封装代码应说明。不要以第三方仓库的许可证替代原始发布包的许可。[原始 wheel](https://files.pythonhosted.org/packages/a2/b5/55bdfe4d669558331499848dbc68269506abb9b4a771e3524a65f9845127/xdevice_devicetest-6.1.0.210-py3-none-any.whl)、[Apache 2.0 条款](https://www.apache.org/licenses/LICENSE-2.0)
原始 `v1.2.2.so` 尾部包含 ELF 签名块标记,并含 Huawei CBG Software Signing Service CA 证书链字符串。这里只确认原件附带签名结构,**没有进行独立密码学验签,也没有据此声称目标设备必然接受**。部署必须保持字节不变,不 strip、不重链接、不截掉 ELF 尾部。该 wheel 没有提供 agent 的 C++ 源码,因此应称“公开分发的官方 agent”,不能称为设备端也能完全自行重建的开源实现。
## 真实选择与启动路径
当前手机 UITest 版本由主任务报告为 `6.0.2.3`。官方 `xdevice-ohos 6.1.0.210` 内:
- `ohos/environment/device.py::check_if_unix_socket_mode`:UITest 版本高于 `6.0.2.1` 且设备不是 x86_64 时使用 Unix socket。
- `ohos/proxy/proxy_base.py::_init_so_resource`:此分支选择 `uitest_agent_v1.2.2.so`;旧分支才使用 v1.1.10/v1.1.5/v1.1.3。
- `ohos/proxy/ui_proxy.py`:默认推送到 `/data/local/tmp/agent.so`,运行 `/system/bin/uitest start-daemon singleness`,连接 `localabstract:uitest_socket`。旧协议为设备 TCP 8012。
因此不能把“设备 6.x”硬编码为旧 TCP 8012,也不能只依据文件名相似选择第三方 agent。[对应公开 wheel](https://files.pythonhosted.org/packages/78/a6/8ac621bb958813cde648d515069c30188939b492ffcdd29e8d1e234550cf/xdevice_ohos-6.1.0.210-py3-none-any.whl)
以下最小命令形态已由主任务用本轮官方原件在当前手机验证;参数仍必须来自所选设备与本机会话:
```text
hdc -t <device> file send <official-agent.so> /data/local/tmp/<session-agent.so>
hdc -t <device> shell uitest start-daemon singleness --extension-name <session-agent.so>
hdc -t <device> fport tcp:<local-port> localabstract:uitest_socket
```
独有 `--extension-name` 来自 OpenHarmony UITest 的扩展加载器:它将名称解析到 `/data/local/tmp/` 并加载扩展。官方 Python 默认使用 `agent.so`,独有名已在本轮商业手机上验证,其他固件仍需兼容验证。不要覆盖其他工具已有的 `agent.so`;官方框架内部的通配清理、强杀其他进程不应照搬到插件。[扩展加载器](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_executor.cpp)
## 持续 JPEG 与控制协议
公开客户端已有实际代码实现,不依赖教程中的口头描述。本节以 `devhelmkit` 锁定提交 `05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00` 为协议参考,设备 agent 原件仍从官方 wheel 获取。该项目的 `device/hdc.py`、`rpc/proxy_v2.py`、`screenshot_stream.py` 分别实现传输、RPC 和连续画面。[传输实现](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/device/hdc.py)、[RPC](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/rpc/proxy_v2.py)、[连续帧](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/screenshot_stream.py)
客户端发送请求的 TCP 封包顺序:
```text
ASCII "_uitestkit_rpc_message_head_"
u32 big-endian session ID
u32 big-endian UTF-8 JSON byte length
JSON body
ASCII "_uitestkit_rpc_message_tail_"
```
初始化和控制使用独立连接。调用 `Driver.create` 后使用返回的实际引用值,不能把 `Driver#0` 当成所有连接的固定保证。外层 `request_id` 为本次唯一字符串,transport session ID 是另一字段。
```json
{
"module": "com.ohos.devicetest.hypiumApiHelper",
"method": "callHypiumApi",
"params": {
"api": "Driver.create",
"this": null,
"args": [],
"message_type": "hypium"
},
"request_id": "<unique-request>"
}
```
后续只替换 `params` 中的 API、引用与参数,外层保持相同。以下是主任务确认的**解析后结果值**,不代表复制完整回包结构:
| API | `this` / `args` | 当前手机结果 |
| --- | --- | --- |
| `Driver.create` | `null` / `[]` | `"Driver#0"` |
| `Driver.getDisplaySize` | 返回的 Driver 引用 / `[]` | `{"displayId":0,"x":1260,"y":2844}` |
| `Driver.getDisplayRotation` | 返回的 Driver 引用 / `[]` | `0` |
| `Driver.wakeUpDisplay` | 返回的 Driver 引用 / `[]` | `null` |
| `Driver.getDeviceInfo` | 返回的 Driver 引用 / `[]` | `No handler found`,当前手机不可用,不用于探活。 |
物理输入坐标应依据屏幕尺寸与旋转转换,不能直接使用 JPEG 缩小后的像素坐标。`getDisplaySize` 可作为独立控制连接的低频探活;无新图不等于连接已断。
在独立采集 socket 发送:
```json
{
"module": "com.ohos.devicetest.hypiumApiHelper",
"method": "Captures",
"params": {
"api": "startCaptureScreen",
"args": { "options": { "displayId": 0, "scale": 0.5 } }
},
"request_id": "<unique-request>"
}
```
注意 `Captures.args` 是对象,与 `callHypiumApi.args` 数组不同。JPEG 被推送到发送采集请求的 socket。**当前官方 v1.2.2 真机回包实测为 JSON 行 `{"result":true,"pts":...}\n` 与裸 JPEG `FF D8 … FF D9` 加换行,不使用上述 HEAD/TAIL 请求封包包住 JPEG。**不能只实现带 HEAD/TAIL 的接收器。参考客户端兼容协议封装与裸 JPEG;实现应依据实际格式使用有界长度及完整帧解析,不能假设一次 `recv` 对应一张图,也不能忽略 ACK/错误后无限等待。[采集请求与分帧](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/screenshot_stream.py#L677)
实时触控使用相同外层与 `method: "Gestures"`,内层为 `api: "touchDown" | "touchMove" | "touchUp"`、`args: {"x": ..., "y": ...}`。客户端实现按下、移动、抬起接口,适合替换拖动后才发送整段 swipe 的延迟;必须限制 move 排队,并保证取消、失焦时结束本会话已按下触点。键盘、文字可继续使用现有已验证的 UITest 输入路径。[触控实现](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/driver.py#L687)
未找到 `Gestures.keyDown`、`Gestures.keyUp` 或 `Gestures.text` 的公开调用依据。普通按键使用 `callHypiumApi`:`Driver.triggerKey` 的 `args` 为 `[keyCode]`;组合键 `Driver.triggerCombineKeys` 为 `[key1,key2]` 或三个键;Home/Back 分别是 `Driver.pressHome` / `Driver.pressBack`、`args:[]`。不要使用不存在的 `Driver.pressKey`。官方 `hypium/uidriver/arkuidriver.py` 声明 `triggerKey`、`triggerCombineKeys`、`pressBack`;其 Home 包装存在 `UiDriver.pressHome` 命名差异,因此仍应按当前设备能力验证客户端使用的 `Driver.pressHome`。[官方 Hypium wheel](https://files.pythonhosted.org/packages/b4/dc/f3b0abf2c518d11d57d8e9487026c133cc7eb0e3479af4b90d13c6db6169/hypium-6.1.0.210-py3-none-any.whl)、[客户端按键调用](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/driver.py#L712)
官方 Python 的 `Driver.inputText` 参数为 `[{"x":x,"y":y},"text"]`,有定位输入的语义;它并不是已确认的 `Gestures` 当前光标文字接口。公开客户端对当前焦点使用 `uitest uiInput text <quoted text>`。当前阶段保留已有验证的文字通道,可避免引入未经验证的光标、追加与清空语义。[当前焦点输入](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/driver.py#L1487)
停止时在采集连接发送 `method: "Captures"`、`params: {"api":"stopCaptureScreen","args":{}}`,随后关闭 socket、停止本机接收线程、移除本会话转发。若启用设备守护进程清理,仅结束自己创建并持有身份的进程,不使用 `killall uitest`。主机解码与渲染只保留最新完整帧;静止画面没有新帧不应误判断线,可用独立控制通道探活。[停止实现](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/screenshot_stream.py#L565)
`startCaptureScreen` 是 agent 提供的 RPC 名称,**不是**设备 `uitest help` 中能直接运行的 CLI 子命令。设备帮助没有它,不证明公开 agent 不能提供它。
## 画面变化、帧率与保活
公开 UITest `screen_copy.cpp` 的采集线程调用 `DisplayManager::GetScreenshot`,通过 `memcmp` 比较前后像素,只有变化才通知 JPEG 编码线程。屏幕关闭时提供黑色画面并暂停常规采集,50 ms 的 `usleep` 仅用于检查关闭的屏幕是否重新亮起,**不能据此推算正常采集被限制在 20 fps**。常规采集循环没有显式 fps 计时上限,编码线程取最新帧。商业 agent 未提供其 C++ 源码,此处解释公开 UITest 实现,不能当作官方 v1.2.2 每一项内部行为均相同的证明;本机静止画面不连续发帧与这套行为一致。[变化检测与屏幕关闭](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/screen_copy.cpp#L153)、[消费最新帧](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/screen_copy.cpp#L245)
核查的公开扩展与客户端只暴露 `displayId`、`scale`,没有可据以实现的 fps 或 JPEG quality 参数。`scale` 在获取原屏截图后缩放像素,能降低输出尺寸与编码、传输成本,但不消除原屏截图成本。Devhelmkit 的 `MAX_RECORD_FPS=30` 只约束事后 MP4 合成,与实时采集上限无关。不要把虚构的 `fps:60` 参数加入产品后声称已提速。[缩放位置](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/screen_copy.cpp#L221)、[实际采集参数](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/screenshot_stream.py#L670)
参考客户端的采集 socket 连接后设为阻塞读取,没有周期发送采集心跳;尚无“必须在采集 socket 定期发送心跳”的源码证据。可保留本机读取超时用来检查取消状态,但超时本身不应标为失败;用独立控制连接的 `Driver.getDisplaySize` 区分静态画面与设备失联。若控制连接也无响应、TCP 关闭或 HDC 确认失联,再终止会话。[socket 建立与持续读取](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/screenshot_stream.py#L651)
## 两个开源客户端的来源核对
| 项目 | 本轮源码与文件核对 | 结论 |
| --- | --- | --- |
| `yabi-zzh/devhelmkit` | Apache 2.0;有连续 JPEG、独立控制 socket、down/move/up 和采集帧率统计;仓库 `agent_v2.so` SHA256 与本次官方 wheel 的 agent 不一致。 | 可参考其客户端协议,不把其未核清来源的 blob 作为发行原件。 |
| `yiyifred/Cloud-Phone` | Apache 2.0;包含 `uitest_agent_v1.1.10.so`,其 SHA256 **完全匹配**官方 wheel v1.1.10;`Gestures` 代码注释中的 ECHO/hdckit 指同类 RPC 操作,并不是设备原生内置 H.264 服务。 | 证明实际客户端采用 Hypium agent;其旧 agent/TCP 假设不能直接用于当前设备。 |
[devhelmkit 许可](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/LICENSE)、[Cloud-Phone agent](https://github.com/yiyifred/Cloud-Phone/blob/98d130b20474cf8b18d51161d3bb0b65472c4778/backend/assets/harmony/uitest_agent_v1.1.10.so)、[Cloud-Phone RPC](https://github.com/yiyifred/Cloud-Phone/blob/98d130b20474cf8b18d51161d3bb0b65472c4778/backend/node/src/services/harmony-cast/uitest-rpc.js)、[Cloud-Phone 许可](https://github.com/yiyifred/Cloud-Phone/blob/98d130b20474cf8b18d51161d3bb0b65472c4778/LICENSE)
## H.264 与其他路径的边界
同一官方资源 wheel 包含 `devicetest/res/recorder/libscrcpy_server1.z.so` 到 `libscrcpy_server4.z.so`。其 `controllers/tools/recorder/record_agent.py` 推送为 `/data/local/tmp/libscreen_recorder.z.so`,通过 UITest extension 启动,参数为 `-p <port> -m 1 -screenId <id>`,默认 gRPC 端口 5001;对应 `rpc_manager.py` 和生成的 protobuf 定义含 `ScrcpyService/onStart`、`onEnd`、`onRequestIDRFrame`。当前 Python 调用方主要用于步骤视频,停止后拉取 `mytest.mp4`。**仅凭资源名不能认定它是可直接使用的实时 H.264 投屏服务**;格式、stream payload 与当前固件兼容性仍需专门验证。它不是本轮 JPEG 路线的前置条件。[官方资源 wheel](https://pypi.org/project/xdevice-devicetest/6.1.0.210/)
设备内置 UITest 公开 CLI 的 screenCap 是单张 PNG,uiRecord 是操作记录,start-daemon 是测试服务入口;不提供已确认的直接视频 stdout 命令。官方系统录屏组件的公开流程是录制后导出文件。对已经确认不存在的 `screenrecord`,不应反复尝试 Android 参数。[UITest 命令](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/application-test/uitest-guidelines.md)、[官方系统录屏](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-screen-recording)
自编 ELF 的执行权限错误与自编 addon 的 XPM 加载拒绝已由主任务复现;它们不应作为继续尝试关闭 XPM、SELinux、root 或借用私人签名的理由。公开 Hypium 原件是另一条正常工具分发与加载链路,需按原件、版本、协议重新验证。
## 产品验收与明确未知
- 当前证据确认公开来源、许可文件、官方选择和加载方式,以及开源客户端连续帧/触控协议;主任务已验证当前手机原件加载、Unix socket、Driver 初始化、JPEG 解码与屏幕尺寸查询。
- 初测黑帧只证明连通,仍需设备解锁后的真实内容与交互画面验证;不得把收到 JPEG 等同于已达到 scrcpy 的流畅体验。
- 用手机实际动画/滑动测量收到与呈现的帧率,独立记录排队及输入往返;静止画面频率不能当作最大帧率。没有实际结果前不承诺 30/60 fps。
- 验证拖动跟手、旋转后的物理坐标、拒绝/断开、停止后不再采集与控制,以及重复启动清理;不影响其他使用 UITest 的工具。
- 未确认 `Captures/Gestures` 跨版本稳定性;应固定已验收的 agent,报告能力与错误,避免无提示降为旧截图模式。
最终用户流程可以保持“连接 USB → 允许调试 → 开始投屏”。官方 agent 的获取、校验、部署、转发和清理应由插件完成,不能再把开发者账号或 HAP 签名作为该流程的要求。
## 2026-09-10:主任务重连验证补充
实际商业手机拒绝普通 `kill <pid>`,返回 `Permission denied`;同一身份的 `kill -9 <pid>` 成功,进程随即消失。此前残留代理能响应 Driver RPC,却无法恢复采集,因此不将无条件复用残留代理作为恢复策略。官方 `xdevice-ohos 6.1.0.210` 的 `ohos/proxy/ui_proxy.py::stop_ui_rpc` 与 `proxy_base.py::_kill_uitest` 均使用 `kill -9`;默认销毁代理对象可不杀进程,显式重连则先停止。插件只借鉴结束自己代理的方式,绝不照搬按名称批量结束所有工具进程的逻辑。[官方连接包](https://pypi.org/project/xdevice-ohos/6.1.0.210/)
生产后端现已连续通过三轮真机开始、解码 JPEG、停止和重新开始,每轮都验证代理和转发清理完成。正常画面 JPEG 宽度实测含尾部对齐填充(640 × 1422 对应逻辑半尺寸 630 × 1422),UI 应裁掉该部分,不能按 JPEG 宽度直接映射物理触点。该结果只证明本轮设备的连通与重连,不代表动态帧率、实时手势或其他固件兼容性全部验收。
-116
View File
@@ -1,116 +0,0 @@
# Harmony Connector:MTP 会话恢复与能力探测
调研日期:2026-09-10。本文补充 [初始技术调研](harmony-connector.md),只记录源码、公开问题和建议验证路线,不声称完成了真机修复。支持范围按实际 MTP 能力判定,不以 HarmonyOS 版本、机型字符串或 USB product 字符串限制。
本次任务提供的现象:macOS、libmtp 1.1.23、libusb 1.0.30;Pura 70 Ultra 的 USB 标识为 `12d1:1101`,USB product 为 `HDC Device`,MTP friendly name 为 `HUAWEI Pura70Ultra`,MTP model 为 `HBP-AL00`。0.1.1 在 `LIBMTP_Release_Device` 的 CloseSession 等待约 61 秒后超时;界面已异步处理断开,但用户报告需要拔插才能重连。上述为主任务提供的真机信息,本研究代理未操作 USB。
## 可用于实现决策的结论
1. **libmtp 现有路径已经会 reset,不能把“新增 reset”当成修复。** `12d1:1101` 表项使用 `DEVICE_FLAGS_ANDROID_BUGS`,其中同时包含 `LONG_TIMEOUT` 和 `FORCE_RESET_ON_CLOSE`。前者选择 60 秒 USB 超时,后者在释放接口后、关闭 USB handle 前 reset。约 61 秒现象与该等待路径吻合,但仍须日志确认实际失败阶段。[设备表](https://github.com/libmtp/libmtp/blob/v1.1.23/src/music-players.h#L3290)、[标志定义](https://github.com/libmtp/libmtp/blob/v1.1.23/src/device-flags.h#L320)、[USB 实现](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libusb1-glue.c#L2087)
2. **OpenHarmony 开源代码存在能产生 CloseSession 无响应的明确控制流。** 它把协议成功码 `0x2001` 当成内部函数返回值,但组包调用方要求 `0`;因此在写响应前退出。该路径也未清除会话状态。它能解释同类症状,尚不能证明这台商业固件包含相同代码。完整链条见下节。
3. **`LIBMTP_Reset_Device` 不是 USB reset。** 它仅在设备广告 `ResetDevice(0x1010)` 时发送 MTP 操作;上述 OpenHarmony 实现又把该操作路由到 CloseSession 的同一实现。不能用它替代 USB 层恢复。[libmtp 实现](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.c#L3633)、[OpenHarmony 路由](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/mtp_operation.cpp#L249)
4. **建议优先验证“短时限关闭 → 释放接口 → 一次 USB reset → 关闭 handle → 新枚举重连”。** 必须记录每阶段结果,不能静默保留会话并显示已断开。单纯更换 session ID、再加既有标志或一直重试,均没有充分依据。
## OpenHarmony CloseSession 的具体问题
此次 GitHub API 查询到的 `openharmony/multimedia_media_library` 当前提交为 `4a411de74cfaf694c14820a51a0e900b04241fe6`。以下结论固定到该提交,描述其开源实现,不将 OpenHarmony 与商业 HarmonyOS 固件等同。
### 响应没有写入的调用链
| 层次 | 所查行为 | 影响 |
| --- | --- | --- |
| 内部成功码 | `MTP_SUCCESS = 0`。 | C++ 内部处理成功应返回 0。[定义](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/frameworks/utils/include/medialibrary_errno.h#L30) |
| 协议成功码 | `MTP_OK_CODE = 0x2001`。 | 这是 MTP 响应容器的 response code。[定义](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/include/mtp_constants.h#L413) |
| CloseSession payload | `Parser` 在 session 已开时返回 `MTP_OK_CODE`;`Maker` 写入该数值后也返回 `MTP_OK_CODE`。 | 返回值与调用层的内部成功码约定不一致。[实现](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/payload_data/close_session_data.cpp#L34) |
| MtpPacket | `ParserPayload` 传播返回值;`MakerPayload`、`Maker` 只有返回 `MTP_SUCCESS` 才继续。 | CloseSession 的组包结果为 `0x2001`,被当作失败返回。[实现](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/mtp_packet.cpp#L166) |
| MtpOperation | 收包失败也尝试 `SendMakeResponsePacket`,但该方法在 `responsePacketPtr_->Maker(false) != MTP_SUCCESS` 时提前返回。 | 无论从正常还是错误响应路径进入,都会在 `Write` 之前结束。[实现](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/mtp_operation.cpp#L129) |
这是静态控制流分析:在所查代码和正常对象初始化前提下,可以追踪到“收到关闭请求,主机却收不到关闭响应”的路径;并非手机端日志或固件反编译结果。`CalculateSize` 也调用这个返回协议码的 `Maker`,不过无需依赖长度问题,调用方对返回值的检查已足以解释写入被跳过。
### 会话状态与重新打开
`DealRequest` 在 OpenSession 时检查 `sessionOpen`,未打开时保存参数中的 `tempSessionID`,已打开时返回 SessionAlreadyOpen;`GetOpenSession` 在成功响应时设置 `sessionOpen=true`。CloseSession 的 `Parser` 只检查已有会话,`GetCloseSession` 只创建 payload,没有将 `sessionOpen` 设为 false 或将 `sessionID` 清零;`ResetOperation` 只重置当前命令、transaction ID 和数据阶段。所查 `services/media_mtp` 源文件未找到关闭时清除这些会话字段的其他赋值。[打开处理与每次操作重置](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/mtp_operation.cpp#L213)、[会话响应处理](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/mtp_operation_utils.cpp#L668)、[初始状态](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/include/mtp_operation_context.h#L49)
因此,新的非零 session ID 不会自动绕过 `sessionOpen` 检查。设备端 `ResetDevice` 和 CloseSession 又使用同一个 payload 与响应处理,不能从 OperationsSupported 含 `0x1010` 推断它真的重置了会话。[payload 工厂](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/packet_payload_factory.cpp#L65)
## libmtp 的关闭、打开与身份语义
1.1.23 的 `LIBMTP_Release_Device` 返回类型是 `void`:它调用 `close_device`,然后清错误栈、释放 `PTP_USB`、`PTPParams`、存储和 device 对象。因而 Release 返回后,旧 `device`、`usbinfo` 和 USB handle 都不可继续使用;仅靠该 API 的返回值也拿不到关闭是否成功。[释放实现](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.c#L2513)
关闭顺序为 `ptp_closesession → close_usb`;后者默认执行 `clear_stall → libusb_release_interface → 按标志 libusb_reset_device → libusb_close`。reset 和 release 的返回值在此没有向调用者报告。连接路径先以 5 秒时限尝试 OpenSession,遇 `PTP_ERROR_IO` 已会 reset、关闭 USB、重新初始化并重试;接受 `OK` 和 `SessionAlreadyOpened`。若回到 retry,正常设备超时可能又是 60 秒。[1.1.23 USB 流程](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libusb1-glue.c#L2304)
截至本次调研的 libmtp 当前提交 `9de46078d15d290ac5c46039937b1164b918dbf0`,CloseSession 和 OpenSession 的上述恢复路径仍存在,未发现可通过直接升级主分支获得的对应修复。[当前代码](https://github.com/libmtp/libmtp/blob/9de46078d15d290ac5c46039937b1164b918dbf0/src/libusb1-glue.c)
libmtp 使用 session 参数 `1`。`ptp_opensession` 自行将 OpenSession transaction ID 归零,并在事务后将本地 session ID 设为传入值。修改随机 ID 必须连同调用约定理解;它不会修复无响应或设备端 `sessionOpen` 不清除。代码中 InvalidTransactionID 分支先加 10,再调用会归零的 `ptp_opensession`,因此也不能把该分支视为可靠的 ID 恢复保证。[PTP 实现](https://github.com/libmtp/libmtp/blob/v1.1.23/src/ptp.c#L1364)
`12d1:1101` 的上游记录来自 Nova14 的 ID 报告,只证明这个 USB ID 已入表;它不是对 Pura 70 Ultra 的型号鉴定。USB product `HDC Device` 也不否定同一复合设备上的 MTP 接口。应用应展示实际 MTP friendly/model 信息,使用选中 USB 实例及实际接口建立会话;不能将表中的 Nova14 名称硬编码为真实机型。[上游 issue #334](https://github.com/libmtp/libmtp/issues/334)、[设备表](https://github.com/libmtp/libmtp/blob/v1.1.23/src/music-players.h#L3290)
libmtp README 本身记录了设备 CloseSession 实现不佳、首次打开成功而后续失败的历史问题;这是固件兼容问题的先例,不是当前设备根因的独立证明。[libmtp README](https://github.com/libmtp/libmtp/blob/v1.1.23/README)
## macOS USB reset 的限制与调用位置
`libusb_reset_device` 的公开契约是 USB port reset,并尝试恢复先前 configuration/alternate settings;如果重枚举后的设备改变而返回 `LIBUSB_ERROR_NOT_FOUND`,调用者应关闭旧 handle 并重新发现设备。它不是“手机已经接受新的 MTP 会话”的保证。[libusb API](https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html#gafee9c4638f1713ca5faa867948878111)
libusb 1.0.30 Darwin 后端在普通、未 capture 的 macOS 路径调用 `USBDeviceReEnumerate`,等待重枚举,再恢复状态;源码说明 10.11 起旧 `ResetDevice` 路径不再有效。它有专门的重枚举等待和返回值,不能省略检查,也不能与另一线程的 close/open/reset 并发。[Darwin reset 实现](https://github.com/libusb/libusb/blob/v1.0.30/libusb/os/darwin_usb.c#L2139)
2026-08 的上游 [issue #1923](https://github.com/libusb/libusb/issues/1923) 报告 Darwin 在接口仍 claimed 时 reset、再 claim 的旧接口泄漏,以及 restore 失败后 claim 位丢失。对应 [PR #1942](https://github.com/libusb/libusb/pull/1942) 在本次查询时仍未合并。报告包含 macOS 实验,但设备是 SEGGER J-Link、部分失败来自故障注入,不能当成本项目手机已命中该问题。它足以说明:**不能因为其他库先 reset 就无条件把本项目 reset 移到 release_interface 之前。**
建议的调用位置:
| 位置 | 判定 |
| --- | --- |
| 传输中、另一个线程仍执行 MTP 时 | 不使用;先串行收束当前事务,避免重置仍在使用的句柄。 |
| CloseSession 失败后、USB handle 尚有效 | 可以由拥有该 handle 的底层实现处理;优先释放接口后 reset,检查结果,再 close。 |
| `LIBMTP_Release_Device` 返回后,拿旧 `usbinfo` reset | 错误:对象和 handle 已释放。 |
| Release 完成后,新枚举并打开同一 USB 实例的全新 libusb handle,再 reset | 可作为受控恢复路线;这时不要先 OpenSession 再 reset。需要准确定位设备、检查返回值并重新枚举,不能仅按 VID/PID 匹配第一台。 |
| 正常关闭也无条件反复 reset | 不作为额外策略;现有 quirk 已可能 reset,重复执行增加重枚举和占用干扰。 |
在本项目固定版本的 C 边界内,关闭超时和原始 reset 状态若无法经公开 API取得,可用小范围、可重现的依赖补丁返回状态;避免在 Rust/前端按内存偏移解包私有结构。若用新 handle 做恢复,不应复用旧 bus/device address 作为永久身份;重枚举可能使它失效。[libusb 枚举与 handle 说明](https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html)
## 其他 macOS MTP 工具能提供什么证据
| 实现 | 观察 | 可借鉴与边界 |
| --- | --- | --- |
| OpenMTP 的 go-mtpx/go-mtpfs | `go-mtpx` 固定到 `go-mtpfs` 提交 `1c3302b3c476`。`Close` 遇 CloseSession 错误会先 USB Reset,再 ReleaseInterface、Close;`Configure` 遇 SessionAlreadyOpened 先尝试 CloseSession/OpenSession,其余失败 reset、close、等约一秒、重新打开。 | 证明一种现实恢复路线存在,但不是这台设备或当前 libusb 1.0.30 的验证。不要直接继承“约一秒足够”的假设。[依赖](https://github.com/ganeshrvel/go-mtpx/blob/18f12db021cc4256396748dbd2996d15853d7033/go.mod)、[关闭和配置](https://github.com/ganeshrvel/go-mtpfs/blob/1c3302b3c476f69e787a0339122a804dd518a007/mtp/mtp.go#L94) |
| go-mtpfs session ID | 每次选择非零、非 `0xffffffff` 的随机 session ID。 | 是它自身的实现选择;没有证明随机化能解决 OpenHarmony 的状态未清除。[实现](https://github.com/ganeshrvel/go-mtpfs/blob/1c3302b3c476f69e787a0339122a804dd518a007/mtp/ops.go#L16) |
| Android File Transfer for Linux/macOS(whoozle) | Session Close 发 CloseSession 并等响应;通用 Device Open 有可选 reset,但其 Darwin backend 的 `Device::Reset()` 是空实现。 | 不能从工具支持 macOS 或方法名推断它进行了有效 USB reset。这是开源独立项目,非 Google 的同名 Android File Transfer。[项目](https://github.com/whoozle/android-file-transfer-linux)、[session](https://github.com/whoozle/android-file-transfer-linux/blob/88926930db41238c7b4d7237fc5849b9586cc7b8/mtp/ptp/Session.cpp#L105)、[Darwin 空实现](https://github.com/whoozle/android-file-transfer-linux/blob/88926930db41238c7b4d7237fc5849b9586cc7b8/mtp/backend/darwin/usb/Device.cpp#L38) |
本次未取得 Google 闭源 Android File Transfer 的第一方 host 源码,因此不声称其关闭顺序或私有恢复方式。OpenMTP 的 Huawei P30 Pro [公开问题 #452](https://github.com/ganeshrvel/openmtp/issues/452) 报告的是目录/传输故障,也不能作为 HarmonyOS 当前设备的重连成功证明。
## 按实际设备能力开放功能
建立会话后读取设备 `OperationsSupported`、`DevicePropertiesSupported`,对象功能还要结合格式、属性描述、存储写权限和操作实际返回值。广告支持不等于本次操作成功,也不等于所有对象类型都可用。OpenHarmony 所查代码声明了 delete、move、copy、thumbnail、battery 和对象属性,但实际商业设备返回值才是应用开关依据。[OpenHarmony DeviceInfo 声明](https://github.com/openharmony/multimedia_media_library/blob/4a411de74cfaf694c14820a51a0e900b04241fe6/services/media_mtp/src/payload_data/get_device_info_data.cpp#L32)
以下为 libmtp 1.1.23 的公开 API 映射;操作码由其 PTP 定义核对。[公开头文件](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.h.in)、[PTP 操作定义](https://github.com/libmtp/libmtp/blob/v1.1.23/src/ptp.h)
| 功能 | 公开 API | 探测与实现边界 |
| --- | --- | --- |
| 改名 | `LIBMTP_Set_File_Name`、`LIBMTP_Set_Folder_Name` | 按实际格式查询 `ObjectFileName(0xdc07)`;要求 `GetObjectPropDesc(0x9802)` 的 GetSet 为可写,并有可用的 `SetObjectPropValue(0x9804)` 或 SetObjectPropList。库的改名 helper 自己检查 GetSet。不要新用已弃用的 `LIBMTP_Set_Object_Filename`。 |
| 删除 | `LIBMTP_Delete_Object` | `DeleteObject(0x100b)`;仍处理 ObjectWriteProtected、AccessDenied 等对象级拒绝。目录递归由应用计划与执行,库文档不保证删除目录会删除全部子对象。 |
| 移动 | `LIBMTP_Check_Capability(...MoveObject)`、`LIBMTP_Move_Object` | `MoveObject(0x1019)`;目标根 parent 参数是 `0`。目录和跨存储行为须验证。标准命令无字节进度,不显示虚假百分比。 |
| 设备内复制 | `LIBMTP_Check_Capability(...CopyObject)`、`LIBMTP_Copy_Object` | `CopyObject(0x101a)`;无字节进度;API 仅返回成功/失败,不返回新对象 ID,完成后重新枚举目标目录。不支持时不可把移动偷偷变成复制后删除。 |
| 缩略图 | `LIBMTP_Get_Thumbnail` | `GetThumb(0x100a)`,并按对象是否有缩略图处理。失败显示普通图标;它不是整图预览保证。RepresentativeSample 是另一组属性/API。 |
| 电量 | `LIBMTP_Get_Batterylevel` | `BatteryLevel(0x5001)` 在设备属性列表中;API还检查 broken battery quirk。只有调用成功且最大值有效时换算,失败/不支持显示未知,不能显示 0%。 |
| 对象属性读取 | `LIBMTP_Is_Property_Supported`、`LIBMTP_Get_String_From_Object`、`LIBMTP_Get_u64/u32/u16/u8_From_Object` | `GetObjectPropsSupported(0x9801)` 按格式,`GetObjectPropDesc(0x9802)`、`GetObjectPropValue(0x9803)` 按具体属性。`Is_Property_Supported` 返回正数/0/负数分别为支持/不支持/查询失败,不能把 -1 当成 true。 |
| 对象属性写入 | `LIBMTP_Set_Object_String/u32/u16/u8`、`LIBMTP_Get_Allowed_Property_Values` | 存在不等于可写;遵循 GetSet、数据类型、枚举/范围。后一个 API 返回合法值,不直接暴露 GetSet;不能据它推断写权限。 |
| 目录下载 | `LIBMTP_Get_Files_And_Folders` + `LIBMTP_Get_File_To_File_Descriptor` | 主机遍历对象树,使用各 storage ID/parent ID;不是单一“递归传输”操作。按实际项汇总大小、进度和失败。 |
| 目录上传 | `LIBMTP_Create_Folder` + `LIBMTP_Send_File_From_File_Descriptor` | 以 association/folder 对象建立层级,再逐文件传输;基础操作为 SendObjectInfo/SendObject 或设备支持的 SendObjectPropList 路径。保留空目录、逐项处理重名和失败。 |
公开 `LIBMTP_Check_Capability` 仅覆盖 GetPartialObject、SendPartialObject、EditObjects、MoveObject、CopyObject,不能拿它询问任意操作码。若前端需要全部 OperationsSupported,应在与固定 libmtp 一起编译的 C 桥接层提供只读投影,或给依赖增加受控 API,不从 UI 解析 `mtp-detect` 文本。[能力实现](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.c#L4132)
GetObjectPropList 也要遵循 libmtp 的已知兼容标志,不能为了“一次取全树”强制启用。对于当前 ID,上游已有 BROKEN_MTPGETOBJPROPLIST/BROKEN_SET_OBJECT_PROPLIST/BROKEN_SEND_OBJECT_PROPLIST;这些是库的兼容策略,不表示设备完全不能改名或传输。[兼容标志](https://github.com/libmtp/libmtp/blob/v1.1.23/src/device-flags.h#L320)
递归删除宜先枚举用户选中的子树,再从叶到根执行。复制/移动目录时,不能仅凭标准操作支持就承诺任意层级、跨存储、同名覆盖或取消的原子性。libmtp 文档明确警告目录操作语义与耗时;应用级递归必须保留部分成功状态,不能自动删除原文件作为未说明的降级。[删除/移动/复制契约](https://github.com/libmtp/libmtp/blob/v1.1.23/src/libmtp.c#L6922)
## 最小验证路线
以下是建议给主任务执行的实验,不是本文已经执行的验证。
1. 串行记录一次连接、只读目录、断开、重连全过程。区分 CloseSession 写请求、等响应、release_interface、USB reset、USB close 的耗时与返回码;确认旧后端进程/线程是否退出、旧 handle 是否完成释放。不要把设备名可枚举当成 MTP 会话已恢复。
2. 将关闭独立于正常传输超时:CloseSession 超时后继续清理,避免反复调用它;不改变大文件传输的合理超时。先保留当前 release→reset 顺序,检查这次 reset 是否真的完成。若产品需要状态回传,在固定库边界内显式提供结果。
3. 旧 handle 全部关闭后,按新枚举结果重连,实际完成 GetStorageIDs 与至少一次目录读取。只观察 UI 按钮恢复、扫描返回设备、reset 返回 0 均不足以通过。
4. 只在基线有完整日志后比较候选方案:释放接口后 reset;必要时用全新 raw USB handle 做独立恢复。一次实验只变一处,避免短超时、随机 ID、不同 reset 顺序、额外库补丁同时变化而无法定位。
5. 若 reset 返回 NOT_FOUND,按 API 重新发现设备;若 reset 无响应或设备不重新出现,显示恢复失败和真实状态,不无限循环。硬件重新插拔能恢复,不足以证明软件重枚举一定具备等价效果。
6. 修复通过后,在同一 USB 插接周期连续完成多次 connect→browse→disconnect→connect,并包含插件关闭/退出再打开。写操作能力在专用测试目录验证:改名、删除、移动、复制、中文/emoji、空文件、空目录、嵌套传输,下载回传比对哈希;缩略图和电量失败应保留其余浏览能力。
本研究实际完成:读取项目固定依赖源码;在线查询并固定第一方仓库提交;核对 OpenHarmony 会话调用链、libmtp API、libusb Darwin reset、OpenMTP 与 whoozle 实现;查询相关公开 issue/PR 状态。未修改实现、未打开或重置 USB、未把开源静态分析标为商业设备实测根因。
-161
View File
@@ -1,161 +0,0 @@
# Harmony Connector 实时视频流调研
> 历史方案记录:本文的 H.264/HAP 实验已停止,实验源码和账号签名流程已移除。当前采用无需用户开发者账号的[官方 Hypium/HDC 路线](harmony-hdc-only-screen.md),状态及验证命令见[持续投屏开发验证](../harmony-realtime-development.md)。下文的早期“无安装”失败仅涉及自编未签名原件,不能推导为必须安装 HAP。
调研日期:2026-09-10。承接 [投屏与控制调研](harmony-screen-control.md),本篇只核查替换逐张截图链路所需的公开 API、进程授权和构建条件。文献研究与本机集成验证分别记录;尚未验证真机 H.264 视频性能。
## 结论与实现顺序
真正的视频流应为 **AVScreenCapture → VideoEncoder 输入 Surface → H.264 输出回调 → 持续 socket → 主机解码**。公开 C API 足以表达这个管线,包含系统录屏授权、帧率控制和编码输出;HDC 只负责连接和转发。`OH_ENCODED_STREAM` 虽然存在于枚举,但当前文档明确尚不支持,不能只修改枚举便取得 H.264。[采集数据类型](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/reference/apis-media-kit/capi-native-avscreen-capture-base-h.md)、[Surface 视频编码](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/media/avcodec/video-encoding.md)
最小验证先尝试无音频、限时停止的普通 shell 原生程序。**没有查到“C API 必须有 UIAbilityContext”的限制;也没有官方文档保证商业手机允许 shell 进程完成录屏授权。** 这必须由实际返回码、系统弹窗和首个可解码帧判定,不能把“未验证”写成“必须安装 HAP”。公开接口不接受 UIAbilityContext;OpenHarmony 媒体服务会自行启动授权窗口。本次集成测试在程序执行阶段已遇到权限拒绝,详见下文,尚未到达 API 授权阶段。[公共 C API](https://github.com/openharmony/multimedia_player_framework/blob/91ee2a20fb294d0d05e8c48d0c0cc96af68f43e6/interfaces/kits/c/native_avscreen_capture.h)、[授权窗口调用](https://github.com/openharmony/multimedia_player_framework/blob/91ee2a20fb294d0d05e8c48d0c0cc96af68f43e6/services/services/screen_capture/server/screen_capture_server.cpp#L2174)
UITest addon 的公开源码扩展仍仅提供连续 JPEG,没有 H.264 入口。可以验证其长连接 JPEG 是否明显改善当前每帧启动截图命令和下载文件的开销,但不得称为 H.264 视频通道。[扩展 ABI](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_c_api.h)、[采集分派](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_executor.cpp#L305)
## 本地 SDK 核查
本机下载的官方 `commandline-tools-mac-arm64-26.0.0.821.zip` 包含以下公共文件,研究直接读取 ZIP 中的头文件核查声明,不依赖论坛示例:
```text
command-line-tools/sdk/default/openharmony/native/sysroot/usr/include/
multimedia/player_framework/native_avscreen_capture.h
multimedia/player_framework/native_avscreen_capture_base.h
multimedia/player_framework/native_avcodec_videoencoder.h
multimedia/player_framework/native_avcodec_base.h
multimedia/player_framework/native_avformat.h
multimedia/player_framework/native_avbuffer.h
multimedia/player_framework/native_avbuffer_info.h
```
头文件及 aarch64 stub 库存在,只证明可以编译和链接,不证明目标手机提供全部 API 或允许该进程采集。应优先使用满足管线的较早公开 API,通过实际返回值检查能力;不能仅因 SDK 是 26.0.0 就要求手机同版本。
| 能力 | 公共接口 | 起始 API |
| --- | --- | --- |
| 创建、初始化、停止、释放 | `OH_AVScreenCapture_Create/Init/StopScreenCapture/Release` | 10 |
| 状态、错误事件 | `OH_AVScreenCapture_SetStateCallback/SetErrorCallback` | 12 |
| 向编码器 Surface 采集 | `OH_AVScreenCapture_StartScreenCaptureWithSurface` | 12 |
| 限制采集帧率 | `OH_AVScreenCapture_SetMaxVideoFrameRate`,启动后调用,最高 60 | 14 |
| 编码器输出 buffer 回调 | `OH_VideoEncoder_RegisterCallback`、`OH_AVCodecCallback` | 11 |
| 编码器输入 Surface | `OH_VideoEncoder_GetSurface` | 9 |
| 编码 B 帧开关 | `OH_MD_KEY_VIDEO_ENCODER_ENABLE_B_FRAME`,默认关闭 | 20 |
| 手机、平板可选屏幕 Picker | `OH_AVScreenCapture_StrategyForPickerPopUp` | 23 |
以上声明可对照 [采集 API](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/reference/apis-media-kit/capi-native-avscreen-capture-h.md)、[视频编码器 API](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/reference/apis-avcodec-kit/capi-native-avcodec-videoencoder-h.md)、[华为 Picker 条件](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/using-avscreencapture-for-file-with-window)。普通录屏授权弹窗与 API 23 的屏幕/窗口选择器是两项能力;不能以设备没有后者判断无法授权录屏。
## 授权:先验证 shell,不绕过系统授权
本地 SDK 的 `SetStateCallback` 声明要求在启动前注册;说明录屏开始前由系统请求用户同意,实际启动成功回调 `OH_SCREEN_CAPTURE_STATE_STARTED`,拒绝回调 `OH_SCREEN_CAPTURE_STATE_CANCELED`。即便 `StartScreenCaptureWithSurface` 返回成功,也只能表示启动请求被接受,必须继续等待状态和编码数据。[状态回调 API](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/reference/apis-media-kit/capi-native-avscreen-capture-h.md#oh_avscreencapture_setstatecallback)
OpenHarmony 当前源码调用链提供了可检验的依据:
1. `ScreenCaptureImpl::StartScreenCaptureWithSurface` 在 `ORIGINAL_STREAM` 模式内部启用隐私授权,再进入服务端。无需调用内部的 `SetPrivacyAuthorityEnabled`,它不是本地公共 C 头文件中的接口。[客户端实现](https://github.com/openharmony/multimedia_player_framework/blob/91ee2a20fb294d0d05e8c48d0c0cc96af68f43e6/frameworks/native/screen_capture/screen_capture_impl.cpp#L397)
2. 服务端从 IPC 取得真实 UID、PID、token,并根据 UID 查询应用包名。普通 UID 进入用户授权逻辑;启用授权时请求系统窗口,未启用授权时才检查 `CAPTURE_SCREEN`。不是要求应用预先拥有免弹窗权限。[调用者与权限](https://github.com/openharmony/multimedia_player_framework/blob/91ee2a20fb294d0d05e8c48d0c0cc96af68f43e6/services/services/screen_capture/server/screen_capture_server.cpp#L1288)
3. 授权请求带 `sessionId`、`callerUid`、`appLabel`,由媒体服务连接系统扩展组件,调用传入的应用 token 为 `nullptr`。已核查的链路未见必须是 HAP token 的检查。[窗口参数](https://github.com/openharmony/multimedia_player_framework/blob/91ee2a20fb294d0d05e8c48d0c0cc96af68f43e6/services/services/screen_capture/server/screen_capture_server.cpp#L2058)
因此 shell 程序在 API 形态上可尝试,**商业固件能否为 shell UID 建立授权弹窗、通知和实际采集仍未知**。最小验证应记录 create/init/configure/getSurface/start 的逐步返回、状态回调、编码输出数量,以及停止结果;设置有限等待与退出。遇到权限拒绝即停止,不使用 root、系统签名、权限白名单或调用系统内部免授权接口补救。
若 shell 路径失败,可把同一 C++ 管线放入普通 HAP 的 native 模块,从手机页面按钮发起。这样调用者具有正常包名和应用生命周期,符合官方示例的应用使用方式,但仍需真机验收,不能从源码推导所有商业设备必然成功。[华为取流实践](https://developer.huawei.com/consumer/cn/doc/doccenter-dev-faq/faqs-media-46)
## 最小采集与编码契约
以下是本项目组装的流程,不是已运行的完整示例。每个返回值均需检查;回调中的停止和释放应派发到管理线程,避免同步销毁回调所属对象。
```text
encoder = OH_VideoEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC)
OH_VideoEncoder_RegisterCallback(encoder, callbacks, session)
format = OH_AVFormat_Create()
设置目标宽高、RGBA surface 输入、帧率、CBR、码率、GOP
OH_VideoEncoder_Configure(encoder, format)
OH_VideoEncoder_GetSurface(encoder, &window)
OH_VideoEncoder_Prepare(encoder)
OH_VideoEncoder_Start(encoder)
capture = OH_AVScreenCapture_Create()
OH_AVScreenCapture_SetStateCallback(capture, onState, session)
OH_AVScreenCapture_SetErrorCallback(capture, onError, session)
config = 全部初始化为零
config.captureMode = OH_CAPTURE_HOME_SCREEN
config.dataType = OH_ORIGINAL_STREAM
config.videoInfo.videoCapInfo.videoSource = OH_VIDEO_SOURCE_SURFACE_RGBA
config.videoInfo.videoCapInfo.videoFrameWidth/Height = 目标宽高
OH_AVScreenCapture_SetMicrophoneEnabled(capture, false)
OH_AVScreenCapture_Init(capture, config)
OH_AVScreenCapture_StartScreenCaptureWithSurface(capture, window)
等待 onState(STARTED),再 OH_AVScreenCapture_SetMaxVideoFrameRate(capture, 30)
```
Surface 模式无需 `OnNeedInputBuffer` 填充像素,无需每帧 PNG 文件和截图下载。Surface 来自编码器并必须在 `Configure` 之后、`Prepare` 之前获取;窗口生命周期由应用管理。音频采集采样率及通道数同时为零表示忽略音频,首版明确关闭麦克风,不索取麦克风权限。[Surface 编码流程](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/media/avcodec/video-encoding.md)、[无音频条件](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/reference/apis-media-kit/capi-avscreencapture-oh-audiocaptureinfo.md)
输出回调为 `onNewOutputBuffer(OH_AVCodec*, uint32_t index, OH_AVBuffer*, void*)`。通过 `OH_AVBuffer_GetBufferAttr/GetAddr/GetCapacity` 检查 offset、size、PTS、flags;复制合法范围的数据后尽快 `OH_VideoEncoder_FreeOutputBuffer(encoder,index)`。参数集、同步帧和普通帧按原始顺序传输,不能随便丢弃依赖帧;遇到积压丢弃到下一关键帧并请求 IDR。`OH_AVCodecBufferAttr.pts` 是微秒,不能与网络到达时间混为端到端时延。[编码数据读取](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/media/avcodec/video-encoding.md)、[buffer 属性](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/reference/apis-avcodec-kit/capi-core-oh-avcodecbufferattr.md)
推荐链接公共库 `libnative_avscreen_capture.so`、`libnative_media_venc.so`、`libnative_media_codecbase.so`、`libnative_media_core.so`、`libnative_window.so`。由实际使用符号补充 native buffer 依赖;不复制设备私有库或 DevEco 私有 agent。[采集依赖](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/media/media/avscreencapture-c-basic-process.md)、[编码依赖](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/media/avcodec/video-encoding-configuration-typical-scenarios.md)
停止顺序应先停止采集输入,通知编码器 EOS 并有界等待,释放采集和编码器、销毁 NativeWindow,再关闭 socket 和本会话转发;拒绝、错误、断线也走相同可重入清理路径。不同旋转尺寸先更新主机几何信息,暂停旧坐标输入,再重建需要变更尺寸的编码器。
## 低延迟配置与实际指标
官方把短距投屏列为低时延编码场景,推荐 CBR、根据目标分辨率/帧率设码率,并支持运行期通过 `OH_MD_KEY_REQUEST_I_FRAME` 请求 IDR。这里建议先从最长边 1280、30 fps、H.264、CBR 4 Mbps、1 秒 GOP 开始测量;这些数值是原型起点,不是官方对该手机的性能保证。[典型场景编码配置](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/media/avcodec/video-encoding-configuration-typical-scenarios.md)
`OH_MD_KEY_VIDEO_ENABLE_LOW_LATENCY` 名字易产生误解:公共 SDK 将其定义为**视频解码**配置,本研究未确认它对编码器有效。编码 B 帧默认关闭,应保留此行为以避免显示顺序重排。API 26 新增的 CBRHQ 应先通过编码能力查询,不能直接强制较旧设备使用。[编解码参数声明](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/reference/apis-avcodec-kit/capi-native-avcodec-base-h.md)
工程上应将采集、编码回调和 socket 发送分开:发送线程使用有界队列、超时与取消;不能在编码回调等待网络。主机也需要持续解码并只渲染最新可用画面,避免 30 fps 视频经慢速 JSON/base64 轮询再次积压。指标分别记录编码帧率、收到帧率、呈现帧率、码率、排队时长、首帧时间;没有时钟同步或可视运动实验时,不把采集耗时称为端到端延迟。
## UITest 扩展:可优化的 JPEG 路线
当前锁定的 OpenHarmony UITest 提交与上一轮一致:`b04e30c40cf266c9abfbd52933e43a0456eb99a2`。
- `startCapture("copyScreen", callback, options)` 读取 `scale`、`displayId`,内部将 screenshot PixelMap 转为 JPEG。`scale` 对外检查严格 `0 < scale < 1`。
- `screen_copy.cpp` 使用独立截图和编码线程,以最近帧替换前帧,只在图像变化时回调。源码中的 50 ms sleep 用于熄屏轮询,**不是运行态固定 20 fps 上限**。
- 回调参数是临时 `Text`,执行完立即 `free(data)`,异步发送必须先复制;不能缓存裸指针。
- `LowLevelFunctions` 没有获取原始 PixelMap、编码器 Surface 或 H.264 的函数。额外直接调用 AVScreenCapture 属于独立的公共 API 路线,不是 UITest 已提供 H.264。
[JPEG 实现](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/screen_copy.cpp)、[回调生命周期](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_executor.cpp#L305)、[ABI](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_c_api.h)
## 若需伴随 HAP:最小应用职责
ArkTS 仅负责正常应用入口、开始/停止共享、授权状态和后台任务;C++ 负责采集、编码及 loopback TCP。录屏授权不赋予任意输入注入权,控制继续复用已授权 HDC 的 UITest。若使用 socket,应用声明普通 `ohos.permission.INTERNET`;仅监听 loopback,并要求本会话随机 token。不要为了投屏申请系统的免授权采集或输入注入权限。
持续录屏退后台需要合法长时任务,否则应用可能挂起。官方任务类型表将 `AUDIO_RECORDING`/`audioRecording` 对应录音、录屏退后台,声明 `ohos.permission.KEEP_BACKGROUND_RUNNING` 并在共享期间保持可见通知,停止时结束任务;`taskKeeping` 在手机上有额外受限权限条件,不适合作为默认捷径。[长时任务规范](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/task-management/continuous-task.md)、[华为取流与后台说明](https://developer.huawei.com/consumer/cn/doc/doccenter-dev-faq/faqs-media-46)
真机 HAP 安装必须签名。调试 profile 绑定包名、证书和允许调试的设备;DevEco Studio 自动签名或 AGC 手动签名均有官方流程。下载 Command Line Tools 和网页登录本身没有生成可安装的签名 HAP;不得用系统样例证书假装适用于商业手机。需要 HAP 时先完成可构建源代码,再配置有效调试签名并进行真机授权测试。[自动签名](https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-signing-auto)、[手动签名](https://developer.huawei.com/consumer/cn/doc/doccenter-deveco-studio/ide-signing-manual)
### 官方 CLI 自动签名路径
华为官网推荐 `@deveco/deveco-cli@stable`,1.3.0 版本新增自动调试签名;本次查询 npm stable 标签为 `1.3.0-stable`。可以在隔离的工具目录安装,使用 Node 22 及以上,配置 `DEVECO_CLI_CLT_PATH` 为完整解压的 Command Line Tools 根目录。只解压 HDC 与 native SDK 不构成该 CLI 所需完整工具链。[官网工具入口](https://developer.huawei.com/consumer/cn/deveco-studio)、[1.3.0 版本说明](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-deveco-cli-releasenote)、[官方 npm 包](https://www.npmjs.com/package/@deveco/deveco-cli)
官方包 README 给出的流程如下;本轮已尝试签名,结果见集成记录:
```text
devecocli auth status
devecocli auth login # 未登录时打开浏览器授权
devecocli create --app-name HarmonyConnector --bundle-name <本项目包名> --project-path <工程目录>
# 在完成代码的工程目录:
devecocli signature generate --product default
devecocli build --modules entry --build-mode debug
```
`signature generate` 生成 p12、csr、p7b、cer 并写工程 `build-profile.json5`。默认使用用户自身团队;需要团队时指定已查询的 team-id,不虚构;已有签名无需加 `--force`。CLI 登录目前不支持海外账户。它的脚手架只提供 Empty Ability,native 模块仍需项目自行配置。[官方 CLI 文档](https://www.npmjs.com/package/@deveco/deveco-cli)
## 本次集成验证的已知边界
同一任务的真机执行方报告:原链路连续 3 帧耗时约 696、803、802 ms,约为 1.3 fps;此为采集周期,不是端到端时延。使用公共 NDK 编译的 `aarch64-linux-ohos` 采集原型被传到本会话独有的 `/data/local/tmp/` 路径并设置执行权限,直接启动返回 `Permission denied`。HDC 自身退出状态为 0,不代表设备端命令成功。程序未执行任何采集 API,未显示授权弹窗,因此本次证据只定位到执行权限边界,不能证明 AVScreenCapture 被拒绝。
UITest addon 未进入 `OnInit`。随后读取设备日志确认:MUSL-LDSO 报 `Xpm check failed`、`errno for mmap is:13`,UiTestKit_Addon 的 `dlopen` 返回 `Permission denied`。这将本次失败定位到临时未签名模块的加载校验,不能解释为 ABI 不兼容;没有尝试关闭或绕过 XPM。临时程序、模块和手机上的本会话文件均已清理。
华为公开 LLDB 调试矩阵将 user 镜像、SELinux 开启场景的支持范围列为签名 debug HAP;并区分其与 root 镜像上的普通可执行文件调试。它与上述限制相符,但不能仅凭该文档判定具体拒绝来自 SELinux、代码签名还是其他策略。当时选择了普通应用签名实验;该路线随后已停止,当前改用官方分发且无需用户签名的 Hypium 代理。[华为 Native 调试条件](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/debug-lldb)
## 最小验收
1. 普通 shell 程序是否能创建采集和编码器、显示系统授权;分别验证同意、拒绝和超时。
2. 取得 H.264 参数集和关键帧,并在 Mac 成功解码连续变化画面。
3. 验证屏幕旋转、锁屏、隐私画面、断线和用户主动停止,保留系统遮挡行为。
4. 持续运行时记录呈现帧率与排队增长;同时验证输入后画面及时变化。
5. 会话结束不再采集、不再注入输入;清理本会话的临时程序/文件和端口转发。
公开源码和 SDK 只能支撑上述实现契约,不能替代 Pura 70 Ultra 的实际授权、解码与延迟验收。
## 伴随应用构建进展
集成方已创建普通手机应用 `ink.render.trove.harmonyconnector`,实现系统授权的 Surface 采集、H.264 编码、带会话校验的 loopback TCP 和停止收尾。官方 CLI 已成功构建 ArkTS 与 arm64-v8a native 模块并生成未签名 HAP。独立 Mac 接收器的 8 项测试通过,其中生成的 20 帧视频经过真实 H.264 解码全部返回。
最初签名预检被账号认证状态阻塞;用户认证并刷新官方授权后,曾成功生成正常调试签名、构建并安装 HAP,收到首个 H.264 帧。接收器在第一帧统计时发生除零错误,故没有取得持续帧率或端到端延迟结果。随后按用户要求停止 HAP 路线,实验源码已从主分支移除;历史实现保留于提交 `ca382e6`。当前无需账号的实现和验证状态见[开发验证说明](../harmony-realtime-development.md)。
-134
View File
@@ -1,134 +0,0 @@
# Harmony Connector 投屏与键鼠控制调研
调研日期:2026-09-10。范围是 macOS 上的 Harmony Connector,目标包括原生 HarmonyOS 和 OpenHarmony,按最新公开资料评估,不以某个系统大版本作硬上限。承接 [设备管理调研](harmony-device-management.md)。本轮仅阅读官方文档及项目源码,未安装工具、执行手机命令或改变设备状态;以下命令是实现契约/待验证方案,不是真机验收记录。
## 结论
**官方 scrcpy 不能直接作为原生鸿蒙投屏后端。** 它的设备端是 Android framework 上的 Java/Dex 程序,经 ADB 启动;把可执行文件名从 adb 换成 hdc,不能替代 Android 运行时、屏幕采集和输入 API。官方 scrcpy 当前 README 为 v4.1,设备要求 Android API 21+,macOS 是支持的电脑平台。只有设备确实提供兼容 Android 的运行时、ADB shell 及相应屏幕/输入能力时,才适合另外启用 scrcpy 通道;手机品牌、能运行某些 APK 或存在兼容容器都不是充分条件。[scrcpy 官方项目](https://github.com/Genymobile/scrcpy)、[服务端架构](https://github.com/Genymobile/scrcpy/blob/master/doc/develop.md)
本项目可按以下顺序推进,优先级是工程建议:
| 路线 | 画面与输入 | 本项目建议 |
| --- | --- | --- |
| HDC + UITest CLI | 单次截图;可轮询预览。点击、长按、滑动、指定文本、常用键。 | P0:复用现有 HDC 后端;显示“屏幕预览与操作”,不承诺视频级流畅度。 |
| 官方 DevEco Studio / DevEco Testing | 官方已提供设备投屏、点击/滑动/字符输入等。 | P0:提供官方工具入口及指引;未查到可嵌入第三方插件的公开投屏协议。 |
| 官方系统录屏 CLI | 录制后导出 MP4。 | P1:作为“录屏导出”独立功能,不把 MP4 文件导出当实时镜像流。 |
| 自建正常签名的鸿蒙伴随应用 + AVScreenCapture + HDC 转发 | 经手机用户同意采集,编码后持续传输;输入可另走 UITest。 | P2:真实视频流的优先独立原型,必须验证商业设备授权、后台持续采集和编码器。 |
| OpenHarmony UITest addon | 自建 native 扩展,JPEG 帧回调及原子输入 API。 | P2:开源实现可研究,商业固件可加载性/ABI 未验证。 |
| 鸿镜 / OpenHarmony_Scrcpy | 已有连续编码视频及控制实现。 | 面向可配置 OpenHarmony 系统的参考,当前系统权限/root 要求不适合作为普通华为手机的默认方案。 |
| 官方 scrcpy | Android 视频流及键鼠控制。 | 仅为已验证 Android/ADB 设备提供独立可选入口。 |
## scrcpy 依赖与直接集成边界
scrcpy 客户端推送 server 到设备并启动,视频、音频、控制使用分离 socket。server 使用 Android `app_process` 加载 Dex,屏幕关联 Surface 经 `MediaCodec` 编码,输入通过 Android `InputManager.injectInputEvent()` 等方法处理;其中包含通过反射调用的隐藏 API。客户端解码使用 FFmpeg、窗口输入使用 SDL。项目明确客户端和 server 必须版本一致,其内部协议不保证跨版本兼容。[官方开发说明](https://github.com/Genymobile/scrcpy/blob/master/doc/develop.md)
因此 `hdc fport` 只能替代传输中的一小部分,不能运行 Android server;要复用 scrcpy UI/解码需要替换连接、启动器、设备端采集/编码/输入,并协调协议,属于移植工程。不能声称下载官方 scrcpy 后便支持原生 HarmonyOS。对具有 Android 兼容能力的设备,按实际授权 ADB 目标识别,运行受控的原生 scrcpy 会话验收;不要根据 HarmonyOS 名称硬编码“可用/不可用”。[scrcpy 连接方式](https://github.com/Genymobile/scrcpy/blob/master/doc/connection.md)、[服务端约束](https://github.com/Genymobile/scrcpy/blob/master/doc/develop.md)
若用户已经安装官方 scrcpy 和 adb,候选 Android 通道的只读预检可使用 `scrcpy --version`、`adb devices -l`、`adb -s <serial> shell getprop ro.build.version.sdk`。实际会话由用户点击后执行以下 argv;macOS 上优先启动独立窗口,减少嵌入原生渲染的成本。此处参数组合是本项目建议,不是鸿蒙兼容证明。[官方连接参数](https://github.com/Genymobile/scrcpy/blob/master/doc/connection.md)、[视频参数](https://github.com/Genymobile/scrcpy/blob/master/doc/video.md)、[控制参数](https://github.com/Genymobile/scrcpy/blob/master/doc/control.md)
```text
scrcpy argv: ["--serial", serial, "--no-audio", "--max-size=1280", "--max-fps=30"]
仅观看时再添加: ["--no-control"]
```
scrcpy 的 `--otg` 通过 Android Open Accessory 模拟 USB HID,不依赖 ADB,但它**没有投屏**。不能将 OTG 模式宣传成绕开鸿蒙设备端视频支持的办法,也不能未验证便承诺手机实现相应 USB accessory 协议。[官方 OTG 文档](https://github.com/Genymobile/scrcpy/blob/master/doc/otg.md)
macOS 官方文档提供预编译包和包管理方式。集成可优先使用用户已安装工具;本轮没有下载运行任何发布二进制。scrcpy 为 Apache-2.0,重新分发还需审核所选构建中的第三方依赖及其声明;这不授予 Huawei SDK 的重打包许可。[macOS 获取方式](https://github.com/Genymobile/scrcpy/blob/master/doc/macos.md)、[scrcpy LICENSE](https://github.com/Genymobile/scrcpy/blob/master/LICENSE)
## 可立即实现:HDC 屏幕预览和操作
HDC 需设备开启开发者调试并信任电脑。以下固定使用用户选择的 `key`;宿主用 argv 调用,不执行本机 `sh -c`。设备端 shell 参数仍需严格转义,坐标和速度仅接受有界数字。[华为 HDC](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)
| 操作 | HDC argv(省略 hdc 本身) |
| --- | --- |
| 版本 | `["-t", key, "shell", "uitest --version"]` |
| 工具帮助 | `["-t", key, "shell", "uitest help"]` |
| 输入帮助 | `["-t", key, "shell", "uitest uiInput help"]` |
| 截图 | `["-t", key, "shell", "uitest screenCap -p /data/local/tmp/curios-screen-<nonce>.png"]` |
| 下载截图 | `["-t", key, "file", "recv", remoteTemp, localTemp]` |
| 点击 | `["-t", key, "shell", "uitest uiInput click 160 240"]` |
| 长按 | `["-t", key, "shell", "uitest uiInput longClick 160 240"]` |
| 滑动 | `["-t", key, "shell", "uitest uiInput swipe 160 720 160 320 600"]` |
| 拖拽 | `["-t", key, "shell", "uitest uiInput drag 160 240 480 640 600"]` |
| 当前焦点文本 | `["-t", key, "shell", "uitest uiInput text <escaped-text>"]` |
| 指定输入框文本 | `["-t", key, "shell", "uitest uiInput inputText 160 240 <escaped-text>"]` |
| 返回/主页 | `["-t", key, "shell", "uitest uiInput keyEvent Back"]` / `Home` |
精确 CLI 来源是 [OpenHarmony UITest 指引](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/application-test/uitest-guidelines.md)。截图 `-p` 只支持 `/data/local/tmp/`;`-d <displayId>` 从 API 20 起。`swipe`/`drag` 最后参数是速度 px/s,范围 200–40000,**不是毫秒时长**。`text` 从 API 18 起,只有当前焦点支持输入时才有实际效果。键码取 OpenHarmony `KeyCode`,不能照搬 Android keycode 或 macOS keycode;特殊 `Back/Home/Power` 不支持组合键。[UITest 操作定义](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/README_zh.md)、[OpenHarmony KeyCode](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/reference/apis-input-kit/js-apis-keycode.md)
工程实现建议:
- 查看预览和启用控制分开;进入页面不自动注入按键。开始预览后串行截图、下载、解码,完成一帧再请求下一帧,避免积压。可先设置约 1 秒的刷新目标,实际显示采集时间,不宣传固定 FPS。
- 按当前 PNG 真实尺寸和画面显示区域计算坐标,排除留白;旋转/折叠后刷新尺寸,暂停旧坐标输入。鼠标拖动收集起终点后发送一次 swipe/drag,不能伪装成实时多点触控。
- 每个目标限制一个屏幕会话,输入队列有上限;用户停止、窗口隐藏、设备断开或授权失效时立即停止排队,避免恢复连接后补发旧输入。文本使用单独输入框和发送按钮,不全局捕获电脑键盘。
- 对中文、空格、引号、emoji、新行和输入法组合做真实验证。不要为此同步读取剪贴板或关闭密码键盘。输出可解析/退出码正常只能表示命令执行结果,截图刷新或实际界面变化才是操作效果证据。
- 保存截图使用临时文件并校验 PNG;默认帧只在本机短暂缓存。清理只删除本会话生成的远端/本地文件,不碰用户相册。锁屏和受保护画面保持系统行为,不自动解锁或关闭隐私保护。
`uiRecord record` 是操作记录,不是视频录屏;其记录可写到 CSV,并不产生 H.264 视频。不要把它接成“录制投屏”。[UITest uiRecord](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/application-test/uitest-guidelines.md)
## 官方工具:确有投屏,但没有可确认的第三方嵌入协议
DevEco Studio 官网当前明确支持连接设备投屏、点击、滑动、字符输入及拖拽包/文件;26.0.0 Release 的官方说明将设备投屏列为新增能力。产品提供 macOS Intel/ARM 版本。它是真实设备投屏能力,不能与“鼠标控制模拟器”的教程混用。[DevEco Studio 官网](https://developer.huawei.com/consumer/cn/deveco-studio)、[26.0.0 官方版本概览](https://developer.huawei.com/consumer/cn/doc/doccenter-release-notes/overview-2600)、[仅针对模拟器的操控屏幕文档](https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-emulator-control-screen)
DevEco Testing 官方 FAQ 确认 Windows/Mac 客户端、设备投屏等测试工具;要求 HarmonyOS NEXT 设备开启开发者模式和 USB 调试,并能被 HDC 识别。官方工具介绍明确其投屏支持在 PC 上操作设备。最新 26.0.0 兼容表仍将“设备投屏”的模拟器列标为不支持;不要把云手机特定接入指引外推到所有本地模拟器。[DevEco Testing FAQ](https://devecotesting.huawei.com/userPortal/help?jobType=100000102)、[官方工具介绍](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V14/devecotesting-V14)、[最新兼容表](https://developer.huawei.com/consumer/cn/doc/doccenter-release-notes/deveco-testing-releasenotes-2600)
本轮没有从这些第一方资料确认可供第三方插件使用的投屏 SDK、稳定 socket 协议、可重新分发的 device agent 或指定设备投屏 deep link。因此可以引导用户打开已安装的官方应用并选择设备;不能提取其私有 agent 并当作插件依赖。`Device Manager` 只是工具内管理入口的名称,不能据二手教程编造其稳定 CLI。本轮未检视商业工具安装包,不判断其内部实现。
## 官方录屏 CLI:结束后导出视频
华为官方录屏文档要求设备解锁;录制中锁屏会退出,文档不支持模拟器录屏。其命令流程是启动系统录屏组件、停止、定位媒体、必要时复制到可下载目录、下载文件,未给出实时 stdout 视频流协议。[华为录屏文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-screen-recording)
```text
开始:
["-t", key, "shell", "aa start -b com.huawei.hmos.screenrecorder -a com.huawei.hmos.screenrecorder.ServiceExtAbility --ps CustomizedFileName curios-record-<nonce>.mp4"]
停止(官方写法仍为 aa start,同组件但不传文件名):
["-t", key, "shell", "aa start -b com.huawei.hmos.screenrecorder -a com.huawei.hmos.screenrecorder.ServiceExtAbility"]
定位录制结果:
["-t", key, "shell", "mediatool query curios-record-<nonce>.mp4 -u"]
查询含 uri 时,用查询返回且已验证的 URI 导出:
["-t", key, "shell", "mediatool recv <escaped-returned-uri> /data/local/tmp"]
下载工具实际返回的路径:
["-t", key, "file", "recv", returnedRecordFile, localTemp]
```
官方说明有 URI 时不能直接下载查询结果里的原始媒体路径;需 `mediatool recv` 得到可下载路径。实现不要只盲取“第几行”,应验证 URI、返回路径和唯一文件名。开始/停止是设备写操作,不能作为能力探测;录屏组件的停止写法也不能无限重试。用会话状态管理、手机通知和最终可解码文件验收,未知状态引导在手机停止。本功能会生成设备媒体文件,默认不能顺手删除相册原件。[华为录屏文件导出流程](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-screen-recording)
## 真正视频流:自建伴随应用路线
AVScreenCapture 支持将采集结果作为码流交给其他模块,以实现桌面共享。最新 OpenHarmony 指引区分文件输出和码流输出,并说明码流录制开始会出现隐私选择弹窗;受保护内容可被屏蔽。华为最新窗口级录屏文档亦明确 Phone/Tablet 的 Picker 能力和权限条件。因此“普通应用一概不能录屏”不正确;需要区别用户授权录屏与系统免授权采集/输入注入。[OpenHarmony 录屏基础流程](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/media/media/avscreencapture-c-basic-process.md)、[华为窗口级录屏与授权](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/using-avscreencapture-for-file-with-window)
建议独立原型链路为:用户启动已签名 HAP并在手机允许共享 → `OH_AVScreenCapture_Create/Init` → 绑定编码器输入 Surface 后 `OH_AVScreenCapture_StartScreenCaptureWithSurface` → `OH_VideoEncoder` 输出 H.264 → 应用内 loopback TCP → HDC fport → macOS VideoToolbox/受控播放器。采集、编码和传输分别处理错误与背压;音频初版关闭。停止使用 `OH_AVScreenCapture_StopScreenCapture/Release`,并关闭编码器和本会话转发。详细 API 配置从配套 SDK 的官方示例实现,不复制系统免授权权限。[官方采集到编码器 Surface 示例](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/media/media/avscreencapture-c-custom-scenarios.md)
传输示例为 `["-t", key, "fport", "tcp:<hostPort>", "tcp:<companionPort>"]`,停止后仅移除本次建立的转发 `["-t", key, "fport", "rm", "tcp:<hostPort>", "tcp:<companionPort>"]`;两个端口是自建协议配置,HDC 本身不会生成视频。使用 loopback、每会话随机 token、长度上限和协议版本;需要网络直连时另行设计用户配对与认证,不默认监听 `0.0.0.0`。手机端普通 HAP 不由录屏授权获得任意输入注入权限;键鼠仍可通过已授权 HDC 的 UITest 命令完成。此路线需新建手机工程、签名与真机验证,当前插件未具备。[华为 HDC 端口转发](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hdc)
## OpenHarmony UITest addon:可研究的 native 扩展
检查官方 `testfwk_arkxtest` 提交 `b04e30c40cf266c9abfbd52933e43a0456eb99a2`:`uitest/addon/extension_c_api.h` 定义 `UiTestExtension_OnInit`、`UiTestExtension_OnRun`,通过 `UiTestPort.initLowLevelFunctions` 获取 `startCapture/stopCapture`、`atomicTouch`、`atomicMouseAction` 等函数。`startCapture` 名称 `copyScreen` 接收 JSON 选项 `scale`(严格 0 到 1 之间)、`displayId`;回调提供 JPEG 字节,当前开源实现反复 `GetScreenshot` 并编码变化帧,不是 H.264 编码流。[扩展 C ABI](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_c_api.h)、[调用实现](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_executor.cpp)、[JPEG 采集实现](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/screen_copy.cpp)
源码可导出的启动契约是 `uitest start-daemon singleness --extension-name curios-screen-<nonce>.so`,加载 `/data/local/tmp/` 下相应文件,默认名 `agent.so`。扩展须自行实现有界传输、停止以及主机认证。**这是源码入口,不是本次确认的商业公共 CLI。** 官方仓库 README 将 start-daemon 标为测试端注入用途;商业固件可能改变加载策略、系统库、ABI 或采集实现。先在自己可控制的 OpenHarmony 测试镜像验证,再专门评估真实商业设备,不应默认为任何手机加载 native 代码。[加载器](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_executor.cpp#L354)、[命令分派](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/server/server_main.cpp#L471)、[官方 README](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/README_zh.md)
不能从论坛提到的 `xdevice_scrcpy` 或 `DevEcoScrcpy` 日志推断商业工具私有协议已公开,也不能把上面的开源 JPEG 路径描述成与商业 H.264 路径完全相同。
## 已核查的开源成品
| 项目与锁定提交 | 真实机制、macOS、授权边界 | 结论 |
| --- | --- | --- |
| [鸿镜 ohos-scrcpy-app](https://github.com/guoxiucai/ohos-scrcpy-app/tree/1d470ea8f571f19fcbb6883cc6118fafed6fe14f) | Flutter macOS/Windows;H.264、VideoToolbox、鼠标/触摸/按键、录制,编码失败可降级 JPEG。设备端为系统 HAP,作者明确要求 Full SDK、目标系统信任签名和权限白名单。 | 可参考本机解码与协议设计;不能普通签名安装到商业手机便承诺工作。 |
| [OpenHarmony_Scrcpy](https://github.com/luodh0157/OpenHarmony_Scrcpy/tree/85890e693fb5b3e4b3ef50a16af688787ca85070) | 项目声明跨桌面平台,设备端明确要求 OpenHarmony 5+ root 版本;原生 AVScreenCapture + H.264/H.265。安装脚本 remount 并写 `/system/bin` 和 `/system/etc/init`,服务具媒体采集权限。 | 只适合受控开发镜像;不用于本项目普通手机自动安装。 |
| [scrcpy-ohos](https://github.com/l376571926/scrcpy-ohos/tree/5d938ac1d125406705c764d7061192d91c33b9d1) | JavaFX/JDK11;源码 `snapshot_display` 截图下载,操作后刷新,uinput 模拟事件;README 明确非实时。未找到根 LICENSE。 | 不是官方 scrcpy 的鸿蒙兼容层,也不提供连续视频流;可看思路,不能据公开代码便假定可复制分发。 |
鸿镜设备权限包括 `CAPTURE_SCREEN`、`EXEMPT_CAPTURE_SCREEN_AUTHORIZE`、`INJECT_INPUT_EVENT`,其文档明确需 ACL、系统签名和指纹白名单。仅将 HAP 换成自己的普通调试证书不解决这些限制。[鸿镜签名要求](https://github.com/guoxiucai/ohos-scrcpy-app/blob/1d470ea8f571f19fcbb6883cc6118fafed6fe14f/README.md#签名与系统权限)、[设备权限声明](https://github.com/guoxiucai/ohos-scrcpy-app/blob/1d470ea8f571f19fcbb6883cc6118fafed6fe14f/scrcpy_server/entry/src/main/module.json5)
鸿镜可复核的传输为 `hdc fport tcp:<localPort> tcp:53535`,设备只监听 `127.0.0.1:53535`。它使用自定义包:大端 u32 类型 + 大端 u32 payload 长度;`0x02` 配置、`0x03` 视频帧、`0x10` 控制。H.264 帧 payload 是 flags u8、PTS u64、NAL 数据;控制下含触摸/键鼠子类型。它与官方 scrcpy wire protocol 不兼容,不能拿其 server 对接官方 scrcpy client。[鸿镜协议](https://github.com/guoxiucai/ohos-scrcpy-app/blob/1d470ea8f571f19fcbb6883cc6118fafed6fe14f/scrcpy_client_flutter/lib/net/protocol.dart)、[loopback 监听](https://github.com/guoxiucai/ohos-scrcpy-app/blob/1d470ea8f571f19fcbb6883cc6118fafed6fe14f/scrcpy_server/entry/src/main/cpp/TcpServer.cpp)
许可分别是鸿镜 MIT、OpenHarmony_Scrcpy Apache-2.0;仍需保留声明、审查依赖和自行构建的分发物。root 项目的安装脚本会遍历目标设备并更改系统分区,本项目不能直接调用它。[鸿镜 LICENSE](https://github.com/guoxiucai/ohos-scrcpy-app/blob/1d470ea8f571f19fcbb6883cc6118fafed6fe14f/LICENSE)、[OpenHarmony_Scrcpy LICENSE](https://github.com/luodh0157/OpenHarmony_Scrcpy/blob/85890e693fb5b3e4b3ef50a16af688787ca85070/LICENSE)、[root 服务要求](https://github.com/luodh0157/OpenHarmony_Scrcpy/blob/85890e693fb5b3e4b3ef50a16af688787ca85070/Server/README.md)、[系统分区安装脚本](https://github.com/luodh0157/OpenHarmony_Scrcpy/blob/85890e693fb5b3e4b3ef50a16af688787ca85070/Server/install_ohscrcpy_server.sh)
## 验收与后续决策
先交付可探测的屏幕预览和明确输入,再进行视频流原型。每条能力分别保存 available/unavailable/unknown 及原因;不把 HDC 已连接等同全部投屏/输入可用。最小真机验收需要覆盖截图解码、旋转坐标、点击/滑动效果、中文输入、断线取消、锁屏、拒绝授权、受保护画面、停止采集后不再传帧和本会话文件/转发清理。视频流另外测首帧时延、实际 FPS、CPU/温度、背压、编码器重建与应用后台状态;完成这些前不使用“低延迟 60 FPS”等宣传。
本轮明确未验证:Pura 70 Ultra 当前固件的 UITest 输入效果、HDC 版本、官方工具真机投屏、伴随应用采集权限、UITest addon 加载、任何社区 HAP/二进制。已确认的是公开能力与实现边界;没有执行设备动作,也没有修改插件代码。
-238
View File
@@ -1,238 +0,0 @@
# Harmony Connector 实时投屏性能:官方资料与协议
调研日期:2026-09-10。范围:macOS、用户已授权的 USB 调试/HDC、官方 UITest 扩展;不要求开发者账号、HAP 安装、root 或修改设备安全策略。本报告仅做官方资料及官方分发包的静态分析,没有启动设备代理或测量手机帧率。用户要求优先完成编码,因而不在本报告中承诺实测 60 fps。
## 结论
应把实时画面从逐帧 JPEG 改为官方 Hypium recorder 的 H.264 视频流,再由 macOS 的持久视频图层显示。Hypium **26.0.0.500** 官方包确实提供已带签名块的 Unix socket recorder,可通过 HDC 的 `localabstract` 转发实现设备端无 TCP 监听的连接。此前检查的 PyPI 6.1.0.210 包缺少这些 Unix recorder,不能据此判断鸿蒙没有这条路径。[官方 Hypium 下载页](https://developer.huawei.com/consumer/cn/download/deveco-testing-hypium)、[官方 26.0.0 发布说明](https://developer.huawei.com/consumer/cn/doc/harmonyos-releases/deveco-testing-releasenotes-2600)
**容易接错的 socket:实时 `-m 0` 使用 `scrcpy_grpc_socket`;文件录制 `-m 1` 使用 `screen_record_grpc_socket`。** 官方 Python 的 `record_agent.py` 是文件录制封装,照抄它的 socket 名并把 mode 改为 0,会连不到实时服务。此区别直接来自 26 包 `libscrcpy_server_unix_6.6-20260629.z.so` 的 `RpcServer::InitServer`,见下文精确位置。
## 官方分发包和本地证据
来源是官方下载页当前列出的 **DevEco Testing Hypium 26.0.0.500,118.7 MB,2026/08/29**。通过用户现有网页登录状态点击正常下载取得官方 CDN 文件,未要求新账号或签名授权。临时签名下载 URL 不写入仓库。
研究文件保存在忽略目录 `dist/harmony-screen-performance-research/`;不是产品运行时路径。
| 文件 | 本地计算的 SHA256 |
| --- | --- |
| `devecotesting-hypium-26.0.0.500.zip` | `1c66fb288a291e3cd1743a4bf82708752bcef58c230748088194182f304a097f` |
| `hypium26/python/xdevice_devicetest-26.0.0.500-py3-none-any.whl` | `810b2d33bb86a6fe91c5f24fe9bc33cc3937e3af84ce1d760e3257bb648f9c06` |
外层 zip 包含 `hypium-26.0.0.500.zip`、`hypium-driver-js-26.0.0500.zip` 和 PyCharm 插件 zip。Python zip 包含 hypium、xdevice、xdevice_ohos、xdevice_devicetest 四个 wheel、API 文档和安装脚本。只解压读取,没有运行安装脚本。以上是下载原件的本地校验摘要,不声称已经另行比对官网公布的 SHA256。
以下资源的完整本地目录为:
```text
dist/harmony-screen-performance-research/hypium26/python/
xdevice_devicetest/devicetest/res/recorder/
```
| Unix recorder 原件 | 字节 | SHA256 |
| --- | ---: | --- |
| `libscrcpy_server_unix_6.3.1-20260113.z.so` | 272567 | `3e29e3a1d23ea6dbd4e6fb3b8f094ca5d4b15b4c580f421fba3d0e1f5429e3fe` |
| `libscrcpy_server_unix_6.4-20260113.z.so` | 264374 | `b698b41ae1351337eeea3dcc87bf0160552196d669ba321b4963c342fa5a47f4` |
| `libscrcpy_server_unix_6.5-20260313.z.so` | 268469 | `2c8f1ef94d581281bf8e86d54f5939af9b205d5db5aa2cfb27f2562215c87b0a` |
| `libscrcpy_server_unix_6.6-20260418.z.so` | 268471 | `98bbc964fef1c4d17b446ec717102b855d09224b23fc1872bf70e1f928d010dd` |
| `libscrcpy_server_unix_6.6-20260629.z.so` | 260278 | `b24231188ab0fffa84c2bd71cab6948c8ffad9d661c80301f32eaf580177ce8e` |
各 Unix 原件均含 `elf sign block`;旧 recorder 也带该块及 Huawei 软件签名证书链字符串。这证明分发文件带有签名结构,不等于本报告独立完成签名密码学验证,也不保证每个系统版本均接受每个原件。必须完整保留原件字节,不能 strip、改监听地址或重签名再声称是官方原件。
同 wheel 还提供 `devicetest/res/prototype/native/uitest_agent_v1.2.3.so`,以及 1.1.3、1.1.5、1.1.12 和 x86_64 资源。wheel 的 `xdevice_devicetest-26.0.0.500.dist-info/licenses/LICENSE` 是 Apache License 2.0;相关 Python 文件带 Huawei Device Co., Ltd. 的 Apache 2.0 版权头。打包时保留对应许可和来源,不把另一个独立下载包的授权凭空套用到这些文件。
`uitest_agent_v1.2.3.so` 原件为 **600246 字节**,SHA256 为 `5dfbaca7e274a85bece8f225148ceb759b94d7319b956b9a7ea40d37944e4a9b`。该 wheel 的 METADATA 明列 `License-File: LICENSE`;解包中未发现另外的 NOTICE 或逐二进制许可文件。产品随两种原件保留这份 LICENSE,并在自己的第三方声明中记录 Huawei 官方分发包、版本、原件路径、摘要及未修改状态;不要将自己补写的来源说明说成上游附带 NOTICE。
## 官方代理选择规则
直接证据文件:
```text
hypium26/python/xdevice_devicetest/devicetest/controllers/tools/recorder/record_agent.py
hypium26/python/xdevice_ohos/ohos/environment/device.py
```
`RecordAgent._start_scrcpy_server` 首先读取 UITest 版本,要求至少 4.1.4.6,随后按 `device.check_if_unix_socket_mode()` 决定传输。后者实际代码判断为 UITest 版本元组大于 `6.0.2.1` 且架构不含 `x86_64`。旁边注释写的是“大于 6.0.2.2”,与代码不完全一致;客户端应使用实际能力和数值版本解析,不照抄字符串元组比较的问题。
Unix 分支只尝试文件名含 `unix` 的资源;非 Unix 分支只尝试不含 `unix` 的资源。官方按 `os.walk` 文件列表倒序逐个启动,**没有把文件名的 6.3.1 / 6.6 映射为 HarmonyOS 产品版本**。插件应固定已支持的原件,或采用明确的兼容性候选顺序;不能声称手机必须运行“鸿蒙 6.6”。
官方会覆盖 `/data/local/tmp/libscreen_recorder.z.so`,以进程命令和 `/proc/net/unix` 检查启动是否成功。产品应为自己的扩展文件与进程记录所有权,不复用或结束别的 UI 调试会话。Unix socket 名在原件中固定,因此冲突时应明确报告,而不是杀掉所有 `uitest` 进程。
## 实时 H.264 的最小连接流程
这是根据官方启动脚本和原件参数解析重建的编码接口,未在本研究中对手机执行。`HDC`、`SERIAL`、`LOCAL_PORT`、`AGENT` 为客户端已校验的值;远端扩展文件名使用本插件自己的固定名称。
```sh
"$HDC" -t "$SERIAL" file send "$AGENT" /data/local/tmp/libharmony_connector_screen.z.so
"$HDC" -t "$SERIAL" fport "tcp:$LOCAL_PORT" localabstract:scrcpy_grpc_socket
"$HDC" -t "$SERIAL" shell /system/bin/uitest start-daemon singleness \
--extension-name libharmony_connector_screen.z.so \
-m 0 -scale 2 -frameRate 60 -bitRate 8000000 -iFrameInterval 1000 -screenId 0
```
随后连接主机端 `127.0.0.1:LOCAL_PORT`,以 HTTP/2 gRPC 调用 `/ScrcpyService/onStart`,请求为 `Empty`,持续读取 server stream。无需创建 Driver、申请开发者证书、安装 HAP 或另发初始化 RPC。控制连接独立使用 UITest agent,避免视频读循环被输入 RPC 阻塞。
`RpcServer::InitServer` 在最新 Unix 原件中的入口是 `0x1a960`:
- `0x1aa40–0x1aa74` 默认初始化 `scrcpy_grpc_socket`,只在 mode 等于 1 时跳转。
- `0x1ada8–0x1adb8` 将 mode 1 的名字替换为 `screen_record_grpc_socket`。
- 字符串地址 `0xe001` 是 `scrcpy_grpc_socket`,`0xe014` 是 `screen_record_grpc_socket`,`0xeeca` 是 `unix-abstract:`。
- `0x1aaf0–0x1ab0c` 拼接 `unix-abstract:` 后调用 gRPC `AddListeningPort`。没有在这个路径构建 IP:port。
官方 Python 的 `-p 5001 -m 1` 是录文件场景。新版 Unix 参数日志将 port 标记为 `[deprecated]`;实时启动不必配置设备 TCP 端口。
设备端抽象 Unix socket 不在 WLAN IP 上监听;主机端仍需确保本地转发仅被期望的本机客户端访问。HDC 本身若被另行配置成网络调试,则不能仅因使用 `localabstract` 就宣称整个 HDC 会话来自 USB。
### 视频与控制同时运行
最简单的官方组合是 **两个独立 `singleness` daemon**,各加载一个扩展。`ExecuteExtension` 只读取第一个 `--extension-name` 并 `dlopen` 一个文件,没有一次加载两个代理的参数语义。[官方扩展加载源码](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_executor.cpp#L353)
`server_main.cpp::StartDaemon` 在 `token == "singleness"` 时直接执行扩展,该分支不调用普通 peer 连接的 `InitAndConnectPeer`,也没有以 singleness 命名的全局互斥锁。这里的字符串不是“设备只能有一个 UITest 进程”的代码证据。[官方 daemon 源码](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/server/server_main.cpp#L471)
官方 Hypium 26 的 `ohos/proxy/ui_proxy.py::start_uitest` 启动 `uitest start-daemon singleness`,默认加载 `/data/local/tmp/agent.so` 并转发 `uitest_socket`;`RecordAgent` 另外启动带 `--extension-name libscreen_recorder.z.so` 的 daemon。其 UI 进程查找还特意排除命令中带 `--extension-name` 的进程,以区分录屏。`devicetest/core/variables.py::_start_record_step_screen` 在测试步骤期间启动 recorder,说明官方设计就是 UI 自动化和录制并行,而不是两个功能择一。
因此插件应采用:
```text
控制:uitest start-daemon singleness --extension-name <本插件的控制原件文件名>
HDC localabstract:uitest_socket → 主机控制端口
视频:uitest start-daemon singleness --extension-name <本插件的视频原件文件名> -m 0 ...
HDC localabstract:scrcpy_grpc_socket → 主机视频端口
```
产品自己的会话检查必须识别这两个进程同属一个会话;不能看到任何其他 `uitest` 进程就拒绝。固定 socket 被其他应用占用时仍属于真实冲突,应保持现有不抢占策略。上述是官方源码和分发包的并行设计证据,不额外声称已对所有商业设备系统实现完成兼容测试。
### 参数
下表来自最新 Unix 原件 `RpcServer::parse_params` (`0x19c70`) 的静态分支,不是面向第三方的长期 API 稳定性承诺。
| 参数 | 已确认规则 |
| --- | --- |
| `-m` | 1 为 MP4 录制;其他值归为 0,实时回传编码 buffer |
| `-scale` | 整数 1–5;小于等于 0 回 1,大于 5 限为 5。不是 JPEG 接口的 0.5 浮点比例 |
| `-frameRate` | 最新 Unix 6.6-20260629 接受 15、30、60;内部计算 `120 / value` 的虚拟屏刷新间隔。120 会回默认 60,不能沿用旧 recorder4 接受 120 的结论 |
| `-bitRate` | 至少 1048576 bit/s,否则恢复默认 31457280;按负载选择码率,不要把最大值当低延迟默认 |
| `-iFrameInterval` | 非负整数;非法值回 2000;需与编码器间隔单位契约一致 |
| `-screenId` | 非负显示 ID,通常主屏为 0 |
| `-repeatInterval` | 新参数,接受 8、16、33,其他值恢复 0;引用 `OH_MD_KEY_VIDEO_ENCODER_REPEAT_PREVIOUS_FRAME_AFTER` 和 `...MAX_COUNT`。它用于重复之前的编码帧,不代表多采到了新画面 |
原件也存在 appPid、windowsId 和其他内部参数。当前需求不需要绕开安全窗口,不能把这些参数用作获取受保护内容的手段。
## gRPC 消息
官方文件位于 `.../controllers/tools/recorder/proto/scrcpy_pb2.py` 和 `scrcpy_pb2_grpc.py`。26.0.0.500 的 `scrcpy_pb2.py` 与下载到的 6.1.0.210 版本内容完全相同。
```proto
syntax = "proto3";
service ScrcpyService {
rpc onStart(Empty) returns (stream ReplyMessage);
rpc onEnd(Empty) returns (ReplyEndMessage);
rpc onRequestIDRFrame(Empty) returns (ReplyEndMessage);
}
message Empty {}
message ReplyEndMessage { int32 result = 1; }
message ReplyMessage {
string data = 1;
int32 reply_type = 2;
map<string, ParamValue> payload = 3;
}
message ParamValue {
oneof values {
int64 val_int = 1;
double val_double = 2;
string val_string = 3;
bool val_bool = 4;
bytes val_bytes = 5;
float val_float = 6;
}
}
```
没有 protobuf package 前缀。三个路径分别为 `/ScrcpyService/onStart`、`/ScrcpyService/onEnd`、`/ScrcpyService/onRequestIDRFrame`。
官方 recorder4 的 `ReplyVideoData` 和静态全局键初始化能确认:`payload["data"].val_bytes` 才是编码数据;`len`、`flags`、`pts` 使用 `val_int`。外层 string `data` 不是视频 bytes。最新 Unix 原件仍有同签名的 `ReplyVideoData` 和同一协议;客户端应验证字段类型、长度和包大小,不根据日志字符串找图像。
`reply_type` 在 proto 中是普通 int32,没有枚举定义。本研究不猜测完整枚举含义。`flags` 来自媒体输出 buffer 属性,应按编码配置、关键帧和 EOS 标志处理,而不是把所有 payload 都当普通视频帧。
旧 recorder4 的 `OnNeedOutputData` 在 mode 0 里取 `OH_AVMemory_GetAddr + offset`、长度和 flags,调用 `ReplyVideoData`;mode 1 才调用 `OH_AVMuxer_WriteSample`。它发送的 pts 来自输出时的 `system_clock.now()`,静态除数和 SDK chrono 单位指向毫秒,而不是直接复制编码器 `attr.pts`。**不要未经归一化将它当微秒或主机单调时钟;首版实时显示可采用立即显示,不等待设备绝对时间。** 最新原件实际时间语义仍应通过客户端观察校准,不在这里声称已测过。
停止时,保持 stream reader 存活,用同一 channel 发 `onEnd(Empty)`,然后释放流、关闭本插件通道并删除本插件 fport。官方 Python 将 `ReplyEndMessage.result` 用作录制帧数;不应把它一概当布尔成功码。`onRequestIDRFrame` 可以用于解码器重新同步;不要每帧请求 IDR。异常时只清理本插件拥有的进程。
官方 `rpc_manager.py` 建立 insecure gRPC channel,未设置应用层 token;其 5 秒 `onStart` 超时适合官方录制封装,不能直接复制为持续投屏生命周期。其最大消息大小 option 拼写为 `grpc_max_receive_message_length`,使用其他客户端时应按对应 gRPC 库的正确配置项设置大小上限。
## 为什么不采用旧 TCP recorder
已下载的官方 PyPI 6.1.0.210 资源只有 `libscrcpy_server1.z.so` 至 `4.z.so`。其中 recorder4 的 `InitServer` 使用固定 `0.0.0.0` 和 `grpc::InsecureServerCredentials()`;完整参数解析没有 host、listen address 或 Unix 参数。它不是仅绑定 USB 的服务,HDC 转发本身不会把设备上已经存在的 TCP listener 限制到 USB。
未找到说明该无鉴权 gRPC 端口必然被系统网络隔离的官方保证。不能依赖这种假设作为默认生产方案,也不能修改签名原件或安全策略。26 包的 Unix 版本已经给出更合适的连接机制。[官方 PyPI 分发原件](https://files.pythonhosted.org/packages/a2/b5/55bdfe4d669558331499848dbc68269506abb9b4a771e3524a65f9845127/xdevice_devicetest-6.1.0.210-py3-none-any.whl)
## 真正的按下、移动、抬起
在 26 官方包的 `uitest_agent_v1.2.3.so` 中,`OHOS::uitest::HandleGesture` (`0x28c34`) 直接确认了以下内层请求:
```json
{"api":"touchDown","args":{"x":120,"y":360}}
{"api":"touchMove","args":{"x":125,"y":370}}
{"api":"touchUp","args":{"x":125,"y":370}}
```
沿用现有 UITest RPC 外层 envelope 和 `method: "Gestures"`,不要把这些请求发往视频 gRPC 服务。
证据链:
- `0x28ca0` 取 `api`,确认是字符串;`0x28cc4` 取 `args`,确认是对象。
- `0x28fd4–0x29028` 的内联字节比较包含 `touchDown` 与 `touchMove`;`0x28e88–0x28ec0` 检查 `touchUp`。
- 三者汇入 `0x28ec4`,读取 `x` (`0xdee2`) 和 `y` (`0xdd4a`);均要求整数类型,然后调用 `EasyJSON::GetInteger`。
- `0x29700–0x2977c` 将 down/move/up 分别映射为动作 1/2/3,经 `Modules::GetUiTestLowLevelFunctions()` 的触控函数注入。
- 成功路径调用结果 callback,结果对象保持 `SetNull()`,不是截图或坐标返回值。错误路径包括 `Illegal gesture api` 和 `Illegal touch point coordination`。
**没有发现 touchCancel 分支,也不能依赖断开连接自动抬起。** 同原件的 `GestureHandleSessionDeathBroadcast` 指向 `0x1d8f4`,该入口只有一条 `ret`。客户端应持有自己按下的触点状态,在 mouse up、窗口失焦、取消和停止时尽力发送最后坐标的 touchUp,再关闭控制连接。只合并尚未发送的 move,不丢 down/up 或跨越它们重排。使用显示内容区域的坐标映射,扣除黑边并处理旋转,避免将窗口坐标直接当设备坐标。
该证据针对 1.2.3 原件;不凭版本相近就声称所有旧 agent 的行为完全一致。研究输出 `.../res/prototype/native/gestures-key.disasm` 保留了相关汇编。
## JPEG 路径的性能上限来自哪里
OpenHarmony 官方 UITest `screen_copy.cpp` 的生产循环先调用 `DisplayManager::GetScreenshot` 取得全屏 PixelMap,再比较像素是否变化,再缩放并 JPEG 编码。缩放函数持有共享 frameLock;JPEG 质量设置为 75。缩小 scale 能减少缩放后编码和传输成本,但不省掉原始全屏截图读回。源码的 50 ms sleep 位于屏幕关闭处理,不是正常推流固定 20 fps 限制。[官方源码固定版本](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/screen_copy.cpp)
因此,仅改浏览器刷新周期、缩图或提高标称 fps 无法把逐帧截图机制变为 scrcpy 的编码视频链路。这里的开源实现解释了 UITest 公开截图机制,不声称商业 Hypium agent 每个内部细节都与该源码相同。
官方 recorder 则引用 `OH_VideoEncoder_CreateByMime`、H.264 MIME、`OH_VideoEncoder_GetSurface` 和虚拟显示 Surface,输出已经编码的媒体 buffer;这才是和 scrcpy 对应的性能方向。实际流畅度仍受设备编码器、虚拟屏刷新、USB/HDC、主机解码与呈现共同影响。
## 对照 scrcpy 的实现原则
Genymobile 官方文档和源码中,Surface 进入 MediaCodec,视频由独立连接传输,控制由独立线程处理;默认立即显示解码后的帧,额外视频缓冲是显式选项。帧率随画面变化,`max-fps` 是上限而不是静止画面也必须重复达到的最低值。[scrcpy 开发文档](https://github.com/Genymobile/scrcpy/blob/19c1261d2e2cbf2b5e6a71a8b64cc1dd3ede06ac/doc/develop.md)、[视频选项](https://github.com/Genymobile/scrcpy/blob/19c1261d2e2cbf2b5e6a71a8b64cc1dd3ede06ac/doc/video.md)
其 `frame_buffer.c` 用 pending/tmp 交换,允许新解码帧覆盖未消费帧;先取得新引用,失败时保留旧帧。这适合用于修复画面闪烁和积压:维持同一个显示 surface,在下一帧确实可呈现时替换,不能先清空旧画面。[scrcpy 帧缓冲源码](https://github.com/Genymobile/scrcpy/blob/19c1261d2e2cbf2b5e6a71a8b64cc1dd3ede06ac/app/src/frame_buffer.c)
可移植到插件的设计建议:
1. HDC + gRPC reader 独立运行,及时取出编码包,不在同一循环更新 UI 或等待触控。
2. H.264 按解码依赖顺序送解码器;不能像独立 JPEG 那样随意丢 P 帧。解码后只保留最新可显示帧;编码队列溢出时明确重同步并请求 IDR。
3. 持久 AppKit 视频视图,避免每帧重建窗口、WebView 图像节点、object URL 或黑底占位。
4. 配置变更/旋转后重建必要的格式描述,普通帧不重建渲染层。停止期间保留最后有效画面直到状态视图接管。
5. 分开统计收到编码帧、成功解码帧、实际显示帧、丢帧和队列长度。静止桌面的低更新数不等于卡顿;不能用接收包数冒充显示 fps。
这些是从官方机制推导的实现建议,不是本研究的真机性能结果。
## macOS 呈现接口
`AVSampleBufferDisplayLayer` 能呈现压缩或未压缩视频。H.264 buffer 应正确组成 `CMSampleBuffer`,维护 SPS/PPS 和格式描述,交由系统视频解码/呈现;若使用 VideoToolbox 解码,可设置实时属性并检查实际是否使用硬件解码。[Apple 图层文档](https://developer.apple.com/documentation/avfoundation/avsamplebufferdisplaylayer)、[VideoToolbox 解压属性](https://developer.apple.com/documentation/videotoolbox/decompression-properties)
`kCMSampleAttachmentKey_DisplayImmediately = true` 应设置在 **sample attachments array**,用于不按时间戳等待的实时显示。不是对样本调用普通 `CMSetAttachment` 就等价。新样本到达后立即呈现,有助于避免设备绝对时间与主机时间基不一致产生的积压。[Apple DisplayImmediately](https://developer.apple.com/documentation/coremedia/kcmsampleattachmentkey_displayimmediately)、[Apple enqueue](https://developer.apple.com/documentation/avfoundation/avsamplebufferdisplaylayer/enqueue(_:))
macOS 14 及以上可使用该层的 sampleBufferRenderer,保留项目要求的旧系统兼容路径。`requestMediaDataWhenReady` 必须与停止请求成对管理,窗口销毁后不能继续向已释放视图派发渲染工作。
## 证据边界
- 已完成:官方 26 包获取、静态解包、资源摘要、启动/选型脚本、proto 对比、Unix 绑定与触控参数的汇编核查。
- 未完成且未声称完成:最新 Unix 原件在用户手机的加载兼容性、连续视频解码结果、流畅度/耗电/延迟测量、旧 1.2.2 agent 的触控全量契约复核。
- 不需要为采用本路径继续下载完整 IDE、创建 HAP 或申请开发者签名;用户的实际前置条件是可用 HDC 与设备调试授权。原件与系统 ABI 的兼容性仍需清楚报告,不能失败后偷偷切回慢截图却继续显示“实时视频”。
## 0.5.1 启动流程修正
用户报告的 `Connection refused` 被 0.5.0 视频 TCP 连接处的通用 `std::io::Error → Fault` 包装成 Mac 文件错误。该代码路径可直接定位,但没有真机复现证据可以把底层拒绝进一步归因为某个代理 ABI。
实现补齐官方 `RecordAgent` 已做的进程/Unix socket 就绪判断,按上表最新至最旧顺序选择能够启动的官方 Unix 原件。视频连接失败时移除并重建自身记录的转发;不会借用未知转发或终止外部进程。HDC 转发包含本机监听与对端激活阶段,发出 `fport` 命令本身不能代替端到端连接。[HDC 官方转发源码](https://github.com/openharmony/developtools_hdc/blob/master/src/common/forward.cpp)
产品错误信息区分设备组件启动、HDC 转发及视频连接;首帧显示前保持“正在建立视频连接”。按用户要求,本轮未跑测试、真机实验或远程安装验证。
-121
View File
@@ -1,121 +0,0 @@
# Trove 底座测试指南
本套件是可安装、可运行的验收材料。以当前 Manifest v1、Wire Protocol v1 和 `@trove/plugin-sdk@0.1.0` 为基础;插件 ID 均使用 `dev.trove.qa.*`,不占用正式插件命名空间。
## 快速验收
1. 按[开发文档](development.md)执行 `pnpm install --frozen-lockfile`、`pnpm run pack`。
2. 在底座添加本地源 `dist/index.json`,先安装「01 · Host 能力检查」「02 · 数据隔离对照」「03 · 原生后端检查」。逐一打开,点击「运行常规检查」。
3. 安装「04 · 公共服务提供方」,再安装并打开「05 · 跨插件调用」。提供方没有 UI;调用时才启动。
4. 安装「06 · 可选依赖缺失」,检查缺失服务报错的同时 Host 仍然可调用。
5. 在 Host 检查页按需执行文件选择、确认对话框、通知、剪贴板、快捷键和窗口操作。结果中「已执行 · 请观察」不代表系统效果已经通过人工验收。
6. 使用「保存 JSON 报告」将报告写入本插件的数据目录;结果会显示路径。也可展开「原始报告」查看全部错误码和返回值。
`pnpm run pack` 会重新构建并覆盖本地生成文件。它不安装插件、不启动桌面应用、不修改官方软件源。源码修改后生成同 ID/版本的新内容,可能被底座的版本不可变校验拒绝;重复开发时先卸载该测试插件,或主动提高版本。
## 插件矩阵
| 工程 | ID 后缀 / 版本 | 覆盖与预期 |
| --- | --- | --- |
| `test-host` | `host` / 1.0.0 | 无后端 UI;SDK 上下文、Unicode/JSON、32 路并发、服务发现、存储/文件 CRUD、错误码、桌面手动操作 |
| `test-isolation` | `isolation` / 1.0.0 | 与 host 使用同一个持久化键;读到各自 ID,关闭并重新打开后保留 |
| `test-self` | `self` / 1.0.0 | UI 打开时启动后端;`@self`、环境与参数、24 路乱序请求、200 KiB 以上消息、取消/超时、Host 回调、事件 |
| `test-provider` | `provider` / 1.0.0 | Backend-only;服务 `dev.trove.qa.provider.probe@1.0.0`;按需启动、20 秒空闲退出 |
| `test-consumer` | `consumer` / 1.0.0 | 必需依赖 provider 1.x;公开服务调用、事件、输入/输出契约 |
| `test-optional` | `optional` / 1.0.0 | 可选服务不存在;UI 与 Host 正常,缺失服务调用返回 `service.not_found` |
| `test-autostart` | `autostart` / 1.0.0 | Backend-only;重启底座后应自动运行,公开服务为 `dev.trove.qa.autostart.probe` |
| `test-upgrade-v1` | `upgrade` / 1.0.0 | 安装基线,在面板手动写入升级标记 |
| `test-upgrade-v2` | `upgrade` / 1.1.0 | 同 ID 更新;读取 1.0.0 写入的标记,支持回滚验收 |
| `test-required` | `required` / 1.0.0 | 故障源:必需服务不存在;当前底座调用时返回 `service.not_found` |
| `test-incompatible` | `incompatible` / 1.0.0 | 故障源:要求 provider 2.x;安装 provider 1.x 后调用返回 `service.version_mismatch` |
| `test-crash` | `crash` / 1.0.0 | 故障源:按钮让自己的后端 exit 42;观察重启、连续崩溃熔断与手动重试 |
| `test-start-timeout` | `start-timeout` / 1.0.0 | 故障源:收到 welcome 但不发送 ready;返回 `provider.start_timeout` |
| `test-bad-handshake` | `bad-handshake` / 1.0.0 | 故障源:错误会话令牌;Host 拒绝连接,进程启动失败 |
| `test-stubborn` | `stubborn` / 1.0.0 | 故障源:忽略 shutdown;超过 500 ms 宽限期后应被底座终止 |
每个工程都有自己的 `curio.json`。UI 源文件来自 `shared/ui/`,构建到 `dist/ui/`;Rust 源码在 `shared/backend/`,构建产物复制到各包的 `backend/probe`。生产包包含 SDK、静态资源和所需原生可执行文件,不依赖工作区相邻目录。
## 生命周期、状态与数据
- **按需启动**:先安装 provider,不进行任何服务调用;在底座看其未启动。打开 consumer 运行检查后应启动;停止调用约 20 秒后退出,再调用得到新 PID。查询 `host.services.getStatus` 不应启动提供方。
- **随底座启动**:安装 autostart,完全退出并重启 Trove。在进行 `info` 调用前,用 Host 检查页的「检查随底座启动的服务」确认 `Available`,或在管理界面确认进程已运行。单纯调用成功不能证明 app-start 生效。
- **UI 生命周期**:重复打开同一插件应聚焦已有窗口;关闭后无失效窗口。重开后读取标记仍存在;退出底座应清理所有测试后端、连接和快捷键。
- **禁用与卸载**:禁用 provider 后 consumer 调用应报告 `provider.disabled`;启用后恢复。卸载后调用应报告 `service.not_found`。
- **崩溃隔离**:安装 crash,先读取 PID,再触发崩溃,读取新 PID。短时间连续崩溃三次后应得到 `provider.crash_loop`。同时操作 Host 与 self 插件,确认未被连带中断;在底座管理页手动重试恢复。
- **停止无响应进程**:打开 stubborn,记录 PID,在底座停止或禁用;约 500 ms 宽限期后该 PID 应消失。
- **存储隔离**:分别运行 host 与 isolation;两者的 `qa.namespace-owner` 值必须是自己的 ID。后端回调 Host storage 的数据归属后端提供方,不能落到 consumer 的命名空间。
常规检查只清理自己创建的临时文件/键。`qa.namespace-owner` 与固定的 `qa.backend-roundtrip` 保留供人工检查,报告和升级标记也会保留。存储大小限制测试使用超过 1 MiB 的值;不会填满整个存储配额。剪贴板按钮会替换系统剪贴板;快捷键使用 Cmd+Shift+Option+F9,等待最多 15 秒,并在结束时注销。系统通知是否可见需人工确认。
## 升级与回滚
常规软件源同时包含 1.0.0 与 1.1.0;默认安装可能直接选到最新版。要测试完整路径,先手动安装基线包:
```sh
"$TROVE_BIN" curio install dist/packages/dev.trove.qa.upgrade-1.0.0.curio --no-input
```
打开插件,点击「写入升级基线标记」,记录 token,再安装更新:
```sh
"$TROVE_BIN" curio install dist/packages/dev.trove.qa.upgrade-1.1.0.curio --no-input
```
运行中更新应正确暂存或切换,不混用两个版本的 UI 资源。关闭并重新打开后应显示 1.1.0,标记仍由 1.0.0 写入、token 不变。使用底座的版本切换/回滚操作回到 1.0.0,验证数据继续保留。卸载时分别验证保留数据和删除数据,再重装读取。
## 故意无效的安装材料
`dist/invalid/cases.json` 列出每个 `.curio` 和应返回的错误码。覆盖 22 种情况:
- JSON 损坏、未知字段、schemaVersion 不支持、保留 ID、非法版本、引擎不兼容。
- UI 缺失、UI/Backend 均未声明、Manifest 路径越界、覆盖保留环境变量。
- 没有后端却提供服务、服务命名空间错误、后端/契约文件缺失、契约版本不一致、非法 JSON Schema。
- ZIP 路径越界、绝对路径、反斜杠路径、大小写碰撞、符号链接、损坏压缩包。
`dist/invalid/source-cases.json` 列出 4 种合法索引携带异常下载元数据的情况:SHA-256 错误、大小错误、目标架构不匹配、索引与包身份不一致。它们不加入常规软件源。
无效安装应有明确错误,已安装列表和当前版本不变化,staging 清理完成。不要将所有 `.curio` 不加区分地批量安装;`dist/packages/` 才是结构合法的测试包。
## 自动验证
```sh
export TROVE_BIN=/absolute/path/to/app/target/debug/trove
export TROVE_APP_DIR=/absolute/path/to/app
# 可选:复用现有 Cargo 缓存;默认使用 dist/host-target。
export TROVE_QA_TARGET_DIR=/absolute/path/to/app/target
pnpm run verify
```
验证流程:
1. 编译 `tools/host-check.rs`,链接指定 app 仓库的真实 `PackageManager`、`ServiceBus`、`BackendSupervisor` 和 Unix Gateway,禁用桌面特性。
2. 用 Trove CLI 的 `protocol test` 检查后端的握手、分片接收、并发请求 ID、结构化错误和优雅退出。
3. 在 `/tmp/trove-qa-*` 创建隔离数据目录,安装真实包;验证无效材料、软件源本地/HTTP 下载、持久化、升级/回滚和卸载。
4. 通过测试桥将**真实生产 SDK** 连接至真实 Service Bus。直接运行 `shared/ui/checks.js` 中的检查;后端走真实 Unix socket 和原生子进程,不使用 mock 服务。
5. 默认策略下检查实际行为,再**单独开启严格契约模式**验证参数和返回值;随后恢复默认模式。测试取消清理、服务禁用、启动超时、认证失败、空闲退出、强制停止、崩溃隔离/熔断。
6. 写入 `dist/verification.json`,失败时非零退出;正常结束时停止测试 HTTP 服务、后端进程,删除隔离目录。不会触碰用户的 Trove 安装注册表或现有插件数据。
首次编译可能需要较长时间。自动结果不等于桌面 WebView/系统交互的完整验收;真实 WebView 注入、窗口视觉、系统权限、快捷键触发、通知展示、app-start 的应用启动调度仍应按上文手动检查。
## 远程软件源验收
发布完成后,必须使用底座实际访问的 Git 仓库单独验收:
```sh
TROVE_APP_DIR=/absolute/path/to/app pnpm run verify \
--remote-source git@git.render.ink:trove/curios.git \
--source-kind git \
--source-id trove-official
```
此模式不读取工作区的 `.curio`:在新的临时目录中通过真实 PackageManager 获取远程仓库、读取索引、安装该提交中的每个版本,检查 UI 入口,启动安装后的原生后端,再执行 consumer → provider 调用。结果写入 `dist/remote-verification.json`;失败时非零退出,不能视为安装通过。独立 HTTP 源可使用 `--source-kind http` 并传入索引 URL。
当前运行探针适用于生产源中的首批验收插件;后续新增产品插件时,应为它们补充对应的运行验收。故障源包含故意无法启动的后端,不能以全部启动成功作为标准。仓库结构、认证和历史发布管理见 [版本与发布](versioning.md)。
## 当前能力边界
- 当前底座默认未调用 `strict_contracts(true)`。公共服务的无效返回值可能直接透传,因此 consumer 面板的「公共服务返回值契约」会报告失败;这是能力探针,不能将其误判成自动通过。验证脚本把默认策略列为观察项,严格模式断言单独标注,不修改底座源码。
- 必需依赖与可选依赖目前都在服务调用阶段暴露缺失;不要预设安装器或打开窗口时一定拦截。两个插件用于观察和比较这一行为。
- 以实际已注册服务为准。规范草案中的 `host.process`、`host.screen`、`host.keychain`、辅助功能、文件 watch 等未被本套件宣称为已实现;没有用模拟服务填补这些能力。
- macOS 原生构建按当前架构出包;另一架构需在对应机器重新构建。自动测试覆盖目标架构拒绝,不验证跨架构二进制执行、签名、公证、真实公网 TLS、断电原子性或系统资源耗尽。
- `dist/` 中本地索引包含绝对 `file://` 地址;移动仓库后重新打包生成索引。HTTP 源由 `pnpm run serve` 动态改写为本机地址,仅监听 127.0.0.1。
+5 -69
View File
@@ -1,73 +1,9 @@
# 插件版本与发布记录
# 版本与 Release
仓库是 monorepo:**每个 Curio 独立版本、独立兼容范围、独立更新**。不要求一次提交中的所有插件一起升级。
每个正式插件独立维护 `curio.json` 的版本,并以 `v<version>` tag 发布对应源码快照。原生项目中的版本必须与 Manifest 一致。
| 信息 | 唯一来源 | 作用 |
| --- | --- | --- |
| 插件版本 | `curios/<name>/curio.json` 的 `version` | 底座安装、更新、切换和回滚的版本 |
| 公共服务版本 | `services.provides[].version`,与 Contract 的 `version` 一致 | 调用方依赖约束;不兼容接口升级主版本 |
| 底座兼容范围 | `engine.trove` | 声明可运行的 Trove 版本;不是插件版本 |
| 已发布版本索引 | `source/index.json` | 保存全部历史版本、平台、URL、大小和 SHA-256 |
| 源码快照 | Git commit;可打 `<curio-id>/v<version>` tag | 从一个已发布插件版本定位对应源代码 |
| 仓库工具版本 | 根 `package.json.version` | 构建/验证工具版本,不用于判断插件是否有更新 |
安装包存放在该插件仓库的 Gitea Release 附件中。附件按插件 ID、版本、平台命名,不使用 `latest` 之类可变下载地址。`trove-release.json` 记录本次发布的所有产物;源索引由收录 workflow 生成。
例如只修复 `dev.trove.qa.host` 的 UI,应将该插件从 `1.0.0` 升到 `1.0.1`,其他插件保持原版本。服务接口没有变化时,服务版本仍可保持 `1.0.0`。
相同插件 ID、版本、平台的已发布内容不能覆盖,修复应发布新版本。一个插件在源索引中对应一个条目,版本位于其 `versions` 数组内。
## 不可变发布
一个产物由 **插件 ID + 插件版本 + 平台架构** 唯一标识:
```text
packages/dev.trove.qa.host/1.0.0/any.curio
packages/dev.trove.qa.self/1.0.0/aarch64-apple-darwin.curio
packages/dev.trove.qa.self/1.0.0/x86_64-apple-darwin.curio
```
同一版本可以补齐另一种架构,但不能替换已经发布的同架构内容。发布后发现问题要发新版本,不能重传覆盖旧文件。回滚切换到已有历史版本,不删除或重写坏版本对应的源码记录。服务有破坏性变更时还需要修改服务主版本和使用方依赖。
当前 `test-upgrade-v1` 与 `test-upgrade-v2` 是验收专用的双版本样本,共用 `dev.trove.qa.upgrade` ID。普通产品插件应只维护一份当前源码;历史通过 Git 和发布目录保存,不为每次升级复制一个源码文件夹。
## 准备发布
先修改需要发布的 Manifest,执行 `pnpm run pack` 和验证。`dist/index.json` 是可反复生成的本地开发索引;不能把它当作历史发布记录。
```sh
pnpm run release:prepare
pnpm run test:release
```
命令会将本次打包的常规插件合并到生产源 `source/index.json`(源 ID 为 `trove-official`),并把不可变安装包复制到 `source/packages/`。索引中的包地址相对于 `source/`,索引与包在同一 Git 提交中发布。`dist/release/` 保留本次发布材料副本。故障注入插件和无效包不会进入生产源。
校验规则:
- 从当前包计算大小和 SHA-256,与打包元数据比较,防止索引指向错误内容。
- 保留旧版本以及其他架构;不会因为本次只发布部分内容而重建/清空历史。
- 同 ID、版本、架构的 SHA-256、大小、URL 必须与历史完全一致;不一致会要求升级插件版本。
- 同一已发布版本的 `engine.trove` 不得悄悄变化。
- 重复准备相同材料不会改变首次发布日期。
- 先完整校验本次所有条目,再复制产物和更新本地索引。
`--catalog <目录>` 可在隔离目录试运行。正式发布应将 `source/` 的历史索引与安装包提交回仓库。该脚本只准备本地材料,不表示推送或远程安装已经成功。
需要独立 HTTP 分发时,可显式传入 `--base-url <HTTPS 分发目录>` 生成绝对下载地址,并使用 `dist/release/` 上传;已有版本的 URL 仍不能覆盖。
## 发布顺序
1. 更新需要发布的插件版本,构建并完成验证。
2. 执行 `pnpm run release:prepare`;审阅本次新增的版本、架构、大小和 SHA-256。
3. 将源码、`source/index.json` 和 `source/packages/` 在同一提交中推送;需要时为各插件打独立 tag。
4. 从远程 Git 仓库刷新、安装并运行验证;不能用本地文件安装冒充远程源验收。
发布工作串行进行;两种架构先各自构建,再将产物合并至同一版本。若更新 `shared/ui/` 或 `shared/backend/`,所有受影响且需要重新发布的插件都要增加插件版本,未受影响的插件不必修改。
## 添加仓库源
在 Trove「软件源 → 添加软件源」中填写:
- 显示名称:Trove 官方软件源
- 唯一 ID:`trove-official`
- 源类型:Git 仓库
- 仓库地址:`git@git.render.ink:trove/curios.git`
- 优先级:`0`
也支持 HTTPS 克隆地址 `https://git.render.ink/trove/curios.git`。私有仓库沿用本机 Git 的 SSH 密钥或 HTTPS credential helper;不要把访问令牌写进 Manifest、索引或 URL。添加后刷新,底座自动读取默认分支的 `source/index.json`,并固定该提交读取仓库内的安装包。用户不需要填写原始 JSON 地址或配置额外的静态下载站点。
详细流程见 [官方软件源的维护与发布](development.md)。