@midscene/computer 1.12.2 → 1.12.3-beta-20260828085408.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
@@ -80,6 +80,7 @@ function _define_property(obj, key, value) {
80
80
  else obj[key] = value;
81
81
  return obj;
82
82
  }
83
+ const MOUSE_COORDINATE_TOLERANCE_PX = 5;
83
84
  class ComputerInputDriver {
84
85
  destroy() {
85
86
  if (this.destroyed) return;
@@ -95,6 +96,15 @@ class ComputerInputDriver {
95
96
  moveMouse(x, y) {
96
97
  this.getLibnutOrThrow('moveMouse').moveMouse(x, y);
97
98
  }
99
+ assertMousePosition(targetX, targetY, context) {
100
+ const current = this.getMousePos();
101
+ const drift = {
102
+ x: current.x - targetX,
103
+ y: current.y - targetY
104
+ };
105
+ if (Math.abs(drift.x) > MOUSE_COORDINATE_TOLERANCE_PX || Math.abs(drift.y) > MOUSE_COORDINATE_TOLERANCE_PX) throw new Error(`${context}: expected (${targetX}, ${targetY}), got (${current.x}, ${current.y}), drift=(${drift.x}, ${drift.y})`);
106
+ return drift;
107
+ }
98
108
  focusActiveWindow() {
99
109
  const lib = this.getLibnutOrThrow('focusActiveWindow');
100
110
  if ('function' != typeof lib.getActiveWindow || 'function' != typeof lib.focusWindow) return false;
@@ -235,6 +245,83 @@ class ComputerInputDriver {
235
245
  this.pendingInputDelayWaits = new Set();
236
246
  }
237
247
  }
248
+ function windows_pointer_define_property(obj, key, value) {
249
+ if (key in obj) Object.defineProperty(obj, key, {
250
+ value: value,
251
+ enumerable: true,
252
+ configurable: true,
253
+ writable: true
254
+ });
255
+ else obj[key] = value;
256
+ return obj;
257
+ }
258
+ const WINDOWS_POINTER_TOLERANCE_PX = 5;
259
+ function assertFinitePoint(point, context) {
260
+ if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) throw new Error(`${context} must contain finite coordinates, got (${point.x}, ${point.y})`);
261
+ }
262
+ function parseWindowsPointerPosition(output, context) {
263
+ const match = output.trim().match(/^(-?\d+),(-?\d+)$/);
264
+ if (!match) throw new Error(`${context} returned an invalid cursor position: ${JSON.stringify(output.trim())}`);
265
+ return {
266
+ x: Number(match[1]),
267
+ y: Number(match[2])
268
+ };
269
+ }
270
+ function windowsPointerPositionScript() {
271
+ return `
272
+ Add-Type -AssemblyName System.Windows.Forms, System.Drawing
273
+ $position = [System.Windows.Forms.Cursor]::Position
274
+ [Console]::Out.Write(('{0},{1}' -f $position.X, $position.Y))
275
+ `.trim();
276
+ }
277
+ function windowsPointerMoveScript(point, options) {
278
+ assertFinitePoint(point, 'Windows pointer target');
279
+ if (options?.smoothSteps !== void 0 && !Number.isFinite(options.smoothSteps) || options?.smoothDelayMs !== void 0 && !Number.isFinite(options.smoothDelayMs)) throw new Error('Windows pointer smoothing options must be finite numbers');
280
+ const targetX = Math.round(point.x);
281
+ const targetY = Math.round(point.y);
282
+ const smoothSteps = Math.max(1, Math.round(options?.smoothSteps ?? 1));
283
+ const smoothDelayMs = Math.max(0, Math.round(options?.smoothDelayMs ?? 0));
284
+ return `
285
+ Add-Type -AssemblyName System.Windows.Forms, System.Drawing
286
+ $targetX = ${targetX}
287
+ $targetY = ${targetY}
288
+ $smoothSteps = ${smoothSteps}
289
+ $smoothDelayMs = ${smoothDelayMs}
290
+ $start = [System.Windows.Forms.Cursor]::Position
291
+ for ($step = 1; $step -le $smoothSteps; $step += 1) {
292
+ $x = [int][Math]::Round($start.X + (($targetX - $start.X) * $step / [double]$smoothSteps))
293
+ $y = [int][Math]::Round($start.Y + (($targetY - $start.Y) * $step / [double]$smoothSteps))
294
+ [System.Windows.Forms.Cursor]::Position = [System.Drawing.Point]::new($x, $y)
295
+ if ($smoothDelayMs -gt 0 -and $step -lt $smoothSteps) {
296
+ Start-Sleep -Milliseconds $smoothDelayMs
297
+ }
298
+ }
299
+ [System.Windows.Forms.Cursor]::Position = [System.Drawing.Point]::new($targetX, $targetY)
300
+ $position = [System.Windows.Forms.Cursor]::Position
301
+ [Console]::Out.Write(('{0},{1}' -f $position.X, $position.Y))
302
+ `.trim();
303
+ }
304
+ class WindowsPointerDriver {
305
+ getPosition() {
306
+ return parseWindowsPointerPosition(this.options.runPowershell(windowsPointerPositionScript()), 'Windows pointer query');
307
+ }
308
+ moveTo(point, options) {
309
+ return parseWindowsPointerPosition(this.options.runPowershell(windowsPointerMoveScript(point, options)), 'Windows pointer move');
310
+ }
311
+ constructor(options){
312
+ windows_pointer_define_property(this, "options", void 0);
313
+ this.options = options;
314
+ }
315
+ }
316
+ function windowsPointerDrift(expected, actual) {
317
+ return {
318
+ x: actual.x - expected.x,
319
+ y: actual.y - expected.y
320
+ };
321
+ }
322
+ function windowsPointerIsWithinTolerance(drift) {
323
+ return Math.abs(drift.x) <= WINDOWS_POINTER_TOLERANCE_PX && Math.abs(drift.y) <= WINDOWS_POINTER_TOLERANCE_PX;
324
+ }
238
325
  const interrupt_namespaceObject = require("@midscene/shared/cli/interrupt");
239
326
  const debugXvfb = (0, logger_namespaceObject.getDebug)('computer:xvfb');
240
327
  const xvfbCleanupMonitorScript = String.raw`
@@ -513,19 +600,33 @@ function runPowershell(script) {
513
600
  windowsHide: true
514
601
  });
515
602
  }
516
- function listWindowsDisplays() {
603
+ function readWindowsDisplayGeometries() {
517
604
  const script = `
518
605
  Add-Type -AssemblyName System.Windows.Forms
519
606
  $s = [System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
520
- [PSCustomObject]@{ id = $_.DeviceName; name = $_.DeviceName; primary = $_.Primary }
607
+ $b = $_.Bounds
608
+ [PSCustomObject]@{
609
+ id = $_.DeviceName
610
+ name = $_.DeviceName
611
+ primary = $_.Primary
612
+ bounds = [PSCustomObject]@{ x = $b.X; y = $b.Y; width = $b.Width; height = $b.Height }
613
+ }
521
614
  }
522
615
  ConvertTo-Json @($s) -Compress
523
616
  `.trim();
524
- const parsed = JSON.parse(runPowershell(script).trim());
525
- return parsed.map((d)=>({
526
- id: String(d.id),
527
- name: d.name || String(d.id),
528
- primary: d.primary || false
617
+ const output = runPowershell(script).trim();
618
+ if (!output) throw new Error('Windows display enumeration returned no data');
619
+ const parsed = JSON.parse(output);
620
+ if (!Array.isArray(parsed)) throw new Error('Windows display enumeration returned invalid data');
621
+ const displays = parsed.filter(isWindowsDisplayGeometry);
622
+ if (displays.length !== parsed.length || 0 === displays.length) throw new Error('Windows display enumeration returned invalid geometry');
623
+ return displays;
624
+ }
625
+ function listWindowsDisplays(geometries = readWindowsDisplayGeometries()) {
626
+ return geometries.map((display)=>({
627
+ id: display.id,
628
+ name: display.name,
629
+ primary: display.primary
529
630
  }));
530
631
  }
531
632
  let device_libnut = null;
@@ -627,6 +728,16 @@ function getDisplayInfoBinary() {
627
728
  function isFiniteNumber(value) {
628
729
  return 'number' == typeof value && Number.isFinite(value);
629
730
  }
731
+ function isDisplayBounds(value) {
732
+ if (!value || 'object' != typeof value) return false;
733
+ const bounds = value;
734
+ return isFiniteNumber(bounds.x) && isFiniteNumber(bounds.y) && isFiniteNumber(bounds.width) && isFiniteNumber(bounds.height) && bounds.width > 0 && bounds.height > 0;
735
+ }
736
+ function isWindowsDisplayGeometry(value) {
737
+ if (!value || 'object' != typeof value) return false;
738
+ const candidate = value;
739
+ return 'string' == typeof candidate.id && candidate.id.length > 0 && 'string' == typeof candidate.name && 'boolean' == typeof candidate.primary && isDisplayBounds(candidate.bounds);
740
+ }
630
741
  function isDarwinDisplayGeometry(value) {
631
742
  if (!value || 'object' != typeof value) return false;
632
743
  const candidate = value;
@@ -669,9 +780,11 @@ function readDarwinFrontmostApplication() {
669
780
  return;
670
781
  }
671
782
  }
672
- async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, holdDuration, reason) {
673
- await inputDriver.delay(CLICK_SETTLE_DELAY);
674
- const current = inputDriver.getMousePos();
783
+ async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, current, holdDuration, reason) {
784
+ const drift = {
785
+ x: current.x - targetX,
786
+ y: current.y - targetY
787
+ };
675
788
  debugComputerInput('tap mouse moved %o', {
676
789
  reason,
677
790
  target: {
@@ -679,10 +792,7 @@ async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, holdDurati
679
792
  y: targetY
680
793
  },
681
794
  current,
682
- drift: {
683
- x: current.x - targetX,
684
- y: current.y - targetY
685
- }
795
+ drift
686
796
  });
687
797
  await inputDriver.withMouseButton('left', async ()=>{
688
798
  debugComputerInput('tap mouse down %o', {
@@ -701,9 +811,18 @@ function resolveDarwinDisplayGeometryFromList(displayId, displays) {
701
811
  if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays.find((display)=>0 === display.screenIndex) || displays[0];
702
812
  return displays.find((display)=>display.screenIndex === screenIndex) || displays.find((display)=>display.cgDisplayId === screenIndex);
703
813
  }
704
- function resolveDisplayGeometry(displayId) {
705
- if ('darwin' !== process.platform) return;
706
- return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
814
+ function resolveWindowsDisplayGeometryFromList(displayId, displays) {
815
+ if (!displays.length) return;
816
+ if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
817
+ return displays.find((display)=>display.id === displayId);
818
+ }
819
+ function resolveDisplayGeometry(displayId, windowsDisplays) {
820
+ if ('darwin' === process.platform) return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
821
+ if ('win32' === process.platform) {
822
+ const geometry = resolveWindowsDisplayGeometryFromList(displayId, windowsDisplays ?? readWindowsDisplayGeometries());
823
+ if (!geometry) throw new Error(displayId ? `Requested Windows display not found: ${displayId}` : 'No Windows displays were detected');
824
+ return geometry;
825
+ }
707
826
  }
708
827
  function mapDisplayLocalPointToGlobal(point, geometry) {
709
828
  if (!geometry) return point;
@@ -789,6 +908,30 @@ function normalizePrimaryKey(key) {
789
908
  return KEY_NAME_MAP[lowerKey] || lowerKey;
790
909
  }
791
910
  class ComputerDevice {
911
+ async moveGlobalPointer(point, context, smooth) {
912
+ if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
913
+ const target = {
914
+ x: Math.round(point.x),
915
+ y: Math.round(point.y)
916
+ };
917
+ if ('win32' === process.platform) {
918
+ const actual = this.windowsPointerDriver.moveTo(target, {
919
+ smoothSteps: smooth?.smoothSteps,
920
+ smoothDelayMs: smooth?.smoothDelay
921
+ });
922
+ const drift = windowsPointerDrift(target, actual);
923
+ if (!windowsPointerIsWithinTolerance(drift)) throw new Error(`${context}: expected (${target.x}, ${target.y}), got (${actual.x}, ${actual.y}), drift=(${drift.x}, ${drift.y})`);
924
+ await this.inputDriver.delay(CLICK_SETTLE_DELAY);
925
+ return actual;
926
+ }
927
+ if (smooth) await this.inputDriver.smoothMoveMouse(target.x, target.y, smooth.smoothSteps, smooth.smoothDelay);
928
+ else this.inputDriver.moveMouse(target.x, target.y);
929
+ await this.inputDriver.delay(CLICK_SETTLE_DELAY);
930
+ return target;
931
+ }
932
+ moveDisplayPointer(point, context, smooth) {
933
+ return this.moveGlobalPointer(this.toGlobalPoint(point), context, smooth);
934
+ }
792
935
  async focusKeyboardTarget(element, delayMs) {
793
936
  const [x, y] = element.center;
794
937
  if ('darwin' === process.platform) await this.inputPrimitives.pointer.tap({
@@ -796,11 +939,10 @@ class ComputerDevice {
796
939
  y
797
940
  });
798
941
  else {
799
- const point = this.toGlobalPoint({
942
+ await this.moveDisplayPointer({
800
943
  x,
801
944
  y
802
- });
803
- this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
945
+ }, 'Mouse did not reach the keyboard focus target');
804
946
  this.inputDriver.mouseClick('left');
805
947
  }
806
948
  await this.inputDriver.delay(delayMs);
@@ -819,7 +961,7 @@ class ComputerDevice {
819
961
  }));
820
962
  } catch (error) {
821
963
  debugDevice(`Failed to list displays: ${error}`);
822
- return [];
964
+ throw new Error(`Failed to list displays: ${error}`);
823
965
  }
824
966
  }
825
967
  async connect() {
@@ -848,9 +990,10 @@ class ComputerDevice {
848
990
  }
849
991
  }
850
992
  device_libnut = await getLibnut();
851
- this.displayGeometry = resolveDisplayGeometry(this.displayId);
993
+ const windowsDisplayGeometries = 'win32' === process.platform ? readWindowsDisplayGeometries() : void 0;
994
+ this.displayGeometry = resolveDisplayGeometry(this.displayId, windowsDisplayGeometries);
852
995
  const size = await this.size();
853
- const displays = await ComputerDevice.listDisplays();
996
+ const displays = windowsDisplayGeometries ? listWindowsDisplays(windowsDisplayGeometries) : await ComputerDevice.listDisplays();
854
997
  const headlessInfo = this.xvfbInstance ? `\nHeadless: true (Xvfb on ${this.xvfbInstance.display})` : '';
855
998
  this.description = `
856
999
  Type: Computer
@@ -860,7 +1003,7 @@ Screen Size: ${size.width}x${size.height}
860
1003
  Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ') : 'Unknown'}${headlessInfo}
861
1004
  `;
862
1005
  debugDevice('Computer device connected', this.description);
863
- await this.healthCheck();
1006
+ await this.healthCheck(displays);
864
1007
  } catch (error) {
865
1008
  if (this.xvfbInstance) {
866
1009
  if (!this.options?.keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
@@ -879,9 +1022,9 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
879
1022
  throw new Error(`Unable to connect to computer device: ${error}`);
880
1023
  }
881
1024
  }
882
- async healthCheck() {
1025
+ async healthCheck(displays) {
883
1026
  console.log('[HealthCheck] Starting health check...');
884
- console.log("[HealthCheck] @midscene/computer v1.12.2");
1027
+ console.log("[HealthCheck] @midscene/computer v1.12.3-beta-20260828085408.0");
885
1028
  console.log('[HealthCheck] Taking screenshot...');
886
1029
  const screenshotTimeout = 15000;
887
1030
  let timeoutId;
@@ -893,23 +1036,38 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
893
1036
  timeoutPromise
894
1037
  ]);
895
1038
  console.log(`[HealthCheck] Screenshot succeeded (length=${base64.length})`);
896
- console.log('[HealthCheck] Moving mouse...');
897
- const startPos = this.inputDriver.getMousePos();
1039
+ console.log('[HealthCheck] Verifying mouse control...');
1040
+ const startPos = 'win32' === process.platform ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
898
1041
  console.log(`[HealthCheck] Current mouse position: (${startPos.x}, ${startPos.y})`);
899
- const offsetX = Math.floor(40 * Math.random()) + 10;
900
- const offsetY = Math.floor(40 * Math.random()) + 10;
901
- const targetX = startPos.x + offsetX;
902
- const targetY = startPos.y + offsetY;
903
- console.log(`[HealthCheck] Moving mouse to (${targetX}, ${targetY})...`);
904
- this.inputDriver.moveMouse(targetX, targetY);
905
- await (0, utils_namespaceObject.sleep)(50);
906
- const movedPos = this.inputDriver.getMousePos();
907
- console.log(`[HealthCheck] Mouse position after move: (${movedPos.x}, ${movedPos.y})`);
908
- const deltaX = Math.abs(movedPos.x - targetX);
909
- const deltaY = Math.abs(movedPos.y - targetY);
910
- if (deltaX > 5 || deltaY > 5) {
911
- const msg = `[HealthCheck] WARNING: Mouse control may not be working. Expected (${targetX}, ${targetY}), got (${movedPos.x}, ${movedPos.y}), delta=(${deltaX}, ${deltaY})`;
912
- warnDevice(msg);
1042
+ if ('win32' === process.platform) {
1043
+ if (!this.displayGeometry) throw new Error('Windows display geometry is unavailable');
1044
+ const bounds = this.displayGeometry.bounds;
1045
+ const target = {
1046
+ x: Math.round(bounds.x + bounds.width / 2),
1047
+ y: Math.round(bounds.y + bounds.height / 2)
1048
+ };
1049
+ try {
1050
+ const actual = this.windowsPointerDriver.moveTo(target);
1051
+ const drift = windowsPointerDrift(target, actual);
1052
+ if (!windowsPointerIsWithinTolerance(drift)) throw new Error(`Windows screenshot-space pointer verification: expected (${target.x}, ${target.y}), got (${actual.x}, ${actual.y}), drift=(${drift.x}, ${drift.y})`);
1053
+ } finally{
1054
+ this.windowsPointerDriver.moveTo(startPos);
1055
+ }
1056
+ console.log(`[HealthCheck] Mouse verified in screenshot-space display bounds (${bounds.x}, ${bounds.y}, ${bounds.width}, ${bounds.height})`);
1057
+ } else {
1058
+ const offsetX = Math.floor(40 * Math.random()) + 10;
1059
+ const offsetY = Math.floor(40 * Math.random()) + 10;
1060
+ const targetX = startPos.x + offsetX;
1061
+ const targetY = startPos.y + offsetY;
1062
+ console.log(`[HealthCheck] Moving mouse to (${targetX}, ${targetY})...`);
1063
+ try {
1064
+ this.inputDriver.moveMouse(targetX, targetY);
1065
+ await (0, utils_namespaceObject.sleep)(CLICK_SETTLE_DELAY);
1066
+ this.inputDriver.assertMousePosition(targetX, targetY, 'Mouse health check failed');
1067
+ } finally{
1068
+ this.inputDriver.moveMouse(startPos.x, startPos.y);
1069
+ console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
1070
+ }
913
1071
  }
914
1072
  if ('win32' === process.platform && !this.isRunningAsAdmin()) {
915
1073
  const hint = [
@@ -919,10 +1077,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
919
1077
  ].join(' ');
920
1078
  warnDevice(`[HealthCheck] ${hint}`);
921
1079
  }
922
- this.inputDriver.moveMouse(startPos.x, startPos.y);
923
- console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
924
1080
  console.log('[HealthCheck] Listing monitors...');
925
- const displays = await ComputerDevice.listDisplays();
926
1081
  if (displays.length > 0) {
927
1082
  console.log(`[HealthCheck] Found ${displays.length} monitor(s):`);
928
1083
  for (const display of displays){
@@ -1105,10 +1260,14 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1105
1260
  resolveUntargetedScrollPoint(screenSize) {
1106
1261
  if ('win32' === process.platform) {
1107
1262
  const activeWindowRect = this.inputDriver.getActiveWindowRect();
1108
- if (activeWindowRect) return {
1109
- x: activeWindowRect.x + activeWindowRect.width / 2,
1110
- y: activeWindowRect.y + activeWindowRect.height / 2
1111
- };
1263
+ if (activeWindowRect) {
1264
+ const activeWindowCenter = {
1265
+ x: activeWindowRect.x + activeWindowRect.width / 2,
1266
+ y: activeWindowRect.y + activeWindowRect.height / 2
1267
+ };
1268
+ const bounds = this.displayGeometry?.bounds;
1269
+ if (!bounds || activeWindowCenter.x >= bounds.x && activeWindowCenter.x < bounds.x + bounds.width && activeWindowCenter.y >= bounds.y && activeWindowCenter.y < bounds.y + bounds.height) return activeWindowCenter;
1270
+ }
1112
1271
  }
1113
1272
  return this.toGlobalPoint({
1114
1273
  x: screenSize.width / 2,
@@ -1119,17 +1278,16 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1119
1278
  if (param.locate) {
1120
1279
  const element = param.locate;
1121
1280
  const [x, y] = element.center;
1122
- const point = this.toGlobalPoint({
1281
+ await this.moveDisplayPointer({
1123
1282
  x,
1124
1283
  y
1125
- });
1126
- this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
1284
+ }, 'Mouse did not reach the scroll target');
1127
1285
  return;
1128
1286
  }
1129
1287
  const screenSize = await this.size();
1130
1288
  if ('win32' === process.platform && this.inputDriver.focusActiveWindow()) await this.inputDriver.delay(CLICK_FOCUS_SETTLE_DELAY);
1131
1289
  const point = this.resolveUntargetedScrollPoint(screenSize);
1132
- this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
1290
+ await this.moveGlobalPointer(point, 'Mouse did not reach the scroll viewport');
1133
1291
  await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
1134
1292
  return screenSize;
1135
1293
  }
@@ -1254,6 +1412,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1254
1412
  runPhasedScroll,
1255
1413
  debug: (message)=>debugDevice(message)
1256
1414
  }));
1415
+ device_define_property(this, "windowsPointerDriver", new WindowsPointerDriver({
1416
+ runPowershell
1417
+ }));
1257
1418
  device_define_property(this, "useAppleScript", void 0);
1258
1419
  device_define_property(this, "adminCheckCache", void 0);
1259
1420
  device_define_property(this, "uri", void 0);
@@ -1278,15 +1439,17 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1278
1439
  },
1279
1440
  holdDuration,
1280
1441
  displayId: this.displayId,
1281
- displayGeometry: this.displayGeometry ? {
1282
- screenIndex: this.displayGeometry.screenIndex,
1283
- cgDisplayId: this.displayGeometry.cgDisplayId,
1284
- bounds: this.displayGeometry.bounds
1285
- } : void 0
1442
+ displayGeometry: this.displayGeometry
1286
1443
  });
1287
1444
  const frontmostBefore = 'darwin' === process.platform ? readDarwinFrontmostApplication() : void 0;
1288
- await this.inputDriver.smoothMoveMouse(targetX, targetY, SMOOTH_MOVE_STEPS_TAP, SMOOTH_MOVE_DELAY_TAP);
1289
- await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, holdDuration, 'primary');
1445
+ const current = await this.moveGlobalPointer({
1446
+ x: targetX,
1447
+ y: targetY
1448
+ }, 'Mouse did not reach the tap target', {
1449
+ smoothSteps: SMOOTH_MOVE_STEPS_TAP,
1450
+ smoothDelay: SMOOTH_MOVE_DELAY_TAP
1451
+ });
1452
+ await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, current, holdDuration, 'primary');
1290
1453
  if (frontmostBefore && 'darwin' === process.platform) {
1291
1454
  await (0, utils_namespaceObject.sleep)(CLICK_FOCUS_SETTLE_DELAY);
1292
1455
  const frontmostAfter = readDarwinFrontmostApplication();
@@ -1297,42 +1460,43 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1297
1460
  focusChanged
1298
1461
  });
1299
1462
  if (focusChanged) {
1300
- this.inputDriver.moveMouse(targetX, targetY);
1301
- await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, holdDuration, 'focus-follow-up');
1463
+ const followUpCurrent = await this.moveGlobalPointer({
1464
+ x: targetX,
1465
+ y: targetY
1466
+ }, 'Mouse did not reach the focus follow-up target');
1467
+ await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, followUpCurrent, holdDuration, 'focus-follow-up');
1302
1468
  }
1303
1469
  }
1304
1470
  },
1305
1471
  doubleClick: async ({ x, y })=>{
1306
- const target = this.toGlobalPoint({
1472
+ await this.moveDisplayPointer({
1307
1473
  x,
1308
1474
  y
1309
- });
1310
- this.inputDriver.moveMouse(Math.round(target.x), Math.round(target.y));
1475
+ }, 'Mouse did not reach the double-click target');
1311
1476
  this.inputDriver.mouseClick('left', true);
1312
1477
  },
1313
1478
  rightClick: async ({ x, y })=>{
1314
- const target = this.toGlobalPoint({
1479
+ await this.moveDisplayPointer({
1315
1480
  x,
1316
1481
  y
1317
- });
1318
- this.inputDriver.moveMouse(Math.round(target.x), Math.round(target.y));
1482
+ }, 'Mouse did not reach the right-click target');
1319
1483
  this.inputDriver.mouseClick('right');
1320
1484
  },
1321
1485
  hover: async ({ x, y })=>{
1322
- const target = this.toGlobalPoint({
1486
+ await this.moveDisplayPointer({
1323
1487
  x,
1324
1488
  y
1489
+ }, 'Mouse did not reach the hover target', {
1490
+ smoothSteps: SMOOTH_MOVE_STEPS_MOUSE_MOVE,
1491
+ smoothDelay: SMOOTH_MOVE_DELAY_MOUSE_MOVE
1325
1492
  });
1326
- await this.inputDriver.smoothMoveMouse(Math.round(target.x), Math.round(target.y), SMOOTH_MOVE_STEPS_MOUSE_MOVE, SMOOTH_MOVE_DELAY_MOUSE_MOVE);
1327
1493
  await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
1328
1494
  },
1329
1495
  dragAndDrop: async (from, to)=>{
1330
- const globalFrom = this.toGlobalPoint(from);
1331
- const globalTo = this.toGlobalPoint(to);
1332
- this.inputDriver.moveMouse(Math.round(globalFrom.x), Math.round(globalFrom.y));
1496
+ await this.moveDisplayPointer(from, 'Mouse did not reach the drag start target');
1333
1497
  await this.inputDriver.withMouseButton('left', async ()=>{
1334
1498
  await this.inputDriver.delay(100);
1335
- this.inputDriver.moveMouse(Math.round(globalTo.x), Math.round(globalTo.y));
1499
+ await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target');
1336
1500
  await this.inputDriver.delay(100);
1337
1501
  });
1338
1502
  }
@@ -2335,7 +2499,7 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
2335
2499
  }
2336
2500
  const env_namespaceObject = require("@midscene/shared/env");
2337
2501
  function version() {
2338
- const currentVersion = "1.12.2";
2502
+ const currentVersion = "1.12.3-beta-20260828085408.0";
2339
2503
  console.log(`@midscene/computer v${currentVersion}`);
2340
2504
  return currentVersion;
2341
2505
  }
@@ -75,6 +75,7 @@ export declare class ComputerDevice implements AbstractInterface {
75
75
  private xvfbCleanup?;
76
76
  private xvfbSignalCleanup?;
77
77
  private readonly inputDriver;
78
+ private readonly windowsPointerDriver;
78
79
  /**
79
80
  * On macOS, use AppleScript for keyboard operations by default
80
81
  * to avoid focus issues with system overlays (e.g. Spotlight).
@@ -85,6 +86,8 @@ export declare class ComputerDevice implements AbstractInterface {
85
86
  uri?: string;
86
87
  readonly inputPrimitives: ComputerInputPrimitives;
87
88
  constructor(options?: ComputerDeviceOpt);
89
+ private moveGlobalPointer;
90
+ private moveDisplayPointer;
88
91
  private focusKeyboardTarget;
89
92
  describe(): string;
90
93
  /**
@@ -115,10 +118,10 @@ export declare class ComputerDevice implements AbstractInterface {
115
118
  * the exact .NET-compiler dependency this PR removes by dropping
116
119
  * screenshot-desktop's polyglot .bat — so it is intentionally avoided here.
117
120
  * As a result, captures on a scaled display come back at logical (scaled)
118
- * resolution. That is sufficient for the #2150 fix (the health check only
119
- * needs a successful capture). Per-monitor DPI / coordinate accuracy is a
120
- * separate Windows concern to be addressed in a follow-up with real-device
121
- * verification.
121
+ * resolution. Display enumeration and Windows pointer movement use the same
122
+ * WinForms logical coordinate space, so screenshot locations and pointer
123
+ * actions remain aligned without comparing them to libnut's process-level
124
+ * DPI coordinate space.
122
125
  */
123
126
  private screenshotViaPowershell;
124
127
  size(): Promise<Size>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/computer",
3
- "version": "1.12.2",
3
+ "version": "1.12.3-beta-20260828085408.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.12.2",
37
- "@midscene/shared": "1.12.2"
36
+ "@midscene/core": "1.12.3-beta-20260828085408.0",
37
+ "@midscene/shared": "1.12.3-beta-20260828085408.0"
38
38
  },
39
39
  "optionalDependencies": {
40
40
  "node-mac-permissions": "2.5.0"