@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/es/cli.mjs CHANGED
@@ -425,6 +425,20 @@ $midsceneSetDpiMethod.SetImplementationFlags(
425
425
  $midsceneSetDpiMethod.GetMethodImplementationFlags() -bor
426
426
  [System.Reflection.MethodImplAttributes]::PreserveSig
427
427
  )
428
+ $midsceneGetDpiForSystemMethod = $midsceneType.DefinePInvokeMethod(
429
+ 'GetDpiForSystem',
430
+ 'user32.dll',
431
+ $midsceneMethodAttributes,
432
+ [System.Reflection.CallingConventions]::Standard,
433
+ [uint32],
434
+ [System.Type[]]@(),
435
+ [System.Runtime.InteropServices.CallingConvention]::Winapi,
436
+ [System.Runtime.InteropServices.CharSet]::None
437
+ )
438
+ $midsceneGetDpiForSystemMethod.SetImplementationFlags(
439
+ $midsceneGetDpiForSystemMethod.GetMethodImplementationFlags() -bor
440
+ [System.Reflection.MethodImplAttributes]::PreserveSig
441
+ )
428
442
  $midsceneGetForegroundWindowMethod = $midsceneType.DefinePInvokeMethod(
429
443
  'GetForegroundWindow',
430
444
  'user32.dll',
@@ -461,12 +475,8 @@ if ($midscenePreviousDpiContext -eq [System.IntPtr]::Zero) {
461
475
  throw 'Unable to enter the Per-Monitor V2 DPI awareness context.'
462
476
  }
463
477
  `.trim();
464
- function runWindowsPhysicalPixelPowershell(script) {
465
- const physicalPixelScript = `$ProgressPreference = 'SilentlyContinue'
466
- $ErrorActionPreference = 'Stop'
467
- ${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
468
- ${script}`;
469
- const encoded = Buffer.from(physicalPixelScript, 'utf16le').toString('base64');
478
+ function runWindowsPowershellScript(script) {
479
+ const encoded = Buffer.from(script, 'utf16le').toString('base64');
470
480
  return execFileSync('powershell.exe', [
471
481
  '-NoProfile',
472
482
  '-NonInteractive',
@@ -479,6 +489,130 @@ ${script}`;
479
489
  windowsHide: true
480
490
  });
481
491
  }
492
+ function runWindowsPowershellCommand(script) {
493
+ return execFileSync('powershell.exe', [
494
+ '-NoProfile',
495
+ '-Command',
496
+ script
497
+ ], {
498
+ encoding: 'utf8',
499
+ timeout: POWERSHELL_TIMEOUT_MS,
500
+ maxBuffer: POWERSHELL_MAX_BUFFER,
501
+ windowsHide: true
502
+ });
503
+ }
504
+ function runWindowsPowershell(script) {
505
+ return runWindowsPowershellCommand(`$ProgressPreference = 'SilentlyContinue'
506
+ $ErrorActionPreference = 'Stop'
507
+ ${script}`);
508
+ }
509
+ function runWindowsPhysicalPixelPowershell(script) {
510
+ const physicalPixelScript = `$ProgressPreference = 'SilentlyContinue'
511
+ $ErrorActionPreference = 'Stop'
512
+ ${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
513
+ ${script}`;
514
+ return runWindowsPowershellScript(physicalPixelScript);
515
+ }
516
+ class WindowsDisplayEnumerationEmptyError extends Error {
517
+ }
518
+ function isFiniteNumber(value) {
519
+ return 'number' == typeof value && Number.isFinite(value);
520
+ }
521
+ function isWindowsDisplayGeometry(value) {
522
+ if (!value || 'object' != typeof value) return false;
523
+ const candidate = value;
524
+ const bounds = candidate.bounds;
525
+ 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;
526
+ }
527
+ function readWindowsDisplayGeometriesWith(runPowershell, context) {
528
+ const serializeScreens = 'legacy' === context ? 'ConvertTo-Json -InputObject $screens -Compress' : '[Console]::Out.Write((ConvertTo-Json -InputObject $screens -Compress))';
529
+ const script = `
530
+ Add-Type -AssemblyName System.Windows.Forms
531
+ $screens = @([System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
532
+ $bounds = $_.Bounds
533
+ [PSCustomObject]@{
534
+ id = $_.DeviceName
535
+ name = $_.DeviceName
536
+ primary = $_.Primary
537
+ bounds = [PSCustomObject]@{
538
+ x = $bounds.X
539
+ y = $bounds.Y
540
+ width = $bounds.Width
541
+ height = $bounds.Height
542
+ }
543
+ }
544
+ })
545
+ ${serializeScreens}
546
+ `.trim();
547
+ const output = runPowershell(script).trim();
548
+ if (!output) throw new WindowsDisplayEnumerationEmptyError(`Windows ${context} display enumeration returned no data`);
549
+ const parsed = JSON.parse(output);
550
+ if (!Array.isArray(parsed)) throw new Error('Windows display enumeration returned invalid data');
551
+ const displays = parsed.filter(isWindowsDisplayGeometry);
552
+ if (displays.length !== parsed.length) throw new Error('Windows display enumeration returned invalid geometry');
553
+ if (0 === displays.length) throw new WindowsDisplayEnumerationEmptyError(`Windows ${context} display enumeration returned no displays`);
554
+ return displays;
555
+ }
556
+ function readWindowsDisplayGeometries() {
557
+ return readWindowsDisplayGeometriesWith(runWindowsPhysicalPixelPowershell, 'physical');
558
+ }
559
+ function discoverWindowsDisplays(runners = {
560
+ physical: runWindowsPhysicalPixelPowershell,
561
+ legacy: runWindowsPowershell
562
+ }) {
563
+ try {
564
+ return {
565
+ geometries: readWindowsDisplayGeometriesWith(runners.physical, 'physical'),
566
+ coordinateMode: 'physical'
567
+ };
568
+ } catch (error) {
569
+ if (!(error instanceof WindowsDisplayEnumerationEmptyError)) throw error;
570
+ const geometries = readWindowsDisplayGeometriesWith(runners.legacy, 'legacy');
571
+ return {
572
+ geometries,
573
+ coordinateMode: 'legacy'
574
+ };
575
+ }
576
+ }
577
+ function readPngDimensions(dataUri) {
578
+ const prefix = 'data:image/png;base64,';
579
+ if (!dataUri.startsWith(prefix)) throw new Error('Screenshot is not a PNG data URI');
580
+ const buffer = Buffer.from(dataUri.slice(prefix.length), 'base64');
581
+ const pngSignature = Buffer.from([
582
+ 137,
583
+ 80,
584
+ 78,
585
+ 71,
586
+ 13,
587
+ 10,
588
+ 26,
589
+ 10
590
+ ]);
591
+ if (buffer.length < 24 || !buffer.subarray(0, pngSignature.length).equals(pngSignature)) throw new Error('Screenshot returned invalid PNG data');
592
+ const width = buffer.readUInt32BE(16);
593
+ const height = buffer.readUInt32BE(20);
594
+ if (width <= 0 || height <= 0) throw new Error('Screenshot returned invalid PNG dimensions');
595
+ return {
596
+ width,
597
+ height
598
+ };
599
+ }
600
+ function assertLegacyWindowsCoordinateCompatibility({ geometry, screenshotBase64, inputSize }) {
601
+ if (!geometry?.primary) throw new Error('Windows legacy coordinate compatibility mode only supports the primary display');
602
+ 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})`);
603
+ const screenshotSize = readPngDimensions(screenshotBase64);
604
+ const displaySize = {
605
+ width: Math.round(geometry.bounds.width),
606
+ height: Math.round(geometry.bounds.height)
607
+ };
608
+ const sizesMatch = screenshotSize.width === displaySize.width && screenshotSize.height === displaySize.height && inputSize.width === displaySize.width && inputSize.height === displaySize.height;
609
+ 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.`);
610
+ }
611
+ function resolveWindowsDisplayGeometryFromList(displayId, displays) {
612
+ if (!displays.length) return;
613
+ if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
614
+ return displays.find((display)=>display.id === displayId);
615
+ }
482
616
  function windows_pointer_define_property(obj, key, value) {
483
617
  if (key in obj) Object.defineProperty(obj, key, {
484
618
  value: value,
@@ -796,28 +930,6 @@ const EDGE_SCROLL_SPEC = {
796
930
  function escapePowershellSingleQuoted(value) {
797
931
  return value.replace(/'/g, "''");
798
932
  }
799
- function readWindowsDisplayGeometries() {
800
- const script = `
801
- Add-Type -AssemblyName System.Windows.Forms
802
- $s = [System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
803
- $b = $_.Bounds
804
- [PSCustomObject]@{
805
- id = $_.DeviceName
806
- name = $_.DeviceName
807
- primary = $_.Primary
808
- bounds = [PSCustomObject]@{ x = $b.X; y = $b.Y; width = $b.Width; height = $b.Height }
809
- }
810
- }
811
- ConvertTo-Json @($s) -Compress
812
- `.trim();
813
- const output = runWindowsPhysicalPixelPowershell(script).trim();
814
- if (!output) throw new Error('Windows display enumeration returned no data');
815
- const parsed = JSON.parse(output);
816
- if (!Array.isArray(parsed)) throw new Error('Windows display enumeration returned invalid data');
817
- const displays = parsed.filter(isWindowsDisplayGeometry);
818
- if (displays.length !== parsed.length || 0 === displays.length) throw new Error('Windows display enumeration returned invalid geometry');
819
- return displays;
820
- }
821
933
  function listWindowsDisplays(geometries = readWindowsDisplayGeometries()) {
822
934
  return geometries.map((display)=>({
823
935
  id: display.id,
@@ -921,23 +1033,13 @@ function getDisplayInfoBinary() {
921
1033
  displayInfoBinaryPath = binPath;
922
1034
  return binPath;
923
1035
  }
924
- function isFiniteNumber(value) {
1036
+ function device_isFiniteNumber(value) {
925
1037
  return 'number' == typeof value && Number.isFinite(value);
926
1038
  }
927
- function isDisplayBounds(value) {
928
- if (!value || 'object' != typeof value) return false;
929
- const bounds = value;
930
- return isFiniteNumber(bounds.x) && isFiniteNumber(bounds.y) && isFiniteNumber(bounds.width) && isFiniteNumber(bounds.height) && bounds.width > 0 && bounds.height > 0;
931
- }
932
- function isWindowsDisplayGeometry(value) {
933
- if (!value || 'object' != typeof value) return false;
934
- const candidate = value;
935
- return 'string' == typeof candidate.id && candidate.id.length > 0 && 'string' == typeof candidate.name && 'boolean' == typeof candidate.primary && isDisplayBounds(candidate.bounds);
936
- }
937
1039
  function isDarwinDisplayGeometry(value) {
938
1040
  if (!value || 'object' != typeof value) return false;
939
1041
  const candidate = value;
940
- 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);
1042
+ 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);
941
1043
  }
942
1044
  function readDarwinDisplayGeometries() {
943
1045
  const bin = getDisplayInfoBinary();
@@ -1007,11 +1109,6 @@ function resolveDarwinDisplayGeometryFromList(displayId, displays) {
1007
1109
  if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays.find((display)=>0 === display.screenIndex) || displays[0];
1008
1110
  return displays.find((display)=>display.screenIndex === screenIndex) || displays.find((display)=>display.cgDisplayId === screenIndex);
1009
1111
  }
1010
- function resolveWindowsDisplayGeometryFromList(displayId, displays) {
1011
- if (!displays.length) return;
1012
- if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
1013
- return displays.find((display)=>display.id === displayId);
1014
- }
1015
1112
  function resolveDisplayGeometry(displayId, windowsDisplays) {
1016
1113
  if ('darwin' === process.platform) return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
1017
1114
  if ('win32' === process.platform) {
@@ -1104,13 +1201,24 @@ function normalizePrimaryKey(key) {
1104
1201
  return KEY_NAME_MAP[lowerKey] || lowerKey;
1105
1202
  }
1106
1203
  class ComputerDevice {
1204
+ usesPhysicalWindowsCoordinates() {
1205
+ return 'win32' === process.platform && 'physical' === this.windowsCoordinateContext.mode;
1206
+ }
1207
+ assertLegacyWindowsCoordinateCompatibility(screenshotBase64) {
1208
+ if ('win32' !== process.platform || 'legacy' !== this.windowsCoordinateContext.mode) return;
1209
+ assertLegacyWindowsCoordinateCompatibility({
1210
+ geometry: this.displayGeometry,
1211
+ screenshotBase64,
1212
+ inputSize: this.inputDriver.getScreenSize()
1213
+ });
1214
+ }
1107
1215
  async moveGlobalPointer(point, context, smooth) {
1108
1216
  if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
1109
1217
  const target = {
1110
1218
  x: Math.round(point.x),
1111
1219
  y: Math.round(point.y)
1112
1220
  };
1113
- if ('win32' === process.platform) {
1221
+ if (this.usesPhysicalWindowsCoordinates()) {
1114
1222
  const actual = this.windowsPointerDriver.moveTo(target, {
1115
1223
  smoothSteps: smooth?.smoothSteps,
1116
1224
  smoothDelayMs: smooth?.smoothDelay
@@ -1162,7 +1270,7 @@ class ComputerDevice {
1162
1270
  }
1163
1271
  static async listDisplays() {
1164
1272
  try {
1165
- if ('win32' === process.platform) return listWindowsDisplays();
1273
+ if ('win32' === process.platform) return listWindowsDisplays(discoverWindowsDisplays().geometries);
1166
1274
  const displays = await screenshot_desktop.listDisplays();
1167
1275
  return displays.map((d)=>({
1168
1276
  id: String(d.id),
@@ -1200,7 +1308,14 @@ class ComputerDevice {
1200
1308
  }
1201
1309
  }
1202
1310
  device_libnut = await getLibnut();
1203
- const windowsDisplayGeometries = 'win32' === process.platform ? readWindowsDisplayGeometries() : void 0;
1311
+ const windowsDisplayDiscovery = 'win32' === process.platform ? discoverWindowsDisplays() : void 0;
1312
+ const windowsDisplayGeometries = windowsDisplayDiscovery?.geometries;
1313
+ 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.');
1314
+ this.windowsCoordinateContext = windowsDisplayDiscovery?.coordinateMode === 'legacy' ? {
1315
+ mode: 'legacy'
1316
+ } : {
1317
+ mode: 'physical'
1318
+ };
1204
1319
  this.displayGeometry = resolveDisplayGeometry(this.displayId, windowsDisplayGeometries);
1205
1320
  const size = await this.size();
1206
1321
  const displays = windowsDisplayGeometries ? listWindowsDisplays(windowsDisplayGeometries) : await ComputerDevice.listDisplays();
@@ -1211,6 +1326,7 @@ Platform: ${process.platform}
1211
1326
  Display: ${this.displayId || 'Primary'}
1212
1327
  Screen Size: ${size.width}x${size.height}
1213
1328
  Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ') : 'Unknown'}${headlessInfo}
1329
+ ${'win32' === process.platform ? `Windows Coordinate Mode: ${this.windowsCoordinateContext.mode}` : ''}
1214
1330
  `;
1215
1331
  debugDevice('Computer device connected', this.description);
1216
1332
  await this.healthCheck(displays);
@@ -1234,7 +1350,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1234
1350
  }
1235
1351
  async healthCheck(displays) {
1236
1352
  console.log('[HealthCheck] Starting health check...');
1237
- console.log("[HealthCheck] @midscene/computer v1.13.0");
1353
+ console.log("[HealthCheck] @midscene/computer v1.13.1-beta-20260921032526.0");
1238
1354
  console.log('[HealthCheck] Taking screenshot...');
1239
1355
  const screenshotTimeout = 15000;
1240
1356
  let timeoutId;
@@ -1246,10 +1362,12 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1246
1362
  timeoutPromise
1247
1363
  ]);
1248
1364
  console.log(`[HealthCheck] Screenshot succeeded (length=${base64.length})`);
1365
+ this.assertLegacyWindowsCoordinateCompatibility(base64);
1249
1366
  console.log('[HealthCheck] Verifying mouse control...');
1250
- const startPos = 'win32' === process.platform ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
1367
+ const usePhysicalWindowsCoordinates = this.usesPhysicalWindowsCoordinates();
1368
+ const startPos = usePhysicalWindowsCoordinates ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
1251
1369
  console.log(`[HealthCheck] Current mouse position: (${startPos.x}, ${startPos.y})`);
1252
- if ('win32' === process.platform) {
1370
+ if (usePhysicalWindowsCoordinates) {
1253
1371
  if (!this.displayGeometry) throw new Error('Windows display geometry is unavailable');
1254
1372
  const bounds = this.displayGeometry.bounds;
1255
1373
  const target = {
@@ -1315,7 +1433,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1315
1433
  debugDevice('Taking screenshot', {
1316
1434
  displayId: this.displayId
1317
1435
  });
1318
- if ('win32' === process.platform) return this.screenshotViaPowershell();
1436
+ if (this.usesPhysicalWindowsCoordinates()) return this.screenshotViaPowershell();
1319
1437
  const options = {
1320
1438
  format: 'png'
1321
1439
  };
@@ -1478,7 +1596,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1478
1596
  this.inputDriver.sendKey(key, modifiers);
1479
1597
  }
1480
1598
  resolveUntargetedScrollPoint(screenSize) {
1481
- if ('win32' === process.platform) {
1599
+ if (this.usesPhysicalWindowsCoordinates()) {
1482
1600
  const activeWindowRect = this.windowsPointerDriver.getActiveWindowRect();
1483
1601
  if (activeWindowRect) {
1484
1602
  const activeWindowCenter = {
@@ -1622,6 +1740,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1622
1740
  device_define_property(this, "options", void 0);
1623
1741
  device_define_property(this, "displayId", void 0);
1624
1742
  device_define_property(this, "displayGeometry", void 0);
1743
+ device_define_property(this, "windowsCoordinateContext", {
1744
+ mode: 'physical'
1745
+ });
1625
1746
  device_define_property(this, "description", void 0);
1626
1747
  device_define_property(this, "destroyed", false);
1627
1748
  device_define_property(this, "xvfbInstance", void 0);
@@ -2723,7 +2844,7 @@ const tools = new ComputerMidsceneTools({
2723
2844
  });
2724
2845
  runToolsCLI(tools, 'midscene-computer', {
2725
2846
  stripPrefix: 'computer_',
2726
- version: "1.13.0",
2847
+ version: "1.13.1-beta-20260921032526.0",
2727
2848
  extraCommands: createReportCliCommands()
2728
2849
  }).catch((e)=>{
2729
2850
  process.exit(reportCLIError(e));