@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/lib/index.js CHANGED
@@ -476,6 +476,20 @@ $midsceneSetDpiMethod.SetImplementationFlags(
476
476
  $midsceneSetDpiMethod.GetMethodImplementationFlags() -bor
477
477
  [System.Reflection.MethodImplAttributes]::PreserveSig
478
478
  )
479
+ $midsceneGetDpiForSystemMethod = $midsceneType.DefinePInvokeMethod(
480
+ 'GetDpiForSystem',
481
+ 'user32.dll',
482
+ $midsceneMethodAttributes,
483
+ [System.Reflection.CallingConventions]::Standard,
484
+ [uint32],
485
+ [System.Type[]]@(),
486
+ [System.Runtime.InteropServices.CallingConvention]::Winapi,
487
+ [System.Runtime.InteropServices.CharSet]::None
488
+ )
489
+ $midsceneGetDpiForSystemMethod.SetImplementationFlags(
490
+ $midsceneGetDpiForSystemMethod.GetMethodImplementationFlags() -bor
491
+ [System.Reflection.MethodImplAttributes]::PreserveSig
492
+ )
479
493
  $midsceneGetForegroundWindowMethod = $midsceneType.DefinePInvokeMethod(
480
494
  'GetForegroundWindow',
481
495
  'user32.dll',
@@ -512,12 +526,8 @@ if ($midscenePreviousDpiContext -eq [System.IntPtr]::Zero) {
512
526
  throw 'Unable to enter the Per-Monitor V2 DPI awareness context.'
513
527
  }
514
528
  `.trim();
515
- function runWindowsPhysicalPixelPowershell(script) {
516
- const physicalPixelScript = `$ProgressPreference = 'SilentlyContinue'
517
- $ErrorActionPreference = 'Stop'
518
- ${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
519
- ${script}`;
520
- const encoded = Buffer.from(physicalPixelScript, 'utf16le').toString('base64');
529
+ function runWindowsPowershellScript(script) {
530
+ const encoded = Buffer.from(script, 'utf16le').toString('base64');
521
531
  return (0, external_node_child_process_namespaceObject.execFileSync)('powershell.exe', [
522
532
  '-NoProfile',
523
533
  '-NonInteractive',
@@ -530,6 +540,137 @@ ${script}`;
530
540
  windowsHide: true
531
541
  });
532
542
  }
543
+ function runWindowsPowershellCommand(script) {
544
+ return (0, external_node_child_process_namespaceObject.execFileSync)('powershell.exe', [
545
+ '-NoProfile',
546
+ '-Command',
547
+ script
548
+ ], {
549
+ encoding: 'utf8',
550
+ timeout: POWERSHELL_TIMEOUT_MS,
551
+ maxBuffer: POWERSHELL_MAX_BUFFER,
552
+ windowsHide: true
553
+ });
554
+ }
555
+ function runWindowsPowershell(script) {
556
+ return runWindowsPowershellCommand(`$ProgressPreference = 'SilentlyContinue'
557
+ $ErrorActionPreference = 'Stop'
558
+ ${script}`);
559
+ }
560
+ function runWindowsPhysicalPixelPowershell(script) {
561
+ const physicalPixelScript = `$ProgressPreference = 'SilentlyContinue'
562
+ $ErrorActionPreference = 'Stop'
563
+ ${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
564
+ ${script}`;
565
+ return runWindowsPowershellScript(physicalPixelScript);
566
+ }
567
+ class WindowsDisplayEnumerationEmptyError extends Error {
568
+ }
569
+ function isFiniteNumber(value) {
570
+ return 'number' == typeof value && Number.isFinite(value);
571
+ }
572
+ function isWindowsDisplayGeometry(value) {
573
+ if (!value || 'object' != typeof value) return false;
574
+ const candidate = value;
575
+ const bounds = candidate.bounds;
576
+ 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;
577
+ }
578
+ function readWindowsDisplayGeometriesWith(runPowershell, context) {
579
+ const serializeScreens = 'legacy' === context ? 'ConvertTo-Json -InputObject $screens -Compress' : '[Console]::Out.Write((ConvertTo-Json -InputObject $screens -Compress))';
580
+ const script = `
581
+ Add-Type -AssemblyName System.Windows.Forms
582
+ $screens = @([System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
583
+ $bounds = $_.Bounds
584
+ [PSCustomObject]@{
585
+ id = $_.DeviceName
586
+ name = $_.DeviceName
587
+ primary = $_.Primary
588
+ bounds = [PSCustomObject]@{
589
+ x = $bounds.X
590
+ y = $bounds.Y
591
+ width = $bounds.Width
592
+ height = $bounds.Height
593
+ }
594
+ }
595
+ })
596
+ ${serializeScreens}
597
+ `.trim();
598
+ const output = runPowershell(script).trim();
599
+ if (!output) throw new WindowsDisplayEnumerationEmptyError(`Windows ${context} display enumeration returned no data`);
600
+ const parsed = JSON.parse(output);
601
+ if (!Array.isArray(parsed)) throw new Error('Windows display enumeration returned invalid data');
602
+ const displays = parsed.filter(isWindowsDisplayGeometry);
603
+ if (displays.length !== parsed.length) throw new Error('Windows display enumeration returned invalid geometry');
604
+ if (0 === displays.length) throw new WindowsDisplayEnumerationEmptyError(`Windows ${context} display enumeration returned no displays`);
605
+ return displays;
606
+ }
607
+ function readWindowsDisplayGeometries() {
608
+ return readWindowsDisplayGeometriesWith(runWindowsPhysicalPixelPowershell, 'physical');
609
+ }
610
+ function discoverWindowsDisplays(runners = {
611
+ physical: runWindowsPhysicalPixelPowershell,
612
+ legacy: runWindowsPowershell
613
+ }) {
614
+ try {
615
+ return {
616
+ geometries: readWindowsDisplayGeometriesWith(runners.physical, 'physical'),
617
+ coordinateMode: 'physical'
618
+ };
619
+ } catch (error) {
620
+ if (!(error instanceof WindowsDisplayEnumerationEmptyError)) throw error;
621
+ const geometries = readWindowsDisplayGeometriesWith(runners.legacy, 'legacy');
622
+ return {
623
+ geometries,
624
+ coordinateMode: 'legacy'
625
+ };
626
+ }
627
+ }
628
+ function readPhysicalWindowsSystemDpi() {
629
+ const output = runWindowsPhysicalPixelPowershell('[Console]::Out.Write($midsceneNativeMethods::GetDpiForSystem())').trim();
630
+ const dpi = Number(output);
631
+ if (!Number.isFinite(dpi) || dpi <= 0) throw new Error(`Windows returned an invalid system DPI value: ${output}`);
632
+ return dpi;
633
+ }
634
+ function readPngDimensions(dataUri) {
635
+ const prefix = 'data:image/png;base64,';
636
+ if (!dataUri.startsWith(prefix)) throw new Error('Screenshot is not a PNG data URI');
637
+ const buffer = Buffer.from(dataUri.slice(prefix.length), 'base64');
638
+ const pngSignature = Buffer.from([
639
+ 137,
640
+ 80,
641
+ 78,
642
+ 71,
643
+ 13,
644
+ 10,
645
+ 26,
646
+ 10
647
+ ]);
648
+ if (buffer.length < 24 || !buffer.subarray(0, pngSignature.length).equals(pngSignature)) throw new Error('Screenshot returned invalid PNG data');
649
+ const width = buffer.readUInt32BE(16);
650
+ const height = buffer.readUInt32BE(20);
651
+ if (width <= 0 || height <= 0) throw new Error('Screenshot returned invalid PNG dimensions');
652
+ return {
653
+ width,
654
+ height
655
+ };
656
+ }
657
+ function assertLegacyWindowsCoordinateCompatibility({ geometry, systemDpi, screenshotBase64, inputSize }) {
658
+ if (!geometry?.primary) throw new Error('Windows legacy coordinate compatibility mode only supports the primary display');
659
+ 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})`);
660
+ if (96 !== systemDpi) throw new Error(`Windows legacy coordinate compatibility mode requires 100% display scaling (96 DPI), got ${systemDpi} DPI`);
661
+ const screenshotSize = readPngDimensions(screenshotBase64);
662
+ const displaySize = {
663
+ width: Math.round(geometry.bounds.width),
664
+ height: Math.round(geometry.bounds.height)
665
+ };
666
+ const sizesMatch = screenshotSize.width === displaySize.width && screenshotSize.height === displaySize.height && inputSize.width === displaySize.width && inputSize.height === displaySize.height;
667
+ 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.`);
668
+ }
669
+ function resolveWindowsDisplayGeometryFromList(displayId, displays) {
670
+ if (!displays.length) return;
671
+ if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
672
+ return displays.find((display)=>display.id === displayId);
673
+ }
533
674
  function windows_pointer_define_property(obj, key, value) {
534
675
  if (key in obj) Object.defineProperty(obj, key, {
535
676
  value: value,
@@ -848,28 +989,6 @@ const EDGE_SCROLL_SPEC = {
848
989
  function escapePowershellSingleQuoted(value) {
849
990
  return value.replace(/'/g, "''");
850
991
  }
851
- function readWindowsDisplayGeometries() {
852
- const script = `
853
- Add-Type -AssemblyName System.Windows.Forms
854
- $s = [System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
855
- $b = $_.Bounds
856
- [PSCustomObject]@{
857
- id = $_.DeviceName
858
- name = $_.DeviceName
859
- primary = $_.Primary
860
- bounds = [PSCustomObject]@{ x = $b.X; y = $b.Y; width = $b.Width; height = $b.Height }
861
- }
862
- }
863
- ConvertTo-Json @($s) -Compress
864
- `.trim();
865
- const output = runWindowsPhysicalPixelPowershell(script).trim();
866
- if (!output) throw new Error('Windows display enumeration returned no data');
867
- const parsed = JSON.parse(output);
868
- if (!Array.isArray(parsed)) throw new Error('Windows display enumeration returned invalid data');
869
- const displays = parsed.filter(isWindowsDisplayGeometry);
870
- if (displays.length !== parsed.length || 0 === displays.length) throw new Error('Windows display enumeration returned invalid geometry');
871
- return displays;
872
- }
873
992
  function listWindowsDisplays(geometries = readWindowsDisplayGeometries()) {
874
993
  return geometries.map((display)=>({
875
994
  id: display.id,
@@ -973,23 +1092,13 @@ function getDisplayInfoBinary() {
973
1092
  displayInfoBinaryPath = binPath;
974
1093
  return binPath;
975
1094
  }
976
- function isFiniteNumber(value) {
1095
+ function device_isFiniteNumber(value) {
977
1096
  return 'number' == typeof value && Number.isFinite(value);
978
1097
  }
979
- function isDisplayBounds(value) {
980
- if (!value || 'object' != typeof value) return false;
981
- const bounds = value;
982
- return isFiniteNumber(bounds.x) && isFiniteNumber(bounds.y) && isFiniteNumber(bounds.width) && isFiniteNumber(bounds.height) && bounds.width > 0 && bounds.height > 0;
983
- }
984
- function isWindowsDisplayGeometry(value) {
985
- if (!value || 'object' != typeof value) return false;
986
- const candidate = value;
987
- return 'string' == typeof candidate.id && candidate.id.length > 0 && 'string' == typeof candidate.name && 'boolean' == typeof candidate.primary && isDisplayBounds(candidate.bounds);
988
- }
989
1098
  function isDarwinDisplayGeometry(value) {
990
1099
  if (!value || 'object' != typeof value) return false;
991
1100
  const candidate = value;
992
- 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);
1101
+ 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);
993
1102
  }
994
1103
  function readDarwinDisplayGeometries() {
995
1104
  const bin = getDisplayInfoBinary();
@@ -1059,11 +1168,6 @@ function resolveDarwinDisplayGeometryFromList(displayId, displays) {
1059
1168
  if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays.find((display)=>0 === display.screenIndex) || displays[0];
1060
1169
  return displays.find((display)=>display.screenIndex === screenIndex) || displays.find((display)=>display.cgDisplayId === screenIndex);
1061
1170
  }
1062
- function resolveWindowsDisplayGeometryFromList(displayId, displays) {
1063
- if (!displays.length) return;
1064
- if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
1065
- return displays.find((display)=>display.id === displayId);
1066
- }
1067
1171
  function resolveDisplayGeometry(displayId, windowsDisplays) {
1068
1172
  if ('darwin' === process.platform) return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
1069
1173
  if ('win32' === process.platform) {
@@ -1156,13 +1260,25 @@ function normalizePrimaryKey(key) {
1156
1260
  return KEY_NAME_MAP[lowerKey] || lowerKey;
1157
1261
  }
1158
1262
  class ComputerDevice {
1263
+ usesPhysicalWindowsCoordinates() {
1264
+ return 'win32' === process.platform && 'physical' === this.windowsCoordinateContext.mode;
1265
+ }
1266
+ assertLegacyWindowsCoordinateCompatibility(screenshotBase64) {
1267
+ if ('win32' !== process.platform || 'legacy' !== this.windowsCoordinateContext.mode) return;
1268
+ assertLegacyWindowsCoordinateCompatibility({
1269
+ geometry: this.displayGeometry,
1270
+ systemDpi: this.windowsCoordinateContext.systemDpi,
1271
+ screenshotBase64,
1272
+ inputSize: this.inputDriver.getScreenSize()
1273
+ });
1274
+ }
1159
1275
  async moveGlobalPointer(point, context, smooth) {
1160
1276
  if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
1161
1277
  const target = {
1162
1278
  x: Math.round(point.x),
1163
1279
  y: Math.round(point.y)
1164
1280
  };
1165
- if ('win32' === process.platform) {
1281
+ if (this.usesPhysicalWindowsCoordinates()) {
1166
1282
  const actual = this.windowsPointerDriver.moveTo(target, {
1167
1283
  smoothSteps: smooth?.smoothSteps,
1168
1284
  smoothDelayMs: smooth?.smoothDelay
@@ -1214,7 +1330,7 @@ class ComputerDevice {
1214
1330
  }
1215
1331
  static async listDisplays() {
1216
1332
  try {
1217
- if ('win32' === process.platform) return listWindowsDisplays();
1333
+ if ('win32' === process.platform) return listWindowsDisplays(discoverWindowsDisplays().geometries);
1218
1334
  const displays = await external_screenshot_desktop_default().listDisplays();
1219
1335
  return displays.map((d)=>({
1220
1336
  id: String(d.id),
@@ -1252,7 +1368,15 @@ class ComputerDevice {
1252
1368
  }
1253
1369
  }
1254
1370
  device_libnut = await getLibnut();
1255
- const windowsDisplayGeometries = 'win32' === process.platform ? readWindowsDisplayGeometries() : void 0;
1371
+ const windowsDisplayDiscovery = 'win32' === process.platform ? discoverWindowsDisplays() : void 0;
1372
+ const windowsDisplayGeometries = windowsDisplayDiscovery?.geometries;
1373
+ 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.');
1374
+ this.windowsCoordinateContext = windowsDisplayDiscovery?.coordinateMode === 'legacy' ? {
1375
+ mode: 'legacy',
1376
+ systemDpi: readPhysicalWindowsSystemDpi()
1377
+ } : {
1378
+ mode: 'physical'
1379
+ };
1256
1380
  this.displayGeometry = resolveDisplayGeometry(this.displayId, windowsDisplayGeometries);
1257
1381
  const size = await this.size();
1258
1382
  const displays = windowsDisplayGeometries ? listWindowsDisplays(windowsDisplayGeometries) : await ComputerDevice.listDisplays();
@@ -1263,6 +1387,7 @@ Platform: ${process.platform}
1263
1387
  Display: ${this.displayId || 'Primary'}
1264
1388
  Screen Size: ${size.width}x${size.height}
1265
1389
  Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ') : 'Unknown'}${headlessInfo}
1390
+ ${'win32' === process.platform ? `Windows Coordinate Mode: ${this.windowsCoordinateContext.mode}` : ''}
1266
1391
  `;
1267
1392
  debugDevice('Computer device connected', this.description);
1268
1393
  await this.healthCheck(displays);
@@ -1286,7 +1411,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1286
1411
  }
1287
1412
  async healthCheck(displays) {
1288
1413
  console.log('[HealthCheck] Starting health check...');
1289
- console.log("[HealthCheck] @midscene/computer v1.13.0");
1414
+ console.log("[HealthCheck] @midscene/computer v1.13.1-beta-20260920094844.0");
1290
1415
  console.log('[HealthCheck] Taking screenshot...');
1291
1416
  const screenshotTimeout = 15000;
1292
1417
  let timeoutId;
@@ -1298,10 +1423,12 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1298
1423
  timeoutPromise
1299
1424
  ]);
1300
1425
  console.log(`[HealthCheck] Screenshot succeeded (length=${base64.length})`);
1426
+ this.assertLegacyWindowsCoordinateCompatibility(base64);
1301
1427
  console.log('[HealthCheck] Verifying mouse control...');
1302
- const startPos = 'win32' === process.platform ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
1428
+ const usePhysicalWindowsCoordinates = this.usesPhysicalWindowsCoordinates();
1429
+ const startPos = usePhysicalWindowsCoordinates ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
1303
1430
  console.log(`[HealthCheck] Current mouse position: (${startPos.x}, ${startPos.y})`);
1304
- if ('win32' === process.platform) {
1431
+ if (usePhysicalWindowsCoordinates) {
1305
1432
  if (!this.displayGeometry) throw new Error('Windows display geometry is unavailable');
1306
1433
  const bounds = this.displayGeometry.bounds;
1307
1434
  const target = {
@@ -1367,7 +1494,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1367
1494
  debugDevice('Taking screenshot', {
1368
1495
  displayId: this.displayId
1369
1496
  });
1370
- if ('win32' === process.platform) return this.screenshotViaPowershell();
1497
+ if (this.usesPhysicalWindowsCoordinates()) return this.screenshotViaPowershell();
1371
1498
  const options = {
1372
1499
  format: 'png'
1373
1500
  };
@@ -1530,7 +1657,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1530
1657
  this.inputDriver.sendKey(key, modifiers);
1531
1658
  }
1532
1659
  resolveUntargetedScrollPoint(screenSize) {
1533
- if ('win32' === process.platform) {
1660
+ if (this.usesPhysicalWindowsCoordinates()) {
1534
1661
  const activeWindowRect = this.windowsPointerDriver.getActiveWindowRect();
1535
1662
  if (activeWindowRect) {
1536
1663
  const activeWindowCenter = {
@@ -1674,6 +1801,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1674
1801
  device_define_property(this, "options", void 0);
1675
1802
  device_define_property(this, "displayId", void 0);
1676
1803
  device_define_property(this, "displayGeometry", void 0);
1804
+ device_define_property(this, "windowsCoordinateContext", {
1805
+ mode: 'physical'
1806
+ });
1677
1807
  device_define_property(this, "description", void 0);
1678
1808
  device_define_property(this, "destroyed", false);
1679
1809
  device_define_property(this, "xvfbInstance", void 0);
@@ -2820,7 +2950,7 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
2820
2950
  }
2821
2951
  const env_namespaceObject = require("@midscene/shared/env");
2822
2952
  function version() {
2823
- const currentVersion = "1.13.0";
2953
+ const currentVersion = "1.13.1-beta-20260920094844.0";
2824
2954
  console.log(`@midscene/computer v${currentVersion}`);
2825
2955
  return currentVersion;
2826
2956
  }
@@ -70,6 +70,7 @@ export declare class ComputerDevice implements AbstractInterface {
70
70
  private options?;
71
71
  private displayId?;
72
72
  private displayGeometry?;
73
+ private windowsCoordinateContext;
73
74
  private description?;
74
75
  private destroyed;
75
76
  private xvfbInstance?;
@@ -87,6 +88,8 @@ export declare class ComputerDevice implements AbstractInterface {
87
88
  uri?: string;
88
89
  readonly inputPrimitives: ComputerInputPrimitives;
89
90
  constructor(options?: ComputerDeviceOpt);
91
+ private usesPhysicalWindowsCoordinates;
92
+ private assertLegacyWindowsCoordinateCompatibility;
90
93
  private moveGlobalPointer;
91
94
  private moveDisplayPointer;
92
95
  private performPointerDrag;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/computer",
3
- "version": "1.13.0",
3
+ "version": "1.13.1-beta-20260920094844.0",
4
4
  "description": "Midscene.js Computer Desktop Automation",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,8 +33,8 @@
33
33
  "@computer-use/libnut": "^4.2.0",
34
34
  "clipboardy": "^4.0.0",
35
35
  "screenshot-desktop": "^1.15.3",
36
- "@midscene/core": "1.13.0",
37
- "@midscene/shared": "1.13.0"
36
+ "@midscene/core": "1.13.1-beta-20260920094844.0",
37
+ "@midscene/shared": "1.13.1-beta-20260920094844.0"
38
38
  },
39
39
  "optionalDependencies": {
40
40
  "node-mac-permissions": "2.5.0"