@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/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;
|
|
@@ -209,6 +219,122 @@ class ComputerInputDriver {
|
|
|
209
219
|
this.pendingInputDelayWaits = new Set();
|
|
210
220
|
}
|
|
211
221
|
}
|
|
222
|
+
const WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE = `
|
|
223
|
+
$midsceneAssemblyName = New-Object System.Reflection.AssemblyName('MidsceneDpiNative')
|
|
224
|
+
$midsceneAssembly = [System.AppDomain]::CurrentDomain.DefineDynamicAssembly(
|
|
225
|
+
$midsceneAssemblyName,
|
|
226
|
+
[System.Reflection.Emit.AssemblyBuilderAccess]::Run
|
|
227
|
+
)
|
|
228
|
+
$midsceneModule = $midsceneAssembly.DefineDynamicModule('MidsceneDpiNativeModule')
|
|
229
|
+
$midsceneType = $midsceneModule.DefineType(
|
|
230
|
+
'MidsceneDpiNative.User32',
|
|
231
|
+
[System.Reflection.TypeAttributes]'Public, Class, Sealed, Abstract'
|
|
232
|
+
)
|
|
233
|
+
$midsceneMethodAttributes =
|
|
234
|
+
[System.Reflection.MethodAttributes]::Public -bor
|
|
235
|
+
[System.Reflection.MethodAttributes]::Static -bor
|
|
236
|
+
[System.Reflection.MethodAttributes]::PinvokeImpl
|
|
237
|
+
$midsceneSetDpiMethod = $midsceneType.DefinePInvokeMethod(
|
|
238
|
+
'SetThreadDpiAwarenessContext',
|
|
239
|
+
'user32.dll',
|
|
240
|
+
$midsceneMethodAttributes,
|
|
241
|
+
[System.Reflection.CallingConventions]::Standard,
|
|
242
|
+
[System.IntPtr],
|
|
243
|
+
[System.Type[]]@([System.IntPtr]),
|
|
244
|
+
[System.Runtime.InteropServices.CallingConvention]::Winapi,
|
|
245
|
+
[System.Runtime.InteropServices.CharSet]::None
|
|
246
|
+
)
|
|
247
|
+
$midsceneSetDpiMethod.SetImplementationFlags(
|
|
248
|
+
$midsceneSetDpiMethod.GetMethodImplementationFlags() -bor
|
|
249
|
+
[System.Reflection.MethodImplAttributes]::PreserveSig
|
|
250
|
+
)
|
|
251
|
+
$midsceneNativeMethods = $midsceneType.CreateType()
|
|
252
|
+
$midscenePerMonitorV2 = [System.IntPtr](-4)
|
|
253
|
+
$midscenePreviousDpiContext =
|
|
254
|
+
$midsceneNativeMethods::SetThreadDpiAwarenessContext($midscenePerMonitorV2)
|
|
255
|
+
if ($midscenePreviousDpiContext -eq [System.IntPtr]::Zero) {
|
|
256
|
+
throw 'Unable to enter the Per-Monitor V2 DPI awareness context.'
|
|
257
|
+
}
|
|
258
|
+
`.trim();
|
|
259
|
+
function windows_pointer_define_property(obj, key, value) {
|
|
260
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
261
|
+
value: value,
|
|
262
|
+
enumerable: true,
|
|
263
|
+
configurable: true,
|
|
264
|
+
writable: true
|
|
265
|
+
});
|
|
266
|
+
else obj[key] = value;
|
|
267
|
+
return obj;
|
|
268
|
+
}
|
|
269
|
+
const WINDOWS_POINTER_TOLERANCE_PX = 5;
|
|
270
|
+
function assertFinitePoint(point, context) {
|
|
271
|
+
if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) throw new Error(`${context} must contain finite coordinates, got (${point.x}, ${point.y})`);
|
|
272
|
+
}
|
|
273
|
+
function parseWindowsPointerPosition(output, context) {
|
|
274
|
+
const match = output.trim().match(/^(-?\d+),(-?\d+)$/);
|
|
275
|
+
if (!match) throw new Error(`${context} returned an invalid cursor position: ${JSON.stringify(output.trim())}`);
|
|
276
|
+
return {
|
|
277
|
+
x: Number(match[1]),
|
|
278
|
+
y: Number(match[2])
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function windowsPointerPositionScript() {
|
|
282
|
+
return `
|
|
283
|
+
${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
|
|
284
|
+
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
|
|
285
|
+
$position = [System.Windows.Forms.Cursor]::Position
|
|
286
|
+
[Console]::Out.Write(('{0},{1}' -f $position.X, $position.Y))
|
|
287
|
+
`.trim();
|
|
288
|
+
}
|
|
289
|
+
function windowsPointerMoveScript(point, options) {
|
|
290
|
+
assertFinitePoint(point, 'Windows pointer target');
|
|
291
|
+
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');
|
|
292
|
+
const targetX = Math.round(point.x);
|
|
293
|
+
const targetY = Math.round(point.y);
|
|
294
|
+
const smoothSteps = Math.max(1, Math.round(options?.smoothSteps ?? 1));
|
|
295
|
+
const smoothDelayMs = Math.max(0, Math.round(options?.smoothDelayMs ?? 0));
|
|
296
|
+
return `
|
|
297
|
+
${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
|
|
298
|
+
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
|
|
299
|
+
$targetX = ${targetX}
|
|
300
|
+
$targetY = ${targetY}
|
|
301
|
+
$smoothSteps = ${smoothSteps}
|
|
302
|
+
$smoothDelayMs = ${smoothDelayMs}
|
|
303
|
+
$start = [System.Windows.Forms.Cursor]::Position
|
|
304
|
+
for ($step = 1; $step -le $smoothSteps; $step += 1) {
|
|
305
|
+
$x = [int][Math]::Round($start.X + (($targetX - $start.X) * $step / [double]$smoothSteps))
|
|
306
|
+
$y = [int][Math]::Round($start.Y + (($targetY - $start.Y) * $step / [double]$smoothSteps))
|
|
307
|
+
[System.Windows.Forms.Cursor]::Position = [System.Drawing.Point]::new($x, $y)
|
|
308
|
+
if ($smoothDelayMs -gt 0 -and $step -lt $smoothSteps) {
|
|
309
|
+
Start-Sleep -Milliseconds $smoothDelayMs
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
[System.Windows.Forms.Cursor]::Position = [System.Drawing.Point]::new($targetX, $targetY)
|
|
313
|
+
$position = [System.Windows.Forms.Cursor]::Position
|
|
314
|
+
[Console]::Out.Write(('{0},{1}' -f $position.X, $position.Y))
|
|
315
|
+
`.trim();
|
|
316
|
+
}
|
|
317
|
+
class WindowsPointerDriver {
|
|
318
|
+
getPosition() {
|
|
319
|
+
return parseWindowsPointerPosition(this.options.runPowershell(windowsPointerPositionScript()), 'Windows pointer query');
|
|
320
|
+
}
|
|
321
|
+
moveTo(point, options) {
|
|
322
|
+
return parseWindowsPointerPosition(this.options.runPowershell(windowsPointerMoveScript(point, options)), 'Windows pointer move');
|
|
323
|
+
}
|
|
324
|
+
constructor(options){
|
|
325
|
+
windows_pointer_define_property(this, "options", void 0);
|
|
326
|
+
this.options = options;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function windowsPointerDrift(expected, actual) {
|
|
330
|
+
return {
|
|
331
|
+
x: actual.x - expected.x,
|
|
332
|
+
y: actual.y - expected.y
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function windowsPointerIsWithinTolerance(drift) {
|
|
336
|
+
return Math.abs(drift.x) <= WINDOWS_POINTER_TOLERANCE_PX && Math.abs(drift.y) <= WINDOWS_POINTER_TOLERANCE_PX;
|
|
337
|
+
}
|
|
212
338
|
const interrupt_namespaceObject = require("@midscene/shared/cli/interrupt");
|
|
213
339
|
const debugXvfb = (0, logger_namespaceObject.getDebug)('computer:xvfb');
|
|
214
340
|
const xvfbCleanupMonitorScript = String.raw`
|
|
@@ -487,19 +613,34 @@ function runPowershell(script) {
|
|
|
487
613
|
windowsHide: true
|
|
488
614
|
});
|
|
489
615
|
}
|
|
490
|
-
function
|
|
616
|
+
function readWindowsDisplayGeometries() {
|
|
491
617
|
const script = `
|
|
618
|
+
${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
|
|
492
619
|
Add-Type -AssemblyName System.Windows.Forms
|
|
493
620
|
$s = [System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
|
|
494
|
-
|
|
621
|
+
$b = $_.Bounds
|
|
622
|
+
[PSCustomObject]@{
|
|
623
|
+
id = $_.DeviceName
|
|
624
|
+
name = $_.DeviceName
|
|
625
|
+
primary = $_.Primary
|
|
626
|
+
bounds = [PSCustomObject]@{ x = $b.X; y = $b.Y; width = $b.Width; height = $b.Height }
|
|
627
|
+
}
|
|
495
628
|
}
|
|
496
629
|
ConvertTo-Json @($s) -Compress
|
|
497
630
|
`.trim();
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
631
|
+
const output = runPowershell(script).trim();
|
|
632
|
+
if (!output) throw new Error('Windows display enumeration returned no data');
|
|
633
|
+
const parsed = JSON.parse(output);
|
|
634
|
+
if (!Array.isArray(parsed)) throw new Error('Windows display enumeration returned invalid data');
|
|
635
|
+
const displays = parsed.filter(isWindowsDisplayGeometry);
|
|
636
|
+
if (displays.length !== parsed.length || 0 === displays.length) throw new Error('Windows display enumeration returned invalid geometry');
|
|
637
|
+
return displays;
|
|
638
|
+
}
|
|
639
|
+
function listWindowsDisplays(geometries = readWindowsDisplayGeometries()) {
|
|
640
|
+
return geometries.map((display)=>({
|
|
641
|
+
id: display.id,
|
|
642
|
+
name: display.name,
|
|
643
|
+
primary: display.primary
|
|
503
644
|
}));
|
|
504
645
|
}
|
|
505
646
|
let device_libnut = null;
|
|
@@ -601,6 +742,16 @@ function getDisplayInfoBinary() {
|
|
|
601
742
|
function isFiniteNumber(value) {
|
|
602
743
|
return 'number' == typeof value && Number.isFinite(value);
|
|
603
744
|
}
|
|
745
|
+
function isDisplayBounds(value) {
|
|
746
|
+
if (!value || 'object' != typeof value) return false;
|
|
747
|
+
const bounds = value;
|
|
748
|
+
return isFiniteNumber(bounds.x) && isFiniteNumber(bounds.y) && isFiniteNumber(bounds.width) && isFiniteNumber(bounds.height) && bounds.width > 0 && bounds.height > 0;
|
|
749
|
+
}
|
|
750
|
+
function isWindowsDisplayGeometry(value) {
|
|
751
|
+
if (!value || 'object' != typeof value) return false;
|
|
752
|
+
const candidate = value;
|
|
753
|
+
return 'string' == typeof candidate.id && candidate.id.length > 0 && 'string' == typeof candidate.name && 'boolean' == typeof candidate.primary && isDisplayBounds(candidate.bounds);
|
|
754
|
+
}
|
|
604
755
|
function isDarwinDisplayGeometry(value) {
|
|
605
756
|
if (!value || 'object' != typeof value) return false;
|
|
606
757
|
const candidate = value;
|
|
@@ -643,9 +794,11 @@ function readDarwinFrontmostApplication() {
|
|
|
643
794
|
return;
|
|
644
795
|
}
|
|
645
796
|
}
|
|
646
|
-
async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, holdDuration, reason) {
|
|
647
|
-
|
|
648
|
-
|
|
797
|
+
async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, current, holdDuration, reason) {
|
|
798
|
+
const drift = {
|
|
799
|
+
x: current.x - targetX,
|
|
800
|
+
y: current.y - targetY
|
|
801
|
+
};
|
|
649
802
|
debugComputerInput('tap mouse moved %o', {
|
|
650
803
|
reason,
|
|
651
804
|
target: {
|
|
@@ -653,10 +806,7 @@ async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, holdDurati
|
|
|
653
806
|
y: targetY
|
|
654
807
|
},
|
|
655
808
|
current,
|
|
656
|
-
drift
|
|
657
|
-
x: current.x - targetX,
|
|
658
|
-
y: current.y - targetY
|
|
659
|
-
}
|
|
809
|
+
drift
|
|
660
810
|
});
|
|
661
811
|
await inputDriver.withMouseButton('left', async ()=>{
|
|
662
812
|
debugComputerInput('tap mouse down %o', {
|
|
@@ -675,9 +825,18 @@ function resolveDarwinDisplayGeometryFromList(displayId, displays) {
|
|
|
675
825
|
if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays.find((display)=>0 === display.screenIndex) || displays[0];
|
|
676
826
|
return displays.find((display)=>display.screenIndex === screenIndex) || displays.find((display)=>display.cgDisplayId === screenIndex);
|
|
677
827
|
}
|
|
678
|
-
function
|
|
679
|
-
if (
|
|
680
|
-
|
|
828
|
+
function resolveWindowsDisplayGeometryFromList(displayId, displays) {
|
|
829
|
+
if (!displays.length) return;
|
|
830
|
+
if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
|
|
831
|
+
return displays.find((display)=>display.id === displayId);
|
|
832
|
+
}
|
|
833
|
+
function resolveDisplayGeometry(displayId, windowsDisplays) {
|
|
834
|
+
if ('darwin' === process.platform) return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
|
|
835
|
+
if ('win32' === process.platform) {
|
|
836
|
+
const geometry = resolveWindowsDisplayGeometryFromList(displayId, windowsDisplays ?? readWindowsDisplayGeometries());
|
|
837
|
+
if (!geometry) throw new Error(displayId ? `Requested Windows display not found: ${displayId}` : 'No Windows displays were detected');
|
|
838
|
+
return geometry;
|
|
839
|
+
}
|
|
681
840
|
}
|
|
682
841
|
function mapDisplayLocalPointToGlobal(point, geometry) {
|
|
683
842
|
if (!geometry) return point;
|
|
@@ -763,6 +922,30 @@ function normalizePrimaryKey(key) {
|
|
|
763
922
|
return KEY_NAME_MAP[lowerKey] || lowerKey;
|
|
764
923
|
}
|
|
765
924
|
class ComputerDevice {
|
|
925
|
+
async moveGlobalPointer(point, context, smooth) {
|
|
926
|
+
if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
|
|
927
|
+
const target = {
|
|
928
|
+
x: Math.round(point.x),
|
|
929
|
+
y: Math.round(point.y)
|
|
930
|
+
};
|
|
931
|
+
if ('win32' === process.platform) {
|
|
932
|
+
const actual = this.windowsPointerDriver.moveTo(target, {
|
|
933
|
+
smoothSteps: smooth?.smoothSteps,
|
|
934
|
+
smoothDelayMs: smooth?.smoothDelay
|
|
935
|
+
});
|
|
936
|
+
const drift = windowsPointerDrift(target, actual);
|
|
937
|
+
if (!windowsPointerIsWithinTolerance(drift)) throw new Error(`${context}: expected (${target.x}, ${target.y}), got (${actual.x}, ${actual.y}), drift=(${drift.x}, ${drift.y})`);
|
|
938
|
+
await this.inputDriver.delay(CLICK_SETTLE_DELAY);
|
|
939
|
+
return actual;
|
|
940
|
+
}
|
|
941
|
+
if (smooth) await this.inputDriver.smoothMoveMouse(target.x, target.y, smooth.smoothSteps, smooth.smoothDelay);
|
|
942
|
+
else this.inputDriver.moveMouse(target.x, target.y);
|
|
943
|
+
await this.inputDriver.delay(CLICK_SETTLE_DELAY);
|
|
944
|
+
return target;
|
|
945
|
+
}
|
|
946
|
+
moveDisplayPointer(point, context, smooth) {
|
|
947
|
+
return this.moveGlobalPointer(this.toGlobalPoint(point), context, smooth);
|
|
948
|
+
}
|
|
766
949
|
async focusKeyboardTarget(element, delayMs) {
|
|
767
950
|
const [x, y] = element.center;
|
|
768
951
|
if ('darwin' === process.platform) await this.inputPrimitives.pointer.tap({
|
|
@@ -770,11 +953,10 @@ class ComputerDevice {
|
|
|
770
953
|
y
|
|
771
954
|
});
|
|
772
955
|
else {
|
|
773
|
-
|
|
956
|
+
await this.moveDisplayPointer({
|
|
774
957
|
x,
|
|
775
958
|
y
|
|
776
|
-
});
|
|
777
|
-
this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
|
|
959
|
+
}, 'Mouse did not reach the keyboard focus target');
|
|
778
960
|
this.inputDriver.mouseClick('left');
|
|
779
961
|
}
|
|
780
962
|
await this.inputDriver.delay(delayMs);
|
|
@@ -793,7 +975,7 @@ class ComputerDevice {
|
|
|
793
975
|
}));
|
|
794
976
|
} catch (error) {
|
|
795
977
|
debugDevice(`Failed to list displays: ${error}`);
|
|
796
|
-
|
|
978
|
+
throw new Error(`Failed to list displays: ${error}`);
|
|
797
979
|
}
|
|
798
980
|
}
|
|
799
981
|
async connect() {
|
|
@@ -822,9 +1004,10 @@ class ComputerDevice {
|
|
|
822
1004
|
}
|
|
823
1005
|
}
|
|
824
1006
|
device_libnut = await getLibnut();
|
|
825
|
-
|
|
1007
|
+
const windowsDisplayGeometries = 'win32' === process.platform ? readWindowsDisplayGeometries() : void 0;
|
|
1008
|
+
this.displayGeometry = resolveDisplayGeometry(this.displayId, windowsDisplayGeometries);
|
|
826
1009
|
const size = await this.size();
|
|
827
|
-
const displays = await ComputerDevice.listDisplays();
|
|
1010
|
+
const displays = windowsDisplayGeometries ? listWindowsDisplays(windowsDisplayGeometries) : await ComputerDevice.listDisplays();
|
|
828
1011
|
const headlessInfo = this.xvfbInstance ? `\nHeadless: true (Xvfb on ${this.xvfbInstance.display})` : '';
|
|
829
1012
|
this.description = `
|
|
830
1013
|
Type: Computer
|
|
@@ -834,7 +1017,7 @@ Screen Size: ${size.width}x${size.height}
|
|
|
834
1017
|
Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ') : 'Unknown'}${headlessInfo}
|
|
835
1018
|
`;
|
|
836
1019
|
debugDevice('Computer device connected', this.description);
|
|
837
|
-
await this.healthCheck();
|
|
1020
|
+
await this.healthCheck(displays);
|
|
838
1021
|
} catch (error) {
|
|
839
1022
|
if (this.xvfbInstance) {
|
|
840
1023
|
if (!this.options?.keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
|
|
@@ -853,9 +1036,9 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
853
1036
|
throw new Error(`Unable to connect to computer device: ${error}`);
|
|
854
1037
|
}
|
|
855
1038
|
}
|
|
856
|
-
async healthCheck() {
|
|
1039
|
+
async healthCheck(displays) {
|
|
857
1040
|
console.log('[HealthCheck] Starting health check...');
|
|
858
|
-
console.log("[HealthCheck] @midscene/computer v1.12.
|
|
1041
|
+
console.log("[HealthCheck] @midscene/computer v1.12.3-beta-20260828110230.0");
|
|
859
1042
|
console.log('[HealthCheck] Taking screenshot...');
|
|
860
1043
|
const screenshotTimeout = 15000;
|
|
861
1044
|
let timeoutId;
|
|
@@ -867,23 +1050,38 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
867
1050
|
timeoutPromise
|
|
868
1051
|
]);
|
|
869
1052
|
console.log(`[HealthCheck] Screenshot succeeded (length=${base64.length})`);
|
|
870
|
-
console.log('[HealthCheck]
|
|
871
|
-
const startPos = this.inputDriver.getMousePos();
|
|
1053
|
+
console.log('[HealthCheck] Verifying mouse control...');
|
|
1054
|
+
const startPos = 'win32' === process.platform ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
|
|
872
1055
|
console.log(`[HealthCheck] Current mouse position: (${startPos.x}, ${startPos.y})`);
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1056
|
+
if ('win32' === process.platform) {
|
|
1057
|
+
if (!this.displayGeometry) throw new Error('Windows display geometry is unavailable');
|
|
1058
|
+
const bounds = this.displayGeometry.bounds;
|
|
1059
|
+
const target = {
|
|
1060
|
+
x: Math.round(bounds.x + bounds.width / 2),
|
|
1061
|
+
y: Math.round(bounds.y + bounds.height / 2)
|
|
1062
|
+
};
|
|
1063
|
+
try {
|
|
1064
|
+
const actual = this.windowsPointerDriver.moveTo(target);
|
|
1065
|
+
const drift = windowsPointerDrift(target, actual);
|
|
1066
|
+
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})`);
|
|
1067
|
+
} finally{
|
|
1068
|
+
this.windowsPointerDriver.moveTo(startPos);
|
|
1069
|
+
}
|
|
1070
|
+
console.log(`[HealthCheck] Mouse verified in screenshot-space display bounds (${bounds.x}, ${bounds.y}, ${bounds.width}, ${bounds.height})`);
|
|
1071
|
+
} else {
|
|
1072
|
+
const offsetX = Math.floor(40 * Math.random()) + 10;
|
|
1073
|
+
const offsetY = Math.floor(40 * Math.random()) + 10;
|
|
1074
|
+
const targetX = startPos.x + offsetX;
|
|
1075
|
+
const targetY = startPos.y + offsetY;
|
|
1076
|
+
console.log(`[HealthCheck] Moving mouse to (${targetX}, ${targetY})...`);
|
|
1077
|
+
try {
|
|
1078
|
+
this.inputDriver.moveMouse(targetX, targetY);
|
|
1079
|
+
await (0, utils_namespaceObject.sleep)(CLICK_SETTLE_DELAY);
|
|
1080
|
+
this.inputDriver.assertMousePosition(targetX, targetY, 'Mouse health check failed');
|
|
1081
|
+
} finally{
|
|
1082
|
+
this.inputDriver.moveMouse(startPos.x, startPos.y);
|
|
1083
|
+
console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
|
|
1084
|
+
}
|
|
887
1085
|
}
|
|
888
1086
|
if ('win32' === process.platform && !this.isRunningAsAdmin()) {
|
|
889
1087
|
const hint = [
|
|
@@ -893,10 +1091,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
893
1091
|
].join(' ');
|
|
894
1092
|
warnDevice(`[HealthCheck] ${hint}`);
|
|
895
1093
|
}
|
|
896
|
-
this.inputDriver.moveMouse(startPos.x, startPos.y);
|
|
897
|
-
console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
|
|
898
1094
|
console.log('[HealthCheck] Listing monitors...');
|
|
899
|
-
const displays = await ComputerDevice.listDisplays();
|
|
900
1095
|
if (displays.length > 0) {
|
|
901
1096
|
console.log(`[HealthCheck] Found ${displays.length} monitor(s):`);
|
|
902
1097
|
for (const display of displays){
|
|
@@ -965,6 +1160,7 @@ $screen = [System.Windows.Forms.Screen]::AllScreens | Where-Object { $_.DeviceNa
|
|
|
965
1160
|
if (-not $screen) { throw "Requested display not found: $dn" }` : '$screen = [System.Windows.Forms.Screen]::PrimaryScreen';
|
|
966
1161
|
const script = `
|
|
967
1162
|
$ErrorActionPreference = 'Stop'
|
|
1163
|
+
${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
|
|
968
1164
|
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
|
|
969
1165
|
${selectScreen}
|
|
970
1166
|
$b = $screen.Bounds
|
|
@@ -1079,10 +1275,14 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1079
1275
|
resolveUntargetedScrollPoint(screenSize) {
|
|
1080
1276
|
if ('win32' === process.platform) {
|
|
1081
1277
|
const activeWindowRect = this.inputDriver.getActiveWindowRect();
|
|
1082
|
-
if (activeWindowRect)
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1278
|
+
if (activeWindowRect) {
|
|
1279
|
+
const activeWindowCenter = {
|
|
1280
|
+
x: activeWindowRect.x + activeWindowRect.width / 2,
|
|
1281
|
+
y: activeWindowRect.y + activeWindowRect.height / 2
|
|
1282
|
+
};
|
|
1283
|
+
const bounds = this.displayGeometry?.bounds;
|
|
1284
|
+
if (!bounds || activeWindowCenter.x >= bounds.x && activeWindowCenter.x < bounds.x + bounds.width && activeWindowCenter.y >= bounds.y && activeWindowCenter.y < bounds.y + bounds.height) return activeWindowCenter;
|
|
1285
|
+
}
|
|
1086
1286
|
}
|
|
1087
1287
|
return this.toGlobalPoint({
|
|
1088
1288
|
x: screenSize.width / 2,
|
|
@@ -1093,17 +1293,16 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1093
1293
|
if (param.locate) {
|
|
1094
1294
|
const element = param.locate;
|
|
1095
1295
|
const [x, y] = element.center;
|
|
1096
|
-
|
|
1296
|
+
await this.moveDisplayPointer({
|
|
1097
1297
|
x,
|
|
1098
1298
|
y
|
|
1099
|
-
});
|
|
1100
|
-
this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
|
|
1299
|
+
}, 'Mouse did not reach the scroll target');
|
|
1101
1300
|
return;
|
|
1102
1301
|
}
|
|
1103
1302
|
const screenSize = await this.size();
|
|
1104
1303
|
if ('win32' === process.platform && this.inputDriver.focusActiveWindow()) await this.inputDriver.delay(CLICK_FOCUS_SETTLE_DELAY);
|
|
1105
1304
|
const point = this.resolveUntargetedScrollPoint(screenSize);
|
|
1106
|
-
this.
|
|
1305
|
+
await this.moveGlobalPointer(point, 'Mouse did not reach the scroll viewport');
|
|
1107
1306
|
await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
|
|
1108
1307
|
return screenSize;
|
|
1109
1308
|
}
|
|
@@ -1228,6 +1427,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1228
1427
|
runPhasedScroll,
|
|
1229
1428
|
debug: (message)=>debugDevice(message)
|
|
1230
1429
|
}));
|
|
1430
|
+
device_define_property(this, "windowsPointerDriver", new WindowsPointerDriver({
|
|
1431
|
+
runPowershell
|
|
1432
|
+
}));
|
|
1231
1433
|
device_define_property(this, "useAppleScript", void 0);
|
|
1232
1434
|
device_define_property(this, "adminCheckCache", void 0);
|
|
1233
1435
|
device_define_property(this, "uri", void 0);
|
|
@@ -1252,15 +1454,17 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1252
1454
|
},
|
|
1253
1455
|
holdDuration,
|
|
1254
1456
|
displayId: this.displayId,
|
|
1255
|
-
displayGeometry: this.displayGeometry
|
|
1256
|
-
screenIndex: this.displayGeometry.screenIndex,
|
|
1257
|
-
cgDisplayId: this.displayGeometry.cgDisplayId,
|
|
1258
|
-
bounds: this.displayGeometry.bounds
|
|
1259
|
-
} : void 0
|
|
1457
|
+
displayGeometry: this.displayGeometry
|
|
1260
1458
|
});
|
|
1261
1459
|
const frontmostBefore = 'darwin' === process.platform ? readDarwinFrontmostApplication() : void 0;
|
|
1262
|
-
await this.
|
|
1263
|
-
|
|
1460
|
+
const current = await this.moveGlobalPointer({
|
|
1461
|
+
x: targetX,
|
|
1462
|
+
y: targetY
|
|
1463
|
+
}, 'Mouse did not reach the tap target', {
|
|
1464
|
+
smoothSteps: SMOOTH_MOVE_STEPS_TAP,
|
|
1465
|
+
smoothDelay: SMOOTH_MOVE_DELAY_TAP
|
|
1466
|
+
});
|
|
1467
|
+
await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, current, holdDuration, 'primary');
|
|
1264
1468
|
if (frontmostBefore && 'darwin' === process.platform) {
|
|
1265
1469
|
await (0, utils_namespaceObject.sleep)(CLICK_FOCUS_SETTLE_DELAY);
|
|
1266
1470
|
const frontmostAfter = readDarwinFrontmostApplication();
|
|
@@ -1271,42 +1475,43 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1271
1475
|
focusChanged
|
|
1272
1476
|
});
|
|
1273
1477
|
if (focusChanged) {
|
|
1274
|
-
this.
|
|
1275
|
-
|
|
1478
|
+
const followUpCurrent = await this.moveGlobalPointer({
|
|
1479
|
+
x: targetX,
|
|
1480
|
+
y: targetY
|
|
1481
|
+
}, 'Mouse did not reach the focus follow-up target');
|
|
1482
|
+
await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, followUpCurrent, holdDuration, 'focus-follow-up');
|
|
1276
1483
|
}
|
|
1277
1484
|
}
|
|
1278
1485
|
},
|
|
1279
1486
|
doubleClick: async ({ x, y })=>{
|
|
1280
|
-
|
|
1487
|
+
await this.moveDisplayPointer({
|
|
1281
1488
|
x,
|
|
1282
1489
|
y
|
|
1283
|
-
});
|
|
1284
|
-
this.inputDriver.moveMouse(Math.round(target.x), Math.round(target.y));
|
|
1490
|
+
}, 'Mouse did not reach the double-click target');
|
|
1285
1491
|
this.inputDriver.mouseClick('left', true);
|
|
1286
1492
|
},
|
|
1287
1493
|
rightClick: async ({ x, y })=>{
|
|
1288
|
-
|
|
1494
|
+
await this.moveDisplayPointer({
|
|
1289
1495
|
x,
|
|
1290
1496
|
y
|
|
1291
|
-
});
|
|
1292
|
-
this.inputDriver.moveMouse(Math.round(target.x), Math.round(target.y));
|
|
1497
|
+
}, 'Mouse did not reach the right-click target');
|
|
1293
1498
|
this.inputDriver.mouseClick('right');
|
|
1294
1499
|
},
|
|
1295
1500
|
hover: async ({ x, y })=>{
|
|
1296
|
-
|
|
1501
|
+
await this.moveDisplayPointer({
|
|
1297
1502
|
x,
|
|
1298
1503
|
y
|
|
1504
|
+
}, 'Mouse did not reach the hover target', {
|
|
1505
|
+
smoothSteps: SMOOTH_MOVE_STEPS_MOUSE_MOVE,
|
|
1506
|
+
smoothDelay: SMOOTH_MOVE_DELAY_MOUSE_MOVE
|
|
1299
1507
|
});
|
|
1300
|
-
await this.inputDriver.smoothMoveMouse(Math.round(target.x), Math.round(target.y), SMOOTH_MOVE_STEPS_MOUSE_MOVE, SMOOTH_MOVE_DELAY_MOUSE_MOVE);
|
|
1301
1508
|
await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
|
|
1302
1509
|
},
|
|
1303
1510
|
dragAndDrop: async (from, to)=>{
|
|
1304
|
-
|
|
1305
|
-
const globalTo = this.toGlobalPoint(to);
|
|
1306
|
-
this.inputDriver.moveMouse(Math.round(globalFrom.x), Math.round(globalFrom.y));
|
|
1511
|
+
await this.moveDisplayPointer(from, 'Mouse did not reach the drag start target');
|
|
1307
1512
|
await this.inputDriver.withMouseButton('left', async ()=>{
|
|
1308
1513
|
await this.inputDriver.delay(100);
|
|
1309
|
-
this.
|
|
1514
|
+
await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target');
|
|
1310
1515
|
await this.inputDriver.delay(100);
|
|
1311
1516
|
});
|
|
1312
1517
|
}
|
|
@@ -2265,7 +2470,7 @@ const tools = new ComputerMidsceneTools({
|
|
|
2265
2470
|
});
|
|
2266
2471
|
(0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-computer', {
|
|
2267
2472
|
stripPrefix: 'computer_',
|
|
2268
|
-
version: "1.12.
|
|
2473
|
+
version: "1.12.3-beta-20260828110230.0",
|
|
2269
2474
|
extraCommands: (0, core_namespaceObject.createReportCliCommands)()
|
|
2270
2475
|
}).catch((e)=>{
|
|
2271
2476
|
process.exit((0, cli_namespaceObject.reportCLIError)(e));
|