@midscene/android 1.10.8 → 1.10.9-beta-20260731103801.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/es/index.mjs CHANGED
@@ -98,20 +98,12 @@ var __webpack_modules__ = {
98
98
  try {
99
99
  this.isConnecting = true;
100
100
  debugScrcpy('Starting scrcpy connection...');
101
- const { AdbScrcpyClient, AdbScrcpyOptions3_3_3 } = await import("@yume-chan/adb-scrcpy");
101
+ const { AdbScrcpyClient } = await import("@yume-chan/adb-scrcpy");
102
102
  const { ReadableStream } = await import("@yume-chan/stream-extra");
103
103
  const { DefaultServerPath } = await import("@yume-chan/scrcpy");
104
104
  const serverBinPath = this.resolveServerBinPath();
105
105
  await AdbScrcpyClient.pushServer(this.adb, ReadableStream.from((0, node_fs__rspack_import_0.createReadStream)(serverBinPath)));
106
- const scrcpyOptions = new AdbScrcpyOptions3_3_3({
107
- audio: false,
108
- control: false,
109
- maxSize: this.options.maxSize,
110
- videoBitRate: this.options.videoBitRate,
111
- maxFps: 10,
112
- sendFrameMeta: true,
113
- videoCodecOptions: 'i-frame-interval=0,bitrate-mode=2'
114
- });
106
+ const scrcpyOptions = await this.createScrcpyOptions();
115
107
  this.scrcpyClient = await AdbScrcpyClient.start(this.adb, DefaultServerPath, scrcpyOptions);
116
108
  serverOutputTask = this.collectServerOutput(this.scrcpyClient.output, serverOutput);
117
109
  const videoStreamPromise = this.scrcpyClient.videoStream;
@@ -139,6 +131,23 @@ var __webpack_modules__ = {
139
131
  this.isConnecting = false;
140
132
  }
141
133
  }
134
+ async createScrcpyOptions() {
135
+ const [{ AdbScrcpyOptions3_3_3 }, { ScrcpyInstanceId }] = await Promise.all([
136
+ import("@yume-chan/adb-scrcpy"),
137
+ import("@yume-chan/scrcpy")
138
+ ]);
139
+ return new AdbScrcpyOptions3_3_3({
140
+ audio: false,
141
+ control: false,
142
+ tunnelForward: true,
143
+ scid: ScrcpyInstanceId.random(),
144
+ maxSize: this.options.maxSize,
145
+ videoBitRate: this.options.videoBitRate,
146
+ maxFps: 10,
147
+ sendFrameMeta: true,
148
+ videoCodecOptions: 'i-frame-interval=0,bitrate-mode=2'
149
+ });
150
+ }
142
151
  async collectServerOutput(output, lines) {
143
152
  const reader = output.getReader();
144
153
  try {
@@ -783,6 +792,230 @@ class ScrcpyDeviceAdapter {
783
792
  this.retryAfter = null;
784
793
  }
785
794
  }
795
+ const TAG_RE = /<(\/?)([A-Za-z_][A-Za-z0-9_.\-:]*)([^>]*?)(\/?)>/g;
796
+ const ATTR_RE = /([A-Za-z_][A-Za-z0-9_.\-:]*)\s*=\s*("([^"]*)"|'([^']*)')/g;
797
+ const BOUNDS_RE = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/;
798
+ const ENTITY_TABLE = {
799
+ amp: '&',
800
+ lt: '<',
801
+ gt: '>',
802
+ quot: '"',
803
+ apos: "'"
804
+ };
805
+ function decodeEntities(value) {
806
+ if (!value.includes('&')) return value;
807
+ return value.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (full, body)=>{
808
+ if (body.startsWith('#x') || body.startsWith('#X')) {
809
+ const code = Number.parseInt(body.slice(2), 16);
810
+ return Number.isFinite(code) ? String.fromCodePoint(code) : full;
811
+ }
812
+ if (body.startsWith('#')) {
813
+ const code = Number.parseInt(body.slice(1), 10);
814
+ return Number.isFinite(code) ? String.fromCodePoint(code) : full;
815
+ }
816
+ return ENTITY_TABLE[body] ?? full;
817
+ });
818
+ }
819
+ function parseAttrs(raw) {
820
+ const attrs = {};
821
+ ATTR_RE.lastIndex = 0;
822
+ let match;
823
+ while(match = ATTR_RE.exec(raw))attrs[match[1]] = decodeEntities(match[3] ?? match[4] ?? '');
824
+ return attrs;
825
+ }
826
+ function parseXml(input) {
827
+ if (0 === input.trim().length) throw new Error('parseAndroidUITreeXml: input is empty');
828
+ const cleaned = input.replace(/<\?xml[\s\S]*?\?>/g, '').replace(/<!DOCTYPE[\s\S]*?>/gi, '').replace(/<!--[\s\S]*?-->/g, '');
829
+ TAG_RE.lastIndex = 0;
830
+ const stack = [];
831
+ let root;
832
+ let match;
833
+ while(match = TAG_RE.exec(cleaned)){
834
+ const isClose = '/' === match[1];
835
+ const name = match[2];
836
+ if (isClose) {
837
+ const current = stack.pop();
838
+ if (!current || current.name !== name) throw new Error(`parseAndroidUITreeXml: unbalanced close tag </${name}>`);
839
+ continue;
840
+ }
841
+ const element = {
842
+ name,
843
+ attrs: parseAttrs(match[3] ?? ''),
844
+ children: []
845
+ };
846
+ const parent = stack[stack.length - 1];
847
+ if (parent) parent.children.push(element);
848
+ else if (root) throw new Error('parseAndroidUITreeXml: more than one top-level element');
849
+ else root = element;
850
+ if ('/' !== match[4]) stack.push(element);
851
+ }
852
+ if (stack.length > 0) throw new Error('parseAndroidUITreeXml: unclosed element');
853
+ if (!root) throw new Error('parseAndroidUITreeXml: no root element');
854
+ return root;
855
+ }
856
+ function parseBounds(raw, devicePixelRatio) {
857
+ const match = raw ? BOUNDS_RE.exec(raw) : void 0;
858
+ if (!match) return {
859
+ left: 0,
860
+ top: 0,
861
+ width: 0,
862
+ height: 0
863
+ };
864
+ const left = Number.parseInt(match[1], 10);
865
+ const top = Number.parseInt(match[2], 10);
866
+ const right = Number.parseInt(match[3], 10);
867
+ const bottom = Number.parseInt(match[4], 10);
868
+ const ratio = devicePixelRatio > 0 ? devicePixelRatio : 1;
869
+ return {
870
+ left: left / ratio,
871
+ top: top / ratio,
872
+ width: Math.max(0, right - left) / ratio,
873
+ height: Math.max(0, bottom - top) / ratio
874
+ };
875
+ }
876
+ function toUiNode(element, devicePixelRatio) {
877
+ const attrs = {};
878
+ for (const [name, value] of Object.entries(element.attrs))if ('bounds' !== name) attrs[name] = value;
879
+ return {
880
+ type: element.attrs.class || element.name,
881
+ attrs,
882
+ bounds: parseBounds(element.attrs.bounds, devicePixelRatio),
883
+ children: element.children.map((child)=>toUiNode(child, devicePixelRatio))
884
+ };
885
+ }
886
+ function uiautomatorXmlToUiNode(xml, devicePixelRatio) {
887
+ const root = parseXml(xml);
888
+ if ('hierarchy' === root.name && 1 === root.children.length) return toUiNode(root.children[0], devicePixelRatio);
889
+ return toUiNode(root, devicePixelRatio);
890
+ }
891
+ const ACCESSIBILITY_DUMP_ATTEMPTS = 3;
892
+ const ACCESSIBILITY_DUMP_TIMEOUT_MS = 5000;
893
+ const ACCESSIBILITY_DUMP_RETRY_DELAY_MS = 250;
894
+ const UIAUTOMATOR_LAYOUT_PATH = '/sdcard/midscene_window_dump.xml';
895
+ const debugUITree = (0, logger_.getDebug)('android:ui-tree');
896
+ function errorMessage(error) {
897
+ return error instanceof Error ? error.message : String(error);
898
+ }
899
+ function validateHierarchyXml(xml) {
900
+ if ('string' != typeof xml || !/<hierarchy(?:\s|>)/.test(xml) || !xml.includes('</hierarchy>')) throw new Error('uiautomator did not produce valid hierarchy XML');
901
+ }
902
+ function focusedPackageForLogicalDisplay(windowDump, logicalDisplayId) {
903
+ const displayMatches = [
904
+ ...windowDump.matchAll(/^\s*Display: mDisplayId=(\d+)\b.*$/gm)
905
+ ];
906
+ const displayIndex = displayMatches.findIndex((match)=>Number(match[1]) === logicalDisplayId);
907
+ if (-1 === displayIndex) return null;
908
+ const start = displayMatches[displayIndex].index ?? 0;
909
+ const end = displayMatches[displayIndex + 1]?.index ?? windowDump.length;
910
+ const displayBlock = windowDump.slice(start, end);
911
+ for (const field of [
912
+ 'mCurrentFocus',
913
+ 'mFocusedApp'
914
+ ]){
915
+ const value = displayBlock.match(new RegExp(`^\\s*${field}=([^\\n]*)$`, 'm'))?.[1];
916
+ const packageName = value?.match(/\bu\d+\s+([A-Za-z0-9_$.-]+)\//)?.[1];
917
+ if (packageName) return packageName;
918
+ }
919
+ return null;
920
+ }
921
+ function firstTreePackage(root) {
922
+ if (root.attrs.package) return root.attrs.package;
923
+ for (const child of root.children){
924
+ const packageName = firstTreePackage(child);
925
+ if (packageName) return packageName;
926
+ }
927
+ return null;
928
+ }
929
+ function windowDisplayIdsForPackage(windowDump, packageName) {
930
+ const windowMatches = [
931
+ ...windowDump.matchAll(/^\s*Window #\d+ Window\{.*$/gm)
932
+ ];
933
+ const displayIds = new Set();
934
+ for(let index = 0; index < windowMatches.length; index++){
935
+ const start = windowMatches[index].index ?? 0;
936
+ const end = windowMatches[index + 1]?.index ?? windowDump.length;
937
+ const windowBlock = windowDump.slice(start, end);
938
+ const ownerPackage = windowBlock.match(/^\s*mOwnerUid=.*\bpackage=([A-Za-z0-9_$.-]+)\b/m)?.[1];
939
+ const headerPackage = windowMatches[index][0].match(/\bu\d+\s+([A-Za-z0-9_$.-]+)\//)?.[1];
940
+ if (ownerPackage !== packageName && headerPackage !== packageName) continue;
941
+ const displayId = windowBlock.match(/^\s*mDisplayId=(\d+)\b/m)?.[1];
942
+ if (void 0 !== displayId) displayIds.add(Number(displayId));
943
+ }
944
+ return [
945
+ ...displayIds
946
+ ];
947
+ }
948
+ function uiTreeExtent(root) {
949
+ let right = root.bounds.left + root.bounds.width;
950
+ let bottom = root.bounds.top + root.bounds.height;
951
+ for (const child of root.children){
952
+ const childExtent = uiTreeExtent(child);
953
+ right = Math.max(right, childExtent.right);
954
+ bottom = Math.max(bottom, childExtent.bottom);
955
+ }
956
+ return {
957
+ right,
958
+ bottom
959
+ };
960
+ }
961
+ async function dumpAccessibilityXml(adb) {
962
+ await runAdbShellStdoutOrThrow(adb, `rm -f ${UIAUTOMATOR_LAYOUT_PATH}`, {
963
+ timeout: ACCESSIBILITY_DUMP_TIMEOUT_MS
964
+ });
965
+ await runAdbShellStdoutOrThrow(adb, `uiautomator dump --compressed ${UIAUTOMATOR_LAYOUT_PATH}`, {
966
+ timeout: ACCESSIBILITY_DUMP_TIMEOUT_MS
967
+ });
968
+ const xml = await runAdbShellStdoutOrThrow(adb, `cat ${UIAUTOMATOR_LAYOUT_PATH}`, {
969
+ timeout: ACCESSIBILITY_DUMP_TIMEOUT_MS
970
+ });
971
+ validateHierarchyXml(xml);
972
+ return xml;
973
+ }
974
+ async function validateUITreeDisplay(root, options) {
975
+ if ('number' != typeof options.displayId) return;
976
+ const { adb, displayId } = options;
977
+ const displayWindowDump = await runAdbShellStdoutOrThrow(adb, 'dumpsys window displays', {
978
+ timeout: ACCESSIBILITY_DUMP_TIMEOUT_MS
979
+ });
980
+ const expectedPackage = focusedPackageForLogicalDisplay(displayWindowDump, displayId);
981
+ const treePackage = firstTreePackage(root);
982
+ if (!expectedPackage) throw new Error(`Unable to verify Android UI tree display alignment for displayId=${displayId}: no focused package found on the target display`);
983
+ if (!treePackage) throw new Error(`Unable to verify Android UI tree display alignment for displayId=${displayId}: accessibility hierarchy has no package`);
984
+ const allWindowDump = await runAdbShellStdoutOrThrow(adb, 'dumpsys window windows', {
985
+ timeout: ACCESSIBILITY_DUMP_TIMEOUT_MS
986
+ });
987
+ const treePackageDisplayIds = windowDisplayIdsForPackage(allWindowDump, treePackage);
988
+ if (0 === treePackageDisplayIds.length) throw new Error(`Unable to verify Android UI tree display alignment for displayId=${displayId}: no window display ownership found for accessibility package ${treePackage}`);
989
+ if (!treePackageDisplayIds.includes(displayId)) throw new Error(`Android UI tree display mismatch for displayId=${displayId}: expected focused package ${expectedPackage}, but accessibility hierarchy belongs to ${treePackage} on displayId=${treePackageDisplayIds.join(',')}`);
990
+ const targetSize = await options.getDisplaySize();
991
+ const extent = uiTreeExtent(root);
992
+ const tolerance = 1;
993
+ if (extent.right > targetSize.width + tolerance || extent.bottom > targetSize.height + tolerance) throw new Error(`Android UI tree bounds exceed displayId=${displayId}: tree ${Math.round(extent.right)}x${Math.round(extent.bottom)} is outside target ${targetSize.width}x${targetSize.height}`);
994
+ }
995
+ async function captureAndroidUITree(options) {
996
+ const failures = [];
997
+ for(let attempt = 1; attempt <= ACCESSIBILITY_DUMP_ATTEMPTS; attempt++){
998
+ const startedAt = Date.now();
999
+ try {
1000
+ const xml = await dumpAccessibilityXml(options.adb);
1001
+ const capturedAt = Date.now();
1002
+ const root = uiautomatorXmlToUiNode(xml, options.devicePixelRatio);
1003
+ await validateUITreeDisplay(root, options);
1004
+ debugUITree('capture source=%s attempt=%d durationMs=%d bytes=%d', 'uiautomator', attempt, Date.now() - startedAt, xml.length);
1005
+ return {
1006
+ platform: 'android',
1007
+ capturedAt,
1008
+ root
1009
+ };
1010
+ } catch (error) {
1011
+ const failure = `uiautomator attempt ${attempt}/${ACCESSIBILITY_DUMP_ATTEMPTS}: ${errorMessage(error)}`;
1012
+ failures.push(failure);
1013
+ debugUITree('capture failed source=%s attempt=%d/%d durationMs=%d error=%s', 'uiautomator', attempt, ACCESSIBILITY_DUMP_ATTEMPTS, Date.now() - startedAt, errorMessage(error));
1014
+ if (attempt < ACCESSIBILITY_DUMP_ATTEMPTS) await sleep(ACCESSIBILITY_DUMP_RETRY_DELAY_MS);
1015
+ }
1016
+ }
1017
+ throw new Error(`Unable to capture Android UI tree after ${failures.length} attempt(s): ${failures.join('; ')}`);
1018
+ }
786
1019
  function device_define_property(obj, key, value) {
787
1020
  if (key in obj) Object.defineProperty(obj, key, {
788
1021
  value: value,
@@ -802,6 +1035,35 @@ const debugDevice = (0, logger_.getDebug)('android:device');
802
1035
  const warnDevice = (0, logger_.getDebug)('android:device', {
803
1036
  console: true
804
1037
  });
1038
+ function displayViewportForLogicalDisplay(displayDump, logicalDisplayId) {
1039
+ for (const viewportMatch of displayDump.matchAll(/DisplayViewport\{([^}]*)\}/g)){
1040
+ const viewport = viewportMatch[1];
1041
+ const displayId = viewport.match(/\bdisplayId=(\d+)\b/)?.[1];
1042
+ if (Number(displayId) === logicalDisplayId) return viewportMatch[0];
1043
+ }
1044
+ return null;
1045
+ }
1046
+ function displayInfoLineForPhysicalDisplay(displayDump, physicalDisplayId) {
1047
+ const lineRegex = new RegExp(`^.*uniqueId ["']local:${physicalDisplayId}["'].*$`, 'm');
1048
+ return displayDump.match(lineRegex)?.[0] ?? null;
1049
+ }
1050
+ function physicalDisplayIdForLogicalDisplay(displayDump, logicalDisplayId) {
1051
+ const viewport = displayViewportForLogicalDisplay(displayDump, logicalDisplayId);
1052
+ if (viewport) {
1053
+ const physicalId = viewport.match(/\buniqueId=['"]local:(\d+)['"]/)?.[1];
1054
+ if (physicalId) return physicalId;
1055
+ }
1056
+ const lines = displayDump.split(/\r?\n/);
1057
+ for(let index = 0; index < lines.length; index++){
1058
+ const displayId = lines[index].match(/^\s*mDisplayId=(\d+)\b/)?.[1];
1059
+ if (Number(displayId) === logicalDisplayId) for(let cursor = index + 1; cursor < lines.length; cursor++){
1060
+ if (/^\s*mDisplayId=\d+\b/.test(lines[cursor])) break;
1061
+ const physicalId = lines[cursor].match(/mBaseDisplayInfo=.*\buniqueId "local:(\d+)"/)?.[1];
1062
+ if (physicalId) return physicalId;
1063
+ }
1064
+ }
1065
+ return null;
1066
+ }
805
1067
  function escapeForShell(text) {
806
1068
  return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
807
1069
  }
@@ -1089,6 +1351,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1089
1351
  children: []
1090
1352
  };
1091
1353
  }
1354
+ async getUITree() {
1355
+ await this.initializeDevicePixelRatio();
1356
+ return captureAndroidUITree({
1357
+ adb: await this.getAdb(),
1358
+ devicePixelRatio: this.devicePixelRatio,
1359
+ displayId: this.options?.displayId,
1360
+ getDisplaySize: ()=>this.size()
1361
+ });
1362
+ }
1092
1363
  async getScreenSize() {
1093
1364
  const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
1094
1365
  debugDevice(`getScreenSize: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedSize=${!!this.cachedScreenSize}`);
@@ -1097,13 +1368,10 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1097
1368
  if ('number' == typeof this.options?.displayId) try {
1098
1369
  const stdout = await adb.shell('dumpsys display');
1099
1370
  if (this.options?.usePhysicalDisplayIdForDisplayLookup) {
1100
- const physicalDisplayId = await this.getPhysicalDisplayId();
1371
+ const physicalDisplayId = await this.resolvePhysicalDisplayId(stdout);
1101
1372
  if (physicalDisplayId) {
1102
- const lineRegex = new RegExp(`^.*uniqueId \"local:${physicalDisplayId}\".*$
1103
- `, 'm');
1104
- const lineMatch = stdout.match(lineRegex);
1105
- if (lineMatch) {
1106
- const targetLine = lineMatch[0];
1373
+ const targetLine = displayInfoLineForPhysicalDisplay(stdout, physicalDisplayId);
1374
+ if (targetLine) {
1107
1375
  const realMatch = targetLine.match(/real (\d+) x (\d+)/);
1108
1376
  const rotationMatch = targetLine.match(/rotation (\d+)/);
1109
1377
  if (realMatch && rotationMatch) {
@@ -1124,10 +1392,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1124
1392
  }
1125
1393
  }
1126
1394
  } else {
1127
- const viewportRegex = new RegExp(`DisplayViewport{[^}]*displayId=${this.options.displayId}[^}]*}`, 'g');
1128
- const match = stdout.match(viewportRegex);
1129
- if (match) {
1130
- const targetLine = match[0];
1395
+ const targetLine = displayViewportForLogicalDisplay(stdout, this.options.displayId);
1396
+ if (targetLine) {
1131
1397
  const physicalFrameMatch = targetLine.match(/physicalFrame=Rect\(\d+, \d+ - (\d+), (\d+)\)/);
1132
1398
  const orientationMatch = targetLine.match(/orientation=(\d+)/);
1133
1399
  if (physicalFrameMatch && orientationMatch) {
@@ -1193,13 +1459,10 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1193
1459
  if ('number' == typeof this.options?.displayId) try {
1194
1460
  const stdout = await adb.shell('dumpsys display');
1195
1461
  if (this.options?.usePhysicalDisplayIdForDisplayLookup) {
1196
- const physicalDisplayId = await this.getPhysicalDisplayId();
1462
+ const physicalDisplayId = await this.resolvePhysicalDisplayId(stdout);
1197
1463
  if (physicalDisplayId) {
1198
- const lineRegex = new RegExp(`^.*uniqueId \"local:${physicalDisplayId}\".*$
1199
- `, 'm');
1200
- const lineMatch = stdout.match(lineRegex);
1201
- if (lineMatch) {
1202
- const targetLine = lineMatch[0];
1464
+ const targetLine = displayInfoLineForPhysicalDisplay(stdout, physicalDisplayId);
1465
+ if (targetLine) {
1203
1466
  const densityMatch = targetLine.match(/density (\d+)/);
1204
1467
  if (densityMatch) {
1205
1468
  const density = Number(densityMatch[1]);
@@ -1337,7 +1600,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1337
1600
  debugDevice('screenshotBase64 end (scrcpy mode)');
1338
1601
  return result;
1339
1602
  } catch (error) {
1340
- debugDevice(`Scrcpy screenshot failed, falling back to standard ADB method.\nError: ${error}`);
1603
+ warnDevice(`Scrcpy screenshot failed, falling back to standard ADB method.\nError: ${error}`);
1341
1604
  }
1342
1605
  const adb = await this.getAdb();
1343
1606
  let screenshotBuffer;
@@ -1824,6 +2087,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1824
2087
  if ('number' == typeof this.options?.displayId && 0 !== this.options.displayId) warnDevice(`yadb ${operation} cannot target display ${this.options.displayId}; it will act on the default display.`);
1825
2088
  }
1826
2089
  async getPhysicalDisplayId() {
2090
+ return this.resolvePhysicalDisplayId();
2091
+ }
2092
+ async resolvePhysicalDisplayId(displayDump) {
1827
2093
  if (void 0 !== this.cachedPhysicalDisplayId) return this.cachedPhysicalDisplayId;
1828
2094
  if ('number' != typeof this.options?.displayId) {
1829
2095
  this.cachedPhysicalDisplayId = null;
@@ -1831,6 +2097,13 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1831
2097
  }
1832
2098
  const adb = await this.getAdb();
1833
2099
  try {
2100
+ const resolvedDisplayDump = displayDump ?? await adb.shell('dumpsys display');
2101
+ const logicalDisplayPhysicalId = physicalDisplayIdForLogicalDisplay(resolvedDisplayDump, this.options.displayId);
2102
+ if (logicalDisplayPhysicalId) {
2103
+ this.cachedPhysicalDisplayId = logicalDisplayPhysicalId;
2104
+ debugDevice(`Found and cached physical display ID: ${logicalDisplayPhysicalId} for logical display ID: ${this.options.displayId}`);
2105
+ return this.cachedPhysicalDisplayId;
2106
+ }
1834
2107
  const stdout = await adb.shell(`dumpsys SurfaceFlinger --display-id ${this.options.displayId}`);
1835
2108
  const regex = new RegExp(`Display (\\d+) \\(HWC display ${this.options.displayId}\\):`);
1836
2109
  const displayMatch = stdout.match(regex);