@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/es/cli.mjs +365 -109
- package/dist/es/index.mjs +365 -109
- package/dist/lib/cli.js +365 -109
- package/dist/lib/index.js +365 -109
- package/dist/types/index.d.ts +7 -9
- package/package.json +3 -3
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
|
|
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
|
-
|
|
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
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
674
|
-
|
|
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
|
|
705
|
-
if (
|
|
706
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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]
|
|
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
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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 =
|
|
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.
|
|
1110
|
-
if (activeWindowRect)
|
|
1111
|
-
|
|
1112
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1291
|
-
|
|
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.
|
|
1303
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
2611
|
+
const currentVersion = "1.12.3";
|
|
2356
2612
|
console.log(`@midscene/computer v${currentVersion}`);
|
|
2357
2613
|
return currentVersion;
|
|
2358
2614
|
}
|