@midscene/computer 1.12.3-beta-20260902081623.0 → 1.12.3

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;
@@ -108,20 +118,6 @@ class ComputerInputDriver {
108
118
  return false;
109
119
  }
110
120
  }
111
- getActiveWindowRect() {
112
- const lib = this.getLibnutOrThrow('getActiveWindowRect');
113
- if ('function' != typeof lib.getActiveWindow || 'function' != typeof lib.getWindowRect) return null;
114
- try {
115
- const handle = lib.getActiveWindow();
116
- if (!handle) return null;
117
- const rect = lib.getWindowRect(handle);
118
- if (!Number.isFinite(rect.x) || !Number.isFinite(rect.y) || !Number.isFinite(rect.width) || !Number.isFinite(rect.height) || rect.width <= 0 || rect.height <= 0) return null;
119
- return rect;
120
- } catch (error) {
121
- this.options.debug(`getActiveWindowRect failed: ${error}`);
122
- return null;
123
- }
124
- }
125
121
  mouseClick(button, double) {
126
122
  const lib = this.getLibnutOrThrow('mouseClick');
127
123
  if (void 0 !== double) lib.mouseClick(button, double);
@@ -235,6 +231,207 @@ class ComputerInputDriver {
235
231
  this.pendingInputDelayWaits = new Set();
236
232
  }
237
233
  }
234
+ const POWERSHELL_TIMEOUT_MS = 15000;
235
+ const POWERSHELL_MAX_BUFFER = 67108864;
236
+ const WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE = `
237
+ $midsceneAssemblyName = New-Object System.Reflection.AssemblyName('MidsceneDpiNative')
238
+ $midsceneAssembly = [System.AppDomain]::CurrentDomain.DefineDynamicAssembly(
239
+ $midsceneAssemblyName,
240
+ [System.Reflection.Emit.AssemblyBuilderAccess]::Run
241
+ )
242
+ $midsceneModule = $midsceneAssembly.DefineDynamicModule('MidsceneDpiNativeModule')
243
+ $midsceneType = $midsceneModule.DefineType(
244
+ 'MidsceneDpiNative.User32',
245
+ [System.Reflection.TypeAttributes]'Public, Class, Sealed, Abstract'
246
+ )
247
+ $midsceneMethodAttributes =
248
+ [System.Reflection.MethodAttributes]::Public -bor
249
+ [System.Reflection.MethodAttributes]::Static -bor
250
+ [System.Reflection.MethodAttributes]::PinvokeImpl
251
+ $midsceneSetDpiMethod = $midsceneType.DefinePInvokeMethod(
252
+ 'SetThreadDpiAwarenessContext',
253
+ 'user32.dll',
254
+ $midsceneMethodAttributes,
255
+ [System.Reflection.CallingConventions]::Standard,
256
+ [System.IntPtr],
257
+ [System.Type[]]@([System.IntPtr]),
258
+ [System.Runtime.InteropServices.CallingConvention]::Winapi,
259
+ [System.Runtime.InteropServices.CharSet]::None
260
+ )
261
+ $midsceneSetDpiMethod.SetImplementationFlags(
262
+ $midsceneSetDpiMethod.GetMethodImplementationFlags() -bor
263
+ [System.Reflection.MethodImplAttributes]::PreserveSig
264
+ )
265
+ $midsceneGetForegroundWindowMethod = $midsceneType.DefinePInvokeMethod(
266
+ 'GetForegroundWindow',
267
+ 'user32.dll',
268
+ $midsceneMethodAttributes,
269
+ [System.Reflection.CallingConventions]::Standard,
270
+ [System.IntPtr],
271
+ [System.Type[]]@(),
272
+ [System.Runtime.InteropServices.CallingConvention]::Winapi,
273
+ [System.Runtime.InteropServices.CharSet]::None
274
+ )
275
+ $midsceneGetForegroundWindowMethod.SetImplementationFlags(
276
+ $midsceneGetForegroundWindowMethod.GetMethodImplementationFlags() -bor
277
+ [System.Reflection.MethodImplAttributes]::PreserveSig
278
+ )
279
+ $midsceneGetWindowRectMethod = $midsceneType.DefinePInvokeMethod(
280
+ 'GetWindowRect',
281
+ 'user32.dll',
282
+ $midsceneMethodAttributes,
283
+ [System.Reflection.CallingConventions]::Standard,
284
+ [bool],
285
+ [System.Type[]]@([System.IntPtr], [System.IntPtr]),
286
+ [System.Runtime.InteropServices.CallingConvention]::Winapi,
287
+ [System.Runtime.InteropServices.CharSet]::None
288
+ )
289
+ $midsceneGetWindowRectMethod.SetImplementationFlags(
290
+ $midsceneGetWindowRectMethod.GetMethodImplementationFlags() -bor
291
+ [System.Reflection.MethodImplAttributes]::PreserveSig
292
+ )
293
+ $midsceneNativeMethods = $midsceneType.CreateType()
294
+ $midscenePerMonitorV2 = [System.IntPtr](-4)
295
+ $midscenePreviousDpiContext =
296
+ $midsceneNativeMethods::SetThreadDpiAwarenessContext($midscenePerMonitorV2)
297
+ if ($midscenePreviousDpiContext -eq [System.IntPtr]::Zero) {
298
+ throw 'Unable to enter the Per-Monitor V2 DPI awareness context.'
299
+ }
300
+ `.trim();
301
+ function runWindowsPhysicalPixelPowershell(script) {
302
+ const physicalPixelScript = `$ProgressPreference = 'SilentlyContinue'
303
+ $ErrorActionPreference = 'Stop'
304
+ ${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
305
+ ${script}`;
306
+ const encoded = Buffer.from(physicalPixelScript, 'utf16le').toString('base64');
307
+ return (0, external_node_child_process_namespaceObject.execFileSync)('powershell.exe', [
308
+ '-NoProfile',
309
+ '-NonInteractive',
310
+ '-EncodedCommand',
311
+ encoded
312
+ ], {
313
+ encoding: 'utf8',
314
+ timeout: POWERSHELL_TIMEOUT_MS,
315
+ maxBuffer: POWERSHELL_MAX_BUFFER,
316
+ windowsHide: true
317
+ });
318
+ }
319
+ function windows_pointer_define_property(obj, key, value) {
320
+ if (key in obj) Object.defineProperty(obj, key, {
321
+ value: value,
322
+ enumerable: true,
323
+ configurable: true,
324
+ writable: true
325
+ });
326
+ else obj[key] = value;
327
+ return obj;
328
+ }
329
+ const WINDOWS_POINTER_TOLERANCE_PX = 5;
330
+ function assertFinitePoint(point, context) {
331
+ if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) throw new Error(`${context} must contain finite coordinates, got (${point.x}, ${point.y})`);
332
+ }
333
+ function parseWindowsPointerPosition(output, context) {
334
+ const match = output.trim().match(/^(-?\d+),(-?\d+)$/);
335
+ if (!match) throw new Error(`${context} returned an invalid cursor position: ${JSON.stringify(output.trim())}`);
336
+ return {
337
+ x: Number(match[1]),
338
+ y: Number(match[2])
339
+ };
340
+ }
341
+ function windowsPointerPositionScript() {
342
+ return `
343
+ Add-Type -AssemblyName System.Windows.Forms, System.Drawing
344
+ $position = [System.Windows.Forms.Cursor]::Position
345
+ [Console]::Out.Write(('{0},{1}' -f $position.X, $position.Y))
346
+ `.trim();
347
+ }
348
+ function windowsPointerMoveScript(point, options) {
349
+ assertFinitePoint(point, 'Windows pointer target');
350
+ 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');
351
+ const targetX = Math.round(point.x);
352
+ const targetY = Math.round(point.y);
353
+ const smoothSteps = Math.max(1, Math.round(options?.smoothSteps ?? 1));
354
+ const smoothDelayMs = Math.max(0, Math.round(options?.smoothDelayMs ?? 0));
355
+ return `
356
+ Add-Type -AssemblyName System.Windows.Forms, System.Drawing
357
+ $targetX = ${targetX}
358
+ $targetY = ${targetY}
359
+ $smoothSteps = ${smoothSteps}
360
+ $smoothDelayMs = ${smoothDelayMs}
361
+ $start = [System.Windows.Forms.Cursor]::Position
362
+ for ($step = 1; $step -le $smoothSteps; $step += 1) {
363
+ $x = [int][Math]::Round($start.X + (($targetX - $start.X) * $step / [double]$smoothSteps))
364
+ $y = [int][Math]::Round($start.Y + (($targetY - $start.Y) * $step / [double]$smoothSteps))
365
+ [System.Windows.Forms.Cursor]::Position = [System.Drawing.Point]::new($x, $y)
366
+ if ($smoothDelayMs -gt 0 -and $step -lt $smoothSteps) {
367
+ Start-Sleep -Milliseconds $smoothDelayMs
368
+ }
369
+ }
370
+ [System.Windows.Forms.Cursor]::Position = [System.Drawing.Point]::new($targetX, $targetY)
371
+ $position = [System.Windows.Forms.Cursor]::Position
372
+ [Console]::Out.Write(('{0},{1}' -f $position.X, $position.Y))
373
+ `.trim();
374
+ }
375
+ function parseWindowsWindowRect(output) {
376
+ const value = output.trim();
377
+ if (!value) return null;
378
+ const match = value.match(/^(-?\d+),(-?\d+),(\d+),(\d+)$/);
379
+ if (!match) throw new Error(`Windows active window query returned an invalid rectangle: ${JSON.stringify(value)}`);
380
+ const rect = {
381
+ x: Number(match[1]),
382
+ y: Number(match[2]),
383
+ width: Number(match[3]),
384
+ height: Number(match[4])
385
+ };
386
+ if (rect.width <= 0 || rect.height <= 0) throw new Error(`Windows active window query returned an invalid rectangle: ${JSON.stringify(value)}`);
387
+ return rect;
388
+ }
389
+ function windowsActiveWindowRectScript() {
390
+ return `
391
+ $midsceneWindowHandle = $midsceneNativeMethods::GetForegroundWindow()
392
+ if ($midsceneWindowHandle -eq [System.IntPtr]::Zero) {
393
+ return
394
+ }
395
+ $midsceneWindowRectBuffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(16)
396
+ try {
397
+ if (-not $midsceneNativeMethods::GetWindowRect($midsceneWindowHandle, $midsceneWindowRectBuffer)) {
398
+ throw 'GetWindowRect failed for the foreground window.'
399
+ }
400
+ $left = [System.Runtime.InteropServices.Marshal]::ReadInt32($midsceneWindowRectBuffer, 0)
401
+ $top = [System.Runtime.InteropServices.Marshal]::ReadInt32($midsceneWindowRectBuffer, 4)
402
+ $right = [System.Runtime.InteropServices.Marshal]::ReadInt32($midsceneWindowRectBuffer, 8)
403
+ $bottom = [System.Runtime.InteropServices.Marshal]::ReadInt32($midsceneWindowRectBuffer, 12)
404
+ [Console]::Out.Write(('{0},{1},{2},{3}' -f $left, $top, ($right - $left), ($bottom - $top)))
405
+ }
406
+ finally {
407
+ [System.Runtime.InteropServices.Marshal]::FreeHGlobal($midsceneWindowRectBuffer)
408
+ }
409
+ `.trim();
410
+ }
411
+ class WindowsPointerDriver {
412
+ getPosition() {
413
+ return parseWindowsPointerPosition(this.options.runPhysicalPixelPowershell(windowsPointerPositionScript()), 'Windows pointer query');
414
+ }
415
+ getActiveWindowRect() {
416
+ return parseWindowsWindowRect(this.options.runPhysicalPixelPowershell(windowsActiveWindowRectScript()));
417
+ }
418
+ moveTo(point, options) {
419
+ return parseWindowsPointerPosition(this.options.runPhysicalPixelPowershell(windowsPointerMoveScript(point, options)), 'Windows pointer move');
420
+ }
421
+ constructor(options){
422
+ windows_pointer_define_property(this, "options", void 0);
423
+ this.options = options;
424
+ }
425
+ }
426
+ function windowsPointerDrift(expected, actual) {
427
+ return {
428
+ x: actual.x - expected.x,
429
+ y: actual.y - expected.y
430
+ };
431
+ }
432
+ function windowsPointerIsWithinTolerance(drift) {
433
+ return Math.abs(drift.x) <= WINDOWS_POINTER_TOLERANCE_PX && Math.abs(drift.y) <= WINDOWS_POINTER_TOLERANCE_PX;
434
+ }
238
435
  const interrupt_namespaceObject = require("@midscene/shared/cli/interrupt");
239
436
  const debugXvfb = (0, logger_namespaceObject.getDebug)('computer:xvfb');
240
437
  const xvfbCleanupMonitorScript = String.raw`
@@ -493,39 +690,36 @@ function sendKeyViaAppleScript(key, modifiers = []) {
493
690
  script
494
691
  ]);
495
692
  }
496
- const POWERSHELL_TIMEOUT_MS = 15000;
497
- const POWERSHELL_MAX_BUFFER = 67108864;
498
693
  function escapePowershellSingleQuoted(value) {
499
694
  return value.replace(/'/g, "''");
500
695
  }
501
- function runPowershell(script) {
502
- const prefixed = `$ProgressPreference = 'SilentlyContinue'\n${script}`;
503
- const encoded = Buffer.from(prefixed, 'utf16le').toString('base64');
504
- return (0, external_node_child_process_namespaceObject.execFileSync)('powershell.exe', [
505
- '-NoProfile',
506
- '-NonInteractive',
507
- '-EncodedCommand',
508
- encoded
509
- ], {
510
- encoding: 'utf8',
511
- timeout: POWERSHELL_TIMEOUT_MS,
512
- maxBuffer: POWERSHELL_MAX_BUFFER,
513
- windowsHide: true
514
- });
515
- }
516
- function listWindowsDisplays() {
696
+ function readWindowsDisplayGeometries() {
517
697
  const script = `
518
698
  Add-Type -AssemblyName System.Windows.Forms
519
699
  $s = [System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
520
- [PSCustomObject]@{ id = $_.DeviceName; name = $_.DeviceName; primary = $_.Primary }
700
+ $b = $_.Bounds
701
+ [PSCustomObject]@{
702
+ id = $_.DeviceName
703
+ name = $_.DeviceName
704
+ primary = $_.Primary
705
+ bounds = [PSCustomObject]@{ x = $b.X; y = $b.Y; width = $b.Width; height = $b.Height }
706
+ }
521
707
  }
522
708
  ConvertTo-Json @($s) -Compress
523
709
  `.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
710
+ const output = runWindowsPhysicalPixelPowershell(script).trim();
711
+ if (!output) throw new Error('Windows display enumeration returned no data');
712
+ const parsed = JSON.parse(output);
713
+ if (!Array.isArray(parsed)) throw new Error('Windows display enumeration returned invalid data');
714
+ const displays = parsed.filter(isWindowsDisplayGeometry);
715
+ if (displays.length !== parsed.length || 0 === displays.length) throw new Error('Windows display enumeration returned invalid geometry');
716
+ return displays;
717
+ }
718
+ function listWindowsDisplays(geometries = readWindowsDisplayGeometries()) {
719
+ return geometries.map((display)=>({
720
+ id: display.id,
721
+ name: display.name,
722
+ primary: display.primary
529
723
  }));
530
724
  }
531
725
  let device_libnut = null;
@@ -627,6 +821,16 @@ function getDisplayInfoBinary() {
627
821
  function isFiniteNumber(value) {
628
822
  return 'number' == typeof value && Number.isFinite(value);
629
823
  }
824
+ function isDisplayBounds(value) {
825
+ if (!value || 'object' != typeof value) return false;
826
+ const bounds = value;
827
+ return isFiniteNumber(bounds.x) && isFiniteNumber(bounds.y) && isFiniteNumber(bounds.width) && isFiniteNumber(bounds.height) && bounds.width > 0 && bounds.height > 0;
828
+ }
829
+ function isWindowsDisplayGeometry(value) {
830
+ if (!value || 'object' != typeof value) return false;
831
+ const candidate = value;
832
+ return 'string' == typeof candidate.id && candidate.id.length > 0 && 'string' == typeof candidate.name && 'boolean' == typeof candidate.primary && isDisplayBounds(candidate.bounds);
833
+ }
630
834
  function isDarwinDisplayGeometry(value) {
631
835
  if (!value || 'object' != typeof value) return false;
632
836
  const candidate = value;
@@ -669,9 +873,11 @@ function readDarwinFrontmostApplication() {
669
873
  return;
670
874
  }
671
875
  }
672
- async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, holdDuration, reason) {
673
- await inputDriver.delay(CLICK_SETTLE_DELAY);
674
- const current = inputDriver.getMousePos();
876
+ async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, current, holdDuration, reason) {
877
+ const drift = {
878
+ x: current.x - targetX,
879
+ y: current.y - targetY
880
+ };
675
881
  debugComputerInput('tap mouse moved %o', {
676
882
  reason,
677
883
  target: {
@@ -679,10 +885,7 @@ async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, holdDurati
679
885
  y: targetY
680
886
  },
681
887
  current,
682
- drift: {
683
- x: current.x - targetX,
684
- y: current.y - targetY
685
- }
888
+ drift
686
889
  });
687
890
  await inputDriver.withMouseButton('left', async ()=>{
688
891
  debugComputerInput('tap mouse down %o', {
@@ -701,9 +904,18 @@ function resolveDarwinDisplayGeometryFromList(displayId, displays) {
701
904
  if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays.find((display)=>0 === display.screenIndex) || displays[0];
702
905
  return displays.find((display)=>display.screenIndex === screenIndex) || displays.find((display)=>display.cgDisplayId === screenIndex);
703
906
  }
704
- function resolveDisplayGeometry(displayId) {
705
- if ('darwin' !== process.platform) return;
706
- return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
907
+ function resolveWindowsDisplayGeometryFromList(displayId, displays) {
908
+ if (!displays.length) return;
909
+ if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
910
+ return displays.find((display)=>display.id === displayId);
911
+ }
912
+ function resolveDisplayGeometry(displayId, windowsDisplays) {
913
+ if ('darwin' === process.platform) return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
914
+ if ('win32' === process.platform) {
915
+ const geometry = resolveWindowsDisplayGeometryFromList(displayId, windowsDisplays ?? readWindowsDisplayGeometries());
916
+ if (!geometry) throw new Error(displayId ? `Requested Windows display not found: ${displayId}` : 'No Windows displays were detected');
917
+ return geometry;
918
+ }
707
919
  }
708
920
  function mapDisplayLocalPointToGlobal(point, geometry) {
709
921
  if (!geometry) return point;
@@ -789,6 +1001,30 @@ function normalizePrimaryKey(key) {
789
1001
  return KEY_NAME_MAP[lowerKey] || lowerKey;
790
1002
  }
791
1003
  class ComputerDevice {
1004
+ async moveGlobalPointer(point, context, smooth) {
1005
+ if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
1006
+ const target = {
1007
+ x: Math.round(point.x),
1008
+ y: Math.round(point.y)
1009
+ };
1010
+ if ('win32' === process.platform) {
1011
+ const actual = this.windowsPointerDriver.moveTo(target, {
1012
+ smoothSteps: smooth?.smoothSteps,
1013
+ smoothDelayMs: smooth?.smoothDelay
1014
+ });
1015
+ const drift = windowsPointerDrift(target, actual);
1016
+ if (!windowsPointerIsWithinTolerance(drift)) throw new Error(`${context}: expected (${target.x}, ${target.y}), got (${actual.x}, ${actual.y}), drift=(${drift.x}, ${drift.y})`);
1017
+ await this.inputDriver.delay(CLICK_SETTLE_DELAY);
1018
+ return actual;
1019
+ }
1020
+ if (smooth) await this.inputDriver.smoothMoveMouse(target.x, target.y, smooth.smoothSteps, smooth.smoothDelay);
1021
+ else this.inputDriver.moveMouse(target.x, target.y);
1022
+ await this.inputDriver.delay(CLICK_SETTLE_DELAY);
1023
+ return this.inputDriver.getMousePos();
1024
+ }
1025
+ moveDisplayPointer(point, context, smooth) {
1026
+ return this.moveGlobalPointer(this.toGlobalPoint(point), context, smooth);
1027
+ }
792
1028
  async focusKeyboardTarget(element, delayMs) {
793
1029
  const [x, y] = element.center;
794
1030
  if ('darwin' === process.platform) await this.inputPrimitives.pointer.tap({
@@ -796,11 +1032,10 @@ class ComputerDevice {
796
1032
  y
797
1033
  });
798
1034
  else {
799
- const point = this.toGlobalPoint({
1035
+ await this.moveDisplayPointer({
800
1036
  x,
801
1037
  y
802
- });
803
- this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
1038
+ }, 'Mouse did not reach the keyboard focus target');
804
1039
  this.inputDriver.mouseClick('left');
805
1040
  }
806
1041
  await this.inputDriver.delay(delayMs);
@@ -819,7 +1054,7 @@ class ComputerDevice {
819
1054
  }));
820
1055
  } catch (error) {
821
1056
  debugDevice(`Failed to list displays: ${error}`);
822
- return [];
1057
+ throw new Error(`Failed to list displays: ${error}`);
823
1058
  }
824
1059
  }
825
1060
  async connect() {
@@ -848,9 +1083,10 @@ class ComputerDevice {
848
1083
  }
849
1084
  }
850
1085
  device_libnut = await getLibnut();
851
- this.displayGeometry = resolveDisplayGeometry(this.displayId);
1086
+ const windowsDisplayGeometries = 'win32' === process.platform ? readWindowsDisplayGeometries() : void 0;
1087
+ this.displayGeometry = resolveDisplayGeometry(this.displayId, windowsDisplayGeometries);
852
1088
  const size = await this.size();
853
- const displays = await ComputerDevice.listDisplays();
1089
+ const displays = windowsDisplayGeometries ? listWindowsDisplays(windowsDisplayGeometries) : await ComputerDevice.listDisplays();
854
1090
  const headlessInfo = this.xvfbInstance ? `\nHeadless: true (Xvfb on ${this.xvfbInstance.display})` : '';
855
1091
  this.description = `
856
1092
  Type: Computer
@@ -860,7 +1096,7 @@ Screen Size: ${size.width}x${size.height}
860
1096
  Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ') : 'Unknown'}${headlessInfo}
861
1097
  `;
862
1098
  debugDevice('Computer device connected', this.description);
863
- await this.healthCheck();
1099
+ await this.healthCheck(displays);
864
1100
  } catch (error) {
865
1101
  if (this.xvfbInstance) {
866
1102
  if (!this.options?.keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
@@ -879,9 +1115,9 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
879
1115
  throw new Error(`Unable to connect to computer device: ${error}`);
880
1116
  }
881
1117
  }
882
- async healthCheck() {
1118
+ async healthCheck(displays) {
883
1119
  console.log('[HealthCheck] Starting health check...');
884
- console.log("[HealthCheck] @midscene/computer v1.12.3-beta-20260902081623.0");
1120
+ console.log("[HealthCheck] @midscene/computer v1.12.3");
885
1121
  console.log('[HealthCheck] Taking screenshot...');
886
1122
  const screenshotTimeout = 15000;
887
1123
  let timeoutId;
@@ -893,23 +1129,38 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
893
1129
  timeoutPromise
894
1130
  ]);
895
1131
  console.log(`[HealthCheck] Screenshot succeeded (length=${base64.length})`);
896
- console.log('[HealthCheck] Moving mouse...');
897
- const startPos = this.inputDriver.getMousePos();
1132
+ console.log('[HealthCheck] Verifying mouse control...');
1133
+ const startPos = 'win32' === process.platform ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
898
1134
  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);
1135
+ if ('win32' === process.platform) {
1136
+ if (!this.displayGeometry) throw new Error('Windows display geometry is unavailable');
1137
+ const bounds = this.displayGeometry.bounds;
1138
+ const target = {
1139
+ x: Math.round(bounds.x + bounds.width / 2),
1140
+ y: Math.round(bounds.y + bounds.height / 2)
1141
+ };
1142
+ try {
1143
+ const actual = this.windowsPointerDriver.moveTo(target);
1144
+ const drift = windowsPointerDrift(target, actual);
1145
+ 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})`);
1146
+ } finally{
1147
+ this.windowsPointerDriver.moveTo(startPos);
1148
+ }
1149
+ console.log(`[HealthCheck] Mouse verified in screenshot-space display bounds (${bounds.x}, ${bounds.y}, ${bounds.width}, ${bounds.height})`);
1150
+ } else {
1151
+ const offsetX = Math.floor(40 * Math.random()) + 10;
1152
+ const offsetY = Math.floor(40 * Math.random()) + 10;
1153
+ const targetX = startPos.x + offsetX;
1154
+ const targetY = startPos.y + offsetY;
1155
+ console.log(`[HealthCheck] Moving mouse to (${targetX}, ${targetY})...`);
1156
+ try {
1157
+ this.inputDriver.moveMouse(targetX, targetY);
1158
+ await (0, utils_namespaceObject.sleep)(CLICK_SETTLE_DELAY);
1159
+ this.inputDriver.assertMousePosition(targetX, targetY, 'Mouse health check failed');
1160
+ } finally{
1161
+ this.inputDriver.moveMouse(startPos.x, startPos.y);
1162
+ console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
1163
+ }
913
1164
  }
914
1165
  if ('win32' === process.platform && !this.isRunningAsAdmin()) {
915
1166
  const hint = [
@@ -919,10 +1170,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
919
1170
  ].join(' ');
920
1171
  warnDevice(`[HealthCheck] ${hint}`);
921
1172
  }
922
- this.inputDriver.moveMouse(startPos.x, startPos.y);
923
- console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
924
1173
  console.log('[HealthCheck] Listing monitors...');
925
- const displays = await ComputerDevice.listDisplays();
926
1174
  if (displays.length > 0) {
927
1175
  console.log(`[HealthCheck] Found ${displays.length} monitor(s):`);
928
1176
  for (const display of displays){
@@ -990,7 +1238,6 @@ Original error: ${lastRawMessage}`);
990
1238
  $screen = [System.Windows.Forms.Screen]::AllScreens | Where-Object { $_.DeviceName -eq $dn } | Select-Object -First 1
991
1239
  if (-not $screen) { throw "Requested display not found: $dn" }` : '$screen = [System.Windows.Forms.Screen]::PrimaryScreen';
992
1240
  const script = `
993
- $ErrorActionPreference = 'Stop'
994
1241
  Add-Type -AssemblyName System.Windows.Forms, System.Drawing
995
1242
  ${selectScreen}
996
1243
  $b = $screen.Bounds
@@ -1004,7 +1251,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1004
1251
  `.trim();
1005
1252
  let stdout;
1006
1253
  try {
1007
- stdout = runPowershell(script);
1254
+ stdout = runWindowsPhysicalPixelPowershell(script);
1008
1255
  } catch (error) {
1009
1256
  const message = error instanceof Error ? error.message : String(error);
1010
1257
  throw new Error(`Failed to take screenshot on Windows: ${message}`);
@@ -1106,11 +1353,15 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1106
1353
  }
1107
1354
  resolveUntargetedScrollPoint(screenSize) {
1108
1355
  if ('win32' === process.platform) {
1109
- const activeWindowRect = this.inputDriver.getActiveWindowRect();
1110
- if (activeWindowRect) return {
1111
- x: activeWindowRect.x + activeWindowRect.width / 2,
1112
- y: activeWindowRect.y + activeWindowRect.height / 2
1113
- };
1356
+ const activeWindowRect = this.windowsPointerDriver.getActiveWindowRect();
1357
+ if (activeWindowRect) {
1358
+ const activeWindowCenter = {
1359
+ x: activeWindowRect.x + activeWindowRect.width / 2,
1360
+ y: activeWindowRect.y + activeWindowRect.height / 2
1361
+ };
1362
+ const bounds = this.displayGeometry?.bounds;
1363
+ if (!bounds || activeWindowCenter.x >= bounds.x && activeWindowCenter.x < bounds.x + bounds.width && activeWindowCenter.y >= bounds.y && activeWindowCenter.y < bounds.y + bounds.height) return activeWindowCenter;
1364
+ }
1114
1365
  }
1115
1366
  return this.toGlobalPoint({
1116
1367
  x: screenSize.width / 2,
@@ -1121,17 +1372,16 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1121
1372
  if (param.locate) {
1122
1373
  const element = param.locate;
1123
1374
  const [x, y] = element.center;
1124
- const point = this.toGlobalPoint({
1375
+ await this.moveDisplayPointer({
1125
1376
  x,
1126
1377
  y
1127
- });
1128
- this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
1378
+ }, 'Mouse did not reach the scroll target');
1129
1379
  return;
1130
1380
  }
1131
1381
  const screenSize = await this.size();
1132
1382
  if ('win32' === process.platform && this.inputDriver.focusActiveWindow()) await this.inputDriver.delay(CLICK_FOCUS_SETTLE_DELAY);
1133
1383
  const point = this.resolveUntargetedScrollPoint(screenSize);
1134
- this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
1384
+ await this.moveGlobalPointer(point, 'Mouse did not reach the scroll viewport');
1135
1385
  await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
1136
1386
  return screenSize;
1137
1387
  }
@@ -1256,6 +1506,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1256
1506
  runPhasedScroll,
1257
1507
  debug: (message)=>debugDevice(message)
1258
1508
  }));
1509
+ device_define_property(this, "windowsPointerDriver", new WindowsPointerDriver({
1510
+ runPhysicalPixelPowershell: runWindowsPhysicalPixelPowershell
1511
+ }));
1259
1512
  device_define_property(this, "useAppleScript", void 0);
1260
1513
  device_define_property(this, "adminCheckCache", void 0);
1261
1514
  device_define_property(this, "uri", void 0);
@@ -1280,15 +1533,17 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1280
1533
  },
1281
1534
  holdDuration,
1282
1535
  displayId: this.displayId,
1283
- displayGeometry: this.displayGeometry ? {
1284
- screenIndex: this.displayGeometry.screenIndex,
1285
- cgDisplayId: this.displayGeometry.cgDisplayId,
1286
- bounds: this.displayGeometry.bounds
1287
- } : void 0
1536
+ displayGeometry: this.displayGeometry
1288
1537
  });
1289
1538
  const frontmostBefore = 'darwin' === process.platform ? readDarwinFrontmostApplication() : void 0;
1290
- await this.inputDriver.smoothMoveMouse(targetX, targetY, SMOOTH_MOVE_STEPS_TAP, SMOOTH_MOVE_DELAY_TAP);
1291
- await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, holdDuration, 'primary');
1539
+ const current = await this.moveGlobalPointer({
1540
+ x: targetX,
1541
+ y: targetY
1542
+ }, 'Mouse did not reach the tap target', {
1543
+ smoothSteps: SMOOTH_MOVE_STEPS_TAP,
1544
+ smoothDelay: SMOOTH_MOVE_DELAY_TAP
1545
+ });
1546
+ await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, current, holdDuration, 'primary');
1292
1547
  if (frontmostBefore && 'darwin' === process.platform) {
1293
1548
  await (0, utils_namespaceObject.sleep)(CLICK_FOCUS_SETTLE_DELAY);
1294
1549
  const frontmostAfter = readDarwinFrontmostApplication();
@@ -1299,42 +1554,43 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1299
1554
  focusChanged
1300
1555
  });
1301
1556
  if (focusChanged) {
1302
- this.inputDriver.moveMouse(targetX, targetY);
1303
- await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, holdDuration, 'focus-follow-up');
1557
+ const followUpCurrent = await this.moveGlobalPointer({
1558
+ x: targetX,
1559
+ y: targetY
1560
+ }, 'Mouse did not reach the focus follow-up target');
1561
+ await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, followUpCurrent, holdDuration, 'focus-follow-up');
1304
1562
  }
1305
1563
  }
1306
1564
  },
1307
1565
  doubleClick: async ({ x, y })=>{
1308
- const target = this.toGlobalPoint({
1566
+ await this.moveDisplayPointer({
1309
1567
  x,
1310
1568
  y
1311
- });
1312
- this.inputDriver.moveMouse(Math.round(target.x), Math.round(target.y));
1569
+ }, 'Mouse did not reach the double-click target');
1313
1570
  this.inputDriver.mouseClick('left', true);
1314
1571
  },
1315
1572
  rightClick: async ({ x, y })=>{
1316
- const target = this.toGlobalPoint({
1573
+ await this.moveDisplayPointer({
1317
1574
  x,
1318
1575
  y
1319
- });
1320
- this.inputDriver.moveMouse(Math.round(target.x), Math.round(target.y));
1576
+ }, 'Mouse did not reach the right-click target');
1321
1577
  this.inputDriver.mouseClick('right');
1322
1578
  },
1323
1579
  hover: async ({ x, y })=>{
1324
- const target = this.toGlobalPoint({
1580
+ await this.moveDisplayPointer({
1325
1581
  x,
1326
1582
  y
1583
+ }, 'Mouse did not reach the hover target', {
1584
+ smoothSteps: SMOOTH_MOVE_STEPS_MOUSE_MOVE,
1585
+ smoothDelay: SMOOTH_MOVE_DELAY_MOUSE_MOVE
1327
1586
  });
1328
- await this.inputDriver.smoothMoveMouse(Math.round(target.x), Math.round(target.y), SMOOTH_MOVE_STEPS_MOUSE_MOVE, SMOOTH_MOVE_DELAY_MOUSE_MOVE);
1329
1587
  await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
1330
1588
  },
1331
1589
  dragAndDrop: async (from, to)=>{
1332
- const globalFrom = this.toGlobalPoint(from);
1333
- const globalTo = this.toGlobalPoint(to);
1334
- this.inputDriver.moveMouse(Math.round(globalFrom.x), Math.round(globalFrom.y));
1590
+ await this.moveDisplayPointer(from, 'Mouse did not reach the drag start target');
1335
1591
  await this.inputDriver.withMouseButton('left', async ()=>{
1336
1592
  await this.inputDriver.delay(100);
1337
- this.inputDriver.moveMouse(Math.round(globalTo.x), Math.round(globalTo.y));
1593
+ await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target');
1338
1594
  await this.inputDriver.delay(100);
1339
1595
  });
1340
1596
  }
@@ -2352,7 +2608,7 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
2352
2608
  }
2353
2609
  const env_namespaceObject = require("@midscene/shared/env");
2354
2610
  function version() {
2355
- const currentVersion = "1.12.3-beta-20260902081623.0";
2611
+ const currentVersion = "1.12.3";
2356
2612
  console.log(`@midscene/computer v${currentVersion}`);
2357
2613
  return currentVersion;
2358
2614
  }