@oniroproject/core 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -47,9 +47,11 @@ __export(index_exports, {
47
47
  SIGNING_ERRORS: () => SIGNING_ERRORS,
48
48
  SdkNotInstalledError: () => SdkNotInstalledError,
49
49
  TMP_DIR_HINT: () => TMP_DIR_HINT,
50
+ TargetNotFoundError: () => TargetNotFoundError,
50
51
  UnsupportedPlatformError: () => UnsupportedPlatformError,
51
52
  VERSION: () => VERSION,
52
53
  applyChanges: () => applyChanges,
54
+ assertLayoutSelector: () => assertLayoutSelector,
53
55
  attemptHdcConnection: () => attemptHdcConnection,
54
56
  buildCmdWrapper: () => buildCmdWrapper,
55
57
  buildGestureCommands: () => buildGestureCommands,
@@ -72,6 +74,7 @@ __export(index_exports, {
72
74
  dedupEntries: () => dedupEntries,
73
75
  defaultContext: () => defaultContext,
74
76
  defaultPaths: () => defaultPaths,
77
+ describeLayoutNode: () => describeLayoutNode,
75
78
  detectAaStartFailure: () => detectAaStartFailure,
76
79
  detectProjectSdkVersion: () => detectProjectSdkVersion,
77
80
  detectRuntimeOs: () => detectRuntimeOs,
@@ -97,8 +100,12 @@ __export(index_exports, {
97
100
  extractZipWithProgress: () => extractZipWithProgress,
98
101
  findAppProcessId: () => findAppProcessId,
99
102
  findCmdToolsSourceDir: () => findCmdToolsSourceDir,
103
+ findCoveredNodes: () => findCoveredNodes,
100
104
  findHarmonyOsSdk: () => findHarmonyOsSdk,
105
+ findKeyboardWindow: () => findKeyboardWindow,
106
+ findLayoutNodes: () => findLayoutNodes,
101
107
  findRunningProcess: () => findRunningProcess,
108
+ flattenLayout: () => flattenLayout,
102
109
  forceStop: () => forceStop,
103
110
  formatBytes: () => formatBytes,
104
111
  generateSigningConfigs: () => generateSigningConfigs,
@@ -113,6 +120,7 @@ __export(index_exports, {
113
120
  getFreeSpaceBytes: () => getFreeSpaceBytes,
114
121
  getHdcPath: () => getHdcPath,
115
122
  getHvigorwPath: () => getHvigorwPath,
123
+ getImeBundles: () => getImeBundles,
116
124
  getInstalledSdks: () => getInstalledSdks,
117
125
  getKey: () => getKey,
118
126
  getMainAbility: () => getMainAbility,
@@ -125,6 +133,7 @@ __export(index_exports, {
125
133
  harmonyOsAutoSign: () => harmonyOsAutoSign,
126
134
  harmonyOsSdkLabelForApi: () => harmonyOsSdkLabelForApi,
127
135
  hdcExec: () => hdcExec,
136
+ hideKeyboard: () => hideKeyboard,
128
137
  installApp: () => installApp,
129
138
  installCmdTools: () => installCmdTools,
130
139
  installEmulator: () => installEmulator,
@@ -141,6 +150,7 @@ __export(index_exports, {
141
150
  listModules: () => listModules,
142
151
  listRunningProcesses: () => listRunningProcesses,
143
152
  listTemplates: () => listTemplates,
153
+ locateTarget: () => locateTarget,
144
154
  needsCmdWrapper: () => needsCmdWrapper,
145
155
  nonInteractivePrompter: () => nonInteractivePrompter,
146
156
  noopLogger: () => noopLogger,
@@ -151,9 +161,12 @@ __export(index_exports, {
151
161
  parseCodelinterFindings: () => parseCodelinterFindings,
152
162
  parseDeviceList: () => parseDeviceList,
153
163
  parseHilogLine: () => parseHilogLine,
164
+ parseImeBundles: () => parseImeBundles,
154
165
  parseMainAbilityFromBmDump: () => parseMainAbilityFromBmDump,
155
166
  parseRemoteExit: () => parseRemoteExit,
167
+ performInput: () => performInput,
156
168
  pickSigningKind: () => pickSigningKind,
169
+ pickTarget: () => pickTarget,
157
170
  prepareSigning: () => prepareSigning,
158
171
  pruneLayout: () => pruneLayout,
159
172
  readJson5File: () => readJson5File,
@@ -166,6 +179,7 @@ __export(index_exports, {
166
179
  removeSdk: () => removeSdk,
167
180
  removeTempWorkDir: () => removeTempWorkDir,
168
181
  requireHarmonyOsSdk: () => requireHarmonyOsSdk,
182
+ resolveKeyEvent: () => resolveKeyEvent,
169
183
  resolveTmpRoot: () => resolveTmpRoot,
170
184
  runCodelinter: () => runCodelinter,
171
185
  runHvigorw: () => runHvigorw,
@@ -195,8 +209,10 @@ __export(index_exports, {
195
209
  waitForBundle: () => waitForBundle,
196
210
  waitForCondition: () => waitForCondition,
197
211
  waitForLog: () => waitForLog,
212
+ waitForTarget: () => waitForTarget,
198
213
  watchLog: () => watchLog,
199
214
  waypointsToRawEvents: () => waypointsToRawEvents,
215
+ withoutWindow: () => withoutWindow,
200
216
  writeJson5File: () => writeJson5File,
201
217
  writeJsonFile: () => writeJsonFile
202
218
  });
@@ -375,7 +391,8 @@ var ALL_SDKS = [
375
391
  { version: "5.1.0", api: "18", tarballStrip: { linuxWindows: 1, darwin: 3 } },
376
392
  { version: "5.1.1", api: "19", tarballStrip: { linuxWindows: 1, darwin: 3 } },
377
393
  { version: "6.0", api: "20", tarballStrip: { linuxWindows: 0, darwin: 3 } },
378
- { version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } }
394
+ { version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } },
395
+ { version: "7.0", api: "26", tarballStrip: { linuxWindows: 1, darwin: 3 }, archiveSuffix: "_20260829" }
379
396
  ];
380
397
  var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
381
398
 
@@ -391,18 +408,20 @@ function getOsFolder() {
391
408
  function getSdkFilename(version) {
392
409
  const platform7 = os2.platform();
393
410
  const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
394
- const strips = ALL_SDKS.find((r) => r.version === v)?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
411
+ const release = ALL_SDKS.find((r) => r.version === v);
412
+ const strips = release?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
413
+ const archive = (base) => `${base}${release?.archiveSuffix ?? ""}.tar.gz`;
395
414
  if (platform7 === "linux") {
396
- return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip: strips.linuxWindows };
415
+ return { filename: archive("ohos-sdk-windows_linux-public"), osFolder: "linux", strip: strips.linuxWindows };
397
416
  }
398
417
  if (platform7 === "darwin") {
399
418
  if (os2.arch() === "arm64") {
400
- return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: strips.darwin };
419
+ return { filename: archive("L2-SDK-MAC-M1-PUBLIC"), osFolder: "darwin", strip: strips.darwin };
401
420
  }
402
- return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: strips.darwin };
421
+ return { filename: archive("ohos-sdk-mac-public"), osFolder: "darwin", strip: strips.darwin };
403
422
  }
404
423
  if (platform7 === "win32") {
405
- return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip: strips.linuxWindows };
424
+ return { filename: archive("ohos-sdk-windows_linux-public"), osFolder: "windows", strip: strips.linuxWindows };
406
425
  }
407
426
  throw new UnsupportedPlatformError(platform7);
408
427
  }
@@ -2464,19 +2483,39 @@ async function isReachable(config, deviceSerial) {
2464
2483
  }
2465
2484
  }
2466
2485
  var TCP_SERIAL_RE = /^.+:\d+$/;
2467
- async function reconnectIfTcp(config, deviceSerial, logger) {
2468
- if (!deviceSerial || !TCP_SERIAL_RE.test(deviceSerial)) return;
2469
- try {
2470
- await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
2471
- } catch {
2472
- }
2486
+ var WEDGED_POLLS_BEFORE_RESTART = 3;
2487
+ function tcpReconnector(config, deviceSerial, logger) {
2488
+ let wedgedPolls = 0;
2489
+ return async () => {
2490
+ if (!deviceSerial || !TCP_SERIAL_RE.test(deviceSerial)) return;
2491
+ let claimsConnected = false;
2492
+ try {
2493
+ const res = await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
2494
+ claimsConnected = /already connected|repeat operation/i.test(`${res.stdout}
2495
+ ${res.stderr}`);
2496
+ } catch {
2497
+ }
2498
+ if (!claimsConnected || await isReachable(config, deviceSerial)) {
2499
+ wedgedPolls = 0;
2500
+ return;
2501
+ }
2502
+ if (++wedgedPolls < WEDGED_POLLS_BEFORE_RESTART) return;
2503
+ wedgedPolls = 0;
2504
+ logger.warn(`hdc reports ${deviceSerial} as connected but it does not answer; restarting the hdc server.`);
2505
+ try {
2506
+ await hdcExec({ config, args: ["start", "-r"], timeoutMs: 15e3, logger });
2507
+ await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
2508
+ } catch {
2509
+ }
2510
+ };
2473
2511
  }
2474
2512
  async function waitForBundle(opts) {
2475
2513
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2476
2514
  const timeoutMs = opts.timeoutMs ?? 18e4;
2515
+ const reconnectIfTcp = tcpReconnector(opts.config, opts.deviceSerial, logger);
2477
2516
  await waitForCondition({
2478
2517
  probe: async () => {
2479
- await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
2518
+ await reconnectIfTcp();
2480
2519
  return await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
2481
2520
  },
2482
2521
  timeoutMs,
@@ -2489,9 +2528,10 @@ async function waitForBundle(opts) {
2489
2528
  async function waitForBoot(opts) {
2490
2529
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2491
2530
  const timeoutMs = opts.timeoutMs ?? 18e4;
2531
+ const reconnectIfTcp = tcpReconnector(opts.config, opts.deviceSerial, logger);
2492
2532
  await waitForCondition({
2493
2533
  probe: async () => {
2494
- await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
2534
+ await reconnectIfTcp();
2495
2535
  if (!await isReachable(opts.config, opts.deviceSerial)) return false;
2496
2536
  if (!opts.untilPidOf) return true;
2497
2537
  return await findRunningProcess({ config: opts.config, bundle: opts.untilPidOf, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
@@ -2595,6 +2635,55 @@ async function captureBurst(opts) {
2595
2635
  }
2596
2636
 
2597
2637
  // src/hdc/input.ts
2638
+ var UITEST_KEY_NAMES = ["Back", "Home", "Power"];
2639
+ var KEY_IDS = {
2640
+ back: 2,
2641
+ home: 1,
2642
+ power: 18,
2643
+ volumeup: 16,
2644
+ volumedown: 17,
2645
+ up: 2012,
2646
+ down: 2013,
2647
+ left: 2014,
2648
+ right: 2015,
2649
+ alt: 2045,
2650
+ shift: 2047,
2651
+ tab: 2049,
2652
+ space: 2050,
2653
+ enter: 2054,
2654
+ delete: 2055,
2655
+ backspace: 2055,
2656
+ pageup: 2068,
2657
+ pagedown: 2069,
2658
+ escape: 2070,
2659
+ esc: 2070,
2660
+ forwarddelete: 2071,
2661
+ ctrl: 2072,
2662
+ meta: 2076,
2663
+ movehome: 2081,
2664
+ moveend: 2082
2665
+ };
2666
+ function keyId(name) {
2667
+ const k = name.trim().toLowerCase();
2668
+ if (/^\d+$/.test(k)) return Number(k);
2669
+ if (/^[a-z]$/.test(k)) return 2017 + (k.charCodeAt(0) - 97);
2670
+ const id = KEY_IDS[k];
2671
+ if (id === void 0) {
2672
+ throw new OniroError(
2673
+ `Unknown key "${name}". Use a numeric KeyCode id, a letter, or one of: ${Object.keys(KEY_IDS).join(", ")}. Combine up to three with "+", e.g. Ctrl+A.`
2674
+ );
2675
+ }
2676
+ return id;
2677
+ }
2678
+ function resolveKeyEvent(key) {
2679
+ const spec = String(key).trim();
2680
+ if (spec === "") throw new OniroError('send input "keyEvent" requires a key.');
2681
+ const native = UITEST_KEY_NAMES.find((n) => n.toLowerCase() === spec.toLowerCase());
2682
+ if (native) return native;
2683
+ const parts = spec.includes("+") ? spec.split("+") : spec.split(/\s+/);
2684
+ if (parts.length > 3) throw new OniroError(`uitest injects at most three keys at once; got "${spec}".`);
2685
+ return parts.map(keyId).join(" ");
2686
+ }
2598
2687
  function reqNum(value, name, type) {
2599
2688
  if (value === void 0 || Number.isNaN(value)) {
2600
2689
  throw new OniroError(`send input "${type}" requires a numeric ${name}.`);
@@ -2615,7 +2704,7 @@ function buildInputCommand(opts) {
2615
2704
  }
2616
2705
  case "keyEvent":
2617
2706
  if (opts.key === void 0 || opts.key === "") throw new OniroError('send input "keyEvent" requires a key.');
2618
- return `uitest uiInput keyEvent ${opts.key}`;
2707
+ return `uitest uiInput keyEvent ${resolveKeyEvent(opts.key)}`;
2619
2708
  case "inputText": {
2620
2709
  const t = String(opts.text ?? "").replace(/'/g, `'\\''`);
2621
2710
  if (opts.pxX !== void 0 && opts.pxY !== void 0) {
@@ -2713,6 +2802,48 @@ var fs20 = __toESM(require("fs"), 1);
2713
2802
  var os10 = __toESM(require("os"), 1);
2714
2803
  var path20 = __toESM(require("path"), 1);
2715
2804
  var import_node_crypto2 = require("crypto");
2805
+
2806
+ // src/hdc/ime.ts
2807
+ var INPUT_METHOD_SA_ID = 3703;
2808
+ var IME_BUNDLE_FALLBACK = /inputmethod|keyboard|(^|\.)ime(\.|$)/i;
2809
+ function parseImeBundles(output) {
2810
+ const current = [];
2811
+ const others = [];
2812
+ const entry = /\{[^{}]*"ime"\s*:\s*"([^"/]+)\/[^"]*"[^{}]*\}/g;
2813
+ for (const m of output.matchAll(entry)) {
2814
+ const bundle = m[1];
2815
+ const list = /"isCurrentIme"\s*:\s*"true"/.test(m[0]) ? current : others;
2816
+ if (!current.includes(bundle) && !others.includes(bundle)) list.push(bundle);
2817
+ }
2818
+ return [...current, ...others];
2819
+ }
2820
+ async function getImeBundles(config, opts = {}) {
2821
+ try {
2822
+ const res = await shell({
2823
+ config,
2824
+ command: `hidumper -s ${INPUT_METHOD_SA_ID} -a '-a'`,
2825
+ deviceSerial: opts.deviceSerial,
2826
+ timeoutMs: 1e4,
2827
+ logger: opts.logger
2828
+ });
2829
+ if (res.code === 0) return parseImeBundles(res.stdout);
2830
+ } catch (err) {
2831
+ opts.logger?.debug(`could not list input methods: ${err instanceof Error ? err.message : String(err)}`);
2832
+ }
2833
+ return [];
2834
+ }
2835
+ function findKeyboardWindow(tree, imeBundles) {
2836
+ const isIme = (bundle) => imeBundles.length > 0 ? imeBundles.includes(bundle) : IME_BUNDLE_FALLBACK.test(bundle);
2837
+ const stack = [tree];
2838
+ while (stack.length > 0) {
2839
+ const n = stack.shift();
2840
+ if (n.bundle && isIme(n.bundle)) return n;
2841
+ stack.push(...n.children ?? []);
2842
+ }
2843
+ return null;
2844
+ }
2845
+
2846
+ // src/hdc/dump.ts
2716
2847
  function parseBounds(s) {
2717
2848
  const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
2718
2849
  if (!m) return null;
@@ -2747,7 +2878,46 @@ var FLAG_MAP = [
2747
2878
  ["scrollable", "scroll"],
2748
2879
  ["longClickable", "longClick"]
2749
2880
  ];
2750
- function pruneLayout(node, display) {
2881
+ function colorAlpha(color) {
2882
+ const m = /^#([0-9a-f]{2})?[0-9a-f]{6}$/i.exec(color ?? "");
2883
+ if (!m) return 0;
2884
+ return m[1] === void 0 ? 255 : parseInt(m[1], 16);
2885
+ }
2886
+ function paintsFill(a) {
2887
+ if (a.visible === "false") return false;
2888
+ if (a.opacity !== void 0 && a.opacity !== "" && !(Number(a.opacity) > 0)) return false;
2889
+ return colorAlpha(a.backgroundColor) > 0;
2890
+ }
2891
+ function findCoveredNodes(root) {
2892
+ const order = [];
2893
+ const fills = [];
2894
+ const visit = (n) => {
2895
+ const a = n.attributes ?? {};
2896
+ const entry = { node: n, bounds: parseBounds(a.bounds), end: 0 };
2897
+ if (entry.bounds && paintsFill(a)) fills.push(order.length);
2898
+ order.push(entry);
2899
+ const kids = (n.children ?? []).map((c, i) => ({ c, i, z: Number(c.attributes?.zIndex) || 0 })).sort((p, q) => p.z - q.z || p.i - q.i);
2900
+ for (const k of kids) visit(k.c);
2901
+ entry.end = order.length;
2902
+ };
2903
+ visit(root);
2904
+ const covered = /* @__PURE__ */ new Set();
2905
+ for (const p of order) {
2906
+ if (!p.bounds) continue;
2907
+ const cx = (p.bounds[0] + p.bounds[2]) / 2;
2908
+ const cy = (p.bounds[1] + p.bounds[3]) / 2;
2909
+ for (const j of fills) {
2910
+ if (j < p.end) continue;
2911
+ const q = order[j].bounds;
2912
+ if (cx >= q[0] && cx < q[2] && cy >= q[1] && cy < q[3]) {
2913
+ covered.add(p.node);
2914
+ break;
2915
+ }
2916
+ }
2917
+ }
2918
+ return covered;
2919
+ }
2920
+ function pruneLayout(node, display, covered) {
2751
2921
  const a = node.attributes ?? {};
2752
2922
  const px = parseBounds(a.bounds);
2753
2923
  const out = {};
@@ -2765,28 +2935,125 @@ function pruneLayout(node, display) {
2765
2935
  if (a.type && !STRUCTURAL_TYPES.has(a.type)) out.type = a.type;
2766
2936
  if (a.text) out.text = a.text;
2767
2937
  if (a.description) out.desc = a.description;
2938
+ if (a.hint) out.hint = a.hint;
2768
2939
  if (a.id) out.id = a.id;
2769
2940
  if (a.key && a.key !== a.id) out.key = a.key;
2770
2941
  if (a.bundleName) out.bundle = a.bundleName;
2771
2942
  for (const [attr, short] of FLAG_MAP) {
2772
2943
  if (a[attr] === "true") out[short] = true;
2773
2944
  }
2945
+ if (a.enabled === "false") out.disabled = true;
2946
+ if (covered?.has(node)) out.covered = true;
2774
2947
  const children = [];
2775
2948
  for (const c of node.children ?? []) {
2776
- const pc = pruneLayout(c, display);
2949
+ const pc = pruneLayout(c, display, covered);
2777
2950
  if (pc) {
2778
2951
  if (Array.isArray(pc)) children.push(...pc);
2779
2952
  else children.push(pc);
2780
2953
  }
2781
2954
  }
2782
2955
  const hasSignal = Boolean(
2783
- out.text || out.desc || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
2956
+ out.text || out.desc || out.hint || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
2784
2957
  );
2785
2958
  if (!hasSignal && children.length === 0) return null;
2786
2959
  if (!hasSignal) return children.length === 1 ? children[0] : children;
2787
2960
  if (children.length) out.children = children;
2788
2961
  return out;
2789
2962
  }
2963
+ function flattenLayout(tree) {
2964
+ const out = [];
2965
+ const visit = (n, window) => {
2966
+ const { children, ...rest } = n;
2967
+ const win = n.bundle ?? window;
2968
+ if (Object.keys(rest).length > 0) out.push({ ...rest, ...win ? { window: win } : {} });
2969
+ for (const c of children ?? []) visit(c, win);
2970
+ };
2971
+ visit(tree, void 0);
2972
+ return out;
2973
+ }
2974
+ function assertLayoutSelector(selector) {
2975
+ if (selector.text === void 0 && selector.id === void 0 && selector.type === void 0) {
2976
+ throw new OniroError("A layout selector needs a text, id, or type.");
2977
+ }
2978
+ if (selector.text !== void 0 && selector.text.trim() === "") {
2979
+ throw new OniroError("A layout selector text must not be blank.");
2980
+ }
2981
+ }
2982
+ function withoutWindow(tree, bundle) {
2983
+ const children = (tree.children ?? []).filter((c) => c.bundle !== bundle).map((c) => withoutWindow(c, bundle));
2984
+ return tree.children ? { ...tree, children } : tree;
2985
+ }
2986
+ function findLayoutNodes(tree, selector) {
2987
+ assertLayoutSelector(selector);
2988
+ const type = selector.type?.toLowerCase();
2989
+ const nodes = flattenLayout(tree).filter(
2990
+ (n) => (type === void 0 || n.type?.toLowerCase() === type) && (selector.id === void 0 || n.id === selector.id || n.key === selector.id)
2991
+ );
2992
+ if (selector.text === void 0) return nodes;
2993
+ const want = selector.text.trim().toLowerCase();
2994
+ const labels = (n) => [n.text, n.desc, n.hint].filter((s) => !!s).map((s) => s.trim().toLowerCase());
2995
+ const exact = nodes.filter((n) => labels(n).some((l) => l === want));
2996
+ if (exact.length > 0) return exact;
2997
+ return nodes.filter((n) => labels(n).some((l) => l.includes(want)));
2998
+ }
2999
+ function describeSelector(s) {
3000
+ const parts = [];
3001
+ if (s.text !== void 0) parts.push(`text "${s.text}"`);
3002
+ if (s.id !== void 0) parts.push(`id "${s.id}"`);
3003
+ if (s.type !== void 0) parts.push(`type ${s.type}`);
3004
+ return parts.join(", ");
3005
+ }
3006
+ function describeLayoutNode(n) {
3007
+ const label = n.text ?? n.desc ?? (n.hint !== void 0 ? `hint: ${n.hint}` : void 0);
3008
+ const at = n.c ? ` at ${n.c[0].toFixed(3)},${n.c[1].toFixed(3)}` : "";
3009
+ return `${n.type ?? "node"}${label !== void 0 ? ` "${label}"` : ""}${n.id ? ` #${n.id}` : ""}${at}`;
3010
+ }
3011
+ var area = (b) => b ? (b[2] - b[0]) * (b[3] - b[1]) : 0;
3012
+ function enclosesTarget(outer, inner) {
3013
+ const b = outer.b;
3014
+ const c = inner.c;
3015
+ if (!b || !c) return false;
3016
+ return c[0] >= b[0] && c[0] <= b[2] && c[1] >= b[1] && c[1] <= b[3] && area(b) > area(inner.b);
3017
+ }
3018
+ var TargetNotFoundError = class extends OniroError {
3019
+ constructor(message, reason) {
3020
+ super(message);
3021
+ this.reason = reason;
3022
+ this.name = "TargetNotFoundError";
3023
+ }
3024
+ reason;
3025
+ };
3026
+ function pickTarget(tree, selector, index) {
3027
+ const matches = findLayoutNodes(tree, selector).filter((n) => n.c !== void 0);
3028
+ const what = describeSelector(selector);
3029
+ if (matches.length === 0) {
3030
+ throw new TargetNotFoundError(`No on-screen element matches ${what}. Run \`oniro-app dump layout --flat\` to see what is there.`, "none");
3031
+ }
3032
+ const visible = matches.filter((n) => !n.covered);
3033
+ if (visible.length === 0) {
3034
+ throw new TargetNotFoundError(
3035
+ `Found ${matches.length} element(s) matching ${what}, but each is covered by an overlay (dialog, keyboard, or another layer): ${matches.map(describeLayoutNode).join("; ")}.`,
3036
+ "covered"
3037
+ );
3038
+ }
3039
+ const clickable = visible.filter((n) => n.click);
3040
+ const preferred = clickable.length > 0 ? clickable : visible;
3041
+ const candidates = preferred.filter((n) => !preferred.some((o) => o !== n && o.click && enclosesTarget(o, n)));
3042
+ if (index !== void 0) {
3043
+ const picked = candidates[index];
3044
+ if (!picked) {
3045
+ throw new TargetNotFoundError(`Index ${index} is out of range: ${candidates.length} element(s) match ${what}.`, "ambiguous");
3046
+ }
3047
+ return picked;
3048
+ }
3049
+ if (candidates.length > 1) {
3050
+ throw new TargetNotFoundError(
3051
+ `${candidates.length} elements match ${what}; pass an index to choose: ` + candidates.map((n, i) => `[${i}] ${describeLayoutNode(n)}`).join("; "),
3052
+ "ambiguous"
3053
+ );
3054
+ }
3055
+ return candidates[0];
3056
+ }
2790
3057
  async function dumpLayout(opts) {
2791
3058
  const timeoutMs = opts.timeoutMs ?? 3e4;
2792
3059
  const bundleArg = opts.bundleName ? ` -b '${opts.bundleName.replace(/'/g, `'\\''`)}'` : "";
@@ -2804,9 +3071,13 @@ ${dump.stdout}`);
2804
3071
  if (opts.prune === false) {
2805
3072
  return { display, tree: parsed };
2806
3073
  }
2807
- const prunedRaw = pruneLayout(parsed, display);
3074
+ const prunedRaw = pruneLayout(parsed, display, findCoveredNodes(parsed));
2808
3075
  const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
2809
- return { display, tree };
3076
+ if (!opts.detectKeyboard || opts.bundleName) return { display, tree };
3077
+ const imeBundles = await getImeBundles(opts.config, { deviceSerial: opts.deviceSerial, logger: opts.logger });
3078
+ const kb = findKeyboardWindow(tree, imeBundles);
3079
+ const keyboard = kb ? { shown: true, bundle: kb.bundle, ...kb.b ? { b: kb.b } : {} } : { shown: false };
3080
+ return { display, tree, keyboard };
2810
3081
  } finally {
2811
3082
  fs20.promises.unlink(local).catch(() => {
2812
3083
  });
@@ -2815,6 +3086,161 @@ ${dump.stdout}`);
2815
3086
  }
2816
3087
  }
2817
3088
 
3089
+ // src/hdc/uiActions.ts
3090
+ var FOCUS_SETTLE_MS = 400;
3091
+ var KEYBOARD_SETTLE_MS = 400;
3092
+ var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
3093
+ function targetTree(layout) {
3094
+ const kb = layout.keyboard;
3095
+ return kb?.shown && kb.bundle ? withoutWindow(layout.tree, kb.bundle) : layout.tree;
3096
+ }
3097
+ function fractionToPixel(f, size) {
3098
+ return Math.min(size - 1, Math.round(f * size));
3099
+ }
3100
+ function pressBack(opts) {
3101
+ return sendInput({ config: opts.config, type: "keyEvent", key: "Back", deviceSerial: opts.deviceSerial, logger: opts.logger });
3102
+ }
3103
+ async function hideKeyboard(opts) {
3104
+ const { keyboard } = await dumpLayout({ ...opts, detectKeyboard: true });
3105
+ if (!keyboard?.shown) return false;
3106
+ await pressBack(opts);
3107
+ await sleep(KEYBOARD_SETTLE_MS);
3108
+ return true;
3109
+ }
3110
+ async function locateTarget(opts) {
3111
+ assertLayoutSelector(opts.selector);
3112
+ const dumpOpts = { config: opts.config, detectKeyboard: true, deviceSerial: opts.deviceSerial, logger: opts.logger };
3113
+ let layout = await dumpLayout(dumpOpts);
3114
+ if (!layout.display) throw new OniroError("Could not read the display size to locate the element.");
3115
+ let keyboardHidden = false;
3116
+ let node;
3117
+ let failure;
3118
+ try {
3119
+ node = pickTarget(targetTree(layout), opts.selector, opts.index);
3120
+ } catch (err) {
3121
+ if (!(err instanceof TargetNotFoundError) || err.reason === "ambiguous") throw err;
3122
+ failure = err;
3123
+ }
3124
+ if ((!node || !node.click) && layout.keyboard?.shown && opts.hideKeyboard !== false) {
3125
+ opts.logger?.debug("target not reachable while the keyboard is up; dismissing it and looking again");
3126
+ await pressBack(opts);
3127
+ await sleep(KEYBOARD_SETTLE_MS);
3128
+ keyboardHidden = true;
3129
+ layout = await dumpLayout(dumpOpts);
3130
+ if (!layout.display) throw new OniroError("Could not read the display size to locate the element.");
3131
+ node = pickTarget(targetTree(layout), opts.selector, opts.index);
3132
+ }
3133
+ if (!node) throw failure;
3134
+ if (!node.c) throw new OniroError("The element has no on-screen position.");
3135
+ const x2 = fractionToPixel(node.c[0], layout.display.width);
3136
+ const y = fractionToPixel(node.c[1], layout.display.height);
3137
+ return { node, x: x2, y, display: layout.display, keyboardHidden };
3138
+ }
3139
+ function fraction(value, name) {
3140
+ if (value === void 0) return void 0;
3141
+ if (!(value >= 0 && value <= 1)) throw new OniroError(`${name} must be a fraction between 0 and 1; got ${value}.`);
3142
+ return value;
3143
+ }
3144
+ function pair(a, b, names) {
3145
+ if (a === void 0 && b === void 0) return void 0;
3146
+ if (a === void 0 || b === void 0) throw new OniroError(`Give both ${names}.`);
3147
+ return [a, b];
3148
+ }
3149
+ async function performInput(opts) {
3150
+ const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
3151
+ const startFx = pair(fraction(opts.fx, "--fx"), fraction(opts.fy, "--fy"), "--fx and --fy");
3152
+ const startPx = pair(opts.pxX, opts.pxY, "--x and --y");
3153
+ const endFx = pair(fraction(opts.fx2, "--fx2"), fraction(opts.fy2, "--fy2"), "--fx2 and --fy2");
3154
+ const endPx = pair(opts.pxX2, opts.pxY2, "--x2 and --y2");
3155
+ if ([opts.target, startFx, startPx].filter((s) => s !== void 0).length > 1) {
3156
+ throw new OniroError("Give the start point one way: a target, fractions (--fx/--fy), or pixels (--x/--y).");
3157
+ }
3158
+ if (endFx && endPx) throw new OniroError("Give the end point one way: fractions (--fx2/--fy2) or pixels (--x2/--y2).");
3159
+ let display;
3160
+ const toPx = async ([fx, fy]) => {
3161
+ display ??= await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial, logger: opts.logger });
3162
+ if (!display) throw new OniroError("Could not read the display size to convert fractions to pixels.");
3163
+ return [fractionToPixel(fx, display.width), fractionToPixel(fy, display.height)];
3164
+ };
3165
+ const result = { keyboardHidden: false };
3166
+ let start = startPx;
3167
+ if (opts.target) {
3168
+ const located = await locateTarget({ ...device, selector: opts.target, index: opts.targetIndex });
3169
+ display = located.display;
3170
+ start = [located.x, located.y];
3171
+ result.target = located.node;
3172
+ result.keyboardHidden = located.keyboardHidden;
3173
+ } else if (startFx) {
3174
+ start = await toPx(startFx);
3175
+ }
3176
+ const end = endFx ? await toPx(endFx) : endPx;
3177
+ if (start) [result.x, result.y] = start;
3178
+ const tapToFocus = async () => {
3179
+ if (!start) return;
3180
+ await sendInput({ ...device, type: "click", pxX: start[0], pxY: start[1] });
3181
+ await sleep(FOCUS_SETTLE_MS);
3182
+ };
3183
+ const clearFocused = async () => {
3184
+ await sendInput({ ...device, type: "keyEvent", key: "Ctrl+A" });
3185
+ await sendInput({ ...device, type: "keyEvent", key: "Delete" });
3186
+ };
3187
+ switch (opts.type) {
3188
+ case "hideKeyboard":
3189
+ result.keyboardHidden = await hideKeyboard(device) || result.keyboardHidden;
3190
+ return result;
3191
+ case "clearText":
3192
+ await tapToFocus();
3193
+ await clearFocused();
3194
+ break;
3195
+ case "inputText":
3196
+ if (opts.clear) {
3197
+ await tapToFocus();
3198
+ await clearFocused();
3199
+ await sendInput({ ...device, type: "inputText", text: opts.text });
3200
+ } else {
3201
+ await sendInput({ ...device, type: "inputText", pxX: start?.[0], pxY: start?.[1], text: opts.text });
3202
+ }
3203
+ break;
3204
+ default:
3205
+ await sendInput({
3206
+ ...device,
3207
+ type: opts.type,
3208
+ pxX: start?.[0],
3209
+ pxY: start?.[1],
3210
+ pxX2: end?.[0],
3211
+ pxY2: end?.[1],
3212
+ speed: opts.speed,
3213
+ key: opts.key,
3214
+ text: opts.text
3215
+ });
3216
+ }
3217
+ if (opts.hideKeyboard) result.keyboardHidden = await hideKeyboard(device) || result.keyboardHidden;
3218
+ return result;
3219
+ }
3220
+ async function waitForTarget(opts) {
3221
+ const { selector } = opts;
3222
+ assertLayoutSelector(selector);
3223
+ let found = null;
3224
+ await waitForCondition({
3225
+ probe: async () => {
3226
+ const layout = await dumpLayout({
3227
+ config: opts.config,
3228
+ detectKeyboard: true,
3229
+ deviceSerial: opts.deviceSerial,
3230
+ logger: opts.logger
3231
+ });
3232
+ found = findLayoutNodes(targetTree(layout), selector).find((n) => !n.covered) ?? null;
3233
+ return opts.gone ? found === null : found !== null;
3234
+ },
3235
+ timeoutMs: opts.timeoutMs,
3236
+ pollMs: opts.pollMs ?? 500,
3237
+ abortSignal: opts.abortSignal,
3238
+ logger: opts.logger,
3239
+ timeoutMessage: opts.gone ? `An element matching the selector was still on screen after ${opts.timeoutMs}ms.` : `No element matching the selector appeared within ${opts.timeoutMs}ms.`
3240
+ });
3241
+ return opts.gone ? null : found;
3242
+ }
3243
+
2818
3244
  // src/hdc/hilog.ts
2819
3245
  function setHilogLevel(opts) {
2820
3246
  const logger = opts.logger ?? noopLogger;
@@ -4947,7 +5373,7 @@ async function harmonyOsAutoSign(opts) {
4947
5373
  }
4948
5374
 
4949
5375
  // src/index.ts
4950
- var VERSION = "0.11.0";
5376
+ var VERSION = "0.13.0";
4951
5377
  // Annotate the CommonJS export names for ESM import in node:
4952
5378
  0 && (module.exports = {
4953
5379
  ALL_SDKS,
@@ -4967,9 +5393,11 @@ var VERSION = "0.11.0";
4967
5393
  SIGNING_ERRORS,
4968
5394
  SdkNotInstalledError,
4969
5395
  TMP_DIR_HINT,
5396
+ TargetNotFoundError,
4970
5397
  UnsupportedPlatformError,
4971
5398
  VERSION,
4972
5399
  applyChanges,
5400
+ assertLayoutSelector,
4973
5401
  attemptHdcConnection,
4974
5402
  buildCmdWrapper,
4975
5403
  buildGestureCommands,
@@ -4992,6 +5420,7 @@ var VERSION = "0.11.0";
4992
5420
  dedupEntries,
4993
5421
  defaultContext,
4994
5422
  defaultPaths,
5423
+ describeLayoutNode,
4995
5424
  detectAaStartFailure,
4996
5425
  detectProjectSdkVersion,
4997
5426
  detectRuntimeOs,
@@ -5017,8 +5446,12 @@ var VERSION = "0.11.0";
5017
5446
  extractZipWithProgress,
5018
5447
  findAppProcessId,
5019
5448
  findCmdToolsSourceDir,
5449
+ findCoveredNodes,
5020
5450
  findHarmonyOsSdk,
5451
+ findKeyboardWindow,
5452
+ findLayoutNodes,
5021
5453
  findRunningProcess,
5454
+ flattenLayout,
5022
5455
  forceStop,
5023
5456
  formatBytes,
5024
5457
  generateSigningConfigs,
@@ -5033,6 +5466,7 @@ var VERSION = "0.11.0";
5033
5466
  getFreeSpaceBytes,
5034
5467
  getHdcPath,
5035
5468
  getHvigorwPath,
5469
+ getImeBundles,
5036
5470
  getInstalledSdks,
5037
5471
  getKey,
5038
5472
  getMainAbility,
@@ -5045,6 +5479,7 @@ var VERSION = "0.11.0";
5045
5479
  harmonyOsAutoSign,
5046
5480
  harmonyOsSdkLabelForApi,
5047
5481
  hdcExec,
5482
+ hideKeyboard,
5048
5483
  installApp,
5049
5484
  installCmdTools,
5050
5485
  installEmulator,
@@ -5061,6 +5496,7 @@ var VERSION = "0.11.0";
5061
5496
  listModules,
5062
5497
  listRunningProcesses,
5063
5498
  listTemplates,
5499
+ locateTarget,
5064
5500
  needsCmdWrapper,
5065
5501
  nonInteractivePrompter,
5066
5502
  noopLogger,
@@ -5071,9 +5507,12 @@ var VERSION = "0.11.0";
5071
5507
  parseCodelinterFindings,
5072
5508
  parseDeviceList,
5073
5509
  parseHilogLine,
5510
+ parseImeBundles,
5074
5511
  parseMainAbilityFromBmDump,
5075
5512
  parseRemoteExit,
5513
+ performInput,
5076
5514
  pickSigningKind,
5515
+ pickTarget,
5077
5516
  prepareSigning,
5078
5517
  pruneLayout,
5079
5518
  readJson5File,
@@ -5086,6 +5525,7 @@ var VERSION = "0.11.0";
5086
5525
  removeSdk,
5087
5526
  removeTempWorkDir,
5088
5527
  requireHarmonyOsSdk,
5528
+ resolveKeyEvent,
5089
5529
  resolveTmpRoot,
5090
5530
  runCodelinter,
5091
5531
  runHvigorw,
@@ -5115,8 +5555,10 @@ var VERSION = "0.11.0";
5115
5555
  waitForBundle,
5116
5556
  waitForCondition,
5117
5557
  waitForLog,
5558
+ waitForTarget,
5118
5559
  watchLog,
5119
5560
  waypointsToRawEvents,
5561
+ withoutWindow,
5120
5562
  writeJson5File,
5121
5563
  writeJsonFile
5122
5564
  });