@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/es/cli.mjs
CHANGED
|
@@ -29,6 +29,7 @@ function _define_property(obj, key, value) {
|
|
|
29
29
|
else obj[key] = value;
|
|
30
30
|
return obj;
|
|
31
31
|
}
|
|
32
|
+
const MOUSE_COORDINATE_TOLERANCE_PX = 5;
|
|
32
33
|
class ComputerInputDriver {
|
|
33
34
|
destroy() {
|
|
34
35
|
if (this.destroyed) return;
|
|
@@ -44,6 +45,15 @@ class ComputerInputDriver {
|
|
|
44
45
|
moveMouse(x, y) {
|
|
45
46
|
this.getLibnutOrThrow('moveMouse').moveMouse(x, y);
|
|
46
47
|
}
|
|
48
|
+
assertMousePosition(targetX, targetY, context) {
|
|
49
|
+
const current = this.getMousePos();
|
|
50
|
+
const drift = {
|
|
51
|
+
x: current.x - targetX,
|
|
52
|
+
y: current.y - targetY
|
|
53
|
+
};
|
|
54
|
+
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})`);
|
|
55
|
+
return drift;
|
|
56
|
+
}
|
|
47
57
|
focusActiveWindow() {
|
|
48
58
|
const lib = this.getLibnutOrThrow('focusActiveWindow');
|
|
49
59
|
if ('function' != typeof lib.getActiveWindow || 'function' != typeof lib.focusWindow) return false;
|
|
@@ -184,6 +194,122 @@ class ComputerInputDriver {
|
|
|
184
194
|
this.pendingInputDelayWaits = new Set();
|
|
185
195
|
}
|
|
186
196
|
}
|
|
197
|
+
const WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE = `
|
|
198
|
+
$midsceneAssemblyName = New-Object System.Reflection.AssemblyName('MidsceneDpiNative')
|
|
199
|
+
$midsceneAssembly = [System.AppDomain]::CurrentDomain.DefineDynamicAssembly(
|
|
200
|
+
$midsceneAssemblyName,
|
|
201
|
+
[System.Reflection.Emit.AssemblyBuilderAccess]::Run
|
|
202
|
+
)
|
|
203
|
+
$midsceneModule = $midsceneAssembly.DefineDynamicModule('MidsceneDpiNativeModule')
|
|
204
|
+
$midsceneType = $midsceneModule.DefineType(
|
|
205
|
+
'MidsceneDpiNative.User32',
|
|
206
|
+
[System.Reflection.TypeAttributes]'Public, Class, Sealed, Abstract'
|
|
207
|
+
)
|
|
208
|
+
$midsceneMethodAttributes =
|
|
209
|
+
[System.Reflection.MethodAttributes]::Public -bor
|
|
210
|
+
[System.Reflection.MethodAttributes]::Static -bor
|
|
211
|
+
[System.Reflection.MethodAttributes]::PinvokeImpl
|
|
212
|
+
$midsceneSetDpiMethod = $midsceneType.DefinePInvokeMethod(
|
|
213
|
+
'SetThreadDpiAwarenessContext',
|
|
214
|
+
'user32.dll',
|
|
215
|
+
$midsceneMethodAttributes,
|
|
216
|
+
[System.Reflection.CallingConventions]::Standard,
|
|
217
|
+
[System.IntPtr],
|
|
218
|
+
[System.Type[]]@([System.IntPtr]),
|
|
219
|
+
[System.Runtime.InteropServices.CallingConvention]::Winapi,
|
|
220
|
+
[System.Runtime.InteropServices.CharSet]::None
|
|
221
|
+
)
|
|
222
|
+
$midsceneSetDpiMethod.SetImplementationFlags(
|
|
223
|
+
$midsceneSetDpiMethod.GetMethodImplementationFlags() -bor
|
|
224
|
+
[System.Reflection.MethodImplAttributes]::PreserveSig
|
|
225
|
+
)
|
|
226
|
+
$midsceneNativeMethods = $midsceneType.CreateType()
|
|
227
|
+
$midscenePerMonitorV2 = [System.IntPtr](-4)
|
|
228
|
+
$midscenePreviousDpiContext =
|
|
229
|
+
$midsceneNativeMethods::SetThreadDpiAwarenessContext($midscenePerMonitorV2)
|
|
230
|
+
if ($midscenePreviousDpiContext -eq [System.IntPtr]::Zero) {
|
|
231
|
+
throw 'Unable to enter the Per-Monitor V2 DPI awareness context.'
|
|
232
|
+
}
|
|
233
|
+
`.trim();
|
|
234
|
+
function windows_pointer_define_property(obj, key, value) {
|
|
235
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
236
|
+
value: value,
|
|
237
|
+
enumerable: true,
|
|
238
|
+
configurable: true,
|
|
239
|
+
writable: true
|
|
240
|
+
});
|
|
241
|
+
else obj[key] = value;
|
|
242
|
+
return obj;
|
|
243
|
+
}
|
|
244
|
+
const WINDOWS_POINTER_TOLERANCE_PX = 5;
|
|
245
|
+
function assertFinitePoint(point, context) {
|
|
246
|
+
if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) throw new Error(`${context} must contain finite coordinates, got (${point.x}, ${point.y})`);
|
|
247
|
+
}
|
|
248
|
+
function parseWindowsPointerPosition(output, context) {
|
|
249
|
+
const match = output.trim().match(/^(-?\d+),(-?\d+)$/);
|
|
250
|
+
if (!match) throw new Error(`${context} returned an invalid cursor position: ${JSON.stringify(output.trim())}`);
|
|
251
|
+
return {
|
|
252
|
+
x: Number(match[1]),
|
|
253
|
+
y: Number(match[2])
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function windowsPointerPositionScript() {
|
|
257
|
+
return `
|
|
258
|
+
${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
|
|
259
|
+
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
|
|
260
|
+
$position = [System.Windows.Forms.Cursor]::Position
|
|
261
|
+
[Console]::Out.Write(('{0},{1}' -f $position.X, $position.Y))
|
|
262
|
+
`.trim();
|
|
263
|
+
}
|
|
264
|
+
function windowsPointerMoveScript(point, options) {
|
|
265
|
+
assertFinitePoint(point, 'Windows pointer target');
|
|
266
|
+
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');
|
|
267
|
+
const targetX = Math.round(point.x);
|
|
268
|
+
const targetY = Math.round(point.y);
|
|
269
|
+
const smoothSteps = Math.max(1, Math.round(options?.smoothSteps ?? 1));
|
|
270
|
+
const smoothDelayMs = Math.max(0, Math.round(options?.smoothDelayMs ?? 0));
|
|
271
|
+
return `
|
|
272
|
+
${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
|
|
273
|
+
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
|
|
274
|
+
$targetX = ${targetX}
|
|
275
|
+
$targetY = ${targetY}
|
|
276
|
+
$smoothSteps = ${smoothSteps}
|
|
277
|
+
$smoothDelayMs = ${smoothDelayMs}
|
|
278
|
+
$start = [System.Windows.Forms.Cursor]::Position
|
|
279
|
+
for ($step = 1; $step -le $smoothSteps; $step += 1) {
|
|
280
|
+
$x = [int][Math]::Round($start.X + (($targetX - $start.X) * $step / [double]$smoothSteps))
|
|
281
|
+
$y = [int][Math]::Round($start.Y + (($targetY - $start.Y) * $step / [double]$smoothSteps))
|
|
282
|
+
[System.Windows.Forms.Cursor]::Position = [System.Drawing.Point]::new($x, $y)
|
|
283
|
+
if ($smoothDelayMs -gt 0 -and $step -lt $smoothSteps) {
|
|
284
|
+
Start-Sleep -Milliseconds $smoothDelayMs
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
[System.Windows.Forms.Cursor]::Position = [System.Drawing.Point]::new($targetX, $targetY)
|
|
288
|
+
$position = [System.Windows.Forms.Cursor]::Position
|
|
289
|
+
[Console]::Out.Write(('{0},{1}' -f $position.X, $position.Y))
|
|
290
|
+
`.trim();
|
|
291
|
+
}
|
|
292
|
+
class WindowsPointerDriver {
|
|
293
|
+
getPosition() {
|
|
294
|
+
return parseWindowsPointerPosition(this.options.runPowershell(windowsPointerPositionScript()), 'Windows pointer query');
|
|
295
|
+
}
|
|
296
|
+
moveTo(point, options) {
|
|
297
|
+
return parseWindowsPointerPosition(this.options.runPowershell(windowsPointerMoveScript(point, options)), 'Windows pointer move');
|
|
298
|
+
}
|
|
299
|
+
constructor(options){
|
|
300
|
+
windows_pointer_define_property(this, "options", void 0);
|
|
301
|
+
this.options = options;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function windowsPointerDrift(expected, actual) {
|
|
305
|
+
return {
|
|
306
|
+
x: actual.x - expected.x,
|
|
307
|
+
y: actual.y - expected.y
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function windowsPointerIsWithinTolerance(drift) {
|
|
311
|
+
return Math.abs(drift.x) <= WINDOWS_POINTER_TOLERANCE_PX && Math.abs(drift.y) <= WINDOWS_POINTER_TOLERANCE_PX;
|
|
312
|
+
}
|
|
187
313
|
const debugXvfb = getDebug('computer:xvfb');
|
|
188
314
|
const xvfbCleanupMonitorScript = String.raw`
|
|
189
315
|
const parentPid = Number(process.argv[1]);
|
|
@@ -461,19 +587,34 @@ function runPowershell(script) {
|
|
|
461
587
|
windowsHide: true
|
|
462
588
|
});
|
|
463
589
|
}
|
|
464
|
-
function
|
|
590
|
+
function readWindowsDisplayGeometries() {
|
|
465
591
|
const script = `
|
|
592
|
+
${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
|
|
466
593
|
Add-Type -AssemblyName System.Windows.Forms
|
|
467
594
|
$s = [System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
|
|
468
|
-
|
|
595
|
+
$b = $_.Bounds
|
|
596
|
+
[PSCustomObject]@{
|
|
597
|
+
id = $_.DeviceName
|
|
598
|
+
name = $_.DeviceName
|
|
599
|
+
primary = $_.Primary
|
|
600
|
+
bounds = [PSCustomObject]@{ x = $b.X; y = $b.Y; width = $b.Width; height = $b.Height }
|
|
601
|
+
}
|
|
469
602
|
}
|
|
470
603
|
ConvertTo-Json @($s) -Compress
|
|
471
604
|
`.trim();
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
605
|
+
const output = runPowershell(script).trim();
|
|
606
|
+
if (!output) throw new Error('Windows display enumeration returned no data');
|
|
607
|
+
const parsed = JSON.parse(output);
|
|
608
|
+
if (!Array.isArray(parsed)) throw new Error('Windows display enumeration returned invalid data');
|
|
609
|
+
const displays = parsed.filter(isWindowsDisplayGeometry);
|
|
610
|
+
if (displays.length !== parsed.length || 0 === displays.length) throw new Error('Windows display enumeration returned invalid geometry');
|
|
611
|
+
return displays;
|
|
612
|
+
}
|
|
613
|
+
function listWindowsDisplays(geometries = readWindowsDisplayGeometries()) {
|
|
614
|
+
return geometries.map((display)=>({
|
|
615
|
+
id: display.id,
|
|
616
|
+
name: display.name,
|
|
617
|
+
primary: display.primary
|
|
477
618
|
}));
|
|
478
619
|
}
|
|
479
620
|
let device_libnut = null;
|
|
@@ -575,6 +716,16 @@ function getDisplayInfoBinary() {
|
|
|
575
716
|
function isFiniteNumber(value) {
|
|
576
717
|
return 'number' == typeof value && Number.isFinite(value);
|
|
577
718
|
}
|
|
719
|
+
function isDisplayBounds(value) {
|
|
720
|
+
if (!value || 'object' != typeof value) return false;
|
|
721
|
+
const bounds = value;
|
|
722
|
+
return isFiniteNumber(bounds.x) && isFiniteNumber(bounds.y) && isFiniteNumber(bounds.width) && isFiniteNumber(bounds.height) && bounds.width > 0 && bounds.height > 0;
|
|
723
|
+
}
|
|
724
|
+
function isWindowsDisplayGeometry(value) {
|
|
725
|
+
if (!value || 'object' != typeof value) return false;
|
|
726
|
+
const candidate = value;
|
|
727
|
+
return 'string' == typeof candidate.id && candidate.id.length > 0 && 'string' == typeof candidate.name && 'boolean' == typeof candidate.primary && isDisplayBounds(candidate.bounds);
|
|
728
|
+
}
|
|
578
729
|
function isDarwinDisplayGeometry(value) {
|
|
579
730
|
if (!value || 'object' != typeof value) return false;
|
|
580
731
|
const candidate = value;
|
|
@@ -617,9 +768,11 @@ function readDarwinFrontmostApplication() {
|
|
|
617
768
|
return;
|
|
618
769
|
}
|
|
619
770
|
}
|
|
620
|
-
async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, holdDuration, reason) {
|
|
621
|
-
|
|
622
|
-
|
|
771
|
+
async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, current, holdDuration, reason) {
|
|
772
|
+
const drift = {
|
|
773
|
+
x: current.x - targetX,
|
|
774
|
+
y: current.y - targetY
|
|
775
|
+
};
|
|
623
776
|
debugComputerInput('tap mouse moved %o', {
|
|
624
777
|
reason,
|
|
625
778
|
target: {
|
|
@@ -627,10 +780,7 @@ async function pressMouseAtGlobalPoint(inputDriver, targetX, targetY, holdDurati
|
|
|
627
780
|
y: targetY
|
|
628
781
|
},
|
|
629
782
|
current,
|
|
630
|
-
drift
|
|
631
|
-
x: current.x - targetX,
|
|
632
|
-
y: current.y - targetY
|
|
633
|
-
}
|
|
783
|
+
drift
|
|
634
784
|
});
|
|
635
785
|
await inputDriver.withMouseButton('left', async ()=>{
|
|
636
786
|
debugComputerInput('tap mouse down %o', {
|
|
@@ -649,9 +799,18 @@ function resolveDarwinDisplayGeometryFromList(displayId, displays) {
|
|
|
649
799
|
if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays.find((display)=>0 === display.screenIndex) || displays[0];
|
|
650
800
|
return displays.find((display)=>display.screenIndex === screenIndex) || displays.find((display)=>display.cgDisplayId === screenIndex);
|
|
651
801
|
}
|
|
652
|
-
function
|
|
653
|
-
if (
|
|
654
|
-
|
|
802
|
+
function resolveWindowsDisplayGeometryFromList(displayId, displays) {
|
|
803
|
+
if (!displays.length) return;
|
|
804
|
+
if (void 0 === displayId || '' === displayId) return displays.find((display)=>display.primary) || displays[0];
|
|
805
|
+
return displays.find((display)=>display.id === displayId);
|
|
806
|
+
}
|
|
807
|
+
function resolveDisplayGeometry(displayId, windowsDisplays) {
|
|
808
|
+
if ('darwin' === process.platform) return resolveDarwinDisplayGeometryFromList(displayId, readDarwinDisplayGeometries());
|
|
809
|
+
if ('win32' === process.platform) {
|
|
810
|
+
const geometry = resolveWindowsDisplayGeometryFromList(displayId, windowsDisplays ?? readWindowsDisplayGeometries());
|
|
811
|
+
if (!geometry) throw new Error(displayId ? `Requested Windows display not found: ${displayId}` : 'No Windows displays were detected');
|
|
812
|
+
return geometry;
|
|
813
|
+
}
|
|
655
814
|
}
|
|
656
815
|
function mapDisplayLocalPointToGlobal(point, geometry) {
|
|
657
816
|
if (!geometry) return point;
|
|
@@ -737,6 +896,30 @@ function normalizePrimaryKey(key) {
|
|
|
737
896
|
return KEY_NAME_MAP[lowerKey] || lowerKey;
|
|
738
897
|
}
|
|
739
898
|
class ComputerDevice {
|
|
899
|
+
async moveGlobalPointer(point, context, smooth) {
|
|
900
|
+
if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
|
|
901
|
+
const target = {
|
|
902
|
+
x: Math.round(point.x),
|
|
903
|
+
y: Math.round(point.y)
|
|
904
|
+
};
|
|
905
|
+
if ('win32' === process.platform) {
|
|
906
|
+
const actual = this.windowsPointerDriver.moveTo(target, {
|
|
907
|
+
smoothSteps: smooth?.smoothSteps,
|
|
908
|
+
smoothDelayMs: smooth?.smoothDelay
|
|
909
|
+
});
|
|
910
|
+
const drift = windowsPointerDrift(target, actual);
|
|
911
|
+
if (!windowsPointerIsWithinTolerance(drift)) throw new Error(`${context}: expected (${target.x}, ${target.y}), got (${actual.x}, ${actual.y}), drift=(${drift.x}, ${drift.y})`);
|
|
912
|
+
await this.inputDriver.delay(CLICK_SETTLE_DELAY);
|
|
913
|
+
return actual;
|
|
914
|
+
}
|
|
915
|
+
if (smooth) await this.inputDriver.smoothMoveMouse(target.x, target.y, smooth.smoothSteps, smooth.smoothDelay);
|
|
916
|
+
else this.inputDriver.moveMouse(target.x, target.y);
|
|
917
|
+
await this.inputDriver.delay(CLICK_SETTLE_DELAY);
|
|
918
|
+
return target;
|
|
919
|
+
}
|
|
920
|
+
moveDisplayPointer(point, context, smooth) {
|
|
921
|
+
return this.moveGlobalPointer(this.toGlobalPoint(point), context, smooth);
|
|
922
|
+
}
|
|
740
923
|
async focusKeyboardTarget(element, delayMs) {
|
|
741
924
|
const [x, y] = element.center;
|
|
742
925
|
if ('darwin' === process.platform) await this.inputPrimitives.pointer.tap({
|
|
@@ -744,11 +927,10 @@ class ComputerDevice {
|
|
|
744
927
|
y
|
|
745
928
|
});
|
|
746
929
|
else {
|
|
747
|
-
|
|
930
|
+
await this.moveDisplayPointer({
|
|
748
931
|
x,
|
|
749
932
|
y
|
|
750
|
-
});
|
|
751
|
-
this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
|
|
933
|
+
}, 'Mouse did not reach the keyboard focus target');
|
|
752
934
|
this.inputDriver.mouseClick('left');
|
|
753
935
|
}
|
|
754
936
|
await this.inputDriver.delay(delayMs);
|
|
@@ -767,7 +949,7 @@ class ComputerDevice {
|
|
|
767
949
|
}));
|
|
768
950
|
} catch (error) {
|
|
769
951
|
debugDevice(`Failed to list displays: ${error}`);
|
|
770
|
-
|
|
952
|
+
throw new Error(`Failed to list displays: ${error}`);
|
|
771
953
|
}
|
|
772
954
|
}
|
|
773
955
|
async connect() {
|
|
@@ -796,9 +978,10 @@ class ComputerDevice {
|
|
|
796
978
|
}
|
|
797
979
|
}
|
|
798
980
|
device_libnut = await getLibnut();
|
|
799
|
-
|
|
981
|
+
const windowsDisplayGeometries = 'win32' === process.platform ? readWindowsDisplayGeometries() : void 0;
|
|
982
|
+
this.displayGeometry = resolveDisplayGeometry(this.displayId, windowsDisplayGeometries);
|
|
800
983
|
const size = await this.size();
|
|
801
|
-
const displays = await ComputerDevice.listDisplays();
|
|
984
|
+
const displays = windowsDisplayGeometries ? listWindowsDisplays(windowsDisplayGeometries) : await ComputerDevice.listDisplays();
|
|
802
985
|
const headlessInfo = this.xvfbInstance ? `\nHeadless: true (Xvfb on ${this.xvfbInstance.display})` : '';
|
|
803
986
|
this.description = `
|
|
804
987
|
Type: Computer
|
|
@@ -808,7 +991,7 @@ Screen Size: ${size.width}x${size.height}
|
|
|
808
991
|
Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ') : 'Unknown'}${headlessInfo}
|
|
809
992
|
`;
|
|
810
993
|
debugDevice('Computer device connected', this.description);
|
|
811
|
-
await this.healthCheck();
|
|
994
|
+
await this.healthCheck(displays);
|
|
812
995
|
} catch (error) {
|
|
813
996
|
if (this.xvfbInstance) {
|
|
814
997
|
if (!this.options?.keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
|
|
@@ -827,9 +1010,9 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
827
1010
|
throw new Error(`Unable to connect to computer device: ${error}`);
|
|
828
1011
|
}
|
|
829
1012
|
}
|
|
830
|
-
async healthCheck() {
|
|
1013
|
+
async healthCheck(displays) {
|
|
831
1014
|
console.log('[HealthCheck] Starting health check...');
|
|
832
|
-
console.log("[HealthCheck] @midscene/computer v1.12.
|
|
1015
|
+
console.log("[HealthCheck] @midscene/computer v1.12.3-beta-20260828110230.0");
|
|
833
1016
|
console.log('[HealthCheck] Taking screenshot...');
|
|
834
1017
|
const screenshotTimeout = 15000;
|
|
835
1018
|
let timeoutId;
|
|
@@ -841,23 +1024,38 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
841
1024
|
timeoutPromise
|
|
842
1025
|
]);
|
|
843
1026
|
console.log(`[HealthCheck] Screenshot succeeded (length=${base64.length})`);
|
|
844
|
-
console.log('[HealthCheck]
|
|
845
|
-
const startPos = this.inputDriver.getMousePos();
|
|
1027
|
+
console.log('[HealthCheck] Verifying mouse control...');
|
|
1028
|
+
const startPos = 'win32' === process.platform ? this.windowsPointerDriver.getPosition() : this.inputDriver.getMousePos();
|
|
846
1029
|
console.log(`[HealthCheck] Current mouse position: (${startPos.x}, ${startPos.y})`);
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1030
|
+
if ('win32' === process.platform) {
|
|
1031
|
+
if (!this.displayGeometry) throw new Error('Windows display geometry is unavailable');
|
|
1032
|
+
const bounds = this.displayGeometry.bounds;
|
|
1033
|
+
const target = {
|
|
1034
|
+
x: Math.round(bounds.x + bounds.width / 2),
|
|
1035
|
+
y: Math.round(bounds.y + bounds.height / 2)
|
|
1036
|
+
};
|
|
1037
|
+
try {
|
|
1038
|
+
const actual = this.windowsPointerDriver.moveTo(target);
|
|
1039
|
+
const drift = windowsPointerDrift(target, actual);
|
|
1040
|
+
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})`);
|
|
1041
|
+
} finally{
|
|
1042
|
+
this.windowsPointerDriver.moveTo(startPos);
|
|
1043
|
+
}
|
|
1044
|
+
console.log(`[HealthCheck] Mouse verified in screenshot-space display bounds (${bounds.x}, ${bounds.y}, ${bounds.width}, ${bounds.height})`);
|
|
1045
|
+
} else {
|
|
1046
|
+
const offsetX = Math.floor(40 * Math.random()) + 10;
|
|
1047
|
+
const offsetY = Math.floor(40 * Math.random()) + 10;
|
|
1048
|
+
const targetX = startPos.x + offsetX;
|
|
1049
|
+
const targetY = startPos.y + offsetY;
|
|
1050
|
+
console.log(`[HealthCheck] Moving mouse to (${targetX}, ${targetY})...`);
|
|
1051
|
+
try {
|
|
1052
|
+
this.inputDriver.moveMouse(targetX, targetY);
|
|
1053
|
+
await sleep(CLICK_SETTLE_DELAY);
|
|
1054
|
+
this.inputDriver.assertMousePosition(targetX, targetY, 'Mouse health check failed');
|
|
1055
|
+
} finally{
|
|
1056
|
+
this.inputDriver.moveMouse(startPos.x, startPos.y);
|
|
1057
|
+
console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
|
|
1058
|
+
}
|
|
861
1059
|
}
|
|
862
1060
|
if ('win32' === process.platform && !this.isRunningAsAdmin()) {
|
|
863
1061
|
const hint = [
|
|
@@ -867,10 +1065,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
867
1065
|
].join(' ');
|
|
868
1066
|
warnDevice(`[HealthCheck] ${hint}`);
|
|
869
1067
|
}
|
|
870
|
-
this.inputDriver.moveMouse(startPos.x, startPos.y);
|
|
871
|
-
console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
|
|
872
1068
|
console.log('[HealthCheck] Listing monitors...');
|
|
873
|
-
const displays = await ComputerDevice.listDisplays();
|
|
874
1069
|
if (displays.length > 0) {
|
|
875
1070
|
console.log(`[HealthCheck] Found ${displays.length} monitor(s):`);
|
|
876
1071
|
for (const display of displays){
|
|
@@ -939,6 +1134,7 @@ $screen = [System.Windows.Forms.Screen]::AllScreens | Where-Object { $_.DeviceNa
|
|
|
939
1134
|
if (-not $screen) { throw "Requested display not found: $dn" }` : '$screen = [System.Windows.Forms.Screen]::PrimaryScreen';
|
|
940
1135
|
const script = `
|
|
941
1136
|
$ErrorActionPreference = 'Stop'
|
|
1137
|
+
${WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE}
|
|
942
1138
|
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
|
|
943
1139
|
${selectScreen}
|
|
944
1140
|
$b = $screen.Bounds
|
|
@@ -1053,10 +1249,14 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1053
1249
|
resolveUntargetedScrollPoint(screenSize) {
|
|
1054
1250
|
if ('win32' === process.platform) {
|
|
1055
1251
|
const activeWindowRect = this.inputDriver.getActiveWindowRect();
|
|
1056
|
-
if (activeWindowRect)
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1252
|
+
if (activeWindowRect) {
|
|
1253
|
+
const activeWindowCenter = {
|
|
1254
|
+
x: activeWindowRect.x + activeWindowRect.width / 2,
|
|
1255
|
+
y: activeWindowRect.y + activeWindowRect.height / 2
|
|
1256
|
+
};
|
|
1257
|
+
const bounds = this.displayGeometry?.bounds;
|
|
1258
|
+
if (!bounds || activeWindowCenter.x >= bounds.x && activeWindowCenter.x < bounds.x + bounds.width && activeWindowCenter.y >= bounds.y && activeWindowCenter.y < bounds.y + bounds.height) return activeWindowCenter;
|
|
1259
|
+
}
|
|
1060
1260
|
}
|
|
1061
1261
|
return this.toGlobalPoint({
|
|
1062
1262
|
x: screenSize.width / 2,
|
|
@@ -1067,17 +1267,16 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1067
1267
|
if (param.locate) {
|
|
1068
1268
|
const element = param.locate;
|
|
1069
1269
|
const [x, y] = element.center;
|
|
1070
|
-
|
|
1270
|
+
await this.moveDisplayPointer({
|
|
1071
1271
|
x,
|
|
1072
1272
|
y
|
|
1073
|
-
});
|
|
1074
|
-
this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
|
|
1273
|
+
}, 'Mouse did not reach the scroll target');
|
|
1075
1274
|
return;
|
|
1076
1275
|
}
|
|
1077
1276
|
const screenSize = await this.size();
|
|
1078
1277
|
if ('win32' === process.platform && this.inputDriver.focusActiveWindow()) await this.inputDriver.delay(CLICK_FOCUS_SETTLE_DELAY);
|
|
1079
1278
|
const point = this.resolveUntargetedScrollPoint(screenSize);
|
|
1080
|
-
this.
|
|
1279
|
+
await this.moveGlobalPointer(point, 'Mouse did not reach the scroll viewport');
|
|
1081
1280
|
await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
|
|
1082
1281
|
return screenSize;
|
|
1083
1282
|
}
|
|
@@ -1202,6 +1401,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1202
1401
|
runPhasedScroll,
|
|
1203
1402
|
debug: (message)=>debugDevice(message)
|
|
1204
1403
|
}));
|
|
1404
|
+
device_define_property(this, "windowsPointerDriver", new WindowsPointerDriver({
|
|
1405
|
+
runPowershell
|
|
1406
|
+
}));
|
|
1205
1407
|
device_define_property(this, "useAppleScript", void 0);
|
|
1206
1408
|
device_define_property(this, "adminCheckCache", void 0);
|
|
1207
1409
|
device_define_property(this, "uri", void 0);
|
|
@@ -1226,15 +1428,17 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1226
1428
|
},
|
|
1227
1429
|
holdDuration,
|
|
1228
1430
|
displayId: this.displayId,
|
|
1229
|
-
displayGeometry: this.displayGeometry
|
|
1230
|
-
screenIndex: this.displayGeometry.screenIndex,
|
|
1231
|
-
cgDisplayId: this.displayGeometry.cgDisplayId,
|
|
1232
|
-
bounds: this.displayGeometry.bounds
|
|
1233
|
-
} : void 0
|
|
1431
|
+
displayGeometry: this.displayGeometry
|
|
1234
1432
|
});
|
|
1235
1433
|
const frontmostBefore = 'darwin' === process.platform ? readDarwinFrontmostApplication() : void 0;
|
|
1236
|
-
await this.
|
|
1237
|
-
|
|
1434
|
+
const current = await this.moveGlobalPointer({
|
|
1435
|
+
x: targetX,
|
|
1436
|
+
y: targetY
|
|
1437
|
+
}, 'Mouse did not reach the tap target', {
|
|
1438
|
+
smoothSteps: SMOOTH_MOVE_STEPS_TAP,
|
|
1439
|
+
smoothDelay: SMOOTH_MOVE_DELAY_TAP
|
|
1440
|
+
});
|
|
1441
|
+
await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, current, holdDuration, 'primary');
|
|
1238
1442
|
if (frontmostBefore && 'darwin' === process.platform) {
|
|
1239
1443
|
await sleep(CLICK_FOCUS_SETTLE_DELAY);
|
|
1240
1444
|
const frontmostAfter = readDarwinFrontmostApplication();
|
|
@@ -1245,42 +1449,43 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1245
1449
|
focusChanged
|
|
1246
1450
|
});
|
|
1247
1451
|
if (focusChanged) {
|
|
1248
|
-
this.
|
|
1249
|
-
|
|
1452
|
+
const followUpCurrent = await this.moveGlobalPointer({
|
|
1453
|
+
x: targetX,
|
|
1454
|
+
y: targetY
|
|
1455
|
+
}, 'Mouse did not reach the focus follow-up target');
|
|
1456
|
+
await pressMouseAtGlobalPoint(this.inputDriver, targetX, targetY, followUpCurrent, holdDuration, 'focus-follow-up');
|
|
1250
1457
|
}
|
|
1251
1458
|
}
|
|
1252
1459
|
},
|
|
1253
1460
|
doubleClick: async ({ x, y })=>{
|
|
1254
|
-
|
|
1461
|
+
await this.moveDisplayPointer({
|
|
1255
1462
|
x,
|
|
1256
1463
|
y
|
|
1257
|
-
});
|
|
1258
|
-
this.inputDriver.moveMouse(Math.round(target.x), Math.round(target.y));
|
|
1464
|
+
}, 'Mouse did not reach the double-click target');
|
|
1259
1465
|
this.inputDriver.mouseClick('left', true);
|
|
1260
1466
|
},
|
|
1261
1467
|
rightClick: async ({ x, y })=>{
|
|
1262
|
-
|
|
1468
|
+
await this.moveDisplayPointer({
|
|
1263
1469
|
x,
|
|
1264
1470
|
y
|
|
1265
|
-
});
|
|
1266
|
-
this.inputDriver.moveMouse(Math.round(target.x), Math.round(target.y));
|
|
1471
|
+
}, 'Mouse did not reach the right-click target');
|
|
1267
1472
|
this.inputDriver.mouseClick('right');
|
|
1268
1473
|
},
|
|
1269
1474
|
hover: async ({ x, y })=>{
|
|
1270
|
-
|
|
1475
|
+
await this.moveDisplayPointer({
|
|
1271
1476
|
x,
|
|
1272
1477
|
y
|
|
1478
|
+
}, 'Mouse did not reach the hover target', {
|
|
1479
|
+
smoothSteps: SMOOTH_MOVE_STEPS_MOUSE_MOVE,
|
|
1480
|
+
smoothDelay: SMOOTH_MOVE_DELAY_MOUSE_MOVE
|
|
1273
1481
|
});
|
|
1274
|
-
await this.inputDriver.smoothMoveMouse(Math.round(target.x), Math.round(target.y), SMOOTH_MOVE_STEPS_MOUSE_MOVE, SMOOTH_MOVE_DELAY_MOUSE_MOVE);
|
|
1275
1482
|
await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
|
|
1276
1483
|
},
|
|
1277
1484
|
dragAndDrop: async (from, to)=>{
|
|
1278
|
-
|
|
1279
|
-
const globalTo = this.toGlobalPoint(to);
|
|
1280
|
-
this.inputDriver.moveMouse(Math.round(globalFrom.x), Math.round(globalFrom.y));
|
|
1485
|
+
await this.moveDisplayPointer(from, 'Mouse did not reach the drag start target');
|
|
1281
1486
|
await this.inputDriver.withMouseButton('left', async ()=>{
|
|
1282
1487
|
await this.inputDriver.delay(100);
|
|
1283
|
-
this.
|
|
1488
|
+
await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target');
|
|
1284
1489
|
await this.inputDriver.delay(100);
|
|
1285
1490
|
});
|
|
1286
1491
|
}
|
|
@@ -2238,7 +2443,7 @@ const tools = new ComputerMidsceneTools({
|
|
|
2238
2443
|
});
|
|
2239
2444
|
runToolsCLI(tools, 'midscene-computer', {
|
|
2240
2445
|
stripPrefix: 'computer_',
|
|
2241
|
-
version: "1.12.
|
|
2446
|
+
version: "1.12.3-beta-20260828110230.0",
|
|
2242
2447
|
extraCommands: createReportCliCommands()
|
|
2243
2448
|
}).catch((e)=>{
|
|
2244
2449
|
process.exit(reportCLIError(e));
|