@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.js CHANGED
@@ -171,7 +171,8 @@ var ALL_SDKS = [
171
171
  { version: "5.1.0", api: "18", tarballStrip: { linuxWindows: 1, darwin: 3 } },
172
172
  { version: "5.1.1", api: "19", tarballStrip: { linuxWindows: 1, darwin: 3 } },
173
173
  { version: "6.0", api: "20", tarballStrip: { linuxWindows: 0, darwin: 3 } },
174
- { version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } }
174
+ { version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } },
175
+ { version: "7.0", api: "26", tarballStrip: { linuxWindows: 1, darwin: 3 }, archiveSuffix: "_20260829" }
175
176
  ];
176
177
  var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
177
178
 
@@ -187,18 +188,20 @@ function getOsFolder() {
187
188
  function getSdkFilename(version) {
188
189
  const platform7 = os2.platform();
189
190
  const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
190
- const strips = ALL_SDKS.find((r) => r.version === v)?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
191
+ const release = ALL_SDKS.find((r) => r.version === v);
192
+ const strips = release?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
193
+ const archive = (base) => `${base}${release?.archiveSuffix ?? ""}.tar.gz`;
191
194
  if (platform7 === "linux") {
192
- return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip: strips.linuxWindows };
195
+ return { filename: archive("ohos-sdk-windows_linux-public"), osFolder: "linux", strip: strips.linuxWindows };
193
196
  }
194
197
  if (platform7 === "darwin") {
195
198
  if (os2.arch() === "arm64") {
196
- return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: strips.darwin };
199
+ return { filename: archive("L2-SDK-MAC-M1-PUBLIC"), osFolder: "darwin", strip: strips.darwin };
197
200
  }
198
- return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: strips.darwin };
201
+ return { filename: archive("ohos-sdk-mac-public"), osFolder: "darwin", strip: strips.darwin };
199
202
  }
200
203
  if (platform7 === "win32") {
201
- return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip: strips.linuxWindows };
204
+ return { filename: archive("ohos-sdk-windows_linux-public"), osFolder: "windows", strip: strips.linuxWindows };
202
205
  }
203
206
  throw new UnsupportedPlatformError(platform7);
204
207
  }
@@ -2260,19 +2263,39 @@ async function isReachable(config, deviceSerial) {
2260
2263
  }
2261
2264
  }
2262
2265
  var TCP_SERIAL_RE = /^.+:\d+$/;
2263
- async function reconnectIfTcp(config, deviceSerial, logger) {
2264
- if (!deviceSerial || !TCP_SERIAL_RE.test(deviceSerial)) return;
2265
- try {
2266
- await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
2267
- } catch {
2268
- }
2266
+ var WEDGED_POLLS_BEFORE_RESTART = 3;
2267
+ function tcpReconnector(config, deviceSerial, logger) {
2268
+ let wedgedPolls = 0;
2269
+ return async () => {
2270
+ if (!deviceSerial || !TCP_SERIAL_RE.test(deviceSerial)) return;
2271
+ let claimsConnected = false;
2272
+ try {
2273
+ const res = await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
2274
+ claimsConnected = /already connected|repeat operation/i.test(`${res.stdout}
2275
+ ${res.stderr}`);
2276
+ } catch {
2277
+ }
2278
+ if (!claimsConnected || await isReachable(config, deviceSerial)) {
2279
+ wedgedPolls = 0;
2280
+ return;
2281
+ }
2282
+ if (++wedgedPolls < WEDGED_POLLS_BEFORE_RESTART) return;
2283
+ wedgedPolls = 0;
2284
+ logger.warn(`hdc reports ${deviceSerial} as connected but it does not answer; restarting the hdc server.`);
2285
+ try {
2286
+ await hdcExec({ config, args: ["start", "-r"], timeoutMs: 15e3, logger });
2287
+ await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
2288
+ } catch {
2289
+ }
2290
+ };
2269
2291
  }
2270
2292
  async function waitForBundle(opts) {
2271
2293
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2272
2294
  const timeoutMs = opts.timeoutMs ?? 18e4;
2295
+ const reconnectIfTcp = tcpReconnector(opts.config, opts.deviceSerial, logger);
2273
2296
  await waitForCondition({
2274
2297
  probe: async () => {
2275
- await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
2298
+ await reconnectIfTcp();
2276
2299
  return await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
2277
2300
  },
2278
2301
  timeoutMs,
@@ -2285,9 +2308,10 @@ async function waitForBundle(opts) {
2285
2308
  async function waitForBoot(opts) {
2286
2309
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2287
2310
  const timeoutMs = opts.timeoutMs ?? 18e4;
2311
+ const reconnectIfTcp = tcpReconnector(opts.config, opts.deviceSerial, logger);
2288
2312
  await waitForCondition({
2289
2313
  probe: async () => {
2290
- await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
2314
+ await reconnectIfTcp();
2291
2315
  if (!await isReachable(opts.config, opts.deviceSerial)) return false;
2292
2316
  if (!opts.untilPidOf) return true;
2293
2317
  return await findRunningProcess({ config: opts.config, bundle: opts.untilPidOf, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
@@ -2391,6 +2415,55 @@ async function captureBurst(opts) {
2391
2415
  }
2392
2416
 
2393
2417
  // src/hdc/input.ts
2418
+ var UITEST_KEY_NAMES = ["Back", "Home", "Power"];
2419
+ var KEY_IDS = {
2420
+ back: 2,
2421
+ home: 1,
2422
+ power: 18,
2423
+ volumeup: 16,
2424
+ volumedown: 17,
2425
+ up: 2012,
2426
+ down: 2013,
2427
+ left: 2014,
2428
+ right: 2015,
2429
+ alt: 2045,
2430
+ shift: 2047,
2431
+ tab: 2049,
2432
+ space: 2050,
2433
+ enter: 2054,
2434
+ delete: 2055,
2435
+ backspace: 2055,
2436
+ pageup: 2068,
2437
+ pagedown: 2069,
2438
+ escape: 2070,
2439
+ esc: 2070,
2440
+ forwarddelete: 2071,
2441
+ ctrl: 2072,
2442
+ meta: 2076,
2443
+ movehome: 2081,
2444
+ moveend: 2082
2445
+ };
2446
+ function keyId(name) {
2447
+ const k = name.trim().toLowerCase();
2448
+ if (/^\d+$/.test(k)) return Number(k);
2449
+ if (/^[a-z]$/.test(k)) return 2017 + (k.charCodeAt(0) - 97);
2450
+ const id = KEY_IDS[k];
2451
+ if (id === void 0) {
2452
+ throw new OniroError(
2453
+ `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.`
2454
+ );
2455
+ }
2456
+ return id;
2457
+ }
2458
+ function resolveKeyEvent(key) {
2459
+ const spec = String(key).trim();
2460
+ if (spec === "") throw new OniroError('send input "keyEvent" requires a key.');
2461
+ const native = UITEST_KEY_NAMES.find((n) => n.toLowerCase() === spec.toLowerCase());
2462
+ if (native) return native;
2463
+ const parts = spec.includes("+") ? spec.split("+") : spec.split(/\s+/);
2464
+ if (parts.length > 3) throw new OniroError(`uitest injects at most three keys at once; got "${spec}".`);
2465
+ return parts.map(keyId).join(" ");
2466
+ }
2394
2467
  function reqNum(value, name, type) {
2395
2468
  if (value === void 0 || Number.isNaN(value)) {
2396
2469
  throw new OniroError(`send input "${type}" requires a numeric ${name}.`);
@@ -2411,7 +2484,7 @@ function buildInputCommand(opts) {
2411
2484
  }
2412
2485
  case "keyEvent":
2413
2486
  if (opts.key === void 0 || opts.key === "") throw new OniroError('send input "keyEvent" requires a key.');
2414
- return `uitest uiInput keyEvent ${opts.key}`;
2487
+ return `uitest uiInput keyEvent ${resolveKeyEvent(opts.key)}`;
2415
2488
  case "inputText": {
2416
2489
  const t = String(opts.text ?? "").replace(/'/g, `'\\''`);
2417
2490
  if (opts.pxX !== void 0 && opts.pxY !== void 0) {
@@ -2509,6 +2582,48 @@ import * as fs20 from "fs";
2509
2582
  import * as os10 from "os";
2510
2583
  import * as path20 from "path";
2511
2584
  import { randomBytes as randomBytes3 } from "crypto";
2585
+
2586
+ // src/hdc/ime.ts
2587
+ var INPUT_METHOD_SA_ID = 3703;
2588
+ var IME_BUNDLE_FALLBACK = /inputmethod|keyboard|(^|\.)ime(\.|$)/i;
2589
+ function parseImeBundles(output) {
2590
+ const current = [];
2591
+ const others = [];
2592
+ const entry = /\{[^{}]*"ime"\s*:\s*"([^"/]+)\/[^"]*"[^{}]*\}/g;
2593
+ for (const m of output.matchAll(entry)) {
2594
+ const bundle = m[1];
2595
+ const list = /"isCurrentIme"\s*:\s*"true"/.test(m[0]) ? current : others;
2596
+ if (!current.includes(bundle) && !others.includes(bundle)) list.push(bundle);
2597
+ }
2598
+ return [...current, ...others];
2599
+ }
2600
+ async function getImeBundles(config, opts = {}) {
2601
+ try {
2602
+ const res = await shell({
2603
+ config,
2604
+ command: `hidumper -s ${INPUT_METHOD_SA_ID} -a '-a'`,
2605
+ deviceSerial: opts.deviceSerial,
2606
+ timeoutMs: 1e4,
2607
+ logger: opts.logger
2608
+ });
2609
+ if (res.code === 0) return parseImeBundles(res.stdout);
2610
+ } catch (err) {
2611
+ opts.logger?.debug(`could not list input methods: ${err instanceof Error ? err.message : String(err)}`);
2612
+ }
2613
+ return [];
2614
+ }
2615
+ function findKeyboardWindow(tree, imeBundles) {
2616
+ const isIme = (bundle) => imeBundles.length > 0 ? imeBundles.includes(bundle) : IME_BUNDLE_FALLBACK.test(bundle);
2617
+ const stack = [tree];
2618
+ while (stack.length > 0) {
2619
+ const n = stack.shift();
2620
+ if (n.bundle && isIme(n.bundle)) return n;
2621
+ stack.push(...n.children ?? []);
2622
+ }
2623
+ return null;
2624
+ }
2625
+
2626
+ // src/hdc/dump.ts
2512
2627
  function parseBounds(s) {
2513
2628
  const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
2514
2629
  if (!m) return null;
@@ -2543,7 +2658,46 @@ var FLAG_MAP = [
2543
2658
  ["scrollable", "scroll"],
2544
2659
  ["longClickable", "longClick"]
2545
2660
  ];
2546
- function pruneLayout(node, display) {
2661
+ function colorAlpha(color) {
2662
+ const m = /^#([0-9a-f]{2})?[0-9a-f]{6}$/i.exec(color ?? "");
2663
+ if (!m) return 0;
2664
+ return m[1] === void 0 ? 255 : parseInt(m[1], 16);
2665
+ }
2666
+ function paintsFill(a) {
2667
+ if (a.visible === "false") return false;
2668
+ if (a.opacity !== void 0 && a.opacity !== "" && !(Number(a.opacity) > 0)) return false;
2669
+ return colorAlpha(a.backgroundColor) > 0;
2670
+ }
2671
+ function findCoveredNodes(root) {
2672
+ const order = [];
2673
+ const fills = [];
2674
+ const visit = (n) => {
2675
+ const a = n.attributes ?? {};
2676
+ const entry = { node: n, bounds: parseBounds(a.bounds), end: 0 };
2677
+ if (entry.bounds && paintsFill(a)) fills.push(order.length);
2678
+ order.push(entry);
2679
+ 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);
2680
+ for (const k of kids) visit(k.c);
2681
+ entry.end = order.length;
2682
+ };
2683
+ visit(root);
2684
+ const covered = /* @__PURE__ */ new Set();
2685
+ for (const p of order) {
2686
+ if (!p.bounds) continue;
2687
+ const cx = (p.bounds[0] + p.bounds[2]) / 2;
2688
+ const cy = (p.bounds[1] + p.bounds[3]) / 2;
2689
+ for (const j of fills) {
2690
+ if (j < p.end) continue;
2691
+ const q = order[j].bounds;
2692
+ if (cx >= q[0] && cx < q[2] && cy >= q[1] && cy < q[3]) {
2693
+ covered.add(p.node);
2694
+ break;
2695
+ }
2696
+ }
2697
+ }
2698
+ return covered;
2699
+ }
2700
+ function pruneLayout(node, display, covered) {
2547
2701
  const a = node.attributes ?? {};
2548
2702
  const px = parseBounds(a.bounds);
2549
2703
  const out = {};
@@ -2561,28 +2715,125 @@ function pruneLayout(node, display) {
2561
2715
  if (a.type && !STRUCTURAL_TYPES.has(a.type)) out.type = a.type;
2562
2716
  if (a.text) out.text = a.text;
2563
2717
  if (a.description) out.desc = a.description;
2718
+ if (a.hint) out.hint = a.hint;
2564
2719
  if (a.id) out.id = a.id;
2565
2720
  if (a.key && a.key !== a.id) out.key = a.key;
2566
2721
  if (a.bundleName) out.bundle = a.bundleName;
2567
2722
  for (const [attr, short] of FLAG_MAP) {
2568
2723
  if (a[attr] === "true") out[short] = true;
2569
2724
  }
2725
+ if (a.enabled === "false") out.disabled = true;
2726
+ if (covered?.has(node)) out.covered = true;
2570
2727
  const children = [];
2571
2728
  for (const c of node.children ?? []) {
2572
- const pc = pruneLayout(c, display);
2729
+ const pc = pruneLayout(c, display, covered);
2573
2730
  if (pc) {
2574
2731
  if (Array.isArray(pc)) children.push(...pc);
2575
2732
  else children.push(pc);
2576
2733
  }
2577
2734
  }
2578
2735
  const hasSignal = Boolean(
2579
- out.text || out.desc || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
2736
+ 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"
2580
2737
  );
2581
2738
  if (!hasSignal && children.length === 0) return null;
2582
2739
  if (!hasSignal) return children.length === 1 ? children[0] : children;
2583
2740
  if (children.length) out.children = children;
2584
2741
  return out;
2585
2742
  }
2743
+ function flattenLayout(tree) {
2744
+ const out = [];
2745
+ const visit = (n, window) => {
2746
+ const { children, ...rest } = n;
2747
+ const win = n.bundle ?? window;
2748
+ if (Object.keys(rest).length > 0) out.push({ ...rest, ...win ? { window: win } : {} });
2749
+ for (const c of children ?? []) visit(c, win);
2750
+ };
2751
+ visit(tree, void 0);
2752
+ return out;
2753
+ }
2754
+ function assertLayoutSelector(selector) {
2755
+ if (selector.text === void 0 && selector.id === void 0 && selector.type === void 0) {
2756
+ throw new OniroError("A layout selector needs a text, id, or type.");
2757
+ }
2758
+ if (selector.text !== void 0 && selector.text.trim() === "") {
2759
+ throw new OniroError("A layout selector text must not be blank.");
2760
+ }
2761
+ }
2762
+ function withoutWindow(tree, bundle) {
2763
+ const children = (tree.children ?? []).filter((c) => c.bundle !== bundle).map((c) => withoutWindow(c, bundle));
2764
+ return tree.children ? { ...tree, children } : tree;
2765
+ }
2766
+ function findLayoutNodes(tree, selector) {
2767
+ assertLayoutSelector(selector);
2768
+ const type = selector.type?.toLowerCase();
2769
+ const nodes = flattenLayout(tree).filter(
2770
+ (n) => (type === void 0 || n.type?.toLowerCase() === type) && (selector.id === void 0 || n.id === selector.id || n.key === selector.id)
2771
+ );
2772
+ if (selector.text === void 0) return nodes;
2773
+ const want = selector.text.trim().toLowerCase();
2774
+ const labels = (n) => [n.text, n.desc, n.hint].filter((s) => !!s).map((s) => s.trim().toLowerCase());
2775
+ const exact = nodes.filter((n) => labels(n).some((l) => l === want));
2776
+ if (exact.length > 0) return exact;
2777
+ return nodes.filter((n) => labels(n).some((l) => l.includes(want)));
2778
+ }
2779
+ function describeSelector(s) {
2780
+ const parts = [];
2781
+ if (s.text !== void 0) parts.push(`text "${s.text}"`);
2782
+ if (s.id !== void 0) parts.push(`id "${s.id}"`);
2783
+ if (s.type !== void 0) parts.push(`type ${s.type}`);
2784
+ return parts.join(", ");
2785
+ }
2786
+ function describeLayoutNode(n) {
2787
+ const label = n.text ?? n.desc ?? (n.hint !== void 0 ? `hint: ${n.hint}` : void 0);
2788
+ const at = n.c ? ` at ${n.c[0].toFixed(3)},${n.c[1].toFixed(3)}` : "";
2789
+ return `${n.type ?? "node"}${label !== void 0 ? ` "${label}"` : ""}${n.id ? ` #${n.id}` : ""}${at}`;
2790
+ }
2791
+ var area = (b) => b ? (b[2] - b[0]) * (b[3] - b[1]) : 0;
2792
+ function enclosesTarget(outer, inner) {
2793
+ const b = outer.b;
2794
+ const c = inner.c;
2795
+ if (!b || !c) return false;
2796
+ return c[0] >= b[0] && c[0] <= b[2] && c[1] >= b[1] && c[1] <= b[3] && area(b) > area(inner.b);
2797
+ }
2798
+ var TargetNotFoundError = class extends OniroError {
2799
+ constructor(message, reason) {
2800
+ super(message);
2801
+ this.reason = reason;
2802
+ this.name = "TargetNotFoundError";
2803
+ }
2804
+ reason;
2805
+ };
2806
+ function pickTarget(tree, selector, index) {
2807
+ const matches = findLayoutNodes(tree, selector).filter((n) => n.c !== void 0);
2808
+ const what = describeSelector(selector);
2809
+ if (matches.length === 0) {
2810
+ throw new TargetNotFoundError(`No on-screen element matches ${what}. Run \`oniro-app dump layout --flat\` to see what is there.`, "none");
2811
+ }
2812
+ const visible = matches.filter((n) => !n.covered);
2813
+ if (visible.length === 0) {
2814
+ throw new TargetNotFoundError(
2815
+ `Found ${matches.length} element(s) matching ${what}, but each is covered by an overlay (dialog, keyboard, or another layer): ${matches.map(describeLayoutNode).join("; ")}.`,
2816
+ "covered"
2817
+ );
2818
+ }
2819
+ const clickable = visible.filter((n) => n.click);
2820
+ const preferred = clickable.length > 0 ? clickable : visible;
2821
+ const candidates = preferred.filter((n) => !preferred.some((o) => o !== n && o.click && enclosesTarget(o, n)));
2822
+ if (index !== void 0) {
2823
+ const picked = candidates[index];
2824
+ if (!picked) {
2825
+ throw new TargetNotFoundError(`Index ${index} is out of range: ${candidates.length} element(s) match ${what}.`, "ambiguous");
2826
+ }
2827
+ return picked;
2828
+ }
2829
+ if (candidates.length > 1) {
2830
+ throw new TargetNotFoundError(
2831
+ `${candidates.length} elements match ${what}; pass an index to choose: ` + candidates.map((n, i) => `[${i}] ${describeLayoutNode(n)}`).join("; "),
2832
+ "ambiguous"
2833
+ );
2834
+ }
2835
+ return candidates[0];
2836
+ }
2586
2837
  async function dumpLayout(opts) {
2587
2838
  const timeoutMs = opts.timeoutMs ?? 3e4;
2588
2839
  const bundleArg = opts.bundleName ? ` -b '${opts.bundleName.replace(/'/g, `'\\''`)}'` : "";
@@ -2600,9 +2851,13 @@ ${dump.stdout}`);
2600
2851
  if (opts.prune === false) {
2601
2852
  return { display, tree: parsed };
2602
2853
  }
2603
- const prunedRaw = pruneLayout(parsed, display);
2854
+ const prunedRaw = pruneLayout(parsed, display, findCoveredNodes(parsed));
2604
2855
  const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
2605
- return { display, tree };
2856
+ if (!opts.detectKeyboard || opts.bundleName) return { display, tree };
2857
+ const imeBundles = await getImeBundles(opts.config, { deviceSerial: opts.deviceSerial, logger: opts.logger });
2858
+ const kb = findKeyboardWindow(tree, imeBundles);
2859
+ const keyboard = kb ? { shown: true, bundle: kb.bundle, ...kb.b ? { b: kb.b } : {} } : { shown: false };
2860
+ return { display, tree, keyboard };
2606
2861
  } finally {
2607
2862
  fs20.promises.unlink(local).catch(() => {
2608
2863
  });
@@ -2611,6 +2866,161 @@ ${dump.stdout}`);
2611
2866
  }
2612
2867
  }
2613
2868
 
2869
+ // src/hdc/uiActions.ts
2870
+ var FOCUS_SETTLE_MS = 400;
2871
+ var KEYBOARD_SETTLE_MS = 400;
2872
+ var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
2873
+ function targetTree(layout) {
2874
+ const kb = layout.keyboard;
2875
+ return kb?.shown && kb.bundle ? withoutWindow(layout.tree, kb.bundle) : layout.tree;
2876
+ }
2877
+ function fractionToPixel(f, size) {
2878
+ return Math.min(size - 1, Math.round(f * size));
2879
+ }
2880
+ function pressBack(opts) {
2881
+ return sendInput({ config: opts.config, type: "keyEvent", key: "Back", deviceSerial: opts.deviceSerial, logger: opts.logger });
2882
+ }
2883
+ async function hideKeyboard(opts) {
2884
+ const { keyboard } = await dumpLayout({ ...opts, detectKeyboard: true });
2885
+ if (!keyboard?.shown) return false;
2886
+ await pressBack(opts);
2887
+ await sleep(KEYBOARD_SETTLE_MS);
2888
+ return true;
2889
+ }
2890
+ async function locateTarget(opts) {
2891
+ assertLayoutSelector(opts.selector);
2892
+ const dumpOpts = { config: opts.config, detectKeyboard: true, deviceSerial: opts.deviceSerial, logger: opts.logger };
2893
+ let layout = await dumpLayout(dumpOpts);
2894
+ if (!layout.display) throw new OniroError("Could not read the display size to locate the element.");
2895
+ let keyboardHidden = false;
2896
+ let node;
2897
+ let failure;
2898
+ try {
2899
+ node = pickTarget(targetTree(layout), opts.selector, opts.index);
2900
+ } catch (err) {
2901
+ if (!(err instanceof TargetNotFoundError) || err.reason === "ambiguous") throw err;
2902
+ failure = err;
2903
+ }
2904
+ if ((!node || !node.click) && layout.keyboard?.shown && opts.hideKeyboard !== false) {
2905
+ opts.logger?.debug("target not reachable while the keyboard is up; dismissing it and looking again");
2906
+ await pressBack(opts);
2907
+ await sleep(KEYBOARD_SETTLE_MS);
2908
+ keyboardHidden = true;
2909
+ layout = await dumpLayout(dumpOpts);
2910
+ if (!layout.display) throw new OniroError("Could not read the display size to locate the element.");
2911
+ node = pickTarget(targetTree(layout), opts.selector, opts.index);
2912
+ }
2913
+ if (!node) throw failure;
2914
+ if (!node.c) throw new OniroError("The element has no on-screen position.");
2915
+ const x2 = fractionToPixel(node.c[0], layout.display.width);
2916
+ const y = fractionToPixel(node.c[1], layout.display.height);
2917
+ return { node, x: x2, y, display: layout.display, keyboardHidden };
2918
+ }
2919
+ function fraction(value, name) {
2920
+ if (value === void 0) return void 0;
2921
+ if (!(value >= 0 && value <= 1)) throw new OniroError(`${name} must be a fraction between 0 and 1; got ${value}.`);
2922
+ return value;
2923
+ }
2924
+ function pair(a, b, names) {
2925
+ if (a === void 0 && b === void 0) return void 0;
2926
+ if (a === void 0 || b === void 0) throw new OniroError(`Give both ${names}.`);
2927
+ return [a, b];
2928
+ }
2929
+ async function performInput(opts) {
2930
+ const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
2931
+ const startFx = pair(fraction(opts.fx, "--fx"), fraction(opts.fy, "--fy"), "--fx and --fy");
2932
+ const startPx = pair(opts.pxX, opts.pxY, "--x and --y");
2933
+ const endFx = pair(fraction(opts.fx2, "--fx2"), fraction(opts.fy2, "--fy2"), "--fx2 and --fy2");
2934
+ const endPx = pair(opts.pxX2, opts.pxY2, "--x2 and --y2");
2935
+ if ([opts.target, startFx, startPx].filter((s) => s !== void 0).length > 1) {
2936
+ throw new OniroError("Give the start point one way: a target, fractions (--fx/--fy), or pixels (--x/--y).");
2937
+ }
2938
+ if (endFx && endPx) throw new OniroError("Give the end point one way: fractions (--fx2/--fy2) or pixels (--x2/--y2).");
2939
+ let display;
2940
+ const toPx = async ([fx, fy]) => {
2941
+ display ??= await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial, logger: opts.logger });
2942
+ if (!display) throw new OniroError("Could not read the display size to convert fractions to pixels.");
2943
+ return [fractionToPixel(fx, display.width), fractionToPixel(fy, display.height)];
2944
+ };
2945
+ const result = { keyboardHidden: false };
2946
+ let start = startPx;
2947
+ if (opts.target) {
2948
+ const located = await locateTarget({ ...device, selector: opts.target, index: opts.targetIndex });
2949
+ display = located.display;
2950
+ start = [located.x, located.y];
2951
+ result.target = located.node;
2952
+ result.keyboardHidden = located.keyboardHidden;
2953
+ } else if (startFx) {
2954
+ start = await toPx(startFx);
2955
+ }
2956
+ const end = endFx ? await toPx(endFx) : endPx;
2957
+ if (start) [result.x, result.y] = start;
2958
+ const tapToFocus = async () => {
2959
+ if (!start) return;
2960
+ await sendInput({ ...device, type: "click", pxX: start[0], pxY: start[1] });
2961
+ await sleep(FOCUS_SETTLE_MS);
2962
+ };
2963
+ const clearFocused = async () => {
2964
+ await sendInput({ ...device, type: "keyEvent", key: "Ctrl+A" });
2965
+ await sendInput({ ...device, type: "keyEvent", key: "Delete" });
2966
+ };
2967
+ switch (opts.type) {
2968
+ case "hideKeyboard":
2969
+ result.keyboardHidden = await hideKeyboard(device) || result.keyboardHidden;
2970
+ return result;
2971
+ case "clearText":
2972
+ await tapToFocus();
2973
+ await clearFocused();
2974
+ break;
2975
+ case "inputText":
2976
+ if (opts.clear) {
2977
+ await tapToFocus();
2978
+ await clearFocused();
2979
+ await sendInput({ ...device, type: "inputText", text: opts.text });
2980
+ } else {
2981
+ await sendInput({ ...device, type: "inputText", pxX: start?.[0], pxY: start?.[1], text: opts.text });
2982
+ }
2983
+ break;
2984
+ default:
2985
+ await sendInput({
2986
+ ...device,
2987
+ type: opts.type,
2988
+ pxX: start?.[0],
2989
+ pxY: start?.[1],
2990
+ pxX2: end?.[0],
2991
+ pxY2: end?.[1],
2992
+ speed: opts.speed,
2993
+ key: opts.key,
2994
+ text: opts.text
2995
+ });
2996
+ }
2997
+ if (opts.hideKeyboard) result.keyboardHidden = await hideKeyboard(device) || result.keyboardHidden;
2998
+ return result;
2999
+ }
3000
+ async function waitForTarget(opts) {
3001
+ const { selector } = opts;
3002
+ assertLayoutSelector(selector);
3003
+ let found = null;
3004
+ await waitForCondition({
3005
+ probe: async () => {
3006
+ const layout = await dumpLayout({
3007
+ config: opts.config,
3008
+ detectKeyboard: true,
3009
+ deviceSerial: opts.deviceSerial,
3010
+ logger: opts.logger
3011
+ });
3012
+ found = findLayoutNodes(targetTree(layout), selector).find((n) => !n.covered) ?? null;
3013
+ return opts.gone ? found === null : found !== null;
3014
+ },
3015
+ timeoutMs: opts.timeoutMs,
3016
+ pollMs: opts.pollMs ?? 500,
3017
+ abortSignal: opts.abortSignal,
3018
+ logger: opts.logger,
3019
+ 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.`
3020
+ });
3021
+ return opts.gone ? null : found;
3022
+ }
3023
+
2614
3024
  // src/hdc/hilog.ts
2615
3025
  function setHilogLevel(opts) {
2616
3026
  const logger = opts.logger ?? noopLogger;
@@ -4743,7 +5153,7 @@ async function harmonyOsAutoSign(opts) {
4743
5153
  }
4744
5154
 
4745
5155
  // src/index.ts
4746
- var VERSION = "0.11.0";
5156
+ var VERSION = "0.13.0";
4747
5157
  export {
4748
5158
  ALL_SDKS,
4749
5159
  APL_VALUES,
@@ -4762,9 +5172,11 @@ export {
4762
5172
  SIGNING_ERRORS,
4763
5173
  SdkNotInstalledError,
4764
5174
  TMP_DIR_HINT,
5175
+ TargetNotFoundError,
4765
5176
  UnsupportedPlatformError,
4766
5177
  VERSION,
4767
5178
  applyChanges,
5179
+ assertLayoutSelector,
4768
5180
  attemptHdcConnection,
4769
5181
  buildCmdWrapper,
4770
5182
  buildGestureCommands,
@@ -4787,6 +5199,7 @@ export {
4787
5199
  dedupEntries,
4788
5200
  defaultContext,
4789
5201
  defaultPaths,
5202
+ describeLayoutNode,
4790
5203
  detectAaStartFailure,
4791
5204
  detectProjectSdkVersion,
4792
5205
  detectRuntimeOs,
@@ -4812,8 +5225,12 @@ export {
4812
5225
  extractZipWithProgress,
4813
5226
  findAppProcessId,
4814
5227
  findCmdToolsSourceDir,
5228
+ findCoveredNodes,
4815
5229
  findHarmonyOsSdk,
5230
+ findKeyboardWindow,
5231
+ findLayoutNodes,
4816
5232
  findRunningProcess,
5233
+ flattenLayout,
4817
5234
  forceStop,
4818
5235
  formatBytes,
4819
5236
  generateSigningConfigs,
@@ -4828,6 +5245,7 @@ export {
4828
5245
  getFreeSpaceBytes,
4829
5246
  getHdcPath,
4830
5247
  getHvigorwPath,
5248
+ getImeBundles,
4831
5249
  getInstalledSdks,
4832
5250
  getKey,
4833
5251
  getMainAbility,
@@ -4840,6 +5258,7 @@ export {
4840
5258
  harmonyOsAutoSign,
4841
5259
  harmonyOsSdkLabelForApi,
4842
5260
  hdcExec,
5261
+ hideKeyboard,
4843
5262
  installApp,
4844
5263
  installCmdTools,
4845
5264
  installEmulator,
@@ -4856,6 +5275,7 @@ export {
4856
5275
  listModules,
4857
5276
  listRunningProcesses,
4858
5277
  listTemplates,
5278
+ locateTarget,
4859
5279
  needsCmdWrapper,
4860
5280
  nonInteractivePrompter,
4861
5281
  noopLogger,
@@ -4866,9 +5286,12 @@ export {
4866
5286
  parseCodelinterFindings,
4867
5287
  parseDeviceList,
4868
5288
  parseHilogLine,
5289
+ parseImeBundles,
4869
5290
  parseMainAbilityFromBmDump,
4870
5291
  parseRemoteExit,
5292
+ performInput,
4871
5293
  pickSigningKind,
5294
+ pickTarget,
4872
5295
  prepareSigning,
4873
5296
  pruneLayout,
4874
5297
  readJson5File,
@@ -4881,6 +5304,7 @@ export {
4881
5304
  removeSdk,
4882
5305
  removeTempWorkDir,
4883
5306
  requireHarmonyOsSdk,
5307
+ resolveKeyEvent,
4884
5308
  resolveTmpRoot,
4885
5309
  runCodelinter,
4886
5310
  runHvigorw,
@@ -4910,8 +5334,10 @@ export {
4910
5334
  waitForBundle,
4911
5335
  waitForCondition,
4912
5336
  waitForLog,
5337
+ waitForTarget,
4913
5338
  watchLog,
4914
5339
  waypointsToRawEvents,
5340
+ withoutWindow,
4915
5341
  writeJson5File,
4916
5342
  writeJsonFile
4917
5343
  };