@midscene/computer 1.13.0 → 1.13.1-beta-20260921032526.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/lib/cli.js CHANGED
@@ -450,6 +450,20 @@ $midsceneSetDpiMethod.SetImplementationFlags(
450
450
  $midsceneSetDpiMethod.GetMethodImplementationFlags() -bor
451
451
  [System.Reflection.MethodImplAttributes]::PreserveSig
452
452
  )
453
+ $midsceneGetDpiForSystemMethod = $midsceneType.DefinePInvokeMethod(
454
+ 'GetDpiForSystem',
455
+ 'user32.dll',
456
+ $midsceneMethodAttributes,
457
+ [System.Reflection.CallingConventions]::Standard,
458
+ [uint32],
459
+ [System.Type[]]@(),
460
+ [System.Runtime.InteropServices.CallingConvention]::Winapi,
461
+ [System.Runtime.InteropServices.CharSet]::None
462
+ )
463
+ $midsceneGetDpiForSystemMethod.SetImplementationFlags(
464
+ $midsceneGetDpiForSystemMethod.GetMethodImplementationFlags() -bor
465
+ [System.Reflection.MethodImplAttributes]::PreserveSig
466
+ )
453
467
  $midsceneGetForegroundWindowMethod = $midsceneType.DefinePInvokeMethod(
454
468
  'GetForegroundWindow',
455
469
  'user32.dll',
@@ -486,12 +500,8 @@ if ($midscenePreviousDpiContext -eq [System.IntPtr]::Zero) {
486
500
  throw 'Unable to enter the Per-Monitor V2 DPI awareness context.'
487
501
  }
488
502
  `.trim();
489
- function runWindowsPhysicalPixelPowershell(script) {
490
- const physicalPixelScript = `$ProgressPreference = 'SilentlyContinue'
491
- $ErrorActionPreference = 'Stop'
492
- ${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
493
- ${script}`;
494
- const encoded = Buffer.from(physicalPixelScript, 'utf16le').toString('base64');
503
+ function runWindowsPowershellScript(script) {
504
+ const encoded = Buffer.from(script, 'utf16le').toString('base64');
495
505
  return (0, external_node_child_process_namespaceObject.execFileSync)('powershell.exe', [
496
506
  '-NoProfile',
497
507
  '-NonInteractive',
@@ -504,6 +514,130 @@ ${script}`;
504
514
  windowsHide: true
505
515
  });
506
516
  }
517
+ function runWindowsPowershellCommand(script) {
518
+ return (0, external_node_child_process_namespaceObject.execFileSync)('powershell.exe', [
519
+ '-NoProfile',
520
+ '-Command',
521
+ script
522
+ ], {
523
+ encoding: 'utf8',
524
+ timeout: POWERSHELL_TIMEOUT_MS,
525
+ maxBuffer: POWERSHELL_MAX_BUFFER,
526
+ windowsHide: true
527
+ });
528
+ }
529
+ function runWindowsPowershell(script) {
530
+ return runWindowsPowershellCommand(`$ProgressPreference = 'SilentlyContinue'
531
+ $ErrorActionPreference = 'Stop'
532
+ ${script}`);
533
+ }
534
+ function runWindowsPhysicalPixelPowershell(script) {
535
+ const physicalPixelScript = `$ProgressPreference = 'SilentlyContinue'
536
+ $ErrorActionPreference = 'Stop'
537
+ ${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
538
+ ${script}`;
539
+ return runWindowsPowershellScript(physicalPixelScript);
540
+ }
541
+ class WindowsDisplayEnumerationEmptyError extends Error {
542
+ }
543
+ function isFiniteNumber(value) {
544
+ return 'number' == typeof value && Number.isFinite(value);
545
+ }
546
+ function isWindowsDisplayGeometry(value) {
547
+ if (!value || 'object' != typeof value) return false;
548
+ const candidate = value;
549
+ const bounds = candidate.bounds;
550
+ return 'string' == typeof candidate.id && candidate.id.length > 0 && 'string' == typeof candidate.name && 'boolean' == typeof candidate.primary && !!bounds && isFiniteNumber(bounds.x) && isFiniteNumber(bounds.y) && isFiniteNumber(bounds.width) && isFiniteNumber(bounds.height) && bounds.width > 0 && bounds.height > 0;
551
+ }
552
+ function readWindowsDisplayGeometriesWith(runPowershell, context) {
553
+ const serializeScreens = 'legacy' === context ? 'ConvertTo-Json -InputObject $screens -Compress' : '[Console]::Out.Write((ConvertTo-Json -InputObject $screens -Compress))';
554
+ const script = `
555
+ Add-Type -AssemblyName System.Windows.Forms
556
+ $screens = @([System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
557
+ $bounds = $_.Bounds
558
+ [PSCustomObject]@{
559
+ id = $_.DeviceName
560
+ name = $_.DeviceName
561
+ primary = $_.Primary
562
+ bounds = [PSCustomObject]@{
563
+ x = $bounds.X
564
+ y = $bounds.Y
565
+ width = $bounds.Width
566
+ height = $bounds.Height
567
+ }
568
+ }
569
+ })
570
+ ${serializeScreens}
571
+ `.trim();
572
+ const output = runPowershell(script).trim();
573
+ if (!output) throw new WindowsDisplayEnumerationEmptyError(`Windows ${context} display enumeration returned no data`);
574
+ const parsed = JSON.parse(output);
575
+ if (!Array.isArray(parsed)) throw new Error('Windows display enumeration returned invalid data');
576
+ const displays = parsed.filter(isWindowsDisplayGeometry);
577
+ if (displays.length !== parsed.length) throw new Error('Windows display enumeration returned invalid geometry');
578
+ if (0 === displays.length) throw new WindowsDisplayEnumerationEmptyError(`Windows ${context} display enumeration returned no displays`);
579
+ return displays;
580
+ }
581
+ function readWindowsDisplayGeometries() {
582
+ return readWindowsDisplayGeometriesWith(runWindowsPhysicalPixelPowershell, 'physical');
583
+ }
584
+ function discoverWindowsDisplays(runners = {
585
+ physical: runWindowsPhysicalPixelPowershell,
586
+ legacy: runWindowsPowershell
587
+ }) {
588
+ try {
589
+ return {
590
+ geometries: readWindowsDisplayGeometriesWith(runners.physical, 'physical'),
591
+ coordinateMode: 'physical'
592
+ };
593
+ } catch (error) {
594
+ if (!(error instanceof WindowsDisplayEnumerationEmptyError)) throw error;
595
+ const geometries = readWindowsDisplayGeometriesWith(runners.legacy, 'legacy');
596
+ return {
597
+ geometries,
598
+ coordinateMode: 'legacy'
599
+ };
600
+ }
601
+ }
602
+ function readPngDimensions(dataUri) {
603
+ const prefix = 'data:image/png;base64,';
604
+ if (!dataUri.startsWith(prefix)) throw new Error('Screenshot is not a PNG data URI');
605
+ const buffer = Buffer.from(dataUri.slice(prefix.length), 'base64');
606
+ const pngSignature = Buffer.from([
607
+ 137,
608
+ 80,
609
+ 78,
610
+ 71,
611
+ 13,
612
+ 10,
613
+ 26,
614
+ 10
615
+ ]);
616
+ if (buffer.length < 24 || !buffer.subarray(0, pngSignature.length).equals(pngSignature)) throw new Error('Screenshot returned invalid PNG data');
617
+ const width = buffer.readUInt32BE(16);
618
+ const height = buffer.readUInt32BE(20);
619
+ if (width <= 0 || height <= 0) throw new Error('Screenshot returned invalid PNG dimensions');
620
+ return {
621
+ width,
622
+ height
623
+ };
624
+ }
625
+ function assertLegacyWindowsCoordinateCompatibility({ geometry, screenshotBase64, inputSize }) {
626
+ if (!geometry?.primary) throw new Error('Windows legacy coordinate compatibility mode only supports the primary display');
627
+ if (0 !== geometry.bounds.x || 0 !== geometry.bounds.y) throw new Error(`Windows legacy coordinate compatibility mode requires a primary display origin of (0, 0), got (${geometry.bounds.x}, ${geometry.bounds.y})`);
628
+ const screenshotSize = readPngDimensions(screenshotBase64);
629
+ const displaySize = {
630
+ width: Math.round(geometry.bounds.width),
631
+ height: Math.round(geometry.bounds.height)
632
+ };
633
+ const sizesMatch = screenshotSize.width === displaySize.width && screenshotSize.height === displaySize.height && inputSize.width === displaySize.width && inputSize.height === displaySize.height;
634
+ if (!sizesMatch) throw new Error(`Windows legacy coordinate compatibility check failed: display=${displaySize.width}x${displaySize.height}, screenshot=${screenshotSize.width}x${screenshotSize.height}, input=${inputSize.width}x${inputSize.height}. Refusing to continue because clicks may use incorrect coordinates.`);
635
+ }
636
+ function resolveWindowsDisplayGeometryFromList(displayId, displays) {
637
+ if (!displays.length) return;
638
+ if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
639
+ return displays.find((display)=>display.id === displayId);
640
+ }
507
641
  function windows_pointer_define_property(obj, key, value) {
508
642
  if (key in obj) Object.defineProperty(obj, key, {
509
643
  value: value,
@@ -822,28 +956,6 @@ const EDGE_SCROLL_SPEC = {
822
956
  function escapePowershellSingleQuoted(value) {
823
957
  return value.replace(/'/g, "''");
824
958
  }
825
- function readWindowsDisplayGeometries() {
826
- const script = `
827
- Add-Type -AssemblyName System.Windows.Forms
828
- $s = [System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
829
- $b = $_.Bounds
830
- [PSCustomObject]@{
831
- id = $_.DeviceName
832
- name = $_.DeviceName
833
- primary = $_.Primary
834
- bounds = [PSCustomObject]@{ x = $b.X; y = $b.Y; width = $b.Width; height = $b.Height }
835
- }
836
- }
837
- ConvertTo-Json @($s) -Compress
838
- `.trim();
839
- const output = runWindowsPhysicalPixelPowershell(script).trim();
840
- if (!output) throw new Error('Windows display enumeration returned no data');
841
- const parsed = JSON.parse(output);
842
- if (!Array.isArray(parsed)) throw new Error('Windows display enumeration returned invalid data');
843
- const displays = parsed.filter(isWindowsDisplayGeometry);
844
- if (displays.length !== parsed.length || 0 === displays.length) throw new Error('Windows display enumeration returned invalid geometry');
845
- return displays;
846
- }
847
959
  function listWindowsDisplays(geometries = readWindowsDisplayGeometries()) {
848
960
  return geometries.map((display)=>({
849
961
  id: display.id,
@@ -947,23 +1059,13 @@ function getDisplayInfoBinary() {
947
1059
  displayInfoBinaryPath = binPath;
948
1060
  return binPath;
949
1061
  }
950
- function isFiniteNumber(value) {
1062
+ function device_isFiniteNumber(value) {
951
1063
  return 'number' == typeof value && Number.isFinite(value);
952
1064
  }
953
- function isDisplayBounds(value) {
954
- if (!value || 'object' != typeof value) return false;
955
- const bounds = value;
956
- return isFiniteNumber(bounds.x) && isFiniteNumber(bounds.y) && isFiniteNumber(bounds.width) && isFiniteNumber(bounds.height) && bounds.width > 0 && bounds.height > 0;
957
- }
958
- function isWindowsDisplayGeometry(value) {
959
- if (!value || 'object' != typeof value) return false;
960
- const candidate = value;
961
- return 'string' == typeof candidate.id && candidate.id.length > 0 && 'string' == typeof candidate.name && 'boolean' == typeof candidate.primary && isDisplayBounds(candidate.bounds);
962
- }
963
1065
  function isDarwinDisplayGeometry(value) {
964
1066
  if (!value || 'object' != typeof value) return false;
965
1067
  const candidate = value;
966
- return Number.isInteger(candidate.screenIndex) && Number.isInteger(candidate.cgDisplayId) && 'boolean' == typeof candidate.primary && !!candidate.bounds && isFiniteNumber(candidate.bounds.x) && isFiniteNumber(candidate.bounds.y) && isFiniteNumber(candidate.bounds.width) && isFiniteNumber(candidate.bounds.height);
1068
+ return Number.isInteger(candidate.screenIndex) && Number.isInteger(candidate.cgDisplayId) && 'boolean' == typeof candidate.primary && !!candidate.bounds && device_isFiniteNumber(candidate.bounds.x) && device_isFiniteNumber(candidate.bounds.y) && device_isFiniteNumber(candidate.bounds.width) && device_isFiniteNumber(candidate.bounds.height);
967
1069
  }
968
1070
  function readDarwinDisplayGeometries() {
969
1071
  const bin = getDisplayInfoBinary();
@@ -1033,11 +1135,6 @@ function resolveDarwinDisplayGeometryFromList(displayId, displays) {
1033
1135
  if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays.find((display)=>0 === display.screenIndex) || displays[0];
1034
1136
  return displays.find((display)=>display.screenIndex === screenIndex) || displays.find((display)=>display.cgDisplayId === screenIndex);
1035
1137
  }
1036
- function resolveWindowsDisplayGeometryFromList(displayId, displays) {
1037
- if (!displays.length) return;
1038
- if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
1039
- return displays.find((display)=>display.id === displayId);
1040
- }
1041
1138
  function resolveDisplayGeometry(displayId, windowsDisplays) {
1042
1139
  if ('darwin' === process.platform) return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
1043
1140
  if ('win32' === process.platform) {
@@ -1130,13 +1227,24 @@ function normalizePrimaryKey(key) {
1130
1227
  return KEY_NAME_MAP[lowerKey] || lowerKey;
1131
1228
  }
1132
1229
  class ComputerDevice {
1230
+ usesPhysicalWindowsCoordinates() {
1231
+ return 'win32' === process.platform && 'physical' === this.windowsCoordinateContext.mode;
1232
+ }
1233
+ assertLegacyWindowsCoordinateCompatibility(screenshotBase64) {
1234
+ if ('win32' !== process.platform || 'legacy' !== this.windowsCoordinateContext.mode) return;
1235
+ assertLegacyWindowsCoordinateCompatibility({
1236
+ geometry: this.displayGeometry,
1237
+ screenshotBase64,
1238
+ inputSize: this.inputDriver.getScreenSize()
1239
+ });
1240
+ }
1133
1241
  async moveGlobalPointer(point, context, smooth) {
1134
1242
  if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
1135
1243
  const target = {
1136
1244
  x: Math.round(point.x),
1137
1245
  y: Math.round(point.y)
1138
1246
  };
1139
- if ('win32' === process.platform) {
1247
+ if (this.usesPhysicalWindowsCoordinates()) {
1140
1248
  const actual = this.windowsPointerDriver.moveTo(target, {
1141
1249
  smoothSteps: smooth?.smoothSteps,
1142
1250
  smoothDelayMs: smooth?.smoothDelay
@@ -1188,7 +1296,7 @@ class ComputerDevice {
1188
1296
  }
1189
1297
  static async listDisplays() {
1190
1298
  try {
1191
- if ('win32' === process.platform) return listWindowsDisplays();
1299
+ if ('win32' === process.platform) return listWindowsDisplays(discoverWindowsDisplays().geometries);
1192
1300
  const displays = await external_screenshot_desktop_default().listDisplays();
1193
1301
  return displays.map((d)=>({
1194
1302
  id: String(d.id),
@@ -1226,7 +1334,14 @@ class ComputerDevice {
1226
1334
  }
1227
1335
  }
1228
1336
  device_libnut = await getLibnut();
1229
- const windowsDisplayGeometries = 'win32' === process.platform ? readWindowsDisplayGeometries() : void 0;
1337
+ const windowsDisplayDiscovery = 'win32' === process.platform ? discoverWindowsDisplays() : void 0;
1338
+ const windowsDisplayGeometries = windowsDisplayDiscovery?.geometries;
1339
+ if (windowsDisplayDiscovery?.coordinateMode === 'legacy') warnDevice('Windows display enumeration returned no data in the Per-Monitor V2 DPI context; using the legacy Windows coordinate path for this device connection.');
1340
+ this.windowsCoordinateContext = windowsDisplayDiscovery?.coordinateMode === 'legacy' ? {
1341
+ mode: 'legacy'
1342
+ } : {
1343
+ mode: 'physical'
1344
+ };
1230
1345
  this.displayGeometry = resolveDisplayGeometry(this.displayId, windowsDisplayGeometries);
1231
1346
  const size = await this.size();
1232
1347
  const displays = windowsDisplayGeometries ? listWindowsDisplays(windowsDisplayGeometries) : await ComputerDevice.listDisplays();
@@ -1237,6 +1352,7 @@ Platform: ${process.platform}
1237
1352
  Display: ${this.displayId || 'Primary'}
1238
1353
  Screen Size: ${size.width}x${size.height}
1239
1354
  Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ') : 'Unknown'}${headlessInfo}
1355
+ ${'win32' === process.platform ? `Windows Coordinate Mode: ${this.windowsCoordinateContext.mode}` : ''}
1240
1356
  `;
1241
1357
  debugDevice('Computer device connected', this.description);
1242
1358
  await this.healthCheck(displays);
@@ -1260,7 +1376,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1260
1376
  }
1261
1377
  async healthCheck(displays) {
1262
1378
  console.log('[HealthCheck] Starting health check...');
1263
- console.log("[HealthCheck] @midscene/computer v1.13.0");
1379
+ console.log("[HealthCheck] @midscene/computer v1.13.1-beta-20260921032526.0");
1264
1380
  console.log('[HealthCheck] Taking screenshot...');
1265
1381
  const screenshotTimeout = 15000;
1266
1382
  let timeoutId;
@@ -1272,10 +1388,12 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1272
1388
  timeoutPromise
1273
1389
  ]);
1274
1390
  console.log(`[HealthCheck] Screenshot succeeded (length=${base64.length})`);
1391
+ this.assertLegacyWindowsCoordinateCompatibility(base64);
1275
1392
  console.log('[HealthCheck] Verifying mouse control...');
1276
- const startPos = 'win32' === process.platform ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
1393
+ const usePhysicalWindowsCoordinates = this.usesPhysicalWindowsCoordinates();
1394
+ const startPos = usePhysicalWindowsCoordinates ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
1277
1395
  console.log(`[HealthCheck] Current mouse position: (${startPos.x}, ${startPos.y})`);
1278
- if ('win32' === process.platform) {
1396
+ if (usePhysicalWindowsCoordinates) {
1279
1397
  if (!this.displayGeometry) throw new Error('Windows display geometry is unavailable');
1280
1398
  const bounds = this.displayGeometry.bounds;
1281
1399
  const target = {
@@ -1341,7 +1459,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1341
1459
  debugDevice('Taking screenshot', {
1342
1460
  displayId: this.displayId
1343
1461
  });
1344
- if ('win32' === process.platform) return this.screenshotViaPowershell();
1462
+ if (this.usesPhysicalWindowsCoordinates()) return this.screenshotViaPowershell();
1345
1463
  const options = {
1346
1464
  format: 'png'
1347
1465
  };
@@ -1504,7 +1622,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1504
1622
  this.inputDriver.sendKey(key, modifiers);
1505
1623
  }
1506
1624
  resolveUntargetedScrollPoint(screenSize) {
1507
- if ('win32' === process.platform) {
1625
+ if (this.usesPhysicalWindowsCoordinates()) {
1508
1626
  const activeWindowRect = this.windowsPointerDriver.getActiveWindowRect();
1509
1627
  if (activeWindowRect) {
1510
1628
  const activeWindowCenter = {
@@ -1648,6 +1766,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1648
1766
  device_define_property(this, "options", void 0);
1649
1767
  device_define_property(this, "displayId", void 0);
1650
1768
  device_define_property(this, "displayGeometry", void 0);
1769
+ device_define_property(this, "windowsCoordinateContext", {
1770
+ mode: 'physical'
1771
+ });
1651
1772
  device_define_property(this, "description", void 0);
1652
1773
  device_define_property(this, "destroyed", false);
1653
1774
  device_define_property(this, "xvfbInstance", void 0);
@@ -2750,7 +2871,7 @@ const tools = new ComputerMidsceneTools({
2750
2871
  });
2751
2872
  (0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-computer', {
2752
2873
  stripPrefix: 'computer_',
2753
- version: "1.13.0",
2874
+ version: "1.13.1-beta-20260921032526.0",
2754
2875
  extraCommands: (0, core_namespaceObject.createReportCliCommands)()
2755
2876
  }).catch((e)=>{
2756
2877
  process.exit((0, cli_namespaceObject.reportCLIError)(e));