@oniroproject/core 0.12.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/README.md +10 -4
- package/dist/index.cjs +425 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +209 -4
- package/dist/index.d.ts +209 -4
- package/dist/index.js +409 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2415,6 +2415,55 @@ async function captureBurst(opts) {
|
|
|
2415
2415
|
}
|
|
2416
2416
|
|
|
2417
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
|
+
}
|
|
2418
2467
|
function reqNum(value, name, type) {
|
|
2419
2468
|
if (value === void 0 || Number.isNaN(value)) {
|
|
2420
2469
|
throw new OniroError(`send input "${type}" requires a numeric ${name}.`);
|
|
@@ -2435,7 +2484,7 @@ function buildInputCommand(opts) {
|
|
|
2435
2484
|
}
|
|
2436
2485
|
case "keyEvent":
|
|
2437
2486
|
if (opts.key === void 0 || opts.key === "") throw new OniroError('send input "keyEvent" requires a key.');
|
|
2438
|
-
return `uitest uiInput keyEvent ${opts.key}`;
|
|
2487
|
+
return `uitest uiInput keyEvent ${resolveKeyEvent(opts.key)}`;
|
|
2439
2488
|
case "inputText": {
|
|
2440
2489
|
const t = String(opts.text ?? "").replace(/'/g, `'\\''`);
|
|
2441
2490
|
if (opts.pxX !== void 0 && opts.pxY !== void 0) {
|
|
@@ -2533,6 +2582,48 @@ import * as fs20 from "fs";
|
|
|
2533
2582
|
import * as os10 from "os";
|
|
2534
2583
|
import * as path20 from "path";
|
|
2535
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
|
|
2536
2627
|
function parseBounds(s) {
|
|
2537
2628
|
const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
|
|
2538
2629
|
if (!m) return null;
|
|
@@ -2567,7 +2658,46 @@ var FLAG_MAP = [
|
|
|
2567
2658
|
["scrollable", "scroll"],
|
|
2568
2659
|
["longClickable", "longClick"]
|
|
2569
2660
|
];
|
|
2570
|
-
function
|
|
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) {
|
|
2571
2701
|
const a = node.attributes ?? {};
|
|
2572
2702
|
const px = parseBounds(a.bounds);
|
|
2573
2703
|
const out = {};
|
|
@@ -2585,28 +2715,125 @@ function pruneLayout(node, display) {
|
|
|
2585
2715
|
if (a.type && !STRUCTURAL_TYPES.has(a.type)) out.type = a.type;
|
|
2586
2716
|
if (a.text) out.text = a.text;
|
|
2587
2717
|
if (a.description) out.desc = a.description;
|
|
2718
|
+
if (a.hint) out.hint = a.hint;
|
|
2588
2719
|
if (a.id) out.id = a.id;
|
|
2589
2720
|
if (a.key && a.key !== a.id) out.key = a.key;
|
|
2590
2721
|
if (a.bundleName) out.bundle = a.bundleName;
|
|
2591
2722
|
for (const [attr, short] of FLAG_MAP) {
|
|
2592
2723
|
if (a[attr] === "true") out[short] = true;
|
|
2593
2724
|
}
|
|
2725
|
+
if (a.enabled === "false") out.disabled = true;
|
|
2726
|
+
if (covered?.has(node)) out.covered = true;
|
|
2594
2727
|
const children = [];
|
|
2595
2728
|
for (const c of node.children ?? []) {
|
|
2596
|
-
const pc = pruneLayout(c, display);
|
|
2729
|
+
const pc = pruneLayout(c, display, covered);
|
|
2597
2730
|
if (pc) {
|
|
2598
2731
|
if (Array.isArray(pc)) children.push(...pc);
|
|
2599
2732
|
else children.push(pc);
|
|
2600
2733
|
}
|
|
2601
2734
|
}
|
|
2602
2735
|
const hasSignal = Boolean(
|
|
2603
|
-
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"
|
|
2604
2737
|
);
|
|
2605
2738
|
if (!hasSignal && children.length === 0) return null;
|
|
2606
2739
|
if (!hasSignal) return children.length === 1 ? children[0] : children;
|
|
2607
2740
|
if (children.length) out.children = children;
|
|
2608
2741
|
return out;
|
|
2609
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
|
+
}
|
|
2610
2837
|
async function dumpLayout(opts) {
|
|
2611
2838
|
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
2612
2839
|
const bundleArg = opts.bundleName ? ` -b '${opts.bundleName.replace(/'/g, `'\\''`)}'` : "";
|
|
@@ -2624,9 +2851,13 @@ ${dump.stdout}`);
|
|
|
2624
2851
|
if (opts.prune === false) {
|
|
2625
2852
|
return { display, tree: parsed };
|
|
2626
2853
|
}
|
|
2627
|
-
const prunedRaw = pruneLayout(parsed, display);
|
|
2854
|
+
const prunedRaw = pruneLayout(parsed, display, findCoveredNodes(parsed));
|
|
2628
2855
|
const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
|
|
2629
|
-
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 };
|
|
2630
2861
|
} finally {
|
|
2631
2862
|
fs20.promises.unlink(local).catch(() => {
|
|
2632
2863
|
});
|
|
@@ -2635,6 +2866,161 @@ ${dump.stdout}`);
|
|
|
2635
2866
|
}
|
|
2636
2867
|
}
|
|
2637
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
|
+
|
|
2638
3024
|
// src/hdc/hilog.ts
|
|
2639
3025
|
function setHilogLevel(opts) {
|
|
2640
3026
|
const logger = opts.logger ?? noopLogger;
|
|
@@ -4767,7 +5153,7 @@ async function harmonyOsAutoSign(opts) {
|
|
|
4767
5153
|
}
|
|
4768
5154
|
|
|
4769
5155
|
// src/index.ts
|
|
4770
|
-
var VERSION = "0.
|
|
5156
|
+
var VERSION = "0.13.0";
|
|
4771
5157
|
export {
|
|
4772
5158
|
ALL_SDKS,
|
|
4773
5159
|
APL_VALUES,
|
|
@@ -4786,9 +5172,11 @@ export {
|
|
|
4786
5172
|
SIGNING_ERRORS,
|
|
4787
5173
|
SdkNotInstalledError,
|
|
4788
5174
|
TMP_DIR_HINT,
|
|
5175
|
+
TargetNotFoundError,
|
|
4789
5176
|
UnsupportedPlatformError,
|
|
4790
5177
|
VERSION,
|
|
4791
5178
|
applyChanges,
|
|
5179
|
+
assertLayoutSelector,
|
|
4792
5180
|
attemptHdcConnection,
|
|
4793
5181
|
buildCmdWrapper,
|
|
4794
5182
|
buildGestureCommands,
|
|
@@ -4811,6 +5199,7 @@ export {
|
|
|
4811
5199
|
dedupEntries,
|
|
4812
5200
|
defaultContext,
|
|
4813
5201
|
defaultPaths,
|
|
5202
|
+
describeLayoutNode,
|
|
4814
5203
|
detectAaStartFailure,
|
|
4815
5204
|
detectProjectSdkVersion,
|
|
4816
5205
|
detectRuntimeOs,
|
|
@@ -4836,8 +5225,12 @@ export {
|
|
|
4836
5225
|
extractZipWithProgress,
|
|
4837
5226
|
findAppProcessId,
|
|
4838
5227
|
findCmdToolsSourceDir,
|
|
5228
|
+
findCoveredNodes,
|
|
4839
5229
|
findHarmonyOsSdk,
|
|
5230
|
+
findKeyboardWindow,
|
|
5231
|
+
findLayoutNodes,
|
|
4840
5232
|
findRunningProcess,
|
|
5233
|
+
flattenLayout,
|
|
4841
5234
|
forceStop,
|
|
4842
5235
|
formatBytes,
|
|
4843
5236
|
generateSigningConfigs,
|
|
@@ -4852,6 +5245,7 @@ export {
|
|
|
4852
5245
|
getFreeSpaceBytes,
|
|
4853
5246
|
getHdcPath,
|
|
4854
5247
|
getHvigorwPath,
|
|
5248
|
+
getImeBundles,
|
|
4855
5249
|
getInstalledSdks,
|
|
4856
5250
|
getKey,
|
|
4857
5251
|
getMainAbility,
|
|
@@ -4864,6 +5258,7 @@ export {
|
|
|
4864
5258
|
harmonyOsAutoSign,
|
|
4865
5259
|
harmonyOsSdkLabelForApi,
|
|
4866
5260
|
hdcExec,
|
|
5261
|
+
hideKeyboard,
|
|
4867
5262
|
installApp,
|
|
4868
5263
|
installCmdTools,
|
|
4869
5264
|
installEmulator,
|
|
@@ -4880,6 +5275,7 @@ export {
|
|
|
4880
5275
|
listModules,
|
|
4881
5276
|
listRunningProcesses,
|
|
4882
5277
|
listTemplates,
|
|
5278
|
+
locateTarget,
|
|
4883
5279
|
needsCmdWrapper,
|
|
4884
5280
|
nonInteractivePrompter,
|
|
4885
5281
|
noopLogger,
|
|
@@ -4890,9 +5286,12 @@ export {
|
|
|
4890
5286
|
parseCodelinterFindings,
|
|
4891
5287
|
parseDeviceList,
|
|
4892
5288
|
parseHilogLine,
|
|
5289
|
+
parseImeBundles,
|
|
4893
5290
|
parseMainAbilityFromBmDump,
|
|
4894
5291
|
parseRemoteExit,
|
|
5292
|
+
performInput,
|
|
4895
5293
|
pickSigningKind,
|
|
5294
|
+
pickTarget,
|
|
4896
5295
|
prepareSigning,
|
|
4897
5296
|
pruneLayout,
|
|
4898
5297
|
readJson5File,
|
|
@@ -4905,6 +5304,7 @@ export {
|
|
|
4905
5304
|
removeSdk,
|
|
4906
5305
|
removeTempWorkDir,
|
|
4907
5306
|
requireHarmonyOsSdk,
|
|
5307
|
+
resolveKeyEvent,
|
|
4908
5308
|
resolveTmpRoot,
|
|
4909
5309
|
runCodelinter,
|
|
4910
5310
|
runHvigorw,
|
|
@@ -4934,8 +5334,10 @@ export {
|
|
|
4934
5334
|
waitForBundle,
|
|
4935
5335
|
waitForCondition,
|
|
4936
5336
|
waitForLog,
|
|
5337
|
+
waitForTarget,
|
|
4937
5338
|
watchLog,
|
|
4938
5339
|
waypointsToRawEvents,
|
|
5340
|
+
withoutWindow,
|
|
4939
5341
|
writeJson5File,
|
|
4940
5342
|
writeJsonFile
|
|
4941
5343
|
};
|