@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/cli.js CHANGED
@@ -54,6 +54,7 @@ function _define_property(obj, key, value) {
54
54
  else obj[key] = value;
55
55
  return obj;
56
56
  }
57
+ const MOUSE_COORDINATE_TOLERANCE_PX = 5;
57
58
  class ComputerInputDriver {
58
59
  destroy() {
59
60
  if (this.destroyed) return;
@@ -69,6 +70,15 @@ class ComputerInputDriver {
69
70
  moveMouse(x, y) {
70
71
  this.getLibnutOrThrow('moveMouse').moveMouse(x, y);
71
72
  }
73
+ assertMousePosition(targetX, targetY, context) {
74
+ const current = this.getMousePos();
75
+ const drift = {
76
+ x: current.x - targetX,
77
+ y: current.y - targetY
78
+ };
79
+ 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})`);
80
+ return drift;
81
+ }
72
82
  focusActiveWindow() {
73
83
  const lib = this.getLibnutOrThrow('focusActiveWindow');
74
84
  if ('function' != typeof lib.getActiveWindow || 'function' != typeof lib.focusWindow) return false;
@@ -82,20 +92,6 @@ class ComputerInputDriver {
82
92
  return false;
83
93
  }
84
94
  }
85
- getActiveWindowRect() {
86
- const lib = this.getLibnutOrThrow('getActiveWindowRect');
87
- if ('function' != typeof lib.getActiveWindow || 'function' != typeof lib.getWindowRect) return null;
88
- try {
89
- const handle = lib.getActiveWindow();
90
- if (!handle) return null;
91
- const rect = lib.getWindowRect(handle);
92
- 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;
93
- return rect;
94
- } catch (error) {
95
- this.options.debug(`getActiveWindowRect failed: ${error}`);
96
- return null;
97
- }
98
- }
99
95
  mouseClick(button, double) {
100
96
  const lib = this.getLibnutOrThrow('mouseClick');
101
97
  if (void 0 !== double) lib.mouseClick(button, double);
@@ -209,6 +205,207 @@ class ComputerInputDriver {
209
205
  this.pendingInputDelayWaits = new Set();
210
206
  }
211
207
  }
208
+ const POWERSHELL_TIMEOUT_MS = 15000;
209
+ const POWERSHELL_MAX_BUFFER = 67108864;
210
+ const WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE = `
211
+ $midsceneAssemblyName = New-Object System.Reflection.AssemblyName('MidsceneDpiNative')
212
+ $midsceneAssembly = [System.AppDomain]::CurrentDomain.DefineDynamicAssembly(
213
+ $midsceneAssemblyName,
214
+ [System.Reflection.Emit.AssemblyBuilderAccess]::Run
215
+ )
216
+ $midsceneModule = $midsceneAssembly.DefineDynamicModule('MidsceneDpiNativeModule')
217
+ $midsceneType = $midsceneModule.DefineType(
218
+ 'MidsceneDpiNative.User32',
219
+ [System.Reflection.TypeAttributes]'Public, Class, Sealed, Abstract'
220
+ )
221
+ $midsceneMethodAttributes =
222
+ [System.Reflection.MethodAttributes]::Public -bor
223
+ [System.Reflection.MethodAttributes]::Static -bor
224
+ [System.Reflection.MethodAttributes]::PinvokeImpl
225
+ $midsceneSetDpiMethod = $midsceneType.DefinePInvokeMethod(
226
+ 'SetThreadDpiAwarenessContext',
227
+ 'user32.dll',
228
+ $midsceneMethodAttributes,
229
+ [System.Reflection.CallingConventions]::Standard,
230
+ [System.IntPtr],
231
+ [System.Type[]]@([System.IntPtr]),
232
+ [System.Runtime.InteropServices.CallingConvention]::Winapi,
233
+ [System.Runtime.InteropServices.CharSet]::None
234
+ )
235
+ $midsceneSetDpiMethod.SetImplementationFlags(
236
+ $midsceneSetDpiMethod.GetMethodImplementationFlags() -bor
237
+ [System.Reflection.MethodImplAttributes]::PreserveSig
238
+ )
239
+ $midsceneGetForegroundWindowMethod = $midsceneType.DefinePInvokeMethod(
240
+ 'GetForegroundWindow',
241
+ 'user32.dll',
242
+ $midsceneMethodAttributes,
243
+ [System.Reflection.CallingConventions]::Standard,
244
+ [System.IntPtr],
245
+ [System.Type[]]@(),
246
+ [System.Runtime.InteropServices.CallingConvention]::Winapi,
247
+ [System.Runtime.InteropServices.CharSet]::None
248
+ )
249
+ $midsceneGetForegroundWindowMethod.SetImplementationFlags(
250
+ $midsceneGetForegroundWindowMethod.GetMethodImplementationFlags() -bor
251
+ [System.Reflection.MethodImplAttributes]::PreserveSig
252
+ )
253
+ $midsceneGetWindowRectMethod = $midsceneType.DefinePInvokeMethod(
254
+ 'GetWindowRect',
255
+ 'user32.dll',
256
+ $midsceneMethodAttributes,
257
+ [System.Reflection.CallingConventions]::Standard,
258
+ [bool],
259
+ [System.Type[]]@([System.IntPtr], [System.IntPtr]),
260
+ [System.Runtime.InteropServices.CallingConvention]::Winapi,
261
+ [System.Runtime.InteropServices.CharSet]::None
262
+ )
263
+ $midsceneGetWindowRectMethod.SetImplementationFlags(
264
+ $midsceneGetWindowRectMethod.GetMethodImplementationFlags() -bor
265
+ [System.Reflection.MethodImplAttributes]::PreserveSig
266
+ )
267
+ $midsceneNativeMethods = $midsceneType.CreateType()
268
+ $midscenePerMonitorV2 = [System.IntPtr](-4)
269
+ $midscenePreviousDpiContext =
270
+ $midsceneNativeMethods::SetThreadDpiAwarenessContext($midscenePerMonitorV2)
271
+ if ($midscenePreviousDpiContext -eq [System.IntPtr]::Zero) {
272
+ throw 'Unable to enter the Per-Monitor V2 DPI awareness context.'
273
+ }
274
+ `.trim();
275
+ function runWindowsPhysicalPixelPowershell(script) {
276
+ const physicalPixelScript = `$ProgressPreference = 'SilentlyContinue'
277
+ $ErrorActionPreference = 'Stop'
278
+ ${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
279
+ ${script}`;
280
+ const encoded = Buffer.from(physicalPixelScript, 'utf16le').toString('base64');
281
+ return (0, external_node_child_process_namespaceObject.execFileSync)('powershell.exe', [
282
+ '-NoProfile',
283
+ '-NonInteractive',
284
+ '-EncodedCommand',
285
+ encoded
286
+ ], {
287
+ encoding: 'utf8',
288
+ timeout: POWERSHELL_TIMEOUT_MS,
289
+ maxBuffer: POWERSHELL_MAX_BUFFER,
290
+ windowsHide: true
291
+ });
292
+ }
293
+ function windows_pointer_define_property(obj, key, value) {
294
+ if (key in obj) Object.defineProperty(obj, key, {
295
+ value: value,
296
+ enumerable: true,
297
+ configurable: true,
298
+ writable: true
299
+ });
300
+ else obj[key] = value;
301
+ return obj;
302
+ }
303
+ const WINDOWS_POINTER_TOLERANCE_PX = 5;
304
+ function assertFinitePoint(point, context) {
305
+ if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) throw new Error(`${context} must contain finite coordinates, got (${point.x}, ${point.y})`);
306
+ }
307
+ function parseWindowsPointerPosition(output, context) {
308
+ const match = output.trim().match(/^(-?\d+),(-?\d+)$/);
309
+ if (!match) throw new Error(`${context} returned an invalid cursor position: ${JSON.stringify(output.trim())}`);
310
+ return {
311
+ x: Number(match[1]),
312
+ y: Number(match[2])
313
+ };
314
+ }
315
+ function windowsPointerPositionScript() {
316
+ return `
317
+ Add-Type -AssemblyName System.Windows.Forms, System.Drawing
318
+ $position = [System.Windows.Forms.Cursor]::Position
319
+ [Console]::Out.Write(('{0},{1}' -f $position.X, $position.Y))
320
+ `.trim();
321
+ }
322
+ function windowsPointerMoveScript(point, options) {
323
+ assertFinitePoint(point, 'Windows pointer target');
324
+ 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');
325
+ const targetX = Math.round(point.x);
326
+ const targetY = Math.round(point.y);
327
+ const smoothSteps = Math.max(1, Math.round(options?.smoothSteps ?? 1));
328
+ const smoothDelayMs = Math.max(0, Math.round(options?.smoothDelayMs ?? 0));
329
+ return `
330
+ Add-Type -AssemblyName System.Windows.Forms, System.Drawing
331
+ $targetX = ${targetX}
332
+ $targetY = ${targetY}
333
+ $smoothSteps = ${smoothSteps}
334
+ $smoothDelayMs = ${smoothDelayMs}
335
+ $start = [System.Windows.Forms.Cursor]::Position
336
+ for ($step = 1; $step -le $smoothSteps; $step += 1) {
337
+ $x = [int][Math]::Round($start.X + (($targetX - $start.X) * $step / [double]$smoothSteps))
338
+ $y = [int][Math]::Round($start.Y + (($targetY - $start.Y) * $step / [double]$smoothSteps))
339
+ [System.Windows.Forms.Cursor]::Position = [System.Drawing.Point]::new($x, $y)
340
+ if ($smoothDelayMs -gt 0 -and $step -lt $smoothSteps) {
341
+ Start-Sleep -Milliseconds $smoothDelayMs
342
+ }
343
+ }
344
+ [System.Windows.Forms.Cursor]::Position = [System.Drawing.Point]::new($targetX, $targetY)
345
+ $position = [System.Windows.Forms.Cursor]::Position
346
+ [Console]::Out.Write(('{0},{1}' -f $position.X, $position.Y))
347
+ `.trim();
348
+ }
349
+ function parseWindowsWindowRect(output) {
350
+ const value = output.trim();
351
+ if (!value) return null;
352
+ const match = value.match(/^(-?\d+),(-?\d+),(\d+),(\d+)$/);
353
+ if (!match) throw new Error(`Windows active window query returned an invalid rectangle: ${JSON.stringify(value)}`);
354
+ const rect = {
355
+ x: Number(match[1]),
356
+ y: Number(match[2]),
357
+ width: Number(match[3]),
358
+ height: Number(match[4])
359
+ };
360
+ if (rect.width <= 0 || rect.height <= 0) throw new Error(`Windows active window query returned an invalid rectangle: ${JSON.stringify(value)}`);
361
+ return rect;
362
+ }
363
+ function windowsActiveWindowRectScript() {
364
+ return `
365
+ $midsceneWindowHandle = $midsceneNativeMethods::GetForegroundWindow()
366
+ if ($midsceneWindowHandle -eq [System.IntPtr]::Zero) {
367
+ return
368
+ }
369
+ $midsceneWindowRectBuffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(16)
370
+ try {
371
+ if (-not $midsceneNativeMethods::GetWindowRect($midsceneWindowHandle, $midsceneWindowRectBuffer)) {
372
+ throw 'GetWindowRect failed for the foreground window.'
373
+ }
374
+ $left = [System.Runtime.InteropServices.Marshal]::ReadInt32($midsceneWindowRectBuffer, 0)
375
+ $top = [System.Runtime.InteropServices.Marshal]::ReadInt32($midsceneWindowRectBuffer, 4)
376
+ $right = [System.Runtime.InteropServices.Marshal]::ReadInt32($midsceneWindowRectBuffer, 8)
377
+ $bottom = [System.Runtime.InteropServices.Marshal]::ReadInt32($midsceneWindowRectBuffer, 12)
378
+ [Console]::Out.Write(('{0},{1},{2},{3}' -f $left, $top, ($right - $left), ($bottom - $top)))
379
+ }
380
+ finally {
381
+ [System.Runtime.InteropServices.Marshal]::FreeHGlobal($midsceneWindowRectBuffer)
382
+ }
383
+ `.trim();
384
+ }
385
+ class WindowsPointerDriver {
386
+ getPosition() {
387
+ return parseWindowsPointerPosition(this.options.runPhysicalPixelPowershell(windowsPointerPositionScript()), 'Windows pointer query');
388
+ }
389
+ getActiveWindowRect() {
390
+ return parseWindowsWindowRect(this.options.runPhysicalPixelPowershell(windowsActiveWindowRectScript()));
391
+ }
392
+ moveTo(point, options) {
393
+ return parseWindowsPointerPosition(this.options.runPhysicalPixelPowershell(windowsPointerMoveScript(point, options)), 'Windows pointer move');
394
+ }
395
+ constructor(options){
396
+ windows_pointer_define_property(this, "options", void 0);
397
+ this.options = options;
398
+ }
399
+ }
400
+ function windowsPointerDrift(expected, actual) {
401
+ return {
402
+ x: actual.x - expected.x,
403
+ y: actual.y - expected.y
404
+ };
405
+ }
406
+ function windowsPointerIsWithinTolerance(drift) {
407
+ return Math.abs(drift.x) <= WINDOWS_POINTER_TOLERANCE_PX && Math.abs(drift.y) <= WINDOWS_POINTER_TOLERANCE_PX;
408
+ }
212
409
  const interrupt_namespaceObject = require("@midscene/shared/cli/interrupt");
213
410
  const debugXvfb = (0, logger_namespaceObject.getDebug)('computer:xvfb');
214
411
  const xvfbCleanupMonitorScript = String.raw`
@@ -467,39 +664,36 @@ function sendKeyViaAppleScript(key, modifiers = []) {
467
664
  script
468
665
  ]);
469
666
  }
470
- const POWERSHELL_TIMEOUT_MS = 15000;
471
- const POWERSHELL_MAX_BUFFER = 67108864;
472
667
  function escapePowershellSingleQuoted(value) {
473
668
  return value.replace(/'/g, "''");
474
669
  }
475
- function runPowershell(script) {
476
- const prefixed = `$ProgressPreference = 'SilentlyContinue'\n${script}`;
477
- const encoded = Buffer.from(prefixed, 'utf16le').toString('base64');
478
- return (0, external_node_child_process_namespaceObject.execFileSync)('powershell.exe', [
479
- '-NoProfile',
480
- '-NonInteractive',
481
- '-EncodedCommand',
482
- encoded
483
- ], {
484
- encoding: 'utf8',
485
- timeout: POWERSHELL_TIMEOUT_MS,
486
- maxBuffer: POWERSHELL_MAX_BUFFER,
487
- windowsHide: true
488
- });
489
- }
490
- function listWindowsDisplays() {
670
+ function readWindowsDisplayGeometries() {
491
671
  const script = `
492
672
  Add-Type -AssemblyName System.Windows.Forms
493
673
  $s = [System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
494
- [PSCustomObject]@{ id = $_.DeviceName; name = $_.DeviceName; primary = $_.Primary }
674
+ $b = $_.Bounds
675
+ [PSCustomObject]@{
676
+ id = $_.DeviceName
677
+ name = $_.DeviceName
678
+ primary = $_.Primary
679
+ bounds = [PSCustomObject]@{ x = $b.X; y = $b.Y; width = $b.Width; height = $b.Height }
680
+ }
495
681
  }
496
682
  ConvertTo-Json @($s) -Compress
497
683
  `.trim();
498
- const parsed = JSON.parse(runPowershell(script).trim());
499
- return parsed.map((d)=>({
500
- id: String(d.id),
501
- name: d.name || String(d.id),
502
- primary: d.primary || false
684
+ const output = runWindowsPhysicalPixelPowershell(script).trim();
685
+ if (!output) throw new Error('Windows display enumeration returned no data');
686
+ const parsed = JSON.parse(output);
687
+ if (!Array.isArray(parsed)) throw new Error('Windows display enumeration returned invalid data');
688
+ const displays = parsed.filter(isWindowsDisplayGeometry);
689
+ if (displays.length !== parsed.length || 0 === displays.length) throw new Error('Windows display enumeration returned invalid geometry');
690
+ return displays;
691
+ }
692
+ function listWindowsDisplays(geometries = readWindowsDisplayGeometries()) {
693
+ return geometries.map((display)=>({
694
+ id: display.id,
695
+ name: display.name,
696
+ primary: display.primary
503
697
  }));
504
698
  }
505
699
  let device_libnut = null;
@@ -601,6 +795,16 @@ function getDisplayInfoBinary() {
601
795
  function isFiniteNumber(value) {
602
796
  return 'number' == typeof value && Number.isFinite(value);
603
797
  }
798
+ function isDisplayBounds(value) {
799
+ if (!value || 'object' != typeof value) return false;
800
+ const bounds = value;
801
+ return isFiniteNumber(bounds.x) && isFiniteNumber(bounds.y) && isFiniteNumber(bounds.width) && isFiniteNumber(bounds.height) && bounds.width > 0 && bounds.height > 0;
802
+ }
803
+ function isWindowsDisplayGeometry(value) {
804
+ if (!value || 'object' != typeof value) return false;
805
+ const candidate = value;
806
+ return 'string' == typeof candidate.id && candidate.id.length > 0 && 'string' == typeof candidate.name && 'boolean' == typeof candidate.primary && isDisplayBounds(candidate.bounds);
807
+ }
604
808
  function isDarwinDisplayGeometry(value) {
605
809
  if (!value || 'object' != typeof value) return false;
606
810
  const candidate = value;
@@ -643,9 +847,11 @@ function readDarwinFrontmostApplication() {
643
847
  return;
644
848
  }
645
849
  }
646
- async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, holdDuration, reason) {
647
- await inputDriver.delay(CLICK_SETTLE_DELAY);
648
- const current = inputDriver.getMousePos();
850
+ async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, current, holdDuration, reason) {
851
+ const drift = {
852
+ x: current.x - targetX,
853
+ y: current.y - targetY
854
+ };
649
855
  debugComputerInput('tap mouse moved %o', {
650
856
  reason,
651
857
  target: {
@@ -653,10 +859,7 @@ async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, holdDurati
653
859
  y: targetY
654
860
  },
655
861
  current,
656
- drift: {
657
- x: current.x - targetX,
658
- y: current.y - targetY
659
- }
862
+ drift
660
863
  });
661
864
  await inputDriver.withMouseButton('left', async ()=>{
662
865
  debugComputerInput('tap mouse down %o', {
@@ -675,9 +878,18 @@ function resolveDarwinDisplayGeometryFromList(displayId, displays) {
675
878
  if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays.find((display)=>0 === display.screenIndex) || displays[0];
676
879
  return displays.find((display)=>display.screenIndex === screenIndex) || displays.find((display)=>display.cgDisplayId === screenIndex);
677
880
  }
678
- function resolveDisplayGeometry(displayId) {
679
- if ('darwin' !== process.platform) return;
680
- return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
881
+ function resolveWindowsDisplayGeometryFromList(displayId, displays) {
882
+ if (!displays.length) return;
883
+ if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
884
+ return displays.find((display)=>display.id === displayId);
885
+ }
886
+ function resolveDisplayGeometry(displayId, windowsDisplays) {
887
+ if ('darwin' === process.platform) return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
888
+ if ('win32' === process.platform) {
889
+ const geometry = resolveWindowsDisplayGeometryFromList(displayId, windowsDisplays ?? readWindowsDisplayGeometries());
890
+ if (!geometry) throw new Error(displayId ? `Requested Windows display not found: ${displayId}` : 'No Windows displays were detected');
891
+ return geometry;
892
+ }
681
893
  }
682
894
  function mapDisplayLocalPointToGlobal(point, geometry) {
683
895
  if (!geometry) return point;
@@ -763,6 +975,30 @@ function normalizePrimaryKey(key) {
763
975
  return KEY_NAME_MAP[lowerKey] || lowerKey;
764
976
  }
765
977
  class ComputerDevice {
978
+ async moveGlobalPointer(point, context, smooth) {
979
+ if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
980
+ const target = {
981
+ x: Math.round(point.x),
982
+ y: Math.round(point.y)
983
+ };
984
+ if ('win32' === process.platform) {
985
+ const actual = this.windowsPointerDriver.moveTo(target, {
986
+ smoothSteps: smooth?.smoothSteps,
987
+ smoothDelayMs: smooth?.smoothDelay
988
+ });
989
+ const drift = windowsPointerDrift(target, actual);
990
+ if (!windowsPointerIsWithinTolerance(drift)) throw new Error(`${context}: expected (${target.x}, ${target.y}), got (${actual.x}, ${actual.y}), drift=(${drift.x}, ${drift.y})`);
991
+ await this.inputDriver.delay(CLICK_SETTLE_DELAY);
992
+ return actual;
993
+ }
994
+ if (smooth) await this.inputDriver.smoothMoveMouse(target.x, target.y, smooth.smoothSteps, smooth.smoothDelay);
995
+ else this.inputDriver.moveMouse(target.x, target.y);
996
+ await this.inputDriver.delay(CLICK_SETTLE_DELAY);
997
+ return this.inputDriver.getMousePos();
998
+ }
999
+ moveDisplayPointer(point, context, smooth) {
1000
+ return this.moveGlobalPointer(this.toGlobalPoint(point), context, smooth);
1001
+ }
766
1002
  async focusKeyboardTarget(element, delayMs) {
767
1003
  const [x, y] = element.center;
768
1004
  if ('darwin' === process.platform) await this.inputPrimitives.pointer.tap({
@@ -770,11 +1006,10 @@ class ComputerDevice {
770
1006
  y
771
1007
  });
772
1008
  else {
773
- const point = this.toGlobalPoint({
1009
+ await this.moveDisplayPointer({
774
1010
  x,
775
1011
  y
776
- });
777
- this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
1012
+ }, 'Mouse did not reach the keyboard focus target');
778
1013
  this.inputDriver.mouseClick('left');
779
1014
  }
780
1015
  await this.inputDriver.delay(delayMs);
@@ -793,7 +1028,7 @@ class ComputerDevice {
793
1028
  }));
794
1029
  } catch (error) {
795
1030
  debugDevice(`Failed to list displays: ${error}`);
796
- return [];
1031
+ throw new Error(`Failed to list displays: ${error}`);
797
1032
  }
798
1033
  }
799
1034
  async connect() {
@@ -822,9 +1057,10 @@ class ComputerDevice {
822
1057
  }
823
1058
  }
824
1059
  device_libnut = await getLibnut();
825
- this.displayGeometry = resolveDisplayGeometry(this.displayId);
1060
+ const windowsDisplayGeometries = 'win32' === process.platform ? readWindowsDisplayGeometries() : void 0;
1061
+ this.displayGeometry = resolveDisplayGeometry(this.displayId, windowsDisplayGeometries);
826
1062
  const size = await this.size();
827
- const displays = await ComputerDevice.listDisplays();
1063
+ const displays = windowsDisplayGeometries ? listWindowsDisplays(windowsDisplayGeometries) : await ComputerDevice.listDisplays();
828
1064
  const headlessInfo = this.xvfbInstance ? `\nHeadless: true (Xvfb on ${this.xvfbInstance.display})` : '';
829
1065
  this.description = `
830
1066
  Type: Computer
@@ -834,7 +1070,7 @@ Screen Size: ${size.width}x${size.height}
834
1070
  Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ') : 'Unknown'}${headlessInfo}
835
1071
  `;
836
1072
  debugDevice('Computer device connected', this.description);
837
- await this.healthCheck();
1073
+ await this.healthCheck(displays);
838
1074
  } catch (error) {
839
1075
  if (this.xvfbInstance) {
840
1076
  if (!this.options?.keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
@@ -853,9 +1089,9 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
853
1089
  throw new Error(`Unable to connect to computer device: ${error}`);
854
1090
  }
855
1091
  }
856
- async healthCheck() {
1092
+ async healthCheck(displays) {
857
1093
  console.log('[HealthCheck] Starting health check...');
858
- console.log("[HealthCheck] @midscene/computer v1.12.3-beta-20260902081623.0");
1094
+ console.log("[HealthCheck] @midscene/computer v1.12.3");
859
1095
  console.log('[HealthCheck] Taking screenshot...');
860
1096
  const screenshotTimeout = 15000;
861
1097
  let timeoutId;
@@ -867,23 +1103,38 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
867
1103
  timeoutPromise
868
1104
  ]);
869
1105
  console.log(`[HealthCheck] Screenshot succeeded (length=${base64.length})`);
870
- console.log('[HealthCheck] Moving mouse...');
871
- const startPos = this.inputDriver.getMousePos();
1106
+ console.log('[HealthCheck] Verifying mouse control...');
1107
+ const startPos = 'win32' === process.platform ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
872
1108
  console.log(`[HealthCheck] Current mouse position: (${startPos.x}, ${startPos.y})`);
873
- const offsetX = Math.floor(40 * Math.random()) + 10;
874
- const offsetY = Math.floor(40 * Math.random()) + 10;
875
- const targetX = startPos.x + offsetX;
876
- const targetY = startPos.y + offsetY;
877
- console.log(`[HealthCheck] Moving mouse to (${targetX}, ${targetY})...`);
878
- this.inputDriver.moveMouse(targetX, targetY);
879
- await (0, utils_namespaceObject.sleep)(50);
880
- const movedPos = this.inputDriver.getMousePos();
881
- console.log(`[HealthCheck] Mouse position after move: (${movedPos.x}, ${movedPos.y})`);
882
- const deltaX = Math.abs(movedPos.x - targetX);
883
- const deltaY = Math.abs(movedPos.y - targetY);
884
- if (deltaX > 5 || deltaY > 5) {
885
- const msg = `[HealthCheck] WARNING: Mouse control may not be working. Expected (${targetX}, ${targetY}), got (${movedPos.x}, ${movedPos.y}), delta=(${deltaX}, ${deltaY})`;
886
- warnDevice(msg);
1109
+ if ('win32' === process.platform) {
1110
+ if (!this.displayGeometry) throw new Error('Windows display geometry is unavailable');
1111
+ const bounds = this.displayGeometry.bounds;
1112
+ const target = {
1113
+ x: Math.round(bounds.x + bounds.width / 2),
1114
+ y: Math.round(bounds.y + bounds.height / 2)
1115
+ };
1116
+ try {
1117
+ const actual = this.windowsPointerDriver.moveTo(target);
1118
+ const drift = windowsPointerDrift(target, actual);
1119
+ 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})`);
1120
+ } finally{
1121
+ this.windowsPointerDriver.moveTo(startPos);
1122
+ }
1123
+ console.log(`[HealthCheck] Mouse verified in screenshot-space display bounds (${bounds.x}, ${bounds.y}, ${bounds.width}, ${bounds.height})`);
1124
+ } else {
1125
+ const offsetX = Math.floor(40 * Math.random()) + 10;
1126
+ const offsetY = Math.floor(40 * Math.random()) + 10;
1127
+ const targetX = startPos.x + offsetX;
1128
+ const targetY = startPos.y + offsetY;
1129
+ console.log(`[HealthCheck] Moving mouse to (${targetX}, ${targetY})...`);
1130
+ try {
1131
+ this.inputDriver.moveMouse(targetX, targetY);
1132
+ await (0, utils_namespaceObject.sleep)(CLICK_SETTLE_DELAY);
1133
+ this.inputDriver.assertMousePosition(targetX, targetY, 'Mouse health check failed');
1134
+ } finally{
1135
+ this.inputDriver.moveMouse(startPos.x, startPos.y);
1136
+ console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
1137
+ }
887
1138
  }
888
1139
  if ('win32' === process.platform && !this.isRunningAsAdmin()) {
889
1140
  const hint = [
@@ -893,10 +1144,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
893
1144
  ].join(' ');
894
1145
  warnDevice(`[HealthCheck] ${hint}`);
895
1146
  }
896
- this.inputDriver.moveMouse(startPos.x, startPos.y);
897
- console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
898
1147
  console.log('[HealthCheck] Listing monitors...');
899
- const displays = await ComputerDevice.listDisplays();
900
1148
  if (displays.length > 0) {
901
1149
  console.log(`[HealthCheck] Found ${displays.length} monitor(s):`);
902
1150
  for (const display of displays){
@@ -964,7 +1212,6 @@ Original error: ${lastRawMessage}`);
964
1212
  $screen = [System.Windows.Forms.Screen]::AllScreens | Where-Object { $_.DeviceName -eq $dn } | Select-Object -First 1
965
1213
  if (-not $screen) { throw "Requested display not found: $dn" }` : '$screen = [System.Windows.Forms.Screen]::PrimaryScreen';
966
1214
  const script = `
967
- $ErrorActionPreference = 'Stop'
968
1215
  Add-Type -AssemblyName System.Windows.Forms, System.Drawing
969
1216
  ${selectScreen}
970
1217
  $b = $screen.Bounds
@@ -978,7 +1225,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
978
1225
  `.trim();
979
1226
  let stdout;
980
1227
  try {
981
- stdout = runPowershell(script);
1228
+ stdout = runWindowsPhysicalPixelPowershell(script);
982
1229
  } catch (error) {
983
1230
  const message = error instanceof Error ? error.message : String(error);
984
1231
  throw new Error(`Failed to take screenshot on Windows: ${message}`);
@@ -1080,11 +1327,15 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1080
1327
  }
1081
1328
  resolveUntargetedScrollPoint(screenSize) {
1082
1329
  if ('win32' === process.platform) {
1083
- const activeWindowRect = this.inputDriver.getActiveWindowRect();
1084
- if (activeWindowRect) return {
1085
- x: activeWindowRect.x + activeWindowRect.width / 2,
1086
- y: activeWindowRect.y + activeWindowRect.height / 2
1087
- };
1330
+ const activeWindowRect = this.windowsPointerDriver.getActiveWindowRect();
1331
+ if (activeWindowRect) {
1332
+ const activeWindowCenter = {
1333
+ x: activeWindowRect.x + activeWindowRect.width / 2,
1334
+ y: activeWindowRect.y + activeWindowRect.height / 2
1335
+ };
1336
+ const bounds = this.displayGeometry?.bounds;
1337
+ if (!bounds || activeWindowCenter.x >= bounds.x && activeWindowCenter.x < bounds.x + bounds.width && activeWindowCenter.y >= bounds.y && activeWindowCenter.y < bounds.y + bounds.height) return activeWindowCenter;
1338
+ }
1088
1339
  }
1089
1340
  return this.toGlobalPoint({
1090
1341
  x: screenSize.width / 2,
@@ -1095,17 +1346,16 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1095
1346
  if (param.locate) {
1096
1347
  const element = param.locate;
1097
1348
  const [x, y] = element.center;
1098
- const point = this.toGlobalPoint({
1349
+ await this.moveDisplayPointer({
1099
1350
  x,
1100
1351
  y
1101
- });
1102
- this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
1352
+ }, 'Mouse did not reach the scroll target');
1103
1353
  return;
1104
1354
  }
1105
1355
  const screenSize = await this.size();
1106
1356
  if ('win32' === process.platform && this.inputDriver.focusActiveWindow()) await this.inputDriver.delay(CLICK_FOCUS_SETTLE_DELAY);
1107
1357
  const point = this.resolveUntargetedScrollPoint(screenSize);
1108
- this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
1358
+ await this.moveGlobalPointer(point, 'Mouse did not reach the scroll viewport');
1109
1359
  await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
1110
1360
  return screenSize;
1111
1361
  }
@@ -1230,6 +1480,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1230
1480
  runPhasedScroll,
1231
1481
  debug: (message)=>debugDevice(message)
1232
1482
  }));
1483
+ device_define_property(this, "windowsPointerDriver", new WindowsPointerDriver({
1484
+ runPhysicalPixelPowershell: runWindowsPhysicalPixelPowershell
1485
+ }));
1233
1486
  device_define_property(this, "useAppleScript", void 0);
1234
1487
  device_define_property(this, "adminCheckCache", void 0);
1235
1488
  device_define_property(this, "uri", void 0);
@@ -1254,15 +1507,17 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1254
1507
  },
1255
1508
  holdDuration,
1256
1509
  displayId: this.displayId,
1257
- displayGeometry: this.displayGeometry ? {
1258
- screenIndex: this.displayGeometry.screenIndex,
1259
- cgDisplayId: this.displayGeometry.cgDisplayId,
1260
- bounds: this.displayGeometry.bounds
1261
- } : void 0
1510
+ displayGeometry: this.displayGeometry
1262
1511
  });
1263
1512
  const frontmostBefore = 'darwin' === process.platform ? readDarwinFrontmostApplication() : void 0;
1264
- await this.inputDriver.smoothMoveMouse(targetX, targetY, SMOOTH_MOVE_STEPS_TAP, SMOOTH_MOVE_DELAY_TAP);
1265
- await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, holdDuration, 'primary');
1513
+ const current = await this.moveGlobalPointer({
1514
+ x: targetX,
1515
+ y: targetY
1516
+ }, 'Mouse did not reach the tap target', {
1517
+ smoothSteps: SMOOTH_MOVE_STEPS_TAP,
1518
+ smoothDelay: SMOOTH_MOVE_DELAY_TAP
1519
+ });
1520
+ await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, current, holdDuration, 'primary');
1266
1521
  if (frontmostBefore && 'darwin' === process.platform) {
1267
1522
  await (0, utils_namespaceObject.sleep)(CLICK_FOCUS_SETTLE_DELAY);
1268
1523
  const frontmostAfter = readDarwinFrontmostApplication();
@@ -1273,42 +1528,43 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1273
1528
  focusChanged
1274
1529
  });
1275
1530
  if (focusChanged) {
1276
- this.inputDriver.moveMouse(targetX, targetY);
1277
- await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, holdDuration, 'focus-follow-up');
1531
+ const followUpCurrent = await this.moveGlobalPointer({
1532
+ x: targetX,
1533
+ y: targetY
1534
+ }, 'Mouse did not reach the focus follow-up target');
1535
+ await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, followUpCurrent, holdDuration, 'focus-follow-up');
1278
1536
  }
1279
1537
  }
1280
1538
  },
1281
1539
  doubleClick: async ({ x, y })=>{
1282
- const target = this.toGlobalPoint({
1540
+ await this.moveDisplayPointer({
1283
1541
  x,
1284
1542
  y
1285
- });
1286
- this.inputDriver.moveMouse(Math.round(target.x), Math.round(target.y));
1543
+ }, 'Mouse did not reach the double-click target');
1287
1544
  this.inputDriver.mouseClick('left', true);
1288
1545
  },
1289
1546
  rightClick: async ({ x, y })=>{
1290
- const target = this.toGlobalPoint({
1547
+ await this.moveDisplayPointer({
1291
1548
  x,
1292
1549
  y
1293
- });
1294
- this.inputDriver.moveMouse(Math.round(target.x), Math.round(target.y));
1550
+ }, 'Mouse did not reach the right-click target');
1295
1551
  this.inputDriver.mouseClick('right');
1296
1552
  },
1297
1553
  hover: async ({ x, y })=>{
1298
- const target = this.toGlobalPoint({
1554
+ await this.moveDisplayPointer({
1299
1555
  x,
1300
1556
  y
1557
+ }, 'Mouse did not reach the hover target', {
1558
+ smoothSteps: SMOOTH_MOVE_STEPS_MOUSE_MOVE,
1559
+ smoothDelay: SMOOTH_MOVE_DELAY_MOUSE_MOVE
1301
1560
  });
1302
- await this.inputDriver.smoothMoveMouse(Math.round(target.x), Math.round(target.y), SMOOTH_MOVE_STEPS_MOUSE_MOVE, SMOOTH_MOVE_DELAY_MOUSE_MOVE);
1303
1561
  await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
1304
1562
  },
1305
1563
  dragAndDrop: async (from, to)=>{
1306
- const globalFrom = this.toGlobalPoint(from);
1307
- const globalTo = this.toGlobalPoint(to);
1308
- this.inputDriver.moveMouse(Math.round(globalFrom.x), Math.round(globalFrom.y));
1564
+ await this.moveDisplayPointer(from, 'Mouse did not reach the drag start target');
1309
1565
  await this.inputDriver.withMouseButton('left', async ()=>{
1310
1566
  await this.inputDriver.delay(100);
1311
- this.inputDriver.moveMouse(Math.round(globalTo.x), Math.round(globalTo.y));
1567
+ await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target');
1312
1568
  await this.inputDriver.delay(100);
1313
1569
  });
1314
1570
  }
@@ -2282,7 +2538,7 @@ const tools = new ComputerMidsceneTools({
2282
2538
  });
2283
2539
  (0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-computer', {
2284
2540
  stripPrefix: 'computer_',
2285
- version: "1.12.3-beta-20260902081623.0",
2541
+ version: "1.12.3",
2286
2542
  extraCommands: (0, core_namespaceObject.createReportCliCommands)()
2287
2543
  }).catch((e)=>{
2288
2544
  process.exit((0, cli_namespaceObject.reportCLIError)(e));