From 5666bb8f4039c0381977ed27ad8447c5dd647668 Mon Sep 17 00:00:00 2001 From: render Date: Thu, 10 Sep 2026 14:33:53 +0800 Subject: [PATCH] fix(harmony): guide official HDC setup and recheck tool availability --- curios/harmony-connector/curio.json | 4 +- curios/harmony-connector/native/Cargo.lock | 2 +- curios/harmony-connector/native/Cargo.toml | 2 +- curios/harmony-connector/native/src/hdc.rs | 80 +++++++++----- .../harmony-connector/native/src/hdc/tests.rs | 70 ++++++++++++ curios/harmony-connector/ui/app.js | 7 +- curios/harmony-connector/ui/index.html | 101 ++++++++++++++---- curios/harmony-connector/ui/management.js | 45 ++++++-- curios/harmony-connector/ui/style.css | 30 ++++++ docs/harmony-connector.md | 16 ++- source/index.json | 17 ++- .../0.3.1/aarch64-apple-darwin.curio | Bin 0 -> 3032158 bytes tools/harmony-ui.test.mjs | 68 +++++++++++- 13 files changed, 372 insertions(+), 70 deletions(-) create mode 100644 source/packages/dev.trove.harmony-connector/0.3.1/aarch64-apple-darwin.curio diff --git a/curios/harmony-connector/curio.json b/curios/harmony-connector/curio.json index 2c67dbb..1823621 100644 --- a/curios/harmony-connector/curio.json +++ b/curios/harmony-connector/curio.json @@ -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"] } } diff --git a/curios/harmony-connector/native/Cargo.lock b/curios/harmony-connector/native/Cargo.lock index ac8c5c8..08f5891 100644 --- a/curios/harmony-connector/native/Cargo.lock +++ b/curios/harmony-connector/native/Cargo.lock @@ -204,7 +204,7 @@ dependencies = [ [[package]] name = "harmony-connector" -version = "0.3.0" +version = "0.3.1" dependencies = [ "anyhow", "base64", diff --git a/curios/harmony-connector/native/Cargo.toml b/curios/harmony-connector/native/Cargo.toml index a121b8e..fea7472 100644 --- a/curios/harmony-connector/native/Cargo.toml +++ b/curios/harmony-connector/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harmony-connector" -version = "0.3.0" +version = "0.3.1" edition = "2021" publish = false diff --git a/curios/harmony-connector/native/src/hdc.rs b/curios/harmony-connector/native/src/hdc.rs index afe97f6..31a4dfd 100644 --- a/curios/harmony-connector/native/src/hdc.rs +++ b/curios/harmony-connector/native/src/hdc.rs @@ -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 { + 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 { + 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 { 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"); diff --git a/curios/harmony-connector/native/src/hdc/tests.rs b/curios/harmony-connector/native/src/hdc/tests.rs index 9aaf02b..9ea8c1d 100644 --- a/curios/harmony-connector/native/src/hdc/tests.rs +++ b/curios/harmony-connector/native/src/hdc/tests.rs @@ -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) { let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("hdc"); diff --git a/curios/harmony-connector/ui/app.js b/curios/harmony-connector/ui/app.js index c85ed1c..2552320 100644 --- a/curios/harmony-connector/ui/app.js +++ b/curios/harmony-connector/ui/app.js @@ -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; diff --git a/curios/harmony-connector/ui/index.html b/curios/harmony-connector/ui/index.html index 956b5fa..b1c1ae0 100644 --- a/curios/harmony-connector/ui/index.html +++ b/curios/harmony-connector/ui/index.html @@ -135,36 +135,91 @@

设备管理

- +
+ + +
-

正在检查官方连接工具…

-

- 在手机开发者选项中启用 USB 调试并信任这台 Mac。请保留 HDC - 同目录中的依赖文件。文件传输无需启用调试。 +

+ 正在检查官方连接工具…

-
- + + -

等待管理连接

-
-
- -
-
- 设备信息 -

-            
-
+