fix(harmony): guide official HDC setup and recheck tool availability
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"id": "dev.trove.harmony-connector",
|
||||
"name": "Harmony Connector",
|
||||
"description": "连接鸿蒙设备,收发完整文件夹、管理应用,查看手机画面并通过键鼠操作。",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"engine": { "trove": ">=0.1.0 <1.0.0" },
|
||||
"icon": "assets/icon.svg",
|
||||
"ui": {
|
||||
@@ -15,7 +15,7 @@
|
||||
"lifecycle": "on-demand", "transport": "unix", "startWithUi": true,
|
||||
"startupTimeoutMs": 5000, "shutdownGraceMs": 5000, "idleTimeoutMs": 0
|
||||
},
|
||||
"services": { "requires": [{ "id": "host.dialog", "version": "^1.0.0" }] },
|
||||
"services": { "requires": [{ "id": "host.dialog", "version": "^1.0.0" }, { "id": "host.runtime", "version": "^1.0.0" }] },
|
||||
"build": { "command": ["node", "build.mjs"] },
|
||||
"package": { "files": ["dist/ui", "backend", "assets", "legal"] }
|
||||
}
|
||||
|
||||
+1
-1
@@ -204,7 +204,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "harmony-connector"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "harmony-connector"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
|
||||
@@ -102,6 +102,33 @@ fn executable(path: &Path) -> bool {
|
||||
.metadata()
|
||||
.is_ok_and(|m| m.permissions().mode() & 0o111 != 0)
|
||||
}
|
||||
fn selected_tool(path: &Path) -> Option<PathBuf> {
|
||||
std::iter::once(path.to_path_buf())
|
||||
.chain(
|
||||
[
|
||||
"hdc",
|
||||
"toolchains/hdc",
|
||||
"openharmony/toolchains/hdc",
|
||||
"sdk/default/openharmony/toolchains/hdc",
|
||||
"command-line-tools/sdk/default/openharmony/toolchains/hdc",
|
||||
"Contents/sdk/default/openharmony/toolchains/hdc",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|suffix| path.join(suffix)),
|
||||
)
|
||||
.find(|p| executable(p))
|
||||
.and_then(|p| p.canonicalize().ok())
|
||||
}
|
||||
fn tool_version(path: &Path, cancel: &AtomicBool) -> Result<String> {
|
||||
let version = command::run(path, &["-v".into()], Duration::from_secs(5), cancel, 8192)?;
|
||||
if !version.contains("Ver:") && !version.to_lowercase().contains("hdc") {
|
||||
return Err(Fault::new(
|
||||
"hdc.invalid_tool",
|
||||
"所选工具没有返回 HDC 版本信息。",
|
||||
));
|
||||
}
|
||||
Ok(version.trim().to_owned())
|
||||
}
|
||||
fn discover() -> Option<PathBuf> {
|
||||
let mut candidates = Vec::new();
|
||||
if let Some(path) = env::var_os("PATH") {
|
||||
@@ -290,42 +317,37 @@ impl Manager {
|
||||
"screenFrame" => self.screen.frame(parse(value)?),
|
||||
"screenInput" => self.screen.input(parse(value)?),
|
||||
"tool" => {
|
||||
let path = self.path.lock().unwrap().clone();
|
||||
let path = {
|
||||
let mut path = self.path.lock().unwrap();
|
||||
if path.is_none() {
|
||||
*path = discover();
|
||||
}
|
||||
path.clone()
|
||||
};
|
||||
let Some(path) = path else {
|
||||
return Ok(json!({"available":false}));
|
||||
};
|
||||
let version = command::run(
|
||||
&path,
|
||||
&["-v".into()],
|
||||
Duration::from_secs(5),
|
||||
&self.cancel,
|
||||
8192,
|
||||
)?;
|
||||
Ok(json!({"available":true,"path":path,"version":version.trim()}))
|
||||
if !executable(&path) {
|
||||
return Ok(
|
||||
json!({"available":false,"path":path,"reason":"此前选择的 HDC 已移动、删除或不可执行,请重新选择。"}),
|
||||
);
|
||||
}
|
||||
match tool_version(&path, &self.cancel) {
|
||||
Ok(version) => Ok(json!({"available":true,"path":path,"version":version})),
|
||||
Err(error) => Ok(
|
||||
json!({"available":false,"path":path,"reason":format!("HDC 无法运行:{} 请检查下载版本与同目录依赖文件,或重新选择工具。", error.message)}),
|
||||
),
|
||||
}
|
||||
}
|
||||
"configure" => {
|
||||
let selected: LocalPath = parse(value)?;
|
||||
let path = PathBuf::from(selected.path);
|
||||
if !executable(&path) {
|
||||
return Err(Fault::new(
|
||||
let path = selected_tool(Path::new(&selected.path)).ok_or_else(|| {
|
||||
Fault::new(
|
||||
"hdc.invalid_tool",
|
||||
"请选择可执行的官方 hdc 文件。",
|
||||
));
|
||||
}
|
||||
let path = path.canonicalize()?;
|
||||
let version = command::run(
|
||||
&path,
|
||||
&["-v".into()],
|
||||
Duration::from_secs(5),
|
||||
&self.cancel,
|
||||
8192,
|
||||
)?;
|
||||
if !version.contains("Ver:") && !version.to_lowercase().contains("hdc") {
|
||||
return Err(Fault::new(
|
||||
"hdc.invalid_tool",
|
||||
"所选文件没有返回 HDC 版本信息。",
|
||||
));
|
||||
}
|
||||
"未找到可执行的 HDC。请先完整解压官方 Command Line Tools,选择其目录或 toolchains 中的 hdc 文件;不能选择 ZIP 压缩包。",
|
||||
)
|
||||
})?;
|
||||
let version = tool_version(&path, &self.cancel)?;
|
||||
if let Some(settings) = &self.settings {
|
||||
fs::create_dir_all(settings.parent().unwrap())?;
|
||||
let temporary = settings.with_extension("tmp");
|
||||
|
||||
@@ -4,6 +4,76 @@ use std::{io::Write, time::Instant};
|
||||
#[path = "screen_tests.rs"]
|
||||
mod screen_tests;
|
||||
|
||||
fn version_tool(path: &Path) {
|
||||
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
fs::write(path, "#!/bin/sh\nprintf 'Ver: fixture\\n'\n").unwrap();
|
||||
fs::set_permissions(path, fs::Permissions::from_mode(0o700)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configures_extracted_official_toolkit_and_preserves_selection_on_error() {
|
||||
let (directory, mut manager) = fixture(false);
|
||||
let settings = directory.path().join("data/harmony-tools.json");
|
||||
Arc::get_mut(&mut manager).unwrap().settings = Some(settings.clone());
|
||||
let root = directory.path().join("download");
|
||||
let hdc = root.join("command-line-tools/sdk/default/openharmony/toolchains/hdc");
|
||||
version_tool(&hdc);
|
||||
let result = manager.call("configure", json!({"path": root})).unwrap();
|
||||
assert_eq!(result["available"], true);
|
||||
assert_eq!(result["path"], json!(hdc.canonicalize().unwrap()));
|
||||
let saved: Value = serde_json::from_slice(&fs::read(&settings).unwrap()).unwrap();
|
||||
assert_eq!(saved["hdcPath"], result["path"]);
|
||||
assert!(manager
|
||||
.call(
|
||||
"configure",
|
||||
json!({"path": directory.path().join("missing")})
|
||||
)
|
||||
.is_err());
|
||||
assert_eq!(
|
||||
manager.call("tool", json!({})).unwrap()["path"],
|
||||
result["path"]
|
||||
);
|
||||
fs::remove_file(&hdc).unwrap();
|
||||
assert_eq!(manager.call("tool", json!({})).unwrap()["available"], false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rechecks_hdc_installed_after_manager_start() {
|
||||
const CHILD: &str = "HARMONY_TOOL_DISCOVERY_TEST";
|
||||
if let Some(root) = env::var_os(CHILD) {
|
||||
let root = PathBuf::from(root);
|
||||
let manager = Manager::new();
|
||||
// Reproduce the running plugin's unconfigured state without depending
|
||||
// on whether the test machine also has a system-wide DevEco install.
|
||||
*manager.path.lock().unwrap() = None;
|
||||
let hdc = root.join("bin/hdc");
|
||||
version_tool(&hdc);
|
||||
let result = manager.call("tool", json!({})).unwrap();
|
||||
assert_eq!(result["available"], true, "{result}");
|
||||
assert_eq!(result["path"], json!(hdc.canonicalize().unwrap()));
|
||||
return;
|
||||
}
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let output = std::process::Command::new(env::current_exe().unwrap())
|
||||
.args([
|
||||
"--exact",
|
||||
"hdc::tests::rechecks_hdc_installed_after_manager_start",
|
||||
"--nocapture",
|
||||
])
|
||||
.env(CHILD, root.path())
|
||||
.env("HOME", root.path())
|
||||
.env("PATH", root.path().join("bin"))
|
||||
.env("TROVE_DATA_DIR", root.path().join("data"))
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"{}\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
fn fixture(debug: bool) -> (tempfile::TempDir, Arc<Manager>) {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let path = directory.path().join("hdc");
|
||||
|
||||
@@ -520,7 +520,12 @@ $("folder-form").addEventListener("submit", (event) => {
|
||||
await load();
|
||||
});
|
||||
});
|
||||
const openManagement = management({ native, host, notice });
|
||||
const openManagement = management({
|
||||
native,
|
||||
host,
|
||||
notice,
|
||||
openUrl: (url) => trove.call("host.runtime", "openUrl", { url }),
|
||||
});
|
||||
for (const view of ["files", "management"])
|
||||
$("tab-" + view).onclick = () => {
|
||||
state.view = view;
|
||||
|
||||
@@ -135,36 +135,91 @@
|
||||
<div class="management-card">
|
||||
<div class="management-heading">
|
||||
<h2>设备管理</h2>
|
||||
<button id="hdc-configure" class="secondary">
|
||||
选择 HDC 工具
|
||||
</button>
|
||||
<div class="management-actions">
|
||||
<button id="hdc-configure" class="secondary">
|
||||
选择 HDC 文件
|
||||
</button>
|
||||
<button id="hdc-refresh" class="secondary">刷新连接</button>
|
||||
</div>
|
||||
</div>
|
||||
<p id="hdc-tool-status" class="muted">正在检查官方连接工具…</p>
|
||||
<p class="muted">
|
||||
在手机开发者选项中启用 USB 调试并信任这台 Mac。请保留 HDC
|
||||
同目录中的依赖文件。文件传输无需启用调试。
|
||||
<p id="hdc-tool-status" class="muted" role="status">
|
||||
正在检查官方连接工具…
|
||||
</p>
|
||||
<div class="device-picker-row">
|
||||
<label for="hdc-target" class="sr-only">管理设备</label
|
||||
><select id="hdc-target"></select
|
||||
><button id="hdc-refresh" class="secondary">刷新连接</button>
|
||||
<section
|
||||
id="hdc-setup"
|
||||
class="tool-setup"
|
||||
aria-label="安装官方连接工具"
|
||||
hidden
|
||||
>
|
||||
<h3>准备好工具,让手机连接桌面</h3>
|
||||
<p>
|
||||
应用管理与投屏需要华为官方 HDC 连接工具。完成以下三步即可开始。
|
||||
</p>
|
||||
<ol class="tool-steps">
|
||||
<li>
|
||||
<strong>下载官方工具</strong>
|
||||
<p>
|
||||
前往华为开发者官网下载 Command Line Tools。Apple 芯片选择
|
||||
Mac (ARM64),Intel 芯片选择 Mac
|
||||
(x86);如页面提示,请登录华为账号。
|
||||
</p>
|
||||
<button id="hdc-download" class="primary">
|
||||
前往华为官网下载 ↗
|
||||
</button>
|
||||
<p id="hdc-download-status" class="muted" role="status"></p>
|
||||
</li>
|
||||
<li>
|
||||
<strong>解压,选择工具目录</strong>
|
||||
<p>
|
||||
将下载的 ZIP 完整解压并保留整个目录,然后选择解压后的
|
||||
Command Line Tools 文件夹。已有 DevEco
|
||||
Studio?也可用上方「选择 HDC 文件」选择其 SDK 中的 hdc。
|
||||
</p>
|
||||
<button id="hdc-directory" class="secondary">
|
||||
选择已解压的工具目录
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<strong>连接并授权手机</strong>
|
||||
<p>
|
||||
用 USB 连接手机,在手机开发者选项中启用 USB
|
||||
调试,解锁并允许这台 Mac
|
||||
的调试请求。配置工具后会自动检查连接,也可点击「刷新连接」。
|
||||
</p>
|
||||
</li>
|
||||
</ol>
|
||||
<p class="muted">
|
||||
只收发文件?可直接使用「文件传输」,无需安装 HDC。
|
||||
</p>
|
||||
</section>
|
||||
<div id="hdc-connection" hidden>
|
||||
<p class="muted">
|
||||
工具已就绪。在手机开发者选项中启用 USB 调试,解锁手机并允许这台
|
||||
Mac 的调试请求。
|
||||
</p>
|
||||
<div class="device-picker-row">
|
||||
<label for="hdc-target" class="sr-only">管理设备</label
|
||||
><select id="hdc-target"></select>
|
||||
</div>
|
||||
<p id="hdc-connection-status" role="status">等待管理连接</p>
|
||||
<div id="hdc-capabilities" class="capabilities"></div>
|
||||
<div class="management-actions">
|
||||
<button id="device-screenshot" class="secondary">
|
||||
保存截图</button
|
||||
><button id="device-logs" class="secondary">导出日志</button
|
||||
><button id="device-report" class="secondary">诊断报告</button>
|
||||
</div>
|
||||
<details>
|
||||
<summary>设备信息</summary>
|
||||
<pre id="device-info"></pre>
|
||||
</details>
|
||||
</div>
|
||||
<p id="hdc-connection-status" role="status">等待管理连接</p>
|
||||
<div id="hdc-capabilities" class="capabilities"></div>
|
||||
<div class="management-actions">
|
||||
<button id="device-screenshot" class="secondary">保存截图</button
|
||||
><button id="device-logs" class="secondary">导出日志</button
|
||||
><button id="device-report" class="secondary">诊断报告</button>
|
||||
</div>
|
||||
<details>
|
||||
<summary>设备信息</summary>
|
||||
<pre id="device-info"></pre>
|
||||
</details>
|
||||
</div>
|
||||
<section
|
||||
id="screen-panel"
|
||||
class="management-card screen-card"
|
||||
aria-label="投屏与控制"
|
||||
hidden
|
||||
>
|
||||
<div class="management-heading">
|
||||
<div>
|
||||
@@ -256,7 +311,7 @@
|
||||
画面连续采集,刷新速度以设备响应为准。切换工作区、隐藏窗口或断开连接会停止投屏。
|
||||
</p>
|
||||
</section>
|
||||
<div class="management-card">
|
||||
<div id="apps-panel" class="management-card" hidden>
|
||||
<div class="management-heading">
|
||||
<h2>应用</h2>
|
||||
<div class="management-actions">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { screen } from "./screen.js";
|
||||
import { bytes } from "./format.js";
|
||||
|
||||
export function management({ native, host, notice }) {
|
||||
export function management({ native, host, notice, openUrl }) {
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const call = (method, params = {}) => native(`hdc.${method}`, params);
|
||||
let screenPanel;
|
||||
@@ -31,7 +31,8 @@ export function management({ native, host, notice }) {
|
||||
const locked = state.working || state.busy || state.screenBusy;
|
||||
for (const button of $("management").querySelectorAll("button"))
|
||||
if (!button.closest("#screen-panel")) button.disabled = locked;
|
||||
$("hdc-target").disabled = locked;
|
||||
$("hdc-target").disabled =
|
||||
locked || !state.available || !state.devices.length;
|
||||
const connected = state.devices.some(
|
||||
(d) => d.id === state.target && d.state === "Connected",
|
||||
);
|
||||
@@ -91,9 +92,23 @@ export function management({ native, host, notice }) {
|
||||
}
|
||||
function showTool(tool) {
|
||||
state.available = !!tool.available;
|
||||
$("hdc-setup").hidden = state.available;
|
||||
for (const id of ["hdc-connection", "screen-panel", "apps-panel"])
|
||||
$(id).hidden = !state.available;
|
||||
$("hdc-tool-status").textContent = tool.available
|
||||
? `${tool.version} · ${tool.path}`
|
||||
: "未发现 HDC。选择 DevEco Studio 或官方 Command Line Tools 中的 hdc 文件。";
|
||||
: tool.reason || "未发现 HDC 连接工具,请先完成以下准备。";
|
||||
if (!state.available) {
|
||||
clearDevice();
|
||||
state.devices = [];
|
||||
state.target = "";
|
||||
$("hdc-target").replaceChildren();
|
||||
$("hdc-connection-status").textContent = "配置官方工具后检查手机连接。";
|
||||
}
|
||||
}
|
||||
async function refreshTool() {
|
||||
showTool(await call("tool"));
|
||||
if (state.available) await refresh();
|
||||
}
|
||||
function renderApps() {
|
||||
const filter = $("apps-search").value.trim().toLowerCase();
|
||||
@@ -283,6 +298,24 @@ export function management({ native, host, notice }) {
|
||||
}
|
||||
const confirm = async (title, message) =>
|
||||
(await host("confirm", { title, message })).confirmed;
|
||||
$("hdc-download").onclick = () =>
|
||||
action(async () => {
|
||||
await openUrl(
|
||||
"https://developer.huawei.com/consumer/cn/download/command-line-tools-for-hmos",
|
||||
);
|
||||
$("hdc-download-status").textContent =
|
||||
"已在浏览器打开华为官方下载页。下载并解压后,返回此处选择工具目录。";
|
||||
});
|
||||
$("hdc-directory").onclick = () =>
|
||||
action(async () => {
|
||||
const { path } = await host("openDirectory", {
|
||||
title: "选择已解压的 Command Line Tools 目录",
|
||||
});
|
||||
if (path) {
|
||||
showTool(await call("configure", { path }));
|
||||
await refresh();
|
||||
}
|
||||
});
|
||||
$("hdc-configure").onclick = () =>
|
||||
action(async () => {
|
||||
const { path } = await host("openFile", {
|
||||
@@ -293,7 +326,7 @@ export function management({ native, host, notice }) {
|
||||
await refresh();
|
||||
}
|
||||
});
|
||||
$("hdc-refresh").onclick = () => action(() => refresh());
|
||||
$("hdc-refresh").onclick = () => action(refreshTool);
|
||||
$("hdc-target").onchange = () =>
|
||||
action(async () => {
|
||||
state.target = $("hdc-target").value;
|
||||
@@ -412,10 +445,8 @@ export function management({ native, host, notice }) {
|
||||
schedule();
|
||||
return;
|
||||
}
|
||||
const tool = await call("tool");
|
||||
showTool(tool);
|
||||
await refreshTool();
|
||||
state.ready = true;
|
||||
if (tool.available) await refresh();
|
||||
}).finally(scheduleMonitor);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -875,6 +875,36 @@ dialog li {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.tool-setup {
|
||||
border-top: 1px solid var(--line);
|
||||
margin-top: 18px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
.tool-setup h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.tool-steps {
|
||||
padding-left: 23px;
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
margin: 24px 0;
|
||||
}
|
||||
.tool-steps li {
|
||||
padding-left: 6px;
|
||||
}
|
||||
.tool-steps li::marker {
|
||||
color: var(--blue);
|
||||
font-weight: 600;
|
||||
}
|
||||
.tool-steps p {
|
||||
color: var(--muted);
|
||||
margin: 7px 0 12px;
|
||||
}
|
||||
#hdc-download-status:empty {
|
||||
display: none;
|
||||
}
|
||||
.application-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(180px, 1fr) minmax(200px, 1fr);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Harmony Connector
|
||||
|
||||
插件 ID:`dev.trove.harmony-connector`。当前版本:`0.3.0`。
|
||||
插件 ID:`dev.trove.harmony-connector`。当前版本:`0.3.1`。
|
||||
|
||||
macOS 上的鸿蒙设备连接工具。通过 USB 收发文件与完整文件夹;使用官方 HDC 管理应用、保存截图、导出日志和调试应用数据。功能按连接工具、设备实际能力和授权开放,不按 HarmonyOS 大版本或机型名称限制。
|
||||
|
||||
@@ -33,13 +33,19 @@ macOS 上的鸿蒙设备连接工具。通过 USB 收发文件与完整文件夹
|
||||
|
||||
## 设备与应用
|
||||
|
||||
在「设备与应用」选择官方 SDK 的 `hdc` 可执行文件。插件会尝试发现 PATH 或 `/Applications`、`~/Applications` 中 DevEco Studio 的内置工具。典型路径:
|
||||
首次进入「设备与应用」,如果没有可用 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 或 `/Applications`、`~/Applications` 中 DevEco Studio 的内置工具。典型路径:
|
||||
|
||||
```text
|
||||
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc
|
||||
```
|
||||
|
||||
也可使用华为官方 Command Line Tools。保留 HDC 同目录的依赖库,插件不重打包官方 SDK。选择路径保存在插件自己的数据目录中。手机需启用开发者模式与 USB 调试,并信任这台 Mac。管理页明确选择 HDC 设备,不根据 MTP USB 地址猜测二者对应关系。
|
||||
请保留完整的工具目录及同目录依赖库,插件不重打包官方 SDK。选择路径保存在插件自己的数据目录中;工具被移动、删除或无法运行时重新显示安装引导和原因。工具准备完成前隐藏空设备列表及不可用的管理面板,文件传输仍可直接使用。管理页明确选择 HDC 设备,不根据 MTP USB 地址猜测二者对应关系。
|
||||
|
||||
| 操作 | 行为与边界 |
|
||||
| --- | --- |
|
||||
@@ -101,7 +107,9 @@ curios/harmony-connector/backend/harmony-connector --check-screen /absolute/path
|
||||
|
||||
该命令走 UI 相同的后端接口,采集并解码 3 帧,输出耗时,最后关闭会话;不启用控制、不发送输入。首次 HDC 连接显示 Unauthorized 时,在手机上允许 USB 调试。`hdc -v` 能运行但管理失败时,先用 `hdc list targets -v` 检查授权状态。
|
||||
|
||||
本次自动化验证通过 39 项原生测试、27 项 UI/格式/坐标测试、3 项 USB C 桥接检查及 3 项底座协议检查。浏览器另外验证了持续显示模拟画面、启用控制、中文输入、放大和停止清空;模拟数据不会包含在发布包内。官方 Command Line Tools 26.0.0.821(Mac ARM)的 HDC 返回 `Ver: 3.2.0f`,已识别真机,但当次状态为 Unauthorized,故投屏和输入的真机效果尚未验收,不能将上述替身测试当作真机通过。
|
||||
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,后续投屏验收因无已授权设备而停止。
|
||||
|
||||
## 构建与发布
|
||||
|
||||
|
||||
+16
-1
@@ -2,7 +2,7 @@
|
||||
"schemaVersion": 1,
|
||||
"id": "trove-official",
|
||||
"name": "Trove 官方软件源",
|
||||
"generatedAt": "2026-09-10T06:14:13.482Z",
|
||||
"generatedAt": "2026-09-10T06:33:16.439Z",
|
||||
"curios": [
|
||||
{
|
||||
"id": "dev.trove.qa.host",
|
||||
@@ -200,6 +200,21 @@
|
||||
"name": "Harmony Connector",
|
||||
"description": "连接鸿蒙设备,收发完整文件夹、管理应用,查看手机画面并通过键鼠操作。",
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.3.1",
|
||||
"engine": {
|
||||
"trove": ">=0.1.0 <1.0.0"
|
||||
},
|
||||
"releasedAt": "2026-09-10T06:33:16.439Z",
|
||||
"artifacts": [
|
||||
{
|
||||
"target": "aarch64-apple-darwin",
|
||||
"url": "packages/dev.trove.harmony-connector/0.3.1/aarch64-apple-darwin.curio",
|
||||
"sha256": "1509fe112cbbeae11c800ac4f637932813468f9adffc7ef1222e5233053a8d3c",
|
||||
"size": 3032158
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.3.0",
|
||||
"engine": {
|
||||
|
||||
Binary file not shown.
@@ -59,6 +59,9 @@ async function app(t) {
|
||||
screenControl: false,
|
||||
screenFrameWait: null,
|
||||
screenFrameError: null,
|
||||
openedUrls: [],
|
||||
selectedDirectory: "/fixture/folder",
|
||||
configureError: null,
|
||||
};
|
||||
const { createMockBridge } = await import(
|
||||
pathToFileURL(path.join(directory, "sdk/index.js"))
|
||||
@@ -120,6 +123,18 @@ async function app(t) {
|
||||
version: "HDC test",
|
||||
path: "/fixture/hdc",
|
||||
})),
|
||||
handler("hdc.configure", () => {
|
||||
if (model.configureError) throw model.configureError;
|
||||
model.hdcAvailable = true;
|
||||
return { available: true, version: "HDC test", path: "/fixture/hdc" };
|
||||
}),
|
||||
[
|
||||
"host.runtime.openUrl",
|
||||
({ url }) => {
|
||||
model.openedUrls.push(url);
|
||||
return { ok: true };
|
||||
},
|
||||
],
|
||||
handler("hdc.devices", () => ({
|
||||
devices: model.hdcConnected
|
||||
? [{ id: "phone", transport: "USB", state: "Connected" }]
|
||||
@@ -152,7 +167,7 @@ async function app(t) {
|
||||
})),
|
||||
handler("hdc.start", () => ({ id: 9 })),
|
||||
["host.dialog.confirm", () => ({ confirmed: model.confirmed })],
|
||||
["host.dialog.openDirectory", () => ({ path: "/fixture/folder" })],
|
||||
["host.dialog.openDirectory", () => ({ path: model.selectedDirectory })],
|
||||
handler("info", () => ({ connection: model.connection, busy: false })),
|
||||
handler("devices", () => ({ devices: model.attached ? [device] : [] })),
|
||||
handler("connect", () => (model.connection = { device, storages })),
|
||||
@@ -374,6 +389,57 @@ test("management is optional and missing HDC does not disable file transfers", a
|
||||
await connect();
|
||||
assert.equal($("browser").hidden, false);
|
||||
});
|
||||
test("missing HDC guides official download and configuring an extracted toolkit", async (t) => {
|
||||
const { $, click, model } = await app(t);
|
||||
await click("tab-management");
|
||||
assert.equal($("hdc-setup")?.hidden, false);
|
||||
assert.match($("hdc-setup").textContent, /Mac.*ARM64/);
|
||||
assert.match($("hdc-setup").textContent, /解压/);
|
||||
assert.match($("hdc-setup").textContent.replace(/\s+/g, " "), /USB 调试/);
|
||||
await click("hdc-download");
|
||||
assert.deepEqual(model.openedUrls, [
|
||||
"https://developer.huawei.com/consumer/cn/download/command-line-tools-for-hmos",
|
||||
]);
|
||||
assert.equal($("hdc-setup").hidden, false);
|
||||
assert.ok(!model.calls.includes("hdc.devices"));
|
||||
await click("hdc-directory");
|
||||
assert.deepEqual(
|
||||
model.parameters.find(([name]) => name === "hdc.configure")[1],
|
||||
{
|
||||
path: "/fixture/folder",
|
||||
},
|
||||
);
|
||||
assert.equal($("hdc-setup").hidden, true);
|
||||
assert.equal($("hdc-target").options.length, 1);
|
||||
});
|
||||
test("refresh rechecks tool availability and clears stale devices if the tool disappears", async (t) => {
|
||||
const { $, click, model } = await app(t);
|
||||
await click("tab-management");
|
||||
await click("hdc-refresh");
|
||||
assert.ok(!model.calls.includes("hdc.devices"));
|
||||
model.hdcAvailable = true;
|
||||
await click("hdc-refresh");
|
||||
assert.equal($("hdc-setup").hidden, true);
|
||||
assert.equal($("screen-start").disabled, false);
|
||||
model.hdcAvailable = false;
|
||||
await click("hdc-refresh");
|
||||
assert.equal($("hdc-setup").hidden, false);
|
||||
assert.equal($("hdc-target").options.length, 0);
|
||||
assert.equal($("screen-start").disabled, true);
|
||||
});
|
||||
test("cancelled or invalid toolkit selection preserves installation guidance", async (t) => {
|
||||
const { $, click, model } = await app(t);
|
||||
await click("tab-management");
|
||||
model.selectedDirectory = null;
|
||||
await click("hdc-directory");
|
||||
assert.ok(!model.calls.includes("hdc.configure"));
|
||||
model.selectedDirectory = "/fixture/wrong";
|
||||
model.configureError = new Error("未找到 HDC,请选择解压后的工具目录。");
|
||||
await click("hdc-directory");
|
||||
assert.equal($("hdc-setup").hidden, false);
|
||||
assert.ok(!model.calls.includes("hdc.devices"));
|
||||
assert.match(document.body.textContent, /未找到 HDC/);
|
||||
});
|
||||
test("application installation requires confirmation and pins the reviewed package hash", async (t) => {
|
||||
const { $, click, model } = await app(t);
|
||||
model.hdcAvailable = true;
|
||||
|
||||
Reference in New Issue
Block a user