@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/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
|
|
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
|
-
|
|
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
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
648
|
-
|
|
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
|
|
679
|
-
if (
|
|
680
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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]
|
|
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
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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 =
|
|
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.
|
|
1084
|
-
if (activeWindowRect)
|
|
1085
|
-
|
|
1086
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1265
|
-
|
|
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.
|
|
1277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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));
|