@midscene/computer 1.13.0 → 1.13.1-beta-20260920094844.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
@@ -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,137 @@ ${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 readPhysicalWindowsSystemDpi() {
578
+ const output = runWindowsPhysicalPixelPowershell('[Console]::Out.Write($midsceneNativeMethods::GetDpiForSystem())').trim();
579
+ const dpi = Number(output);
580
+ if (!Number.isFinite(dpi) || dpi <= 0) throw new Error(`Windows returned an invalid system DPI value: ${output}`);
581
+ return dpi;
582
+ }
583
+ function readPngDimensions(dataUri) {
584
+ const prefix = 'data:image/png;base64,';
585
+ if (!dataUri.startsWith(prefix)) throw new Error('Screenshot is not a PNG data URI');
586
+ const buffer = Buffer.from(dataUri.slice(prefix.length), 'base64');
587
+ const pngSignature = Buffer.from([
588
+ 137,
589
+ 80,
590
+ 78,
591
+ 71,
592
+ 13,
593
+ 10,
594
+ 26,
595
+ 10
596
+ ]);
597
+ if (buffer.length < 24 || !buffer.subarray(0, pngSignature.length).equals(pngSignature)) throw new Error('Screenshot returned invalid PNG data');
598
+ const width = buffer.readUInt32BE(16);
599
+ const height = buffer.readUInt32BE(20);
600
+ if (width <= 0 || height <= 0) throw new Error('Screenshot returned invalid PNG dimensions');
601
+ return {
602
+ width,
603
+ height
604
+ };
605
+ }
606
+ function assertLegacyWindowsCoordinateCompatibility({ geometry, systemDpi, screenshotBase64, inputSize }) {
607
+ if (!geometry?.primary) throw new Error('Windows legacy coordinate compatibility mode only supports the primary display');
608
+ 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})`);
609
+ if (96 !== systemDpi) throw new Error(`Windows legacy coordinate compatibility mode requires 100% display scaling (96 DPI), got ${systemDpi} DPI`);
610
+ const screenshotSize = readPngDimensions(screenshotBase64);
611
+ const displaySize = {
612
+ width: Math.round(geometry.bounds.width),
613
+ height: Math.round(geometry.bounds.height)
614
+ };
615
+ const sizesMatch = screenshotSize.width === displaySize.width && screenshotSize.height === displaySize.height && inputSize.width === displaySize.width && inputSize.height === displaySize.height;
616
+ 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.`);
617
+ }
618
+ function resolveWindowsDisplayGeometryFromList(displayId, displays) {
619
+ if (!displays.length) return;
620
+ if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
621
+ return displays.find((display)=>display.id === displayId);
622
+ }
482
623
  function windows_pointer_define_property(obj, key, value) {
483
624
  if (key in obj) Object.defineProperty(obj, key, {
484
625
  value: value,
@@ -796,28 +937,6 @@ const EDGE_SCROLL_SPEC = {
796
937
  function escapePowershellSingleQuoted(value) {
797
938
  return value.replace(/'/g, "''");
798
939
  }
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
940
  function listWindowsDisplays(geometries = readWindowsDisplayGeometries()) {
822
941
  return geometries.map((display)=>({
823
942
  id: display.id,
@@ -921,23 +1040,13 @@ function getDisplayInfoBinary() {
921
1040
  displayInfoBinaryPath = binPath;
922
1041
  return binPath;
923
1042
  }
924
- function isFiniteNumber(value) {
1043
+ function device_isFiniteNumber(value) {
925
1044
  return 'number' == typeof value && Number.isFinite(value);
926
1045
  }
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
1046
  function isDarwinDisplayGeometry(value) {
938
1047
  if (!value || 'object' != typeof value) return false;
939
1048
  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);
1049
+ 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
1050
  }
942
1051
  function readDarwinDisplayGeometries() {
943
1052
  const bin = getDisplayInfoBinary();
@@ -1007,11 +1116,6 @@ function resolveDarwinDisplayGeometryFromList(displayId, displays) {
1007
1116
  if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays.find((display)=>0 === display.screenIndex) || displays[0];
1008
1117
  return displays.find((display)=>display.screenIndex === screenIndex) || displays.find((display)=>display.cgDisplayId === screenIndex);
1009
1118
  }
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
1119
  function resolveDisplayGeometry(displayId, windowsDisplays) {
1016
1120
  if ('darwin' === process.platform) return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
1017
1121
  if ('win32' === process.platform) {
@@ -1104,13 +1208,25 @@ function normalizePrimaryKey(key) {
1104
1208
  return KEY_NAME_MAP[lowerKey] || lowerKey;
1105
1209
  }
1106
1210
  class ComputerDevice {
1211
+ usesPhysicalWindowsCoordinates() {
1212
+ return 'win32' === process.platform && 'physical' === this.windowsCoordinateContext.mode;
1213
+ }
1214
+ assertLegacyWindowsCoordinateCompatibility(screenshotBase64) {
1215
+ if ('win32' !== process.platform || 'legacy' !== this.windowsCoordinateContext.mode) return;
1216
+ assertLegacyWindowsCoordinateCompatibility({
1217
+ geometry: this.displayGeometry,
1218
+ systemDpi: this.windowsCoordinateContext.systemDpi,
1219
+ screenshotBase64,
1220
+ inputSize: this.inputDriver.getScreenSize()
1221
+ });
1222
+ }
1107
1223
  async moveGlobalPointer(point, context, smooth) {
1108
1224
  if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
1109
1225
  const target = {
1110
1226
  x: Math.round(point.x),
1111
1227
  y: Math.round(point.y)
1112
1228
  };
1113
- if ('win32' === process.platform) {
1229
+ if (this.usesPhysicalWindowsCoordinates()) {
1114
1230
  const actual = this.windowsPointerDriver.moveTo(target, {
1115
1231
  smoothSteps: smooth?.smoothSteps,
1116
1232
  smoothDelayMs: smooth?.smoothDelay
@@ -1162,7 +1278,7 @@ class ComputerDevice {
1162
1278
  }
1163
1279
  static async listDisplays() {
1164
1280
  try {
1165
- if ('win32' === process.platform) return listWindowsDisplays();
1281
+ if ('win32' === process.platform) return listWindowsDisplays(discoverWindowsDisplays().geometries);
1166
1282
  const displays = await screenshot_desktop.listDisplays();
1167
1283
  return displays.map((d)=>({
1168
1284
  id: String(d.id),
@@ -1200,7 +1316,15 @@ class ComputerDevice {
1200
1316
  }
1201
1317
  }
1202
1318
  device_libnut = await getLibnut();
1203
- const windowsDisplayGeometries = 'win32' === process.platform ? readWindowsDisplayGeometries() : void 0;
1319
+ const windowsDisplayDiscovery = 'win32' === process.platform ? discoverWindowsDisplays() : void 0;
1320
+ const windowsDisplayGeometries = windowsDisplayDiscovery?.geometries;
1321
+ 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.');
1322
+ this.windowsCoordinateContext = windowsDisplayDiscovery?.coordinateMode === 'legacy' ? {
1323
+ mode: 'legacy',
1324
+ systemDpi: readPhysicalWindowsSystemDpi()
1325
+ } : {
1326
+ mode: 'physical'
1327
+ };
1204
1328
  this.displayGeometry = resolveDisplayGeometry(this.displayId, windowsDisplayGeometries);
1205
1329
  const size = await this.size();
1206
1330
  const displays = windowsDisplayGeometries ? listWindowsDisplays(windowsDisplayGeometries) : await ComputerDevice.listDisplays();
@@ -1211,6 +1335,7 @@ Platform: ${process.platform}
1211
1335
  Display: ${this.displayId || 'Primary'}
1212
1336
  Screen Size: ${size.width}x${size.height}
1213
1337
  Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ') : 'Unknown'}${headlessInfo}
1338
+ ${'win32' === process.platform ? `Windows Coordinate Mode: ${this.windowsCoordinateContext.mode}` : ''}
1214
1339
  `;
1215
1340
  debugDevice('Computer device connected', this.description);
1216
1341
  await this.healthCheck(displays);
@@ -1234,7 +1359,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1234
1359
  }
1235
1360
  async healthCheck(displays) {
1236
1361
  console.log('[HealthCheck] Starting health check...');
1237
- console.log("[HealthCheck] @midscene/computer v1.13.0");
1362
+ console.log("[HealthCheck] @midscene/computer v1.13.1-beta-20260920094844.0");
1238
1363
  console.log('[HealthCheck] Taking screenshot...');
1239
1364
  const screenshotTimeout = 15000;
1240
1365
  let timeoutId;
@@ -1246,10 +1371,12 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1246
1371
  timeoutPromise
1247
1372
  ]);
1248
1373
  console.log(`[HealthCheck] Screenshot succeeded (length=${base64.length})`);
1374
+ this.assertLegacyWindowsCoordinateCompatibility(base64);
1249
1375
  console.log('[HealthCheck] Verifying mouse control...');
1250
- const startPos = 'win32' === process.platform ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
1376
+ const usePhysicalWindowsCoordinates = this.usesPhysicalWindowsCoordinates();
1377
+ const startPos = usePhysicalWindowsCoordinates ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
1251
1378
  console.log(`[HealthCheck] Current mouse position: (${startPos.x}, ${startPos.y})`);
1252
- if ('win32' === process.platform) {
1379
+ if (usePhysicalWindowsCoordinates) {
1253
1380
  if (!this.displayGeometry) throw new Error('Windows display geometry is unavailable');
1254
1381
  const bounds = this.displayGeometry.bounds;
1255
1382
  const target = {
@@ -1315,7 +1442,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1315
1442
  debugDevice('Taking screenshot', {
1316
1443
  displayId: this.displayId
1317
1444
  });
1318
- if ('win32' === process.platform) return this.screenshotViaPowershell();
1445
+ if (this.usesPhysicalWindowsCoordinates()) return this.screenshotViaPowershell();
1319
1446
  const options = {
1320
1447
  format: 'png'
1321
1448
  };
@@ -1478,7 +1605,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1478
1605
  this.inputDriver.sendKey(key, modifiers);
1479
1606
  }
1480
1607
  resolveUntargetedScrollPoint(screenSize) {
1481
- if ('win32' === process.platform) {
1608
+ if (this.usesPhysicalWindowsCoordinates()) {
1482
1609
  const activeWindowRect = this.windowsPointerDriver.getActiveWindowRect();
1483
1610
  if (activeWindowRect) {
1484
1611
  const activeWindowCenter = {
@@ -1622,6 +1749,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1622
1749
  device_define_property(this, "options", void 0);
1623
1750
  device_define_property(this, "displayId", void 0);
1624
1751
  device_define_property(this, "displayGeometry", void 0);
1752
+ device_define_property(this, "windowsCoordinateContext", {
1753
+ mode: 'physical'
1754
+ });
1625
1755
  device_define_property(this, "description", void 0);
1626
1756
  device_define_property(this, "destroyed", false);
1627
1757
  device_define_property(this, "xvfbInstance", void 0);
@@ -2762,7 +2892,7 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
2762
2892
  }
2763
2893
  }
2764
2894
  function version() {
2765
- const currentVersion = "1.13.0";
2895
+ const currentVersion = "1.13.1-beta-20260920094844.0";
2766
2896
  console.log(`@midscene/computer v${currentVersion}`);
2767
2897
  return currentVersion;
2768
2898
  }