@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/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,130 @@ ${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 readPngDimensions(dataUri) {
629
+ const prefix = 'data:image/png;base64,';
630
+ if (!dataUri.startsWith(prefix)) throw new Error('Screenshot is not a PNG data URI');
631
+ const buffer = Buffer.from(dataUri.slice(prefix.length), 'base64');
632
+ const pngSignature = Buffer.from([
633
+ 137,
634
+ 80,
635
+ 78,
636
+ 71,
637
+ 13,
638
+ 10,
639
+ 26,
640
+ 10
641
+ ]);
642
+ if (buffer.length < 24 || !buffer.subarray(0, pngSignature.length).equals(pngSignature)) throw new Error('Screenshot returned invalid PNG data');
643
+ const width = buffer.readUInt32BE(16);
644
+ const height = buffer.readUInt32BE(20);
645
+ if (width <= 0 || height <= 0) throw new Error('Screenshot returned invalid PNG dimensions');
646
+ return {
647
+ width,
648
+ height
649
+ };
650
+ }
651
+ function assertLegacyWindowsCoordinateCompatibility({ geometry, screenshotBase64, inputSize }) {
652
+ if (!geometry?.primary) throw new Error('Windows legacy coordinate compatibility mode only supports the primary display');
653
+ 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})`);
654
+ const screenshotSize = readPngDimensions(screenshotBase64);
655
+ const displaySize = {
656
+ width: Math.round(geometry.bounds.width),
657
+ height: Math.round(geometry.bounds.height)
658
+ };
659
+ const sizesMatch = screenshotSize.width === displaySize.width && screenshotSize.height === displaySize.height && inputSize.width === displaySize.width && inputSize.height === displaySize.height;
660
+ 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.`);
661
+ }
662
+ function resolveWindowsDisplayGeometryFromList(displayId, displays) {
663
+ if (!displays.length) return;
664
+ if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
665
+ return displays.find((display)=>display.id === displayId);
666
+ }
533
667
  function windows_pointer_define_property(obj, key, value) {
534
668
  if (key in obj) Object.defineProperty(obj, key, {
535
669
  value: value,
@@ -848,28 +982,6 @@ const EDGE_SCROLL_SPEC = {
848
982
  function escapePowershellSingleQuoted(value) {
849
983
  return value.replace(/'/g, "''");
850
984
  }
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
985
  function listWindowsDisplays(geometries = readWindowsDisplayGeometries()) {
874
986
  return geometries.map((display)=>({
875
987
  id: display.id,
@@ -973,23 +1085,13 @@ function getDisplayInfoBinary() {
973
1085
  displayInfoBinaryPath = binPath;
974
1086
  return binPath;
975
1087
  }
976
- function isFiniteNumber(value) {
1088
+ function device_isFiniteNumber(value) {
977
1089
  return 'number' == typeof value && Number.isFinite(value);
978
1090
  }
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
1091
  function isDarwinDisplayGeometry(value) {
990
1092
  if (!value || 'object' != typeof value) return false;
991
1093
  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);
1094
+ 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
1095
  }
994
1096
  function readDarwinDisplayGeometries() {
995
1097
  const bin = getDisplayInfoBinary();
@@ -1059,11 +1161,6 @@ function resolveDarwinDisplayGeometryFromList(displayId, displays) {
1059
1161
  if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays.find((display)=>0 === display.screenIndex) || displays[0];
1060
1162
  return displays.find((display)=>display.screenIndex === screenIndex) || displays.find((display)=>display.cgDisplayId === screenIndex);
1061
1163
  }
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
1164
  function resolveDisplayGeometry(displayId, windowsDisplays) {
1068
1165
  if ('darwin' === process.platform) return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
1069
1166
  if ('win32' === process.platform) {
@@ -1156,13 +1253,24 @@ function normalizePrimaryKey(key) {
1156
1253
  return KEY_NAME_MAP[lowerKey] || lowerKey;
1157
1254
  }
1158
1255
  class ComputerDevice {
1256
+ usesPhysicalWindowsCoordinates() {
1257
+ return 'win32' === process.platform && 'physical' === this.windowsCoordinateContext.mode;
1258
+ }
1259
+ assertLegacyWindowsCoordinateCompatibility(screenshotBase64) {
1260
+ if ('win32' !== process.platform || 'legacy' !== this.windowsCoordinateContext.mode) return;
1261
+ assertLegacyWindowsCoordinateCompatibility({
1262
+ geometry: this.displayGeometry,
1263
+ screenshotBase64,
1264
+ inputSize: this.inputDriver.getScreenSize()
1265
+ });
1266
+ }
1159
1267
  async moveGlobalPointer(point, context, smooth) {
1160
1268
  if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
1161
1269
  const target = {
1162
1270
  x: Math.round(point.x),
1163
1271
  y: Math.round(point.y)
1164
1272
  };
1165
- if ('win32' === process.platform) {
1273
+ if (this.usesPhysicalWindowsCoordinates()) {
1166
1274
  const actual = this.windowsPointerDriver.moveTo(target, {
1167
1275
  smoothSteps: smooth?.smoothSteps,
1168
1276
  smoothDelayMs: smooth?.smoothDelay
@@ -1214,7 +1322,7 @@ class ComputerDevice {
1214
1322
  }
1215
1323
  static async listDisplays() {
1216
1324
  try {
1217
- if ('win32' === process.platform) return listWindowsDisplays();
1325
+ if ('win32' === process.platform) return listWindowsDisplays(discoverWindowsDisplays().geometries);
1218
1326
  const displays = await external_screenshot_desktop_default().listDisplays();
1219
1327
  return displays.map((d)=>({
1220
1328
  id: String(d.id),
@@ -1252,7 +1360,14 @@ class ComputerDevice {
1252
1360
  }
1253
1361
  }
1254
1362
  device_libnut = await getLibnut();
1255
- const windowsDisplayGeometries = 'win32' === process.platform ? readWindowsDisplayGeometries() : void 0;
1363
+ const windowsDisplayDiscovery = 'win32' === process.platform ? discoverWindowsDisplays() : void 0;
1364
+ const windowsDisplayGeometries = windowsDisplayDiscovery?.geometries;
1365
+ 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.');
1366
+ this.windowsCoordinateContext = windowsDisplayDiscovery?.coordinateMode === 'legacy' ? {
1367
+ mode: 'legacy'
1368
+ } : {
1369
+ mode: 'physical'
1370
+ };
1256
1371
  this.displayGeometry = resolveDisplayGeometry(this.displayId, windowsDisplayGeometries);
1257
1372
  const size = await this.size();
1258
1373
  const displays = windowsDisplayGeometries ? listWindowsDisplays(windowsDisplayGeometries) : await ComputerDevice.listDisplays();
@@ -1263,6 +1378,7 @@ Platform: ${process.platform}
1263
1378
  Display: ${this.displayId || 'Primary'}
1264
1379
  Screen Size: ${size.width}x${size.height}
1265
1380
  Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ') : 'Unknown'}${headlessInfo}
1381
+ ${'win32' === process.platform ? `Windows Coordinate Mode: ${this.windowsCoordinateContext.mode}` : ''}
1266
1382
  `;
1267
1383
  debugDevice('Computer device connected', this.description);
1268
1384
  await this.healthCheck(displays);
@@ -1286,7 +1402,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1286
1402
  }
1287
1403
  async healthCheck(displays) {
1288
1404
  console.log('[HealthCheck] Starting health check...');
1289
- console.log("[HealthCheck] @midscene/computer v1.13.0");
1405
+ console.log("[HealthCheck] @midscene/computer v1.13.1-beta-20260921032526.0");
1290
1406
  console.log('[HealthCheck] Taking screenshot...');
1291
1407
  const screenshotTimeout = 15000;
1292
1408
  let timeoutId;
@@ -1298,10 +1414,12 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1298
1414
  timeoutPromise
1299
1415
  ]);
1300
1416
  console.log(`[HealthCheck] Screenshot succeeded (length=${base64.length})`);
1417
+ this.assertLegacyWindowsCoordinateCompatibility(base64);
1301
1418
  console.log('[HealthCheck] Verifying mouse control...');
1302
- const startPos = 'win32' === process.platform ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
1419
+ const usePhysicalWindowsCoordinates = this.usesPhysicalWindowsCoordinates();
1420
+ const startPos = usePhysicalWindowsCoordinates ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
1303
1421
  console.log(`[HealthCheck] Current mouse position: (${startPos.x}, ${startPos.y})`);
1304
- if ('win32' === process.platform) {
1422
+ if (usePhysicalWindowsCoordinates) {
1305
1423
  if (!this.displayGeometry) throw new Error('Windows display geometry is unavailable');
1306
1424
  const bounds = this.displayGeometry.bounds;
1307
1425
  const target = {
@@ -1367,7 +1485,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1367
1485
  debugDevice('Taking screenshot', {
1368
1486
  displayId: this.displayId
1369
1487
  });
1370
- if ('win32' === process.platform) return this.screenshotViaPowershell();
1488
+ if (this.usesPhysicalWindowsCoordinates()) return this.screenshotViaPowershell();
1371
1489
  const options = {
1372
1490
  format: 'png'
1373
1491
  };
@@ -1530,7 +1648,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1530
1648
  this.inputDriver.sendKey(key, modifiers);
1531
1649
  }
1532
1650
  resolveUntargetedScrollPoint(screenSize) {
1533
- if ('win32' === process.platform) {
1651
+ if (this.usesPhysicalWindowsCoordinates()) {
1534
1652
  const activeWindowRect = this.windowsPointerDriver.getActiveWindowRect();
1535
1653
  if (activeWindowRect) {
1536
1654
  const activeWindowCenter = {
@@ -1674,6 +1792,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1674
1792
  device_define_property(this, "options", void 0);
1675
1793
  device_define_property(this, "displayId", void 0);
1676
1794
  device_define_property(this, "displayGeometry", void 0);
1795
+ device_define_property(this, "windowsCoordinateContext", {
1796
+ mode: 'physical'
1797
+ });
1677
1798
  device_define_property(this, "description", void 0);
1678
1799
  device_define_property(this, "destroyed", false);
1679
1800
  device_define_property(this, "xvfbInstance", void 0);
@@ -2820,7 +2941,7 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
2820
2941
  }
2821
2942
  const env_namespaceObject = require("@midscene/shared/env");
2822
2943
  function version() {
2823
- const currentVersion = "1.13.0";
2944
+ const currentVersion = "1.13.1-beta-20260921032526.0";
2824
2945
  console.log(`@midscene/computer v${currentVersion}`);
2825
2946
  return currentVersion;
2826
2947
  }
@@ -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-20260921032526.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-20260921032526.0",
37
+ "@midscene/shared": "1.13.1-beta-20260921032526.0"
38
38
  },
39
39
  "optionalDependencies": {
40
40
  "node-mac-permissions": "2.5.0"