obsidian-integration-testing 12.1.0 → 12.1.1

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.
Files changed (43) hide show
  1. package/dist/lib/cjs/adb.cjs +61 -0
  2. package/dist/lib/cjs/adb.d.cts +61 -0
  3. package/dist/lib/cjs/device-screenshot.cjs +42 -0
  4. package/dist/lib/cjs/device-screenshot.d.cts +36 -0
  5. package/dist/lib/cjs/device-setting-restore.cjs +45 -0
  6. package/dist/lib/cjs/device-setting-restore.d.cts +60 -0
  7. package/dist/lib/cjs/device-settings-command.cjs +49 -0
  8. package/dist/lib/cjs/device-settings-command.d.cts +76 -0
  9. package/dist/lib/cjs/device-settings.cjs +88 -0
  10. package/dist/lib/cjs/device-settings.d.cts +94 -0
  11. package/dist/lib/cjs/emulator-device-id.cjs +32 -0
  12. package/dist/lib/cjs/emulator-device-id.d.cts +49 -0
  13. package/dist/lib/cjs/index.cjs +56 -3
  14. package/dist/lib/cjs/index.d.cts +18 -0
  15. package/dist/lib/cjs/library.cjs +1 -1
  16. package/dist/lib/cjs/resolve-emulator-device-id.cjs +54 -0
  17. package/dist/lib/cjs/resolve-emulator-device-id.d.cts +28 -0
  18. package/dist/lib/cjs/soft-keyboard-geometry.cjs +75 -0
  19. package/dist/lib/cjs/soft-keyboard-geometry.d.cts +156 -0
  20. package/dist/lib/cjs/soft-keyboard.cjs +118 -0
  21. package/dist/lib/cjs/soft-keyboard.d.cts +82 -0
  22. package/dist/lib/esm/adb.d.mts +61 -0
  23. package/dist/lib/esm/adb.mjs +35 -0
  24. package/dist/lib/esm/device-screenshot.d.mts +36 -0
  25. package/dist/lib/esm/device-screenshot.mjs +18 -0
  26. package/dist/lib/esm/device-setting-restore.d.mts +60 -0
  27. package/dist/lib/esm/device-setting-restore.mjs +20 -0
  28. package/dist/lib/esm/device-settings-command.d.mts +76 -0
  29. package/dist/lib/esm/device-settings-command.mjs +23 -0
  30. package/dist/lib/esm/device-settings.d.mts +94 -0
  31. package/dist/lib/esm/device-settings.mjs +67 -0
  32. package/dist/lib/esm/emulator-device-id.d.mts +49 -0
  33. package/dist/lib/esm/emulator-device-id.mjs +8 -0
  34. package/dist/lib/esm/index.d.mts +18 -0
  35. package/dist/lib/esm/index.mjs +58 -2
  36. package/dist/lib/esm/library.mjs +1 -1
  37. package/dist/lib/esm/resolve-emulator-device-id.d.mts +28 -0
  38. package/dist/lib/esm/resolve-emulator-device-id.mjs +33 -0
  39. package/dist/lib/esm/soft-keyboard-geometry.d.mts +156 -0
  40. package/dist/lib/esm/soft-keyboard-geometry.mjs +47 -0
  41. package/dist/lib/esm/soft-keyboard.d.mts +82 -0
  42. package/dist/lib/esm/soft-keyboard.mjs +91 -0
  43. package/package.json +4 -3
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @file
3
+ *
4
+ * Decides whether the Android soft keyboard is up, and where to touch to raise
5
+ * it — from geometry alone.
6
+ *
7
+ * **Nothing in the page reports the keyboard.** `innerHeight`, `visualViewport`
8
+ * and the modal container all stay at their full height with the keyboard shown
9
+ * and `dumpsys input_method` reporting `mInputShown=true` — Obsidian Mobile keeps
10
+ * a full-screen container and lifts its contents inside it. The only signal is
11
+ * the field's own offset from the bottom, which is why the detection here is
12
+ * geometric rather than an API call. Two runs failed on that before a framebuffer
13
+ * dump said so, which is also why the diagnostic below exists.
14
+ *
15
+ * Pure and unit-tested; the touching and the capture live in `soft-keyboard`.
16
+ */
17
+ /**
18
+ * Parameters for {@link buildSoftKeyboardDiagnosticMessage}.
19
+ */
20
+ export interface BuildSoftKeyboardDiagnosticMessageParams {
21
+ /**
22
+ * The lines of `dumpsys input_method` worth reading, as {@link parseInputMethodState} returned them.
23
+ */
24
+ readonly inputMethodState: string;
25
+ /**
26
+ * Where the device framebuffer was written, so the reader can look at what the device was showing.
27
+ */
28
+ readonly screenshotPath: string;
29
+ /**
30
+ * The geometry read after the last touch.
31
+ */
32
+ readonly snapshot: SoftKeyboardViewportSnapshot;
33
+ }
34
+ /**
35
+ * Parameters for {@link checkIsSoftKeyboardUp}.
36
+ */
37
+ export interface CheckIsSoftKeyboardUpParams {
38
+ /**
39
+ * The least a raised keyboard lifts the field by, so a stray rounding pixel is not read as one.
40
+ *
41
+ * @default {@link DEFAULT_MINIMUM_KEYBOARD_HEIGHT_IN_PIXELS}
42
+ */
43
+ readonly minimumKeyboardHeightInPixels?: number;
44
+ /**
45
+ * The geometry to judge.
46
+ */
47
+ readonly snapshot: SoftKeyboardViewportSnapshot;
48
+ }
49
+ /**
50
+ * A rectangle as the renderer reports it, in CSS pixels.
51
+ */
52
+ export interface ElementRect {
53
+ /**
54
+ * The rectangle's height.
55
+ */
56
+ readonly height: number;
57
+ /**
58
+ * The distance from the viewport's left edge.
59
+ */
60
+ readonly left: number;
61
+ /**
62
+ * The distance from the viewport's top edge.
63
+ */
64
+ readonly top: number;
65
+ /**
66
+ * The rectangle's width.
67
+ */
68
+ readonly width: number;
69
+ }
70
+ /**
71
+ * Parameters for {@link resolveSoftKeyboardTapPoints}.
72
+ */
73
+ export interface ResolveSoftKeyboardTapPointsParams {
74
+ /**
75
+ * The geometry the tap is aimed from.
76
+ */
77
+ readonly snapshot: SoftKeyboardViewportSnapshot;
78
+ }
79
+ /**
80
+ * A point on the device's screen, in device pixels.
81
+ */
82
+ export interface SoftKeyboardTapPoint {
83
+ /**
84
+ * The distance from the screen's left edge.
85
+ */
86
+ readonly xInPixels: number;
87
+ /**
88
+ * The distance from the screen's top edge.
89
+ */
90
+ readonly yInPixels: number;
91
+ }
92
+ /**
93
+ * What the renderer knows about its own geometry.
94
+ */
95
+ export interface SoftKeyboardViewportSnapshot {
96
+ /**
97
+ * The ratio between CSS pixels and the device pixels `adb shell input tap` speaks in.
98
+ */
99
+ readonly devicePixelRatio: number;
100
+ /**
101
+ * The viewport's height in CSS pixels.
102
+ *
103
+ * Kept for the diagnostic as much as the decision: it does **not** shrink when the keyboard comes up.
104
+ */
105
+ readonly innerHeight: number;
106
+ /**
107
+ * The field's rect, which is what says whether the keyboard is up, or `null` when it is not on screen.
108
+ */
109
+ readonly inputRect: ElementRect | null;
110
+ /**
111
+ * The window's offset from the top of the screen, as the page understands it.
112
+ */
113
+ readonly screenY: number;
114
+ }
115
+ /**
116
+ * The least a raised keyboard lifts the field by, so a stray rounding pixel is not read as one.
117
+ *
118
+ * Exported because it is the number both {@link checkIsSoftKeyboardUp} and `raiseSoftKeyboard` default to,
119
+ * and a suite tightening it wants to say so relative to this rather than in the abstract.
120
+ */
121
+ export declare const DEFAULT_MINIMUM_KEYBOARD_HEIGHT_IN_PIXELS = 100;
122
+ /**
123
+ * Builds the message a failed raise reports.
124
+ *
125
+ * A bare "the keyboard did not come up" is unreadable — what the reader needs is what the page saw and what
126
+ * the device thought, side by side, because the two disagreeing is the whole diagnosis.
127
+ *
128
+ * @param params - The evidence gathered after the last touch.
129
+ * @returns The message, ready to throw.
130
+ */
131
+ export declare function buildSoftKeyboardDiagnosticMessage(params: BuildSoftKeyboardDiagnosticMessageParams): string;
132
+ /**
133
+ * Decides whether the IME is up, from the page's own geometry.
134
+ *
135
+ * @param params - The geometry to judge, and how far the field must have lifted.
136
+ * @returns Whether the field has stopped short of the bottom to make room for a keyboard.
137
+ */
138
+ export declare function checkIsSoftKeyboardUp(params: CheckIsSoftKeyboardUpParams): boolean;
139
+ /**
140
+ * Reduces `dumpsys input_method` to the handful of fields that say whether the IME is showing.
141
+ *
142
+ * @param dumpsysOutput - Raw stdout of `adb shell dumpsys input_method`.
143
+ * @returns The interesting fields, trimmed and joined, or an empty string when none were reported.
144
+ */
145
+ export declare function parseInputMethodState(dumpsysOutput: string): string;
146
+ /**
147
+ * Works out where to touch, in the device pixels `adb shell input tap` speaks.
148
+ *
149
+ * Two candidates, not one: the WebView may or may not start at the top of the screen, and the page cannot
150
+ * tell which. Both land inside the field — it is taller than the offset — so a wrong guess costs one touch
151
+ * rather than a mis-tap on whatever sits below.
152
+ *
153
+ * @param params - The geometry the tap is aimed from.
154
+ * @returns The points to try, in order, or an empty array when the field is not on screen.
155
+ */
156
+ export declare function resolveSoftKeyboardTapPoints(params: ResolveSoftKeyboardTapPointsParams): SoftKeyboardTapPoint[];
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var soft_keyboard_exports = {};
30
+ __export(soft_keyboard_exports, {
31
+ raiseSoftKeyboard: () => raiseSoftKeyboard,
32
+ tapDevice: () => tapDevice
33
+ });
34
+ module.exports = __toCommonJS(soft_keyboard_exports);
35
+ var import_node_fs = require("node:fs");
36
+ var import_node_path = require("node:path");
37
+ var import_node_process = __toESM(require("node:process"), 1);
38
+ var import_promises = require("node:timers/promises");
39
+ var import_adb = require("./adb.cjs");
40
+ var import_device_screenshot = require("./device-screenshot.cjs");
41
+ var import_eval_in_obsidian = require("./eval-in-obsidian.cjs");
42
+ var import_soft_keyboard_geometry = require("./soft-keyboard-geometry.cjs");
43
+ const DEFAULT_DIAGNOSTICS_DIRECTORY = (0, import_node_path.join)(import_node_process.default.cwd(), "dist", "screenshots");
44
+ const KEYBOARD_SETTLE_DELAY_IN_MILLISECONDS = 1500;
45
+ async function raiseSoftKeyboard(params) {
46
+ let snapshot = await readSoftKeyboardViewport(params);
47
+ if (!snapshot.inputRect) {
48
+ throw new Error(`raiseSoftKeyboard: nothing matches "${params.inputSelector}", so there is no field to touch.`);
49
+ }
50
+ for (const point of (0, import_soft_keyboard_geometry.resolveSoftKeyboardTapPoints)({ snapshot })) {
51
+ if (checkIsUp(snapshot)) {
52
+ break;
53
+ }
54
+ await tapDevice({ deviceId: params.deviceId, point });
55
+ await (0, import_promises.setTimeout)(KEYBOARD_SETTLE_DELAY_IN_MILLISECONDS);
56
+ snapshot = await readSoftKeyboardViewport(params);
57
+ }
58
+ if (!checkIsUp(snapshot)) {
59
+ throw new Error(await buildFailureMessage(params, snapshot));
60
+ }
61
+ return snapshot;
62
+ function checkIsUp(current) {
63
+ return (0, import_soft_keyboard_geometry.checkIsSoftKeyboardUp)({
64
+ ...params.minimumKeyboardHeightInPixels !== void 0 && { minimumKeyboardHeightInPixels: params.minimumKeyboardHeightInPixels },
65
+ snapshot: current
66
+ });
67
+ }
68
+ }
69
+ async function tapDevice(params) {
70
+ await (0, import_adb.runAdbText)({
71
+ commandArguments: ["shell", "input", "tap", String(params.point.xInPixels), String(params.point.yInPixels)],
72
+ deviceId: params.deviceId
73
+ });
74
+ }
75
+ async function buildFailureMessage(params, snapshot) {
76
+ const diagnosticsDirectory = params.diagnosticsDirectory ?? DEFAULT_DIAGNOSTICS_DIRECTORY;
77
+ (0, import_node_fs.mkdirSync)(diagnosticsDirectory, { recursive: true });
78
+ const screenshotPath = (0, import_node_path.join)(diagnosticsDirectory, "keyboard-not-raised.png");
79
+ (0, import_node_fs.writeFileSync)(screenshotPath, await (0, import_device_screenshot.captureDeviceScreenshot)({ deviceId: params.deviceId }));
80
+ const dumpsysOutput = await (0, import_adb.runAdbText)({
81
+ commandArguments: ["shell", "dumpsys", "input_method"],
82
+ deviceId: params.deviceId
83
+ });
84
+ return (0, import_soft_keyboard_geometry.buildSoftKeyboardDiagnosticMessage)({
85
+ inputMethodState: (0, import_soft_keyboard_geometry.parseInputMethodState)(dumpsysOutput),
86
+ screenshotPath,
87
+ snapshot
88
+ });
89
+ }
90
+ async function readSoftKeyboardViewport(params) {
91
+ return await (0, import_eval_in_obsidian.evalInObsidian)({
92
+ callback({ inputSelector }) {
93
+ const inputEl = document.querySelector(inputSelector);
94
+ function toRect(rect) {
95
+ return {
96
+ height: rect.height,
97
+ left: rect.left,
98
+ top: rect.top,
99
+ width: rect.width
100
+ };
101
+ }
102
+ return {
103
+ devicePixelRatio: window.devicePixelRatio,
104
+ innerHeight: window.innerHeight,
105
+ inputRect: inputEl ? toRect(inputEl.getBoundingClientRect()) : null,
106
+ screenY: window.screenY
107
+ };
108
+ },
109
+ input: { inputSelector: params.inputSelector },
110
+ ...params.vaultPath !== void 0 && { vaultPath: params.vaultPath }
111
+ });
112
+ }
113
+ // Annotate the CommonJS export names for ESM import in node:
114
+ 0 && (module.exports = {
115
+ raiseSoftKeyboard,
116
+ tapDevice
117
+ });
118
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3NvZnQta2V5Ym9hcmQudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qKlxuICogQGZpbGVcbiAqXG4gKiBSYWlzZXMgdGhlIEFuZHJvaWQgc29mdCBrZXlib2FyZCBmb3IgYSBzY3JlZW5zaG90LCBhbmQgcHJvdmVzIGl0IGNhbWUgdXAuXG4gKlxuICogVHdvIHRoaW5ncyBoYXZlIHRvIGJlIHRydWUsIGFuZCB0aGUgc2Vjb25kIGlzIHRoZSBvbmUgdGhhdCBpcyBlYXN5IHRvIG1pc3M6XG4gKlxuICogMS4gVGhlIGRldmljZSBtdXN0IGJlIGFsbG93ZWQgdG8gZHJhdyBhIGtleWJvYXJkIGF0IGFsbC4gVGhlIHNjcmVlbnNob3QgQVZEc1xuICogICAgYXJlIGJ1aWx0IGBody5rZXlib2FyZD15ZXNgLCBzbyBBbmRyb2lkIHN1cHByZXNzZXMgdGhlIG9uLXNjcmVlbiBvbmUgXHUyMDE0XG4gKiAgICBgd2l0aFNvZnRLZXlib2FyZEVuYWJsZWRgIGluIGBkZXZpY2Utc2V0dGluZ3NgIGlzIHdoYXQgbGlmdHMgdGhhdC5cbiAqIDIuIFRoZSBJTUUgbXVzdCBiZSAqYXNrZWQqIGZvciBieSBhIGdlc3R1cmUuIEEgZmllbGQgdGhhdCB0YWtlcyBmb2N1c1xuICogICAgcHJvZ3JhbW1hdGljYWxseSBkb2VzIG5vdCBnZXQgb25lOiBhbiBBbmRyb2lkIFdlYlZpZXcgcmFpc2VzIHRoZSBJTUUgb24gYVxuICogICAgcmVhbCB0b3VjaCwgYW5kIGEgcnVuIHdpdGggdGhlIHNldHRpbmcgZmxpcHBlZCBhbmQgbm8gdG91Y2ggY29tZXMgYmFjayB3aXRoXG4gKiAgICBleGFjdGx5IHRoZSBlbXB0eSBiYW5kIGl0IGhhZCBiZWZvcmUuIGBhZGIgc2hlbGwgaW5wdXQgdGFwYCBpcyB0aGF0IGdlc3R1cmUuXG4gKlxuICogVGhlIGdlb21ldHJ5IHRoYXQgZGVjaWRlcyB3aGV0aGVyIGl0IHdvcmtlZCBpcyB1bml0LXRlc3RlZCBpblxuICogYHNvZnQta2V5Ym9hcmQtZ2VvbWV0cnlgOyBldmVyeXRoaW5nIGhlcmUgZHJpdmVzIGEgcmVhbCBkZXZpY2UsIHNvIHRoZSB3aG9sZVxuICogbW9kdWxlIGlzIGludGVncmF0aW9uLXRpbWUgY29kZS5cbiAqL1xuXG4vKiB2OCBpZ25vcmUgc3RhcnQgLS0gSW50ZWdyYXRpb24tdGltZSBjb2RlIChkcml2ZXMgYSBsaXZlIE9ic2lkaWFuIG9uIGEgcmVhbCBkZXZpY2UpIGNvdmVyZWQgYnkgaW50ZWdyYXRpb24gdGVzdHMsIG5vdCB1bml0IHRlc3RzLiAqL1xuXG5pbXBvcnQge1xuICBta2RpclN5bmMsXG4gIHdyaXRlRmlsZVN5bmNcbn0gZnJvbSAnbm9kZTpmcyc7XG5pbXBvcnQgeyBqb2luIH0gZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCBwcm9jZXNzIGZyb20gJ25vZGU6cHJvY2Vzcyc7XG5pbXBvcnQgeyBzZXRUaW1lb3V0IGFzIHNsZWVwIH0gZnJvbSAnbm9kZTp0aW1lcnMvcHJvbWlzZXMnO1xuXG5pbXBvcnQgdHlwZSB7XG4gIEVsZW1lbnRSZWN0LFxuICBTb2Z0S2V5Ym9hcmRUYXBQb2ludCxcbiAgU29mdEtleWJvYXJkVmlld3BvcnRTbmFwc2hvdFxufSBmcm9tICcuL3NvZnQta2V5Ym9hcmQtZ2VvbWV0cnkuY2pzJztcblxuaW1wb3J0IHsgcnVuQWRiVGV4dCB9IGZyb20gJy4vYWRiLmNqcyc7XG5pbXBvcnQgeyBjYXB0dXJlRGV2aWNlU2NyZWVuc2hvdCB9IGZyb20gJy4vZGV2aWNlLXNjcmVlbnNob3QuY2pzJztcbmltcG9ydCB7IGV2YWxJbk9ic2lkaWFuIH0gZnJvbSAnLi9ldmFsLWluLW9ic2lkaWFuLmNqcyc7XG5pbXBvcnQge1xuICBidWlsZFNvZnRLZXlib2FyZERpYWdub3N0aWNNZXNzYWdlLFxuICBjaGVja0lzU29mdEtleWJvYXJkVXAsXG4gIHBhcnNlSW5wdXRNZXRob2RTdGF0ZSxcbiAgcmVzb2x2ZVNvZnRLZXlib2FyZFRhcFBvaW50c1xufSBmcm9tICcuL3NvZnQta2V5Ym9hcmQtZ2VvbWV0cnkuY2pzJztcblxuLyoqXG4gKiBQYXJhbWV0ZXJzIGZvciB7QGxpbmsgcmFpc2VTb2Z0S2V5Ym9hcmR9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJhaXNlU29mdEtleWJvYXJkUGFyYW1zIHtcbiAgLyoqXG4gICAqIFRoZSBkZXZpY2UgdG8gdG91Y2guXG4gICAqL1xuICByZWFkb25seSBkZXZpY2VJZDogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBXaGVyZSBhIGZhaWxlZCBhdHRlbXB0IHdyaXRlcyB0aGUgZGV2aWNlIGZyYW1lYnVmZmVyLlxuICAgKlxuICAgKiBXaGVuIG9taXR0ZWQsIGl0IGlzIGBkaXN0L3NjcmVlbnNob3RzYCB1bmRlciB0aGUgd29ya2luZyBkaXJlY3RvcnkgXHUyMDE0IHJlc29sdmVkIGF0IGxvYWQsIHNvIHRoZXJlIGlzIG5vXG4gICAqIGxpdGVyYWwgZGVmYXVsdCB0byBzdGF0ZSBoZXJlLlxuICAgKi9cbiAgcmVhZG9ubHkgZGlhZ25vc3RpY3NEaXJlY3Rvcnk/OiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFRoZSBDU1Mgc2VsZWN0b3Igb2YgdGhlIGZpZWxkIHRvIHRvdWNoLCBlLmcuIGAucHJvbXB0LWlucHV0YC5cbiAgICovXG4gIHJlYWRvbmx5IGlucHV0U2VsZWN0b3I6IHN0cmluZztcblxuICAvKipcbiAgICogVGhlIGxlYXN0IGEgcmFpc2VkIGtleWJvYXJkIGxpZnRzIHRoZSBmaWVsZCBieS5cbiAgICpcbiAgICogV2hlbiBvbWl0dGVkLCBgREVGQVVMVF9NSU5JTVVNX0tFWUJPQVJEX0hFSUdIVF9JTl9QSVhFTFNgIGZyb20gYHNvZnQta2V5Ym9hcmQtZ2VvbWV0cnlgIGFwcGxpZXMuXG4gICAqL1xuICByZWFkb25seSBtaW5pbXVtS2V5Ym9hcmRIZWlnaHRJblBpeGVscz86IG51bWJlcjtcblxuICAvKipcbiAgICogVGhlIHZhdWx0IHRvIHJlYWQgdGhlIGdlb21ldHJ5IGZyb20uIFdoZW4gb21pdHRlZCwgdGhlIGN1cnJlbnQgdGVzdCBjb250ZXh0J3MgdmF1bHQgaXMgdXNlZC5cbiAgICovXG4gIHJlYWRvbmx5IHZhdWx0UGF0aD86IHN0cmluZztcbn1cblxuLyoqXG4gKiBQYXJhbWV0ZXJzIGZvciB7QGxpbmsgdGFwRGV2aWNlfS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBUYXBEZXZpY2VQYXJhbXMge1xuICAvKipcbiAgICogVGhlIGRldmljZSB0byB0b3VjaC5cbiAgICovXG4gIHJlYWRvbmx5IGRldmljZUlkOiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFdoZXJlIHRvIHRvdWNoIGl0LlxuICAgKi9cbiAgcmVhZG9ubHkgcG9pbnQ6IFNvZnRLZXlib2FyZFRhcFBvaW50O1xufVxuXG4vKipcbiAqIFdoZXJlIGEgZmFpbGVkIGF0dGVtcHQgbGVhdmVzIGl0cyBldmlkZW5jZSBcdTIwMTQgZ2l0aWdub3JlZCBhY3Jvc3MgdGhlIGZsZWV0LCBsaWtlIHRoZSBjYXB0dXJlIHNjcmF0Y2ggZmlsZXMuXG4gKi9cbmNvbnN0IERFRkFVTFRfRElBR05PU1RJQ1NfRElSRUNUT1JZID0gam9pbihwcm9jZXNzLmN3ZCgpLCAnZGlzdCcsICdzY3JlZW5zaG90cycpO1xuXG4vKipcbiAqIEhvdyBsb25nIHRoZSBJTUUgdGFrZXMgdG8gZmluaXNoIGFuaW1hdGluZyBpbiwgYWZ0ZXIgd2hpY2ggdGhlIGxheW91dCBoYXMgc2V0dGxlZC5cbiAqL1xuY29uc3QgS0VZQk9BUkRfU0VUVExFX0RFTEFZX0lOX01JTExJU0VDT05EUyA9IDE1MDA7XG5cbi8qKlxuICogUmFpc2VzIHRoZSBvbi1zY3JlZW4ga2V5Ym9hcmQgd2l0aCBhIHJlYWwgdG91Y2ggb24gYSBmaWVsZCwgYW5kIGNvbmZpcm1zIGl0IGNhbWUgdXAuXG4gKlxuICogQ2FsbCBpdCBpbnNpZGUgYHdpdGhTb2Z0S2V5Ym9hcmRFbmFibGVkYCBcdTIwMTQgdGhlIGRldmljZSBzZXR0aW5nIGFsb25lIGRvZXMgbm90IHJhaXNlIHRoZSBrZXlib2FyZCwgYW5kIHRoaXNcbiAqIHRvdWNoIGFsb25lIGNhbm5vdCB3aGlsZSB0aGUgc2V0dGluZyBzdXBwcmVzc2VzIGl0LlxuICpcbiAqIEBwYXJhbSBwYXJhbXMgLSBUaGUgZGV2aWNlLCB0aGUgZmllbGQgdG8gdG91Y2gsIGFuZCBob3cgZmFyIGl0IG11c3QgbGlmdC5cbiAqIEByZXR1cm5zIEEge0BsaW5rIFByb21pc2V9IHRoYXQgcmVzb2x2ZXMgdG8gdGhlIGdlb21ldHJ5IHJlYWQgb25jZSB0aGUga2V5Ym9hcmQgaXMgdXAuXG4gKiBAdGhyb3dzIEVycm9yIGlmIHRoZSBrZXlib2FyZCBuZXZlciBjYW1lIHVwLCBhZnRlciB3cml0aW5nIHRoZSBkZXZpY2UgZnJhbWVidWZmZXIgYW5kIHRoZSBkZXZpY2UncyBvd25cbiAqICAgYGlucHV0X21ldGhvZGAgc3RhdGUgdG8gdGhlIGRpYWdub3N0aWNzIGRpcmVjdG9yeS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJhaXNlU29mdEtleWJvYXJkKHBhcmFtczogUmFpc2VTb2Z0S2V5Ym9hcmRQYXJhbXMpOiBQcm9taXNlPFNvZnRLZXlib2FyZFZpZXdwb3J0U25hcHNob3Q+IHtcbiAgbGV0IHNuYXBzaG90ID0gYXdhaXQgcmVhZFNvZnRLZXlib2FyZFZpZXdwb3J0KHBhcmFtcyk7XG5cbiAgaWYgKCFzbmFwc2hvdC5pbnB1dFJlY3QpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYHJhaXNlU29mdEtleWJvYXJkOiBub3RoaW5nIG1hdGNoZXMgXCIke3BhcmFtcy5pbnB1dFNlbGVjdG9yfVwiLCBzbyB0aGVyZSBpcyBubyBmaWVsZCB0byB0b3VjaC5gKTtcbiAgfVxuXG4gIGZvciAoY29uc3QgcG9pbnQgb2YgcmVzb2x2ZVNvZnRLZXlib2FyZFRhcFBvaW50cyh7IHNuYXBzaG90IH0pKSB7XG4gICAgaWYgKGNoZWNrSXNVcChzbmFwc2hvdCkpIHtcbiAgICAgIGJyZWFrO1xuICAgIH1cblxuICAgIGF3YWl0IHRhcERldmljZSh7IGRldmljZUlkOiBwYXJhbXMuZGV2aWNlSWQsIHBvaW50IH0pO1xuICAgIGF3YWl0IHNsZWVwKEtFWUJPQVJEX1NFVFRMRV9ERUxBWV9JTl9NSUxMSVNFQ09ORFMpO1xuICAgIHNuYXBzaG90ID0gYXdhaXQgcmVhZFNvZnRLZXlib2FyZFZpZXdwb3J0KHBhcmFtcyk7XG4gIH1cblxuICBpZiAoIWNoZWNrSXNVcChzbmFwc2hvdCkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYXdhaXQgYnVpbGRGYWlsdXJlTWVzc2FnZShwYXJhbXMsIHNuYXBzaG90KSk7XG4gIH1cblxuICByZXR1cm4gc25hcHNob3Q7XG5cbiAgZnVuY3Rpb24gY2hlY2tJc1VwKGN1cnJlbnQ6IFNvZnRLZXlib2FyZFZpZXdwb3J0U25hcHNob3QpOiBib29sZWFuIHtcbiAgICByZXR1cm4gY2hlY2tJc1NvZnRLZXlib2FyZFVwKHtcbiAgICAgIC4uLihwYXJhbXMubWluaW11bUtleWJvYXJkSGVpZ2h0SW5QaXhlbHMgIT09IHVuZGVmaW5lZCAmJiB7IG1pbmltdW1LZXlib2FyZEhlaWdodEluUGl4ZWxzOiBwYXJhbXMubWluaW11bUtleWJvYXJkSGVpZ2h0SW5QaXhlbHMgfSksXG4gICAgICBzbmFwc2hvdDogY3VycmVudFxuICAgIH0pO1xuICB9XG59XG5cbi8qKlxuICogVG91Y2hlcyB0aGUgZGV2aWNlIGF0IGEgcG9pbnQsIHRoZSB3YXkgYSB0aHVtYiB3b3VsZC5cbiAqXG4gKiBAcGFyYW0gcGFyYW1zIC0gVGhlIGRldmljZSBhbmQgd2hlcmUgdG8gdG91Y2ggaXQuXG4gKiBAcmV0dXJucyBBIHtAbGluayBQcm9taXNlfSB0aGF0IHJlc29sdmVzIG9uY2UgdGhlIHRvdWNoIGhhcyBiZWVuIGRpc3BhdGNoZWQuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB0YXBEZXZpY2UocGFyYW1zOiBUYXBEZXZpY2VQYXJhbXMpOiBQcm9taXNlPHZvaWQ+IHtcbiAgYXdhaXQgcnVuQWRiVGV4dCh7XG4gICAgY29tbWFuZEFyZ3VtZW50czogWydzaGVsbCcsICdpbnB1dCcsICd0YXAnLCBTdHJpbmcocGFyYW1zLnBvaW50LnhJblBpeGVscyksIFN0cmluZyhwYXJhbXMucG9pbnQueUluUGl4ZWxzKV0sXG4gICAgZGV2aWNlSWQ6IHBhcmFtcy5kZXZpY2VJZFxuICB9KTtcbn1cblxuLyoqXG4gKiBXcml0ZXMgdGhlIGRldmljZSBmcmFtZWJ1ZmZlciBhbmQgcmVhZHMgdGhlIGRldmljZSdzIGBpbnB1dF9tZXRob2RgIHN0YXRlLCB0aGVuIGNvbXBvc2VzIHRoZSBmYWlsdXJlLlxuICpcbiAqIEBwYXJhbSBwYXJhbXMgLSBUaGUgZGV2aWNlIGFuZCB0aGUgZGlhZ25vc3RpY3MgZGlyZWN0b3J5LlxuICogQHBhcmFtIHNuYXBzaG90IC0gVGhlIGdlb21ldHJ5IHJlYWQgYWZ0ZXIgdGhlIGxhc3QgdG91Y2guXG4gKiBAcmV0dXJucyBBIHtAbGluayBQcm9taXNlfSB0aGF0IHJlc29sdmVzIHRvIHRoZSBtZXNzYWdlIHRvIHRocm93LlxuICovXG5hc3luYyBmdW5jdGlvbiBidWlsZEZhaWx1cmVNZXNzYWdlKHBhcmFtczogUmFpc2VTb2Z0S2V5Ym9hcmRQYXJhbXMsIHNuYXBzaG90OiBTb2Z0S2V5Ym9hcmRWaWV3cG9ydFNuYXBzaG90KTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgY29uc3QgZGlhZ25vc3RpY3NEaXJlY3RvcnkgPSBwYXJhbXMuZGlhZ25vc3RpY3NEaXJlY3RvcnkgPz8gREVGQVVMVF9ESUFHTk9TVElDU19ESVJFQ1RPUlk7XG4gIG1rZGlyU3luYyhkaWFnbm9zdGljc0RpcmVjdG9yeSwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG5cbiAgY29uc3Qgc2NyZWVuc2hvdFBhdGggPSBqb2luKGRpYWdub3N0aWNzRGlyZWN0b3J5LCAna2V5Ym9hcmQtbm90LXJhaXNlZC5wbmcnKTtcbiAgd3JpdGVGaWxlU3luYyhzY3JlZW5zaG90UGF0aCwgYXdhaXQgY2FwdHVyZURldmljZVNjcmVlbnNob3QoeyBkZXZpY2VJZDogcGFyYW1zLmRldmljZUlkIH0pKTtcblxuICBjb25zdCBkdW1wc3lzT3V0cHV0ID0gYXdhaXQgcnVuQWRiVGV4dCh7XG4gICAgY29tbWFuZEFyZ3VtZW50czogWydzaGVsbCcsICdkdW1wc3lzJywgJ2lucHV0X21ldGhvZCddLFxuICAgIGRldmljZUlkOiBwYXJhbXMuZGV2aWNlSWRcbiAgfSk7XG5cbiAgcmV0dXJuIGJ1aWxkU29mdEtleWJvYXJkRGlhZ25vc3RpY01lc3NhZ2Uoe1xuICAgIGlucHV0TWV0aG9kU3RhdGU6IHBhcnNlSW5wdXRNZXRob2RTdGF0ZShkdW1wc3lzT3V0cHV0KSxcbiAgICBzY3JlZW5zaG90UGF0aCxcbiAgICBzbmFwc2hvdFxuICB9KTtcbn1cblxuLyoqXG4gKiBSZWFkcyB3aGF0IHRoZSByZW5kZXJlciBrbm93cyBhYm91dCBpdHMgb3duIGdlb21ldHJ5LlxuICpcbiAqIEBwYXJhbSBwYXJhbXMgLSBUaGUgZmllbGQgdG8gbWVhc3VyZSBhbmQgdGhlIHZhdWx0IHRvIHJlYWQgaXQgZnJvbS5cbiAqIEByZXR1cm5zIEEge0BsaW5rIFByb21pc2V9IHRoYXQgcmVzb2x2ZXMgdG8gdGhlIHZpZXdwb3J0IGFuZCB0aGUgZmllbGQncyByZWN0LCBpbiBDU1MgcGl4ZWxzLlxuICovXG5hc3luYyBmdW5jdGlvbiByZWFkU29mdEtleWJvYXJkVmlld3BvcnQocGFyYW1zOiBSYWlzZVNvZnRLZXlib2FyZFBhcmFtcyk6IFByb21pc2U8U29mdEtleWJvYXJkVmlld3BvcnRTbmFwc2hvdD4ge1xuICByZXR1cm4gYXdhaXQgZXZhbEluT2JzaWRpYW4oe1xuICAgIGNhbGxiYWNrKHsgaW5wdXRTZWxlY3RvciB9KTogU29mdEtleWJvYXJkVmlld3BvcnRTbmFwc2hvdCB7XG4gICAgICBjb25zdCBpbnB1dEVsID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcihpbnB1dFNlbGVjdG9yKTtcblxuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIHVuaWNvcm4vY29uc2lzdGVudC1mdW5jdGlvbi1zY29waW5nIC0tIFRoaXMgY2xvc3VyZSBpcyBzZXJpYWxpemVkIGFuZCBldmFsdWF0ZWQgaW4gdGhlIHJlbmRlcmVyLCBzbyBhIGhlbHBlciBob2lzdGVkIHRvIG1vZHVsZSBzY29wZSB3b3VsZCBub3QgZXhpc3QgdGhlcmUuXG4gICAgICBmdW5jdGlvbiB0b1JlY3QocmVjdDogRE9NUmVjdCk6IEVsZW1lbnRSZWN0IHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBoZWlnaHQ6IHJlY3QuaGVpZ2h0LFxuICAgICAgICAgIGxlZnQ6IHJlY3QubGVmdCxcbiAgICAgICAgICB0b3A6IHJlY3QudG9wLFxuICAgICAgICAgIHdpZHRoOiByZWN0LndpZHRoXG4gICAgICAgIH07XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGRldmljZVBpeGVsUmF0aW86IHdpbmRvdy5kZXZpY2VQaXhlbFJhdGlvLFxuICAgICAgICBpbm5lckhlaWdodDogd2luZG93LmlubmVySGVpZ2h0LFxuICAgICAgICBpbnB1dFJlY3Q6IGlucHV0RWwgPyB0b1JlY3QoaW5wdXRFbC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSkgOiBudWxsLFxuICAgICAgICBzY3JlZW5ZOiB3aW5kb3cuc2NyZWVuWVxuICAgICAgfTtcbiAgICB9LFxuICAgIGlucHV0OiB7IGlucHV0U2VsZWN0b3I6IHBhcmFtcy5pbnB1dFNlbGVjdG9yIH0sXG4gICAgLi4uKHBhcmFtcy52YXVsdFBhdGggIT09IHVuZGVmaW5lZCAmJiB7IHZhdWx0UGF0aDogcGFyYW1zLnZhdWx0UGF0aCB9KVxuICB9KTtcbn1cblxuLyogdjggaWdub3JlIHN0b3AgKi9cbiJdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFzQkEscUJBR087QUFDUCx1QkFBcUI7QUFDckIsMEJBQW9CO0FBQ3BCLHNCQUFvQztBQVFwQyxpQkFBMkI7QUFDM0IsK0JBQXdDO0FBQ3hDLDhCQUErQjtBQUMvQixvQ0FLTztBQXVEUCxNQUFNLG9DQUFn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
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @file
3
+ *
4
+ * Raises the Android soft keyboard for a screenshot, and proves it came up.
5
+ *
6
+ * Two things have to be true, and the second is the one that is easy to miss:
7
+ *
8
+ * 1. The device must be allowed to draw a keyboard at all. The screenshot AVDs
9
+ * are built `hw.keyboard=yes`, so Android suppresses the on-screen one —
10
+ * `withSoftKeyboardEnabled` in `device-settings` is what lifts that.
11
+ * 2. The IME must be *asked* for by a gesture. A field that takes focus
12
+ * programmatically does not get one: an Android WebView raises the IME on a
13
+ * real touch, and a run with the setting flipped and no touch comes back with
14
+ * exactly the empty band it had before. `adb shell input tap` is that gesture.
15
+ *
16
+ * The geometry that decides whether it worked is unit-tested in
17
+ * `soft-keyboard-geometry`; everything here drives a real device, so the whole
18
+ * module is integration-time code.
19
+ */
20
+ import type { SoftKeyboardTapPoint, SoftKeyboardViewportSnapshot } from './soft-keyboard-geometry.cjs';
21
+ /**
22
+ * Parameters for {@link raiseSoftKeyboard}.
23
+ */
24
+ export interface RaiseSoftKeyboardParams {
25
+ /**
26
+ * The device to touch.
27
+ */
28
+ readonly deviceId: string;
29
+ /**
30
+ * Where a failed attempt writes the device framebuffer.
31
+ *
32
+ * When omitted, it is `dist/screenshots` under the working directory — resolved at load, so there is no
33
+ * literal default to state here.
34
+ */
35
+ readonly diagnosticsDirectory?: string;
36
+ /**
37
+ * The CSS selector of the field to touch, e.g. `.prompt-input`.
38
+ */
39
+ readonly inputSelector: string;
40
+ /**
41
+ * The least a raised keyboard lifts the field by.
42
+ *
43
+ * When omitted, `DEFAULT_MINIMUM_KEYBOARD_HEIGHT_IN_PIXELS` from `soft-keyboard-geometry` applies.
44
+ */
45
+ readonly minimumKeyboardHeightInPixels?: number;
46
+ /**
47
+ * The vault to read the geometry from. When omitted, the current test context's vault is used.
48
+ */
49
+ readonly vaultPath?: string;
50
+ }
51
+ /**
52
+ * Parameters for {@link tapDevice}.
53
+ */
54
+ export interface TapDeviceParams {
55
+ /**
56
+ * The device to touch.
57
+ */
58
+ readonly deviceId: string;
59
+ /**
60
+ * Where to touch it.
61
+ */
62
+ readonly point: SoftKeyboardTapPoint;
63
+ }
64
+ /**
65
+ * Raises the on-screen keyboard with a real touch on a field, and confirms it came up.
66
+ *
67
+ * Call it inside `withSoftKeyboardEnabled` — the device setting alone does not raise the keyboard, and this
68
+ * touch alone cannot while the setting suppresses it.
69
+ *
70
+ * @param params - The device, the field to touch, and how far it must lift.
71
+ * @returns A {@link Promise} that resolves to the geometry read once the keyboard is up.
72
+ * @throws Error if the keyboard never came up, after writing the device framebuffer and the device's own
73
+ * `input_method` state to the diagnostics directory.
74
+ */
75
+ export declare function raiseSoftKeyboard(params: RaiseSoftKeyboardParams): Promise<SoftKeyboardViewportSnapshot>;
76
+ /**
77
+ * Touches the device at a point, the way a thumb would.
78
+ *
79
+ * @param params - The device and where to touch it.
80
+ * @returns A {@link Promise} that resolves once the touch has been dispatched.
81
+ */
82
+ export declare function tapDevice(params: TapDeviceParams): Promise<void>;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @file
3
+ *
4
+ * Runs `adb` and hands back what it printed — as text, or as raw bytes.
5
+ *
6
+ * The harness already shells out to `adb` in half a dozen places inside
7
+ * `transport-factory` / `transport-appium`, but always through `exec`, which
8
+ * decodes stdout as UTF-8. That is fine for `devices` and `settings get` and
9
+ * fatally wrong for `exec-out screencap -p`, whose stdout is a PNG: decoding it
10
+ * as text corrupts every byte above 0x7F. Hence the two runners here — the same
11
+ * command shape, differing only in whether the output is decoded.
12
+ *
13
+ * These are the process layer for the device-facing helpers a capture suite
14
+ * uses (`device-screenshot`, `device-settings`, `soft-keyboard`); they are not a
15
+ * general-purpose adb wrapper and deliberately do not try to become one.
16
+ *
17
+ * Every function here shells out, so the whole module is integration-time code.
18
+ * That is also why the parsing and geometry these helpers depend on live in
19
+ * their own modules — the same split `adb-device-list` already has from the
20
+ * transport factory, so a sort rule can never move a unit-tested function
21
+ * inside a coverage-ignored block.
22
+ */
23
+ /**
24
+ * Parameters for {@link runAdbBinary} and {@link runAdbText}.
25
+ */
26
+ export interface RunAdbParams {
27
+ /**
28
+ * The arguments to pass after `-s <deviceId>`, e.g. `['shell', 'input', 'tap', '10', '20']`.
29
+ */
30
+ readonly commandArguments: readonly string[];
31
+ /**
32
+ * The device to address, as `adb devices` lists it.
33
+ */
34
+ readonly deviceId: string;
35
+ }
36
+ /**
37
+ * Runs `adb -s <deviceId> <commandArguments>` and returns stdout **undecoded**.
38
+ *
39
+ * @param params - The device and the arguments to run.
40
+ * @returns A {@link Promise} that resolves to the raw stdout bytes.
41
+ * @throws Error if adb could not be run, or exited non-zero.
42
+ */
43
+ export declare function runAdbBinary(params: RunAdbParams): Promise<Uint8Array>;
44
+ /**
45
+ * Runs `adb -s <deviceId> <commandArguments>` and returns stdout as trimmed text.
46
+ *
47
+ * @param params - The device and the arguments to run.
48
+ * @returns A {@link Promise} that resolves to stdout, with surrounding whitespace removed.
49
+ * @throws Error if adb could not be run, or exited non-zero.
50
+ */
51
+ export declare function runAdbText(params: RunAdbParams): Promise<string>;
52
+ /**
53
+ * Runs `adb <commandArguments>` with no device selected.
54
+ *
55
+ * Only `devices` needs this — every other call in this family addresses one device.
56
+ *
57
+ * @param commandArguments - The arguments to run.
58
+ * @returns A {@link Promise} that resolves to stdout, with surrounding whitespace removed.
59
+ * @throws Error if adb could not be run, or exited non-zero.
60
+ */
61
+ export declare function runAdbTextWithoutDevice(commandArguments: readonly string[]): Promise<string>;
@@ -0,0 +1,35 @@
1
+ import { execFile } from "node:child_process";
2
+ const OUTPUT_MAX_BUFFER_IN_BYTES = 67108864;
3
+ async function runAdbBinary(params) {
4
+ const commandArguments = ["-s", params.deviceId, ...params.commandArguments];
5
+ return await new Promise((resolve, reject) => {
6
+ execFile("adb", commandArguments, { encoding: "buffer", maxBuffer: OUTPUT_MAX_BUFFER_IN_BYTES }, (error, stdout) => {
7
+ if (error) {
8
+ reject(new Error(`Failed to run 'adb ${commandArguments.join(" ")}': ${error.message}. Is ADB installed and in PATH?`));
9
+ return;
10
+ }
11
+ resolve(stdout);
12
+ });
13
+ });
14
+ }
15
+ async function runAdbText(params) {
16
+ const stdout = await runAdbBinary(params);
17
+ return new TextDecoder().decode(stdout).trim();
18
+ }
19
+ async function runAdbTextWithoutDevice(commandArguments) {
20
+ return await new Promise((resolve, reject) => {
21
+ execFile("adb", [...commandArguments], { encoding: "utf-8", maxBuffer: OUTPUT_MAX_BUFFER_IN_BYTES }, (error, stdout) => {
22
+ if (error) {
23
+ reject(new Error(`Failed to run 'adb ${commandArguments.join(" ")}': ${error.message}. Is ADB installed and in PATH?`));
24
+ return;
25
+ }
26
+ resolve(stdout.trim());
27
+ });
28
+ });
29
+ }
30
+ export {
31
+ runAdbBinary,
32
+ runAdbText,
33
+ runAdbTextWithoutDevice
34
+ };
35
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL2FkYi50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyoqXG4gKiBAZmlsZVxuICpcbiAqIFJ1bnMgYGFkYmAgYW5kIGhhbmRzIGJhY2sgd2hhdCBpdCBwcmludGVkIFx1MjAxNCBhcyB0ZXh0LCBvciBhcyByYXcgYnl0ZXMuXG4gKlxuICogVGhlIGhhcm5lc3MgYWxyZWFkeSBzaGVsbHMgb3V0IHRvIGBhZGJgIGluIGhhbGYgYSBkb3plbiBwbGFjZXMgaW5zaWRlXG4gKiBgdHJhbnNwb3J0LWZhY3RvcnlgIC8gYHRyYW5zcG9ydC1hcHBpdW1gLCBidXQgYWx3YXlzIHRocm91Z2ggYGV4ZWNgLCB3aGljaFxuICogZGVjb2RlcyBzdGRvdXQgYXMgVVRGLTguIFRoYXQgaXMgZmluZSBmb3IgYGRldmljZXNgIGFuZCBgc2V0dGluZ3MgZ2V0YCBhbmRcbiAqIGZhdGFsbHkgd3JvbmcgZm9yIGBleGVjLW91dCBzY3JlZW5jYXAgLXBgLCB3aG9zZSBzdGRvdXQgaXMgYSBQTkc6IGRlY29kaW5nIGl0XG4gKiBhcyB0ZXh0IGNvcnJ1cHRzIGV2ZXJ5IGJ5dGUgYWJvdmUgMHg3Ri4gSGVuY2UgdGhlIHR3byBydW5uZXJzIGhlcmUgXHUyMDE0IHRoZSBzYW1lXG4gKiBjb21tYW5kIHNoYXBlLCBkaWZmZXJpbmcgb25seSBpbiB3aGV0aGVyIHRoZSBvdXRwdXQgaXMgZGVjb2RlZC5cbiAqXG4gKiBUaGVzZSBhcmUgdGhlIHByb2Nlc3MgbGF5ZXIgZm9yIHRoZSBkZXZpY2UtZmFjaW5nIGhlbHBlcnMgYSBjYXB0dXJlIHN1aXRlXG4gKiB1c2VzIChgZGV2aWNlLXNjcmVlbnNob3RgLCBgZGV2aWNlLXNldHRpbmdzYCwgYHNvZnQta2V5Ym9hcmRgKTsgdGhleSBhcmUgbm90IGFcbiAqIGdlbmVyYWwtcHVycG9zZSBhZGIgd3JhcHBlciBhbmQgZGVsaWJlcmF0ZWx5IGRvIG5vdCB0cnkgdG8gYmVjb21lIG9uZS5cbiAqXG4gKiBFdmVyeSBmdW5jdGlvbiBoZXJlIHNoZWxscyBvdXQsIHNvIHRoZSB3aG9sZSBtb2R1bGUgaXMgaW50ZWdyYXRpb24tdGltZSBjb2RlLlxuICogVGhhdCBpcyBhbHNvIHdoeSB0aGUgcGFyc2luZyBhbmQgZ2VvbWV0cnkgdGhlc2UgaGVscGVycyBkZXBlbmQgb24gbGl2ZSBpblxuICogdGhlaXIgb3duIG1vZHVsZXMgXHUyMDE0IHRoZSBzYW1lIHNwbGl0IGBhZGItZGV2aWNlLWxpc3RgIGFscmVhZHkgaGFzIGZyb20gdGhlXG4gKiB0cmFuc3BvcnQgZmFjdG9yeSwgc28gYSBzb3J0IHJ1bGUgY2FuIG5ldmVyIG1vdmUgYSB1bml0LXRlc3RlZCBmdW5jdGlvblxuICogaW5zaWRlIGEgY292ZXJhZ2UtaWdub3JlZCBibG9jay5cbiAqL1xuXG4vKiB2OCBpZ25vcmUgc3RhcnQgLS0gSW50ZWdyYXRpb24tdGltZSBjb2RlIChzaGVsbHMgb3V0IHRvIGEgcmVhbCBkZXZpY2UpIGNvdmVyZWQgYnkgaW50ZWdyYXRpb24gdGVzdHMsIG5vdCB1bml0IHRlc3RzLiAqL1xuXG5pbXBvcnQgeyBleGVjRmlsZSB9IGZyb20gJ25vZGU6Y2hpbGRfcHJvY2Vzcyc7XG5cbi8qKlxuICogUGFyYW1ldGVycyBmb3Ige0BsaW5rIHJ1bkFkYkJpbmFyeX0gYW5kIHtAbGluayBydW5BZGJUZXh0fS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBSdW5BZGJQYXJhbXMge1xuICAvKipcbiAgICogVGhlIGFyZ3VtZW50cyB0byBwYXNzIGFmdGVyIGAtcyA8ZGV2aWNlSWQ+YCwgZS5nLiBgWydzaGVsbCcsICdpbnB1dCcsICd0YXAnLCAnMTAnLCAnMjAnXWAuXG4gICAqL1xuICByZWFkb25seSBjb21tYW5kQXJndW1lbnRzOiByZWFkb25seSBzdHJpbmdbXTtcblxuICAvKipcbiAgICogVGhlIGRldmljZSB0byBhZGRyZXNzLCBhcyBgYWRiIGRldmljZXNgIGxpc3RzIGl0LlxuICAgKi9cbiAgcmVhZG9ubHkgZGV2aWNlSWQ6IHN0cmluZztcbn1cblxuLyoqXG4gKiBBIHNjcmVlbmNhcCBvZiBhIDkwMHgxNjAwIGRldmljZSBydW5zIHRvIGFib3V0IDEwMCBLQiwgYnV0IGEgdGFibGV0IEFWRCdzIGZyYW1lYnVmZmVyIGlzIHNldmVyYWwgTUIgYW5kXG4gKiB0aGUgZGVmYXVsdCAxIE1pQiBgbWF4QnVmZmVyYCBmYWlscyB0aGUgY2FsbCByYXRoZXIgdGhhbiByZXR1cm5pbmcgYSB1c2FibGUgaW1hZ2UuXG4gKi9cbmNvbnN0IE9VVFBVVF9NQVhfQlVGRkVSX0lOX0JZVEVTID0gNjdfMTA4Xzg2NDtcblxuLyoqXG4gKiBSdW5zIGBhZGIgLXMgPGRldmljZUlkPiA8Y29tbWFuZEFyZ3VtZW50cz5gIGFuZCByZXR1cm5zIHN0ZG91dCAqKnVuZGVjb2RlZCoqLlxuICpcbiAqIEBwYXJhbSBwYXJhbXMgLSBUaGUgZGV2aWNlIGFuZCB0aGUgYXJndW1lbnRzIHRvIHJ1bi5cbiAqIEByZXR1cm5zIEEge0BsaW5rIFByb21pc2V9IHRoYXQgcmVzb2x2ZXMgdG8gdGhlIHJhdyBzdGRvdXQgYnl0ZXMuXG4gKiBAdGhyb3dzIEVycm9yIGlmIGFkYiBjb3VsZCBub3QgYmUgcnVuLCBvciBleGl0ZWQgbm9uLXplcm8uXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBydW5BZGJCaW5hcnkocGFyYW1zOiBSdW5BZGJQYXJhbXMpOiBQcm9taXNlPFVpbnQ4QXJyYXk+IHtcbiAgY29uc3QgY29tbWFuZEFyZ3VtZW50cyA9IFsnLXMnLCBwYXJhbXMuZGV2aWNlSWQsIC4uLnBhcmFtcy5jb21tYW5kQXJndW1lbnRzXTtcblxuICByZXR1cm4gYXdhaXQgbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgIGV4ZWNGaWxlKCdhZGInLCBjb21tYW5kQXJndW1lbnRzLCB7IGVuY29kaW5nOiAnYnVmZmVyJywgbWF4QnVmZmVyOiBPVVRQVVRfTUFYX0JVRkZFUl9JTl9CWVRFUyB9LCAoZXJyb3IsIHN0ZG91dCkgPT4ge1xuICAgICAgaWYgKGVycm9yKSB7XG4gICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBydW4gJ2FkYiAke2NvbW1hbmRBcmd1bWVudHMuam9pbignICcpfSc6ICR7ZXJyb3IubWVzc2FnZX0uIElzIEFEQiBpbnN0YWxsZWQgYW5kIGluIFBBVEg/YCkpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIHJlc29sdmUoc3Rkb3V0KTtcbiAgICB9KTtcbiAgfSk7XG59XG5cbi8qKlxuICogUnVucyBgYWRiIC1zIDxkZXZpY2VJZD4gPGNvbW1hbmRBcmd1bWVudHM+YCBhbmQgcmV0dXJucyBzdGRvdXQgYXMgdHJpbW1lZCB0ZXh0LlxuICpcbiAqIEBwYXJhbSBwYXJhbXMgLSBUaGUgZGV2aWNlIGFuZCB0aGUgYXJndW1lbnRzIHRvIHJ1bi5cbiAqIEByZXR1cm5zIEEge0BsaW5rIFByb21pc2V9IHRoYXQgcmVzb2x2ZXMgdG8gc3Rkb3V0LCB3aXRoIHN1cnJvdW5kaW5nIHdoaXRlc3BhY2UgcmVtb3ZlZC5cbiAqIEB0aHJvd3MgRXJyb3IgaWYgYWRiIGNvdWxkIG5vdCBiZSBydW4sIG9yIGV4aXRlZCBub24temVyby5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJ1bkFkYlRleHQocGFyYW1zOiBSdW5BZGJQYXJhbXMpOiBQcm9taXNlPHN0cmluZz4ge1xuICBjb25zdCBzdGRvdXQgPSBhd2FpdCBydW5BZGJCaW5hcnkocGFyYW1zKTtcbiAgcmV0dXJuIG5ldyBUZXh0RGVjb2RlcigpLmRlY29kZShzdGRvdXQpLnRyaW0oKTtcbn1cblxuLyoqXG4gKiBSdW5zIGBhZGIgPGNvbW1hbmRBcmd1bWVudHM+YCB3aXRoIG5vIGRldmljZSBzZWxlY3RlZC5cbiAqXG4gKiBPbmx5IGBkZXZpY2VzYCBuZWVkcyB0aGlzIFx1MjAxNCBldmVyeSBvdGhlciBjYWxsIGluIHRoaXMgZmFtaWx5IGFkZHJlc3NlcyBvbmUgZGV2aWNlLlxuICpcbiAqIEBwYXJhbSBjb21tYW5kQXJndW1lbnRzIC0gVGhlIGFyZ3VtZW50cyB0byBydW4uXG4gKiBAcmV0dXJucyBBIHtAbGluayBQcm9taXNlfSB0aGF0IHJlc29sdmVzIHRvIHN0ZG91dCwgd2l0aCBzdXJyb3VuZGluZyB3aGl0ZXNwYWNlIHJlbW92ZWQuXG4gKiBAdGhyb3dzIEVycm9yIGlmIGFkYiBjb3VsZCBub3QgYmUgcnVuLCBvciBleGl0ZWQgbm9uLXplcm8uXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBydW5BZGJUZXh0V2l0aG91dERldmljZShjb21tYW5kQXJndW1lbnRzOiByZWFkb25seSBzdHJpbmdbXSk6IFByb21pc2U8c3RyaW5nPiB7XG4gIHJldHVybiBhd2FpdCBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgZXhlY0ZpbGUoJ2FkYicsIFsuLi5jb21tYW5kQXJndW1lbnRzXSwgeyBlbmNvZGluZzogJ3V0Zi04JywgbWF4QnVmZmVyOiBPVVRQVVRfTUFYX0JVRkZFUl9JTl9CWVRFUyB9LCAoZXJyb3IsIHN0ZG91dCkgPT4ge1xuICAgICAgaWYgKGVycm9yKSB7XG4gICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBydW4gJ2FkYiAke2NvbW1hbmRBcmd1bWVudHMuam9pbignICcpfSc6ICR7ZXJyb3IubWVzc2FnZX0uIElzIEFEQiBpbnN0YWxsZWQgYW5kIGluIFBBVEg/YCkpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIHJlc29sdmUoc3Rkb3V0LnRyaW0oKSk7XG4gICAgfSk7XG4gIH0pO1xufVxuXG4vKiB2OCBpZ25vcmUgc3RvcCAqL1xuIl0sCiAgIm1hcHBpbmdzIjogIkFBeUJBLFNBQVMsZ0JBQWdCO0FBcUJ6QixNQUFNLDZCQUE2QjtBQVNuQyxlQUFzQixhQUFhLFFBQTJDO0FBQzVFLFFBQU0sbUJBQW1CLENBQUMsTUFBTSxPQUFPLFVBQVUsR0FBRyxPQUFPLGdCQUFnQjtBQUUzRSxTQUFPLE1BQU0sSUFBSSxRQUFRLENBQUMsU0FBUyxXQUFXO0FBQzVDLGFBQVMsT0FBTyxrQkFBa0IsRUFBRSxVQUFVLFVBQVUsV0FBVywyQkFBMkIsR0FBRyxDQUFDLE9BQU8sV0FBVztBQUNsSCxVQUFJLE9BQU87QUFDVCxlQUFPLElBQUksTUFBTSxzQkFBc0IsaUJBQWlCLEtBQUssR0FBRyxDQUFDLE1BQU0sTUFBTSxPQUFPLGlDQUFpQyxDQUFDO0FBQ3RIO0FBQUEsTUFDRjtBQUVBLGNBQVEsTUFBTTtBQUFBLElBQ2hCLENBQUM7QUFBQSxFQUNILENBQUM7QUFDSDtBQVNBLGVBQXNCLFdBQVcsUUFBdUM7QUFDdEUsUUFBTSxTQUFTLE1BQU0sYUFBYSxNQUFNO0FBQ3hDLFNBQU8sSUFBSSxZQUFZLEVBQUUsT0FBTyxNQUFNLEVBQUUsS0FBSztBQUMvQztBQVdBLGVBQXNCLHdCQUF3QixrQkFBc0Q7QUFDbEcsU0FBTyxNQUFNLElBQUksUUFBUSxDQUFDLFNBQVMsV0FBVztBQUM1QyxhQUFTLE9BQU8sQ0FBQyxHQUFHLGdCQUFnQixHQUFHLEVBQUUsVUFBVSxTQUFTLFdBQVcsMkJBQTJCLEdBQUcsQ0FBQyxPQUFPLFdBQVc7QUFDdEgsVUFBSSxPQUFPO0FBQ1QsZUFBTyxJQUFJLE1BQU0sc0JBQXNCLGlCQUFpQixLQUFLLEdBQUcsQ0FBQyxNQUFNLE1BQU0sT0FBTyxpQ0FBaUMsQ0FBQztBQUN0SDtBQUFBLE1BQ0Y7QUFFQSxjQUFRLE9BQU8sS0FBSyxDQUFDO0FBQUEsSUFDdkIsQ0FBQztBQUFBLEVBQ0gsQ0FBQztBQUNIOyIsCiAgIm5hbWVzIjogW10KfQo=
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @file
3
+ *
4
+ * Photographs the DEVICE, not the web page.
5
+ *
6
+ * `captureObsidianScreenshot` goes through Appium in the WebView context, so on
7
+ * Android it captures the page: no status bar, and — the reason this exists —
8
+ * no soft keyboard, because the IME is a system window and not part of the
9
+ * page. A frame that is meant to show what a phone looks like therefore cannot
10
+ * be taken that way.
11
+ *
12
+ * `adb exec-out screencap -p` reads the framebuffer instead, which is the same
13
+ * route the trusted-input passes already use. The trade is deliberate and worth
14
+ * writing down at every call site: a device capture carries the status-bar clock
15
+ * and battery, so it is **not byte-reproducible** the way a page capture is.
16
+ * Reach for this only for the shots that need the keyboard; leave the rest on
17
+ * `captureObsidianScreenshot`.
18
+ */
19
+ /**
20
+ * Parameters for {@link captureDeviceScreenshot}.
21
+ */
22
+ export interface CaptureDeviceScreenshotParams {
23
+ /**
24
+ * The device to photograph.
25
+ */
26
+ readonly deviceId: string;
27
+ }
28
+ /**
29
+ * Captures the device's framebuffer as a PNG.
30
+ *
31
+ * @param params - The device to photograph.
32
+ * @returns A {@link Promise} that resolves to the raw PNG bytes.
33
+ * @throws Error if what came back is not a PNG — which is what a truncated or text-decoded capture looks
34
+ * like, and is far cheaper to catch here than in an image diff.
35
+ */
36
+ export declare function captureDeviceScreenshot(params: CaptureDeviceScreenshotParams): Promise<Uint8Array>;
@@ -0,0 +1,18 @@
1
+ import { runAdbBinary } from "./adb.mjs";
2
+ import { isPng } from "./capture-screenshot.mjs";
3
+ async function captureDeviceScreenshot(params) {
4
+ const bytes = await runAdbBinary({
5
+ commandArguments: ["exec-out", "screencap", "-p"],
6
+ deviceId: params.deviceId
7
+ });
8
+ if (!isPng(bytes)) {
9
+ throw new Error(
10
+ `captureDeviceScreenshot: ${params.deviceId} returned ${String(bytes.length)} bytes that are not a PNG. A device that is still booting, or an adb transport that decoded the stream as text, both look like this.`
11
+ );
12
+ }
13
+ return bytes;
14
+ }
15
+ export {
16
+ captureDeviceScreenshot
17
+ };
18
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL2RldmljZS1zY3JlZW5zaG90LnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKipcbiAqIEBmaWxlXG4gKlxuICogUGhvdG9ncmFwaHMgdGhlIERFVklDRSwgbm90IHRoZSB3ZWIgcGFnZS5cbiAqXG4gKiBgY2FwdHVyZU9ic2lkaWFuU2NyZWVuc2hvdGAgZ29lcyB0aHJvdWdoIEFwcGl1bSBpbiB0aGUgV2ViVmlldyBjb250ZXh0LCBzbyBvblxuICogQW5kcm9pZCBpdCBjYXB0dXJlcyB0aGUgcGFnZTogbm8gc3RhdHVzIGJhciwgYW5kIFx1MjAxNCB0aGUgcmVhc29uIHRoaXMgZXhpc3RzIFx1MjAxNFxuICogbm8gc29mdCBrZXlib2FyZCwgYmVjYXVzZSB0aGUgSU1FIGlzIGEgc3lzdGVtIHdpbmRvdyBhbmQgbm90IHBhcnQgb2YgdGhlXG4gKiBwYWdlLiBBIGZyYW1lIHRoYXQgaXMgbWVhbnQgdG8gc2hvdyB3aGF0IGEgcGhvbmUgbG9va3MgbGlrZSB0aGVyZWZvcmUgY2Fubm90XG4gKiBiZSB0YWtlbiB0aGF0IHdheS5cbiAqXG4gKiBgYWRiIGV4ZWMtb3V0IHNjcmVlbmNhcCAtcGAgcmVhZHMgdGhlIGZyYW1lYnVmZmVyIGluc3RlYWQsIHdoaWNoIGlzIHRoZSBzYW1lXG4gKiByb3V0ZSB0aGUgdHJ1c3RlZC1pbnB1dCBwYXNzZXMgYWxyZWFkeSB1c2UuIFRoZSB0cmFkZSBpcyBkZWxpYmVyYXRlIGFuZCB3b3J0aFxuICogd3JpdGluZyBkb3duIGF0IGV2ZXJ5IGNhbGwgc2l0ZTogYSBkZXZpY2UgY2FwdHVyZSBjYXJyaWVzIHRoZSBzdGF0dXMtYmFyIGNsb2NrXG4gKiBhbmQgYmF0dGVyeSwgc28gaXQgaXMgKipub3QgYnl0ZS1yZXByb2R1Y2libGUqKiB0aGUgd2F5IGEgcGFnZSBjYXB0dXJlIGlzLlxuICogUmVhY2ggZm9yIHRoaXMgb25seSBmb3IgdGhlIHNob3RzIHRoYXQgbmVlZCB0aGUga2V5Ym9hcmQ7IGxlYXZlIHRoZSByZXN0IG9uXG4gKiBgY2FwdHVyZU9ic2lkaWFuU2NyZWVuc2hvdGAuXG4gKi9cblxuaW1wb3J0IHsgcnVuQWRiQmluYXJ5IH0gZnJvbSAnLi9hZGIubWpzJztcbmltcG9ydCB7IGlzUG5nIH0gZnJvbSAnLi9jYXB0dXJlLXNjcmVlbnNob3QubWpzJztcblxuLyoqXG4gKiBQYXJhbWV0ZXJzIGZvciB7QGxpbmsgY2FwdHVyZURldmljZVNjcmVlbnNob3R9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIENhcHR1cmVEZXZpY2VTY3JlZW5zaG90UGFyYW1zIHtcbiAgLyoqXG4gICAqIFRoZSBkZXZpY2UgdG8gcGhvdG9ncmFwaC5cbiAgICovXG4gIHJlYWRvbmx5IGRldmljZUlkOiBzdHJpbmc7XG59XG5cbi8qIHY4IGlnbm9yZSBzdGFydCAtLSBJbnRlZ3JhdGlvbi10aW1lIGNvZGUgKHNoZWxscyBvdXQgdG8gYSByZWFsIGRldmljZSkgY292ZXJlZCBieSBpbnRlZ3JhdGlvbiB0ZXN0cywgbm90IHVuaXQgdGVzdHMuICovXG5cbi8qKlxuICogQ2FwdHVyZXMgdGhlIGRldmljZSdzIGZyYW1lYnVmZmVyIGFzIGEgUE5HLlxuICpcbiAqIEBwYXJhbSBwYXJhbXMgLSBUaGUgZGV2aWNlIHRvIHBob3RvZ3JhcGguXG4gKiBAcmV0dXJucyBBIHtAbGluayBQcm9taXNlfSB0aGF0IHJlc29sdmVzIHRvIHRoZSByYXcgUE5HIGJ5dGVzLlxuICogQHRocm93cyBFcnJvciBpZiB3aGF0IGNhbWUgYmFjayBpcyBub3QgYSBQTkcgXHUyMDE0IHdoaWNoIGlzIHdoYXQgYSB0cnVuY2F0ZWQgb3IgdGV4dC1kZWNvZGVkIGNhcHR1cmUgbG9va3NcbiAqICAgbGlrZSwgYW5kIGlzIGZhciBjaGVhcGVyIHRvIGNhdGNoIGhlcmUgdGhhbiBpbiBhbiBpbWFnZSBkaWZmLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY2FwdHVyZURldmljZVNjcmVlbnNob3QocGFyYW1zOiBDYXB0dXJlRGV2aWNlU2NyZWVuc2hvdFBhcmFtcyk6IFByb21pc2U8VWludDhBcnJheT4ge1xuICBjb25zdCBieXRlcyA9IGF3YWl0IHJ1bkFkYkJpbmFyeSh7XG4gICAgY29tbWFuZEFyZ3VtZW50czogWydleGVjLW91dCcsICdzY3JlZW5jYXAnLCAnLXAnXSxcbiAgICBkZXZpY2VJZDogcGFyYW1zLmRldmljZUlkXG4gIH0pO1xuXG4gIGlmICghaXNQbmcoYnl0ZXMpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYGNhcHR1cmVEZXZpY2VTY3JlZW5zaG90OiAke3BhcmFtcy5kZXZpY2VJZH0gcmV0dXJuZWQgJHtTdHJpbmcoYnl0ZXMubGVuZ3RoKX0gYnl0ZXMgdGhhdCBhcmUgbm90IGEgUE5HLiBgXG4gICAgICAgICsgJ0EgZGV2aWNlIHRoYXQgaXMgc3RpbGwgYm9vdGluZywgb3IgYW4gYWRiIHRyYW5zcG9ydCB0aGF0IGRlY29kZWQgdGhlIHN0cmVhbSBhcyB0ZXh0LCBib3RoIGxvb2sgbGlrZSB0aGlzLidcbiAgICApO1xuICB9XG5cbiAgcmV0dXJuIGJ5dGVzO1xufVxuXG4vKiB2OCBpZ25vcmUgc3RvcCAqL1xuIl0sCiAgIm1hcHBpbmdzIjogIkFBbUJBLFNBQVMsb0JBQW9CO0FBQzdCLFNBQVMsYUFBYTtBQXNCdEIsZUFBc0Isd0JBQXdCLFFBQTREO0FBQ3hHLFFBQU0sUUFBUSxNQUFNLGFBQWE7QUFBQSxJQUMvQixrQkFBa0IsQ0FBQyxZQUFZLGFBQWEsSUFBSTtBQUFBLElBQ2hELFVBQVUsT0FBTztBQUFBLEVBQ25CLENBQUM7QUFFRCxNQUFJLENBQUMsTUFBTSxLQUFLLEdBQUc7QUFDakIsVUFBTSxJQUFJO0FBQUEsTUFDUiw0QkFBNEIsT0FBTyxRQUFRLGFBQWEsT0FBTyxNQUFNLE1BQU0sQ0FBQztBQUFBLElBRTlFO0FBQUEsRUFDRjtBQUVBLFNBQU87QUFDVDsiLAogICJuYW1lcyI6IFtdCn0K
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @file
3
+ *
4
+ * Decides how a device setting that was changed for a capture gets put back.
5
+ *
6
+ * A suite that flips a setting owes the device its original value back —
7
+ * including the case where it never had one. `settings get` prints the literal
8
+ * `null` for a setting that has never been written, and `settings put … null`
9
+ * does **not** reproduce that state: it writes the four-character string
10
+ * `null`. Restoring an unset setting takes `settings delete`, which is why this
11
+ * is a decision rather than a straight write-back.
12
+ *
13
+ * Pure and unit-tested; the calls that carry it out live in `device-settings`.
14
+ */
15
+ /**
16
+ * How a setting is restored.
17
+ */
18
+ export declare enum DeviceSettingRestoreKind {
19
+ /**
20
+ * The setting had never been written, so restoring it means removing it again.
21
+ */
22
+ Delete = "delete",
23
+ /**
24
+ * The setting held a value, which is written back verbatim.
25
+ */
26
+ Write = "write"
27
+ }
28
+ /**
29
+ * What restoring a setting requires: putting a value back, or removing it again.
30
+ */
31
+ export type DeviceSettingRestore = DeviceSettingRestoreDelete | DeviceSettingRestoreWrite;
32
+ /**
33
+ * Restoring a setting by removing it, because it had never been written.
34
+ */
35
+ export interface DeviceSettingRestoreDelete {
36
+ /**
37
+ * Discriminates the union.
38
+ */
39
+ readonly kind: DeviceSettingRestoreKind.Delete;
40
+ }
41
+ /**
42
+ * Restoring a setting by writing its previous value back.
43
+ */
44
+ export interface DeviceSettingRestoreWrite {
45
+ /**
46
+ * Discriminates the union.
47
+ */
48
+ readonly kind: DeviceSettingRestoreKind.Write;
49
+ /**
50
+ * The value to write back, exactly as it was read.
51
+ */
52
+ readonly value: string;
53
+ }
54
+ /**
55
+ * Decides how a setting read before a change is put back afterwards.
56
+ *
57
+ * @param previousValue - Exactly what `readDeviceSetting` returned before the change.
58
+ * @returns Whether to write the value back or to remove the setting again.
59
+ */
60
+ export declare function resolveDeviceSettingRestore(previousValue: string): DeviceSettingRestore;
@@ -0,0 +1,20 @@
1
+ var DeviceSettingRestoreKind = /* @__PURE__ */ ((DeviceSettingRestoreKind2) => {
2
+ DeviceSettingRestoreKind2["Delete"] = "delete";
3
+ DeviceSettingRestoreKind2["Write"] = "write";
4
+ return DeviceSettingRestoreKind2;
5
+ })(DeviceSettingRestoreKind || {});
6
+ const UNSET_SETTING_VALUE = "null";
7
+ function resolveDeviceSettingRestore(previousValue) {
8
+ if (previousValue === UNSET_SETTING_VALUE) {
9
+ return { kind: "delete" /* Delete */ };
10
+ }
11
+ return {
12
+ kind: "write" /* Write */,
13
+ value: previousValue
14
+ };
15
+ }
16
+ export {
17
+ DeviceSettingRestoreKind,
18
+ resolveDeviceSettingRestore
19
+ };
20
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL2RldmljZS1zZXR0aW5nLXJlc3RvcmUudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qKlxuICogQGZpbGVcbiAqXG4gKiBEZWNpZGVzIGhvdyBhIGRldmljZSBzZXR0aW5nIHRoYXQgd2FzIGNoYW5nZWQgZm9yIGEgY2FwdHVyZSBnZXRzIHB1dCBiYWNrLlxuICpcbiAqIEEgc3VpdGUgdGhhdCBmbGlwcyBhIHNldHRpbmcgb3dlcyB0aGUgZGV2aWNlIGl0cyBvcmlnaW5hbCB2YWx1ZSBiYWNrIFx1MjAxNFxuICogaW5jbHVkaW5nIHRoZSBjYXNlIHdoZXJlIGl0IG5ldmVyIGhhZCBvbmUuIGBzZXR0aW5ncyBnZXRgIHByaW50cyB0aGUgbGl0ZXJhbFxuICogYG51bGxgIGZvciBhIHNldHRpbmcgdGhhdCBoYXMgbmV2ZXIgYmVlbiB3cml0dGVuLCBhbmQgYHNldHRpbmdzIHB1dCBcdTIwMjYgbnVsbGBcbiAqIGRvZXMgKipub3QqKiByZXByb2R1Y2UgdGhhdCBzdGF0ZTogaXQgd3JpdGVzIHRoZSBmb3VyLWNoYXJhY3RlciBzdHJpbmdcbiAqIGBudWxsYC4gUmVzdG9yaW5nIGFuIHVuc2V0IHNldHRpbmcgdGFrZXMgYHNldHRpbmdzIGRlbGV0ZWAsIHdoaWNoIGlzIHdoeSB0aGlzXG4gKiBpcyBhIGRlY2lzaW9uIHJhdGhlciB0aGFuIGEgc3RyYWlnaHQgd3JpdGUtYmFjay5cbiAqXG4gKiBQdXJlIGFuZCB1bml0LXRlc3RlZDsgdGhlIGNhbGxzIHRoYXQgY2FycnkgaXQgb3V0IGxpdmUgaW4gYGRldmljZS1zZXR0aW5nc2AuXG4gKi9cblxuLyoqXG4gKiBIb3cgYSBzZXR0aW5nIGlzIHJlc3RvcmVkLlxuICovXG5leHBvcnQgZW51bSBEZXZpY2VTZXR0aW5nUmVzdG9yZUtpbmQge1xuICAvKipcbiAgICogVGhlIHNldHRpbmcgaGFkIG5ldmVyIGJlZW4gd3JpdHRlbiwgc28gcmVzdG9yaW5nIGl0IG1lYW5zIHJlbW92aW5nIGl0IGFnYWluLlxuICAgKi9cbiAgRGVsZXRlID0gJ2RlbGV0ZScsXG5cbiAgLyoqXG4gICAqIFRoZSBzZXR0aW5nIGhlbGQgYSB2YWx1ZSwgd2hpY2ggaXMgd3JpdHRlbiBiYWNrIHZlcmJhdGltLlxuICAgKi9cbiAgV3JpdGUgPSAnd3JpdGUnXG59XG5cbi8qKlxuICogV2hhdCByZXN0b3JpbmcgYSBzZXR0aW5nIHJlcXVpcmVzOiBwdXR0aW5nIGEgdmFsdWUgYmFjaywgb3IgcmVtb3ZpbmcgaXQgYWdhaW4uXG4gKi9cbmV4cG9ydCB0eXBlIERldmljZVNldHRpbmdSZXN0b3JlID0gRGV2aWNlU2V0dGluZ1Jlc3RvcmVEZWxldGUgfCBEZXZpY2VTZXR0aW5nUmVzdG9yZVdyaXRlO1xuXG4vKipcbiAqIFJlc3RvcmluZyBhIHNldHRpbmcgYnkgcmVtb3ZpbmcgaXQsIGJlY2F1c2UgaXQgaGFkIG5ldmVyIGJlZW4gd3JpdHRlbi5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBEZXZpY2VTZXR0aW5nUmVzdG9yZURlbGV0ZSB7XG4gIC8qKlxuICAgKiBEaXNjcmltaW5hdGVzIHRoZSB1bmlvbi5cbiAgICovXG4gIHJlYWRvbmx5IGtpbmQ6IERldmljZVNldHRpbmdSZXN0b3JlS2luZC5EZWxldGU7XG59XG5cbi8qKlxuICogUmVzdG9yaW5nIGEgc2V0dGluZyBieSB3cml0aW5nIGl0cyBwcmV2aW91cyB2YWx1ZSBiYWNrLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIERldmljZVNldHRpbmdSZXN0b3JlV3JpdGUge1xuICAvKipcbiAgICogRGlzY3JpbWluYXRlcyB0aGUgdW5pb24uXG4gICAqL1xuICByZWFkb25seSBraW5kOiBEZXZpY2VTZXR0aW5nUmVzdG9yZUtpbmQuV3JpdGU7XG5cbiAgLyoqXG4gICAqIFRoZSB2YWx1ZSB0byB3cml0ZSBiYWNrLCBleGFjdGx5IGFzIGl0IHdhcyByZWFkLlxuICAgKi9cbiAgcmVhZG9ubHkgdmFsdWU6IHN0cmluZztcbn1cblxuLyoqXG4gKiBXaGF0IGBzZXR0aW5ncyBnZXRgIHByaW50cyBmb3IgYSBzZXR0aW5nIHRoYXQgaGFzIG5ldmVyIGJlZW4gd3JpdHRlbi5cbiAqL1xuY29uc3QgVU5TRVRfU0VUVElOR19WQUxVRSA9ICdudWxsJztcblxuLyoqXG4gKiBEZWNpZGVzIGhvdyBhIHNldHRpbmcgcmVhZCBiZWZvcmUgYSBjaGFuZ2UgaXMgcHV0IGJhY2sgYWZ0ZXJ3YXJkcy5cbiAqXG4gKiBAcGFyYW0gcHJldmlvdXNWYWx1ZSAtIEV4YWN0bHkgd2hhdCBgcmVhZERldmljZVNldHRpbmdgIHJldHVybmVkIGJlZm9yZSB0aGUgY2hhbmdlLlxuICogQHJldHVybnMgV2hldGhlciB0byB3cml0ZSB0aGUgdmFsdWUgYmFjayBvciB0byByZW1vdmUgdGhlIHNldHRpbmcgYWdhaW4uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlRGV2aWNlU2V0dGluZ1Jlc3RvcmUocHJldmlvdXNWYWx1ZTogc3RyaW5nKTogRGV2aWNlU2V0dGluZ1Jlc3RvcmUge1xuICBpZiAocHJldmlvdXNWYWx1ZSA9PT0gVU5TRVRfU0VUVElOR19WQUxVRSkge1xuICAgIHJldHVybiB7IGtpbmQ6IERldmljZVNldHRpbmdSZXN0b3JlS2luZC5EZWxldGUgfTtcbiAgfVxuXG4gIHJldHVybiB7XG4gICAga2luZDogRGV2aWNlU2V0dGluZ1Jlc3RvcmVLaW5kLldyaXRlLFxuICAgIHZhbHVlOiBwcmV2aW91c1ZhbHVlXG4gIH07XG59XG4iXSwKICAibWFwcGluZ3MiOiAiQUFrQk8sSUFBSywyQkFBTCxrQkFBS0EsOEJBQUw7QUFJTCxFQUFBQSwwQkFBQSxZQUFTO0FBS1QsRUFBQUEsMEJBQUEsV0FBUTtBQVRFLFNBQUFBO0FBQUEsR0FBQTtBQTZDWixNQUFNLHNCQUFzQjtBQVFyQixTQUFTLDRCQUE0QixlQUE2QztBQUN2RixNQUFJLGtCQUFrQixxQkFBcUI7QUFDekMsV0FBTyxFQUFFLE1BQU0sc0JBQWdDO0FBQUEsRUFDakQ7QUFFQSxTQUFPO0FBQUEsSUFDTCxNQUFNO0FBQUEsSUFDTixPQUFPO0FBQUEsRUFDVDtBQUNGOyIsCiAgIm5hbWVzIjogWyJEZXZpY2VTZXR0aW5nUmVzdG9yZUtpbmQiXQp9Cg==