diff --git a/curios/harmony-connector/curio.json b/curios/harmony-connector/curio.json index 1823621..0890d9a 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.1", + "version": "0.3.2", "engine": { "trove": ">=0.1.0 <1.0.0" }, "icon": "assets/icon.svg", "ui": { diff --git a/curios/harmony-connector/native/Cargo.lock b/curios/harmony-connector/native/Cargo.lock index 08f5891..f823192 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.1" +version = "0.3.2" dependencies = [ "anyhow", "base64", diff --git a/curios/harmony-connector/native/Cargo.toml b/curios/harmony-connector/native/Cargo.toml index fea7472..2cc9eb9 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.1" +version = "0.3.2" edition = "2021" publish = false diff --git a/curios/harmony-connector/native/src/hdc.rs b/curios/harmony-connector/native/src/hdc.rs index 31a4dfd..7347ce8 100644 --- a/curios/harmony-connector/native/src/hdc.rs +++ b/curios/harmony-connector/native/src/hdc.rs @@ -129,11 +129,65 @@ fn tool_version(path: &Path, cancel: &AtomicBool) -> Result { } Ok(version.trim().to_owned()) } +fn installed_toolkits(home: &Path) -> Vec { + let root = home.join("Library/HarmonyConnector/tools"); + let mut versions: Vec<_> = fs::read_dir(&root) + .into_iter() + .flatten() + .flatten() + .filter(|entry| entry.path().is_dir()) + .filter_map(|entry| { + let version = entry + .file_name() + .to_str()? + .split('.') + .map(str::parse::) + .collect::, _>>() + .ok()?; + Some((version, entry.path())) + }) + .collect(); + versions.sort_by(|a, b| b.cmp(a)); + let mut candidates: Vec<_> = versions + .into_iter() + .filter_map(|(_, path)| selected_tool(&path)) + .collect(); + candidates.extend(selected_tool(&root)); + if !candidates.is_empty() { + return candidates; + } + // Only inspect named, extracted toolkits directly under Downloads. ZIPs + // and unfinished browser downloads are not executable installations. + let mut downloads: Vec<_> = fs::read_dir(home.join("Downloads")) + .into_iter() + .flatten() + .flatten() + .filter(|entry| { + let name = entry.file_name().to_string_lossy().to_ascii_lowercase(); + entry.path().is_dir() + && (name.starts_with("command-line-tools") || name.starts_with("commandline-tools")) + }) + .map(|entry| entry.path()) + .collect(); + downloads.sort(); + candidates.extend( + downloads + .into_iter() + .filter_map(|path| selected_tool(&path)), + ); + candidates +} fn discover() -> Option { let mut candidates = Vec::new(); if let Some(path) = env::var_os("PATH") { candidates.extend(env::split_paths(&path).map(|p| p.join("hdc"))); } + if let Some(path) = candidates.iter().find(|p| executable(p)) { + return path.canonicalize().ok(); + } + if let Some(home) = env::var_os("HOME") { + candidates.extend(installed_toolkits(Path::new(&home))); + } for root in [ Some(PathBuf::from("/Applications")), env::var_os("HOME").map(|p| PathBuf::from(p).join("Applications")), diff --git a/curios/harmony-connector/native/src/hdc/tests.rs b/curios/harmony-connector/native/src/hdc/tests.rs index 9ea8c1d..cefb047 100644 --- a/curios/harmony-connector/native/src/hdc/tests.rs +++ b/curios/harmony-connector/native/src/hdc/tests.rs @@ -74,6 +74,63 @@ fn rechecks_hdc_installed_after_manager_start() { ); } +#[test] +fn automatically_discovers_downloaded_toolkit_without_manual_configuration() { + const CHILD: &str = "HARMONY_DOWNLOADED_TOOL_TEST"; + if let Some(root) = env::var_os(CHILD) { + let root = PathBuf::from(root); + let installations = root.join("Library/HarmonyConnector/tools"); + let previous = installations.join("9.9.0.1/toolchains/hdc"); + let current = installations.join("26.0.0.821/toolchains/hdc"); + version_tool(&previous); + version_tool(¤t); + // An incomplete, newer extraction must not hide a usable installation. + fs::create_dir_all(installations.join("27.0.0.1/toolchains")).unwrap(); + let manager = Manager::new(); + let result = manager.call("tool", json!({})).unwrap(); + assert_eq!(result["available"], true, "{result}"); + assert_eq!(result["path"], json!(current.canonicalize().unwrap())); + assert!(!root.join("data/harmony-tools.json").exists()); + // A user's explicit selection still wins over automatic discovery. + let chosen = root.join("chosen/hdc"); + version_tool(&chosen); + manager.call("configure", json!({"path": chosen})).unwrap(); + assert_eq!( + Manager::new().call("tool", json!({})).unwrap()["path"], + json!(chosen.canonicalize().unwrap()) + ); + fs::remove_file(chosen).unwrap(); + fs::remove_dir_all(installations).unwrap(); + // Also support a normal Finder extraction in Downloads, retaining the + // official command-line-tools/sdk/default/openharmony layout. + let downloaded = root.join("Downloads/commandline-tools-mac-arm64-26.0.0.821/command-line-tools/sdk/default/openharmony/toolchains/hdc"); + version_tool(&downloaded); + let result = Manager::new().call("tool", json!({})).unwrap(); + assert_eq!(result["available"], true); + assert_eq!(result["path"], json!(downloaded.canonicalize().unwrap())); + return; + } + let root = tempfile::tempdir().unwrap(); + let output = std::process::Command::new(env::current_exe().unwrap()) + .args([ + "--exact", + "hdc::tests::automatically_discovers_downloaded_toolkit_without_manual_configuration", + "--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/docs/harmony-connector.md b/docs/harmony-connector.md index 3639682..2f073a9 100644 --- a/docs/harmony-connector.md +++ b/docs/harmony-connector.md @@ -1,6 +1,6 @@ # Harmony Connector -插件 ID:`dev.trove.harmony-connector`。当前版本:`0.3.1`。 +插件 ID:`dev.trove.harmony-connector`。当前版本:`0.3.2`。 macOS 上的鸿蒙设备连接工具。通过 USB 收发文件与完整文件夹;使用官方 HDC 管理应用、保存截图、导出日志和调试应用数据。功能按连接工具、设备实际能力和授权开放,不按 HarmonyOS 大版本或机型名称限制。 @@ -39,7 +39,7 @@ macOS 上的鸿蒙设备连接工具。通过 USB 收发文件与完整文件夹 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 的内置工具。典型路径: +已有 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 @@ -111,6 +111,8 @@ curios/harmony-connector/backend/harmony-connector --check-screen /absolute/path 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/协议检查全部通过。 + ## 构建与发布 要求 macOS、Xcode Command Line Tools、Rust/Cargo、Node.js、pnpm、make、pkg-config。固定源码归档与哈希位于 `vendor/harmony-connector/`。 diff --git a/source/index.json b/source/index.json index 17a43d0..61c07b1 100644 --- a/source/index.json +++ b/source/index.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "id": "trove-official", "name": "Trove 官方软件源", - "generatedAt": "2026-09-10T06:33:16.439Z", + "generatedAt": "2026-09-10T06:45:52.251Z", "curios": [ { "id": "dev.trove.qa.host", @@ -200,6 +200,21 @@ "name": "Harmony Connector", "description": "连接鸿蒙设备,收发完整文件夹、管理应用,查看手机画面并通过键鼠操作。", "versions": [ + { + "version": "0.3.2", + "engine": { + "trove": ">=0.1.0 <1.0.0" + }, + "releasedAt": "2026-09-10T06:45:52.251Z", + "artifacts": [ + { + "target": "aarch64-apple-darwin", + "url": "packages/dev.trove.harmony-connector/0.3.2/aarch64-apple-darwin.curio", + "sha256": "eb0ec5d5a8b7f4e3150cd80351eaadd5ef13535d8cbf388e955218b0f60b0f3d", + "size": 2939665 + } + ] + }, { "version": "0.3.1", "engine": { diff --git a/source/packages/dev.trove.harmony-connector/0.3.2/aarch64-apple-darwin.curio b/source/packages/dev.trove.harmony-connector/0.3.2/aarch64-apple-darwin.curio new file mode 100644 index 0000000..ad9d01c Binary files /dev/null and b/source/packages/dev.trove.harmony-connector/0.3.2/aarch64-apple-darwin.curio differ