@midscene/computer 1.12.2 → 1.12.3-beta-20260828110230.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/cli.mjs +280 -75
- package/dist/es/index.mjs +280 -75
- package/dist/lib/cli.js +280 -75
- package/dist/lib/index.js +280 -75
- 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;
|
|
@@ -235,6 +245,122 @@ class ComputerInputDriver {
|
|
|
235
245
|
this.pendingInputDelayWaits = new Set();
|
|
236
246
|
}
|
|
237
247
|
}
|
|
248
|
+
const WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE = `
|
|
249
|
+
$midsceneAssemblyName = New-Object System.Reflection.AssemblyName('MidsceneDpiNative')
|
|
250
|
+
$midsceneAssembly = [System.AppDomain]::CurrentDomain.DefineDynamicAssembly(
|
|
251
|
+
$midsceneAssemblyName,
|
|
252
|
+
[System.Reflection.Emit.AssemblyBuilderAccess]::Run
|
|
253
|
+
)
|
|
254
|
+
$midsceneModule = $midsceneAssembly.DefineDynamicModule('MidsceneDpiNativeModule')
|
|
255
|
+
$midsceneType = $midsceneModule.DefineType(
|
|
256
|
+
'MidsceneDpiNative.User32',
|
|
257
|
+
[System.Reflection.TypeAttributes]'Public, Class, Sealed, Abstract'
|
|
258
|
+
)
|
|
259
|
+
$midsceneMethodAttributes =
|
|
260
|
+
[System.Reflection.MethodAttributes]::Public -bor
|
|
261
|
+
[System.Reflection.MethodAttributes]::Static -bor
|
|
262
|
+
[System.Reflection.MethodAttributes]::PinvokeImpl
|
|
263
|
+
$midsceneSetDpiMethod = $midsceneType.DefinePInvokeMethod(
|
|
264
|
+
'SetThreadDpiAwarenessContext',
|
|
265
|
+
'user32.dll',
|
|
266
|
+
$midsceneMethodAttributes,
|
|
267
|
+
[System.Reflection.CallingConventions]::Standard,
|
|
268
|
+
[System.IntPtr],
|
|
269
|
+
[System.Type[]]@([System.IntPtr]),
|
|
270
|
+
[System.Runtime.InteropServices.CallingConvention]::Winapi,
|
|
271
|
+
[System.Runtime.InteropServices.CharSet]::None
|
|
272
|
+
)
|
|
273
|
+
$midsceneSetDpiMethod.SetImplementationFlags(
|
|
274
|
+
$midsceneSetDpiMethod.GetMethodImplementationFlags() -bor
|
|
275
|
+
[System.Reflection.MethodImplAttributes]::PreserveSig
|
|
276
|
+
)
|
|
277
|
+
$midsceneNativeMethods = $midsceneType.CreateType()
|
|
278
|
+
$midscenePerMonitorV2 = [System.IntPtr](-4)
|
|
279
|
+
$midscenePreviousDpiContext =
|
|
280
|
+
$midsceneNativeMethods::SetThreadDpiAwarenessContext($midscenePerMonitorV2)
|
|
281
|
+
if ($midscenePreviousDpiContext -eq [System.IntPtr]::Zero) {
|
|
282
|
+
throw 'Unable to enter the Per-Monitor V2 DPI awareness context.'
|
|
283
|
+
}
|
|
284
|
+
`.trim();
|
|
285
|
+
function windows_pointer_define_property(obj, key, value) {
|
|
286
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
287
|
+
value: value,
|
|
288
|
+
enumerable: true,
|
|
289
|
+
configurable: true,
|
|
290
|
+
writable: true
|
|
291
|
+
});
|
|
292
|
+
else obj[key] = value;
|
|
293
|
+
return obj;
|
|
294
|
+
}
|
|
295
|
+
const WINDOWS_POINTER_TOLERANCE_PX = 5;
|
|
296
|
+
function assertFinitePoint(point, context) {
|
|
297
|
+
if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) throw new Error(`${context} must contain finite coordinates, got (${point.x}, ${point.y})`);
|
|
298
|
+
}
|
|
299
|
+
function parseWindowsPointerPosition(output, context) {
|
|
300
|
+
const match = output.trim().match(/^(-?\d+),(-?\d+)$/);
|
|
301
|
+
if (!match) throw new Error(`${context} returned an invalid cursor position: ${JSON.stringify(output.trim())}`);
|
|
302
|
+
return {
|
|
303
|
+
x: Number(match[1]),
|
|
304
|
+
y: Number(match[2])
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function windowsPointerPositionScript() {
|
|
308
|
+
return `
|
|
309
|
+
${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
|
|
310
|
+
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
|
|
311
|
+
$position = [System.Windows.Forms.Cursor]::Position
|
|
312
|
+
[Console]::Out.Write(('{0},{1}' -f $position.X, $position.Y))
|
|
313
|
+
`.trim();
|
|
314
|
+
}
|
|
315
|
+
function windowsPointerMoveScript(point, options) {
|
|
316
|
+
assertFinitePoint(point, 'Windows pointer target');
|
|
317
|
+
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');
|
|
318
|
+
const targetX = Math.round(point.x);
|
|
319
|
+
const targetY = Math.round(point.y);
|
|
320
|
+
const smoothSteps = Math.max(1, Math.round(options?.smoothSteps ?? 1));
|
|
321
|
+
const smoothDelayMs = Math.max(0, Math.round(options?.smoothDelayMs ?? 0));
|
|
322
|
+
return `
|
|
323
|
+
${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
|
|
324
|
+
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
|
|
325
|
+
$targetX = ${targetX}
|
|
326
|
+
$targetY = ${targetY}
|
|
327
|
+
$smoothSteps = ${smoothSteps}
|
|
328
|
+
$smoothDelayMs = ${smoothDelayMs}
|
|
329
|
+
$start = [System.Windows.Forms.Cursor]::Position
|
|
330
|
+
for ($step = 1; $step -le $smoothSteps; $step += 1) {
|
|
331
|
+
$x = [int][Math]::Round($start.X + (($targetX - $start.X) * $step / [double]$smoothSteps))
|
|
332
|
+
$y = [int][Math]::Round($start.Y + (($targetY - $start.Y) * $step / [double]$smoothSteps))
|
|
333
|
+
[System.Windows.Forms.Cursor]::Position = [System.Drawing.Point]::new($x, $y)
|
|
334
|
+
if ($smoothDelayMs -gt 0 -and $step -lt $smoothSteps) {
|
|
335
|
+
Start-Sleep -Milliseconds $smoothDelayMs
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
[System.Windows.Forms.Cursor]::Position = [System.Drawing.Point]::new($targetX, $targetY)
|
|
339
|
+
$position = [System.Windows.Forms.Cursor]::Position
|
|
340
|
+
[Console]::Out.Write(('{0},{1}' -f $position.X, $position.Y))
|
|
341
|
+
`.trim();
|
|
342
|
+
}
|
|
343
|
+
class WindowsPointerDriver {
|
|
344
|
+
getPosition() {
|
|
345
|
+
return parseWindowsPointerPosition(this.options.runPowershell(windowsPointerPositionScript()), 'Windows pointer query');
|
|
346
|
+
}
|
|
347
|
+
moveTo(point, options) {
|
|
348
|
+
return parseWindowsPointerPosition(this.options.runPowershell(windowsPointerMoveScript(point, options)), 'Windows pointer move');
|
|
349
|
+
}
|
|
350
|
+
constructor(options){
|
|
351
|
+
windows_pointer_define_property(this, "options", void 0);
|
|
352
|
+
this.options = options;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function windowsPointerDrift(expected, actual) {
|
|
356
|
+
return {
|
|
357
|
+
x: actual.x - expected.x,
|
|
358
|
+
y: actual.y - expected.y
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function windowsPointerIsWithinTolerance(drift) {
|
|
362
|
+
return Math.abs(drift.x) <= WINDOWS_POINTER_TOLERANCE_PX && Math.abs(drift.y) <= WINDOWS_POINTER_TOLERANCE_PX;
|
|
363
|
+
}
|
|
238
364
|
const interrupt_namespaceObject = require("@midscene/shared/cli/interrupt");
|
|
239
365
|
const debugXvfb = (0, logger_namespaceObject.getDebug)('computer:xvfb');
|
|
240
366
|
const xvfbCleanupMonitorScript = String.raw`
|
|
@@ -513,19 +639,34 @@ function runPowershell(script) {
|
|
|
513
639
|
windowsHide: true
|
|
514
640
|
});
|
|
515
641
|
}
|
|
516
|
-
function
|
|
642
|
+
function readWindowsDisplayGeometries() {
|
|
517
643
|
const script = `
|
|
644
|
+
${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
|
|
518
645
|
Add-Type -AssemblyName System.Windows.Forms
|
|
519
646
|
$s = [System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
|
|
520
|
-
|
|
647
|
+
$b = $_.Bounds
|
|
648
|
+
[PSCustomObject]@{
|
|
649
|
+
id = $_.DeviceName
|
|
650
|
+
name = $_.DeviceName
|
|
651
|
+
primary = $_.Primary
|
|
652
|
+
bounds = [PSCustomObject]@{ x = $b.X; y = $b.Y; width = $b.Width; height = $b.Height }
|
|
653
|
+
}
|
|
521
654
|
}
|
|
522
655
|
ConvertTo-Json @($s) -Compress
|
|
523
656
|
`.trim();
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
657
|
+
const output = runPowershell(script).trim();
|
|
658
|
+
if (!output) throw new Error('Windows display enumeration returned no data');
|
|
659
|
+
const parsed = JSON.parse(output);
|
|
660
|
+
if (!Array.isArray(parsed)) throw new Error('Windows display enumeration returned invalid data');
|
|
661
|
+
const displays = parsed.filter(isWindowsDisplayGeometry);
|
|
662
|
+
if (displays.length !== parsed.length || 0 === displays.length) throw new Error('Windows display enumeration returned invalid geometry');
|
|
663
|
+
return displays;
|
|
664
|
+
}
|
|
665
|
+
function listWindowsDisplays(geometries = readWindowsDisplayGeometries()) {
|
|
666
|
+
return geometries.map((display)=>({
|
|
667
|
+
id: display.id,
|
|
668
|
+
name: display.name,
|
|
669
|
+
primary: display.primary
|
|
529
670
|
}));
|
|
530
671
|
}
|
|
531
672
|
let device_libnut = null;
|
|
@@ -627,6 +768,16 @@ function getDisplayInfoBinary() {
|
|
|
627
768
|
function isFiniteNumber(value) {
|
|
628
769
|
return 'number' == typeof value && Number.isFinite(value);
|
|
629
770
|
}
|
|
771
|
+
function isDisplayBounds(value) {
|
|
772
|
+
if (!value || 'object' != typeof value) return false;
|
|
773
|
+
const bounds = value;
|
|
774
|
+
return isFiniteNumber(bounds.x) && isFiniteNumber(bounds.y) && isFiniteNumber(bounds.width) && isFiniteNumber(bounds.height) && bounds.width > 0 && bounds.height > 0;
|
|
775
|
+
}
|
|
776
|
+
function isWindowsDisplayGeometry(value) {
|
|
777
|
+
if (!value || 'object' != typeof value) return false;
|
|
778
|
+
const candidate = value;
|
|
779
|
+
return 'string' == typeof candidate.id && candidate.id.length > 0 && 'string' == typeof candidate.name && 'boolean' == typeof candidate.primary && isDisplayBounds(candidate.bounds);
|
|
780
|
+
}
|
|
630
781
|
function isDarwinDisplayGeometry(value) {
|
|
631
782
|
if (!value || 'object' != typeof value) return false;
|
|
632
783
|
const candidate = value;
|
|
@@ -669,9 +820,11 @@ function readDarwinFrontmostApplication() {
|
|
|
669
820
|
return;
|
|
670
821
|
}
|
|
671
822
|
}
|
|
672
|
-
async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, holdDuration, reason) {
|
|
673
|
-
|
|
674
|
-
|
|
823
|
+
async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, current, holdDuration, reason) {
|
|
824
|
+
const drift = {
|
|
825
|
+
x: current.x - targetX,
|
|
826
|
+
y: current.y - targetY
|
|
827
|
+
};
|
|
675
828
|
debugComputerInput('tap mouse moved %o', {
|
|
676
829
|
reason,
|
|
677
830
|
target: {
|
|
@@ -679,10 +832,7 @@ async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, holdDurati
|
|
|
679
832
|
y: targetY
|
|
680
833
|
},
|
|
681
834
|
current,
|
|
682
|
-
drift
|
|
683
|
-
x: current.x - targetX,
|
|
684
|
-
y: current.y - targetY
|
|
685
|
-
}
|
|
835
|
+
drift
|
|
686
836
|
});
|
|
687
837
|
await inputDriver.withMouseButton('left', async ()=>{
|
|
688
838
|
debugComputerInput('tap mouse down %o', {
|
|
@@ -701,9 +851,18 @@ function resolveDarwinDisplayGeometryFromList(displayId, displays) {
|
|
|
701
851
|
if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays.find((display)=>0 === display.screenIndex) || displays[0];
|
|
702
852
|
return displays.find((display)=>display.screenIndex === screenIndex) || displays.find((display)=>display.cgDisplayId === screenIndex);
|
|
703
853
|
}
|
|
704
|
-
function
|
|
705
|
-
if (
|
|
706
|
-
|
|
854
|
+
function resolveWindowsDisplayGeometryFromList(displayId, displays) {
|
|
855
|
+
if (!displays.length) return;
|
|
856
|
+
if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
|
|
857
|
+
return displays.find((display)=>display.id === displayId);
|
|
858
|
+
}
|
|
859
|
+
function resolveDisplayGeometry(displayId, windowsDisplays) {
|
|
860
|
+
if ('darwin' === process.platform) return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
|
|
861
|
+
if ('win32' === process.platform) {
|
|
862
|
+
const geometry = resolveWindowsDisplayGeometryFromList(displayId, windowsDisplays ?? readWindowsDisplayGeometries());
|
|
863
|
+
if (!geometry) throw new Error(displayId ? `Requested Windows display not found: ${displayId}` : 'No Windows displays were detected');
|
|
864
|
+
return geometry;
|
|
865
|
+
}
|
|
707
866
|
}
|
|
708
867
|
function mapDisplayLocalPointToGlobal(point, geometry) {
|
|
709
868
|
if (!geometry) return point;
|
|
@@ -789,6 +948,30 @@ function normalizePrimaryKey(key) {
|
|
|
789
948
|
return KEY_NAME_MAP[lowerKey] || lowerKey;
|
|
790
949
|
}
|
|
791
950
|
class ComputerDevice {
|
|
951
|
+
async moveGlobalPointer(point, context, smooth) {
|
|
952
|
+
if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
|
|
953
|
+
const target = {
|
|
954
|
+
x: Math.round(point.x),
|
|
955
|
+
y: Math.round(point.y)
|
|
956
|
+
};
|
|
957
|
+
if ('win32' === process.platform) {
|
|
958
|
+
const actual = this.windowsPointerDriver.moveTo(target, {
|
|
959
|
+
smoothSteps: smooth?.smoothSteps,
|
|
960
|
+
smoothDelayMs: smooth?.smoothDelay
|
|
961
|
+
});
|
|
962
|
+
const drift = windowsPointerDrift(target, actual);
|
|
963
|
+
if (!windowsPointerIsWithinTolerance(drift)) throw new Error(`${context}: expected (${target.x}, ${target.y}), got (${actual.x}, ${actual.y}), drift=(${drift.x}, ${drift.y})`);
|
|
964
|
+
await this.inputDriver.delay(CLICK_SETTLE_DELAY);
|
|
965
|
+
return actual;
|
|
966
|
+
}
|
|
967
|
+
if (smooth) await this.inputDriver.smoothMoveMouse(target.x, target.y, smooth.smoothSteps, smooth.smoothDelay);
|
|
968
|
+
else this.inputDriver.moveMouse(target.x, target.y);
|
|
969
|
+
await this.inputDriver.delay(CLICK_SETTLE_DELAY);
|
|
970
|
+
return target;
|
|
971
|
+
}
|
|
972
|
+
moveDisplayPointer(point, context, smooth) {
|
|
973
|
+
return this.moveGlobalPointer(this.toGlobalPoint(point), context, smooth);
|
|
974
|
+
}
|
|
792
975
|
async focusKeyboardTarget(element, delayMs) {
|
|
793
976
|
const [x, y] = element.center;
|
|
794
977
|
if ('darwin' === process.platform) await this.inputPrimitives.pointer.tap({
|
|
@@ -796,11 +979,10 @@ class ComputerDevice {
|
|
|
796
979
|
y
|
|
797
980
|
});
|
|
798
981
|
else {
|
|
799
|
-
|
|
982
|
+
await this.moveDisplayPointer({
|
|
800
983
|
x,
|
|
801
984
|
y
|
|
802
|
-
});
|
|
803
|
-
this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
|
|
985
|
+
}, 'Mouse did not reach the keyboard focus target');
|
|
804
986
|
this.inputDriver.mouseClick('left');
|
|
805
987
|
}
|
|
806
988
|
await this.inputDriver.delay(delayMs);
|
|
@@ -819,7 +1001,7 @@ class ComputerDevice {
|
|
|
819
1001
|
}));
|
|
820
1002
|
} catch (error) {
|
|
821
1003
|
debugDevice(`Failed to list displays: ${error}`);
|
|
822
|
-
|
|
1004
|
+
throw new Error(`Failed to list displays: ${error}`);
|
|
823
1005
|
}
|
|
824
1006
|
}
|
|
825
1007
|
async connect() {
|
|
@@ -848,9 +1030,10 @@ class ComputerDevice {
|
|
|
848
1030
|
}
|
|
849
1031
|
}
|
|
850
1032
|
device_libnut = await getLibnut();
|
|
851
|
-
|
|
1033
|
+
const windowsDisplayGeometries = 'win32' === process.platform ? readWindowsDisplayGeometries() : void 0;
|
|
1034
|
+
this.displayGeometry = resolveDisplayGeometry(this.displayId, windowsDisplayGeometries);
|
|
852
1035
|
const size = await this.size();
|
|
853
|
-
const displays = await ComputerDevice.listDisplays();
|
|
1036
|
+
const displays = windowsDisplayGeometries ? listWindowsDisplays(windowsDisplayGeometries) : await ComputerDevice.listDisplays();
|
|
854
1037
|
const headlessInfo = this.xvfbInstance ? `\nHeadless: true (Xvfb on ${this.xvfbInstance.display})` : '';
|
|
855
1038
|
this.description = `
|
|
856
1039
|
Type: Computer
|
|
@@ -860,7 +1043,7 @@ Screen Size: ${size.width}x${size.height}
|
|
|
860
1043
|
Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ') : 'Unknown'}${headlessInfo}
|
|
861
1044
|
`;
|
|
862
1045
|
debugDevice('Computer device connected', this.description);
|
|
863
|
-
await this.healthCheck();
|
|
1046
|
+
await this.healthCheck(displays);
|
|
864
1047
|
} catch (error) {
|
|
865
1048
|
if (this.xvfbInstance) {
|
|
866
1049
|
if (!this.options?.keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
|
|
@@ -879,9 +1062,9 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
879
1062
|
throw new Error(`Unable to connect to computer device: ${error}`);
|
|
880
1063
|
}
|
|
881
1064
|
}
|
|
882
|
-
async healthCheck() {
|
|
1065
|
+
async healthCheck(displays) {
|
|
883
1066
|
console.log('[HealthCheck] Starting health check...');
|
|
884
|
-
console.log("[HealthCheck] @midscene/computer v1.12.
|
|
1067
|
+
console.log("[HealthCheck] @midscene/computer v1.12.3-beta-20260828110230.0");
|
|
885
1068
|
console.log('[HealthCheck] Taking screenshot...');
|
|
886
1069
|
const screenshotTimeout = 15000;
|
|
887
1070
|
let timeoutId;
|
|
@@ -893,23 +1076,38 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
893
1076
|
timeoutPromise
|
|
894
1077
|
]);
|
|
895
1078
|
console.log(`[HealthCheck] Screenshot succeeded (length=${base64.length})`);
|
|
896
|
-
console.log('[HealthCheck]
|
|
897
|
-
const startPos = this.inputDriver.getMousePos();
|
|
1079
|
+
console.log('[HealthCheck] Verifying mouse control...');
|
|
1080
|
+
const startPos = 'win32' === process.platform ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
|
|
898
1081
|
console.log(`[HealthCheck] Current mouse position: (${startPos.x}, ${startPos.y})`);
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1082
|
+
if ('win32' === process.platform) {
|
|
1083
|
+
if (!this.displayGeometry) throw new Error('Windows display geometry is unavailable');
|
|
1084
|
+
const bounds = this.displayGeometry.bounds;
|
|
1085
|
+
const target = {
|
|
1086
|
+
x: Math.round(bounds.x + bounds.width / 2),
|
|
1087
|
+
y: Math.round(bounds.y + bounds.height / 2)
|
|
1088
|
+
};
|
|
1089
|
+
try {
|
|
1090
|
+
const actual = this.windowsPointerDriver.moveTo(target);
|
|
1091
|
+
const drift = windowsPointerDrift(target, actual);
|
|
1092
|
+
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})`);
|
|
1093
|
+
} finally{
|
|
1094
|
+
this.windowsPointerDriver.moveTo(startPos);
|
|
1095
|
+
}
|
|
1096
|
+
console.log(`[HealthCheck] Mouse verified in screenshot-space display bounds (${bounds.x}, ${bounds.y}, ${bounds.width}, ${bounds.height})`);
|
|
1097
|
+
} else {
|
|
1098
|
+
const offsetX = Math.floor(40 * Math.random()) + 10;
|
|
1099
|
+
const offsetY = Math.floor(40 * Math.random()) + 10;
|
|
1100
|
+
const targetX = startPos.x + offsetX;
|
|
1101
|
+
const targetY = startPos.y + offsetY;
|
|
1102
|
+
console.log(`[HealthCheck] Moving mouse to (${targetX}, ${targetY})...`);
|
|
1103
|
+
try {
|
|
1104
|
+
this.inputDriver.moveMouse(targetX, targetY);
|
|
1105
|
+
await (0, utils_namespaceObject.sleep)(CLICK_SETTLE_DELAY);
|
|
1106
|
+
this.inputDriver.assertMousePosition(targetX, targetY, 'Mouse health check failed');
|
|
1107
|
+
} finally{
|
|
1108
|
+
this.inputDriver.moveMouse(startPos.x, startPos.y);
|
|
1109
|
+
console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
|
|
1110
|
+
}
|
|
913
1111
|
}
|
|
914
1112
|
if ('win32' === process.platform && !this.isRunningAsAdmin()) {
|
|
915
1113
|
const hint = [
|
|
@@ -919,10 +1117,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
919
1117
|
].join(' ');
|
|
920
1118
|
warnDevice(`[HealthCheck] ${hint}`);
|
|
921
1119
|
}
|
|
922
|
-
this.inputDriver.moveMouse(startPos.x, startPos.y);
|
|
923
|
-
console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
|
|
924
1120
|
console.log('[HealthCheck] Listing monitors...');
|
|
925
|
-
const displays = await ComputerDevice.listDisplays();
|
|
926
1121
|
if (displays.length > 0) {
|
|
927
1122
|
console.log(`[HealthCheck] Found ${displays.length} monitor(s):`);
|
|
928
1123
|
for (const display of displays){
|
|
@@ -991,6 +1186,7 @@ $screen = [System.Windows.Forms.Screen]::AllScreens | Where-Object { $_.DeviceNa
|
|
|
991
1186
|
if (-not $screen) { throw "Requested display not found: $dn" }` : '$screen = [System.Windows.Forms.Screen]::PrimaryScreen';
|
|
992
1187
|
const script = `
|
|
993
1188
|
$ErrorActionPreference = 'Stop'
|
|
1189
|
+
${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
|
|
994
1190
|
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
|
|
995
1191
|
${selectScreen}
|
|
996
1192
|
$b = $screen.Bounds
|
|
@@ -1105,10 +1301,14 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1105
1301
|
resolveUntargetedScrollPoint(screenSize) {
|
|
1106
1302
|
if ('win32' === process.platform) {
|
|
1107
1303
|
const activeWindowRect = this.inputDriver.getActiveWindowRect();
|
|
1108
|
-
if (activeWindowRect)
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1304
|
+
if (activeWindowRect) {
|
|
1305
|
+
const activeWindowCenter = {
|
|
1306
|
+
x: activeWindowRect.x + activeWindowRect.width / 2,
|
|
1307
|
+
y: activeWindowRect.y + activeWindowRect.height / 2
|
|
1308
|
+
};
|
|
1309
|
+
const bounds = this.displayGeometry?.bounds;
|
|
1310
|
+
if (!bounds || activeWindowCenter.x >= bounds.x && activeWindowCenter.x < bounds.x + bounds.width && activeWindowCenter.y >= bounds.y && activeWindowCenter.y < bounds.y + bounds.height) return activeWindowCenter;
|
|
1311
|
+
}
|
|
1112
1312
|
}
|
|
1113
1313
|
return this.toGlobalPoint({
|
|
1114
1314
|
x: screenSize.width / 2,
|
|
@@ -1119,17 +1319,16 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1119
1319
|
if (param.locate) {
|
|
1120
1320
|
const element = param.locate;
|
|
1121
1321
|
const [x, y] = element.center;
|
|
1122
|
-
|
|
1322
|
+
await this.moveDisplayPointer({
|
|
1123
1323
|
x,
|
|
1124
1324
|
y
|
|
1125
|
-
});
|
|
1126
|
-
this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
|
|
1325
|
+
}, 'Mouse did not reach the scroll target');
|
|
1127
1326
|
return;
|
|
1128
1327
|
}
|
|
1129
1328
|
const screenSize = await this.size();
|
|
1130
1329
|
if ('win32' === process.platform && this.inputDriver.focusActiveWindow()) await this.inputDriver.delay(CLICK_FOCUS_SETTLE_DELAY);
|
|
1131
1330
|
const point = this.resolveUntargetedScrollPoint(screenSize);
|
|
1132
|
-
this.
|
|
1331
|
+
await this.moveGlobalPointer(point, 'Mouse did not reach the scroll viewport');
|
|
1133
1332
|
await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
|
|
1134
1333
|
return screenSize;
|
|
1135
1334
|
}
|
|
@@ -1254,6 +1453,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1254
1453
|
runPhasedScroll,
|
|
1255
1454
|
debug: (message)=>debugDevice(message)
|
|
1256
1455
|
}));
|
|
1456
|
+
device_define_property(this, "windowsPointerDriver", new WindowsPointerDriver({
|
|
1457
|
+
runPowershell
|
|
1458
|
+
}));
|
|
1257
1459
|
device_define_property(this, "useAppleScript", void 0);
|
|
1258
1460
|
device_define_property(this, "adminCheckCache", void 0);
|
|
1259
1461
|
device_define_property(this, "uri", void 0);
|
|
@@ -1278,15 +1480,17 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1278
1480
|
},
|
|
1279
1481
|
holdDuration,
|
|
1280
1482
|
displayId: this.displayId,
|
|
1281
|
-
displayGeometry: this.displayGeometry
|
|
1282
|
-
screenIndex: this.displayGeometry.screenIndex,
|
|
1283
|
-
cgDisplayId: this.displayGeometry.cgDisplayId,
|
|
1284
|
-
bounds: this.displayGeometry.bounds
|
|
1285
|
-
} : void 0
|
|
1483
|
+
displayGeometry: this.displayGeometry
|
|
1286
1484
|
});
|
|
1287
1485
|
const frontmostBefore = 'darwin' === process.platform ? readDarwinFrontmostApplication() : void 0;
|
|
1288
|
-
await this.
|
|
1289
|
-
|
|
1486
|
+
const current = await this.moveGlobalPointer({
|
|
1487
|
+
x: targetX,
|
|
1488
|
+
y: targetY
|
|
1489
|
+
}, 'Mouse did not reach the tap target', {
|
|
1490
|
+
smoothSteps: SMOOTH_MOVE_STEPS_TAP,
|
|
1491
|
+
smoothDelay: SMOOTH_MOVE_DELAY_TAP
|
|
1492
|
+
});
|
|
1493
|
+
await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, current, holdDuration, 'primary');
|
|
1290
1494
|
if (frontmostBefore && 'darwin' === process.platform) {
|
|
1291
1495
|
await (0, utils_namespaceObject.sleep)(CLICK_FOCUS_SETTLE_DELAY);
|
|
1292
1496
|
const frontmostAfter = readDarwinFrontmostApplication();
|
|
@@ -1297,42 +1501,43 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1297
1501
|
focusChanged
|
|
1298
1502
|
});
|
|
1299
1503
|
if (focusChanged) {
|
|
1300
|
-
this.
|
|
1301
|
-
|
|
1504
|
+
const followUpCurrent = await this.moveGlobalPointer({
|
|
1505
|
+
x: targetX,
|
|
1506
|
+
y: targetY
|
|
1507
|
+
}, 'Mouse did not reach the focus follow-up target');
|
|
1508
|
+
await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, followUpCurrent, holdDuration, 'focus-follow-up');
|
|
1302
1509
|
}
|
|
1303
1510
|
}
|
|
1304
1511
|
},
|
|
1305
1512
|
doubleClick: async ({ x, y })=>{
|
|
1306
|
-
|
|
1513
|
+
await this.moveDisplayPointer({
|
|
1307
1514
|
x,
|
|
1308
1515
|
y
|
|
1309
|
-
});
|
|
1310
|
-
this.inputDriver.moveMouse(Math.round(target.x), Math.round(target.y));
|
|
1516
|
+
}, 'Mouse did not reach the double-click target');
|
|
1311
1517
|
this.inputDriver.mouseClick('left', true);
|
|
1312
1518
|
},
|
|
1313
1519
|
rightClick: async ({ x, y })=>{
|
|
1314
|
-
|
|
1520
|
+
await this.moveDisplayPointer({
|
|
1315
1521
|
x,
|
|
1316
1522
|
y
|
|
1317
|
-
});
|
|
1318
|
-
this.inputDriver.moveMouse(Math.round(target.x), Math.round(target.y));
|
|
1523
|
+
}, 'Mouse did not reach the right-click target');
|
|
1319
1524
|
this.inputDriver.mouseClick('right');
|
|
1320
1525
|
},
|
|
1321
1526
|
hover: async ({ x, y })=>{
|
|
1322
|
-
|
|
1527
|
+
await this.moveDisplayPointer({
|
|
1323
1528
|
x,
|
|
1324
1529
|
y
|
|
1530
|
+
}, 'Mouse did not reach the hover target', {
|
|
1531
|
+
smoothSteps: SMOOTH_MOVE_STEPS_MOUSE_MOVE,
|
|
1532
|
+
smoothDelay: SMOOTH_MOVE_DELAY_MOUSE_MOVE
|
|
1325
1533
|
});
|
|
1326
|
-
await this.inputDriver.smoothMoveMouse(Math.round(target.x), Math.round(target.y), SMOOTH_MOVE_STEPS_MOUSE_MOVE, SMOOTH_MOVE_DELAY_MOUSE_MOVE);
|
|
1327
1534
|
await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
|
|
1328
1535
|
},
|
|
1329
1536
|
dragAndDrop: async (from, to)=>{
|
|
1330
|
-
|
|
1331
|
-
const globalTo = this.toGlobalPoint(to);
|
|
1332
|
-
this.inputDriver.moveMouse(Math.round(globalFrom.x), Math.round(globalFrom.y));
|
|
1537
|
+
await this.moveDisplayPointer(from, 'Mouse did not reach the drag start target');
|
|
1333
1538
|
await this.inputDriver.withMouseButton('left', async ()=>{
|
|
1334
1539
|
await this.inputDriver.delay(100);
|
|
1335
|
-
this.
|
|
1540
|
+
await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target');
|
|
1336
1541
|
await this.inputDriver.delay(100);
|
|
1337
1542
|
});
|
|
1338
1543
|
}
|
|
@@ -2335,7 +2540,7 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
|
|
|
2335
2540
|
}
|
|
2336
2541
|
const env_namespaceObject = require("@midscene/shared/env");
|
|
2337
2542
|
function version() {
|
|
2338
|
-
const currentVersion = "1.12.
|
|
2543
|
+
const currentVersion = "1.12.3-beta-20260828110230.0";
|
|
2339
2544
|
console.log(`@midscene/computer v${currentVersion}`);
|
|
2340
2545
|
return currentVersion;
|
|
2341
2546
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -75,6 +75,7 @@ export declare class ComputerDevice implements AbstractInterface {
|
|
|
75
75
|
private xvfbCleanup?;
|
|
76
76
|
private xvfbSignalCleanup?;
|
|
77
77
|
private readonly inputDriver;
|
|
78
|
+
private readonly windowsPointerDriver;
|
|
78
79
|
/**
|
|
79
80
|
* On macOS, use AppleScript for keyboard operations by default
|
|
80
81
|
* to avoid focus issues with system overlays (e.g. Spotlight).
|
|
@@ -85,6 +86,8 @@ export declare class ComputerDevice implements AbstractInterface {
|
|
|
85
86
|
uri?: string;
|
|
86
87
|
readonly inputPrimitives: ComputerInputPrimitives;
|
|
87
88
|
constructor(options?: ComputerDeviceOpt);
|
|
89
|
+
private moveGlobalPointer;
|
|
90
|
+
private moveDisplayPointer;
|
|
88
91
|
private focusKeyboardTarget;
|
|
89
92
|
describe(): string;
|
|
90
93
|
/**
|
|
@@ -110,15 +113,10 @@ export declare class ComputerDevice implements AbstractInterface {
|
|
|
110
113
|
* DeviceName and captures in virtual-desktop coordinates, so secondary
|
|
111
114
|
* displays — including those at negative offsets — are supported.
|
|
112
115
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* As a result, captures on a scaled display come back at logical (scaled)
|
|
118
|
-
* resolution. That is sufficient for the #2150 fix (the health check only
|
|
119
|
-
* needs a successful capture). Per-monitor DPI / coordinate accuracy is a
|
|
120
|
-
* separate Windows concern to be addressed in a follow-up with real-device
|
|
121
|
-
* verification.
|
|
116
|
+
* The PowerShell thread is switched to Per-Monitor V2 before WinForms is
|
|
117
|
+
* loaded. Screen.Bounds, CopyFromScreen, and pointer movement therefore use
|
|
118
|
+
* physical pixels even when Windows display scaling is enabled. The native
|
|
119
|
+
* declaration is emitted in memory, so this does not require csc.exe.
|
|
122
120
|
*/
|
|
123
121
|
private screenshotViaPowershell;
|
|
124
122
|
size(): Promise<Size>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@midscene/computer",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.3-beta-20260828110230.0",
|
|
4
4
|
"description": "Midscene.js Computer Desktop Automation",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"@computer-use/libnut": "^4.2.0",
|
|
34
34
|
"clipboardy": "^4.0.0",
|
|
35
35
|
"screenshot-desktop": "^1.15.3",
|
|
36
|
-
"@midscene/core": "1.12.
|
|
37
|
-
"@midscene/shared": "1.12.
|
|
36
|
+
"@midscene/core": "1.12.3-beta-20260828110230.0",
|
|
37
|
+
"@midscene/shared": "1.12.3-beta-20260828110230.0"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
40
|
"node-mac-permissions": "2.5.0"
|