forge-jsxy 1.0.74 → 1.0.76
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/fsProtocol.js
CHANGED
|
@@ -2084,6 +2084,46 @@ function screenshotHardCapBytes(overrideMaxBytes) {
|
|
|
2084
2084
|
}
|
|
2085
2085
|
return effectiveScreenshotMaxBytes();
|
|
2086
2086
|
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Lower bound for remote-stream JPEG target bytes as a ratio of the negotiated
|
|
2089
|
+
* hard cap. Higher ratio means less over-compression (clearer text), while
|
|
2090
|
+
* still respecting the absolute cap. Range: 0.55 .. 0.98.
|
|
2091
|
+
*/
|
|
2092
|
+
function remoteStreamJpegFloorRatio() {
|
|
2093
|
+
const raw = (process.env.FORGE_JS_REMOTE_STREAM_JPEG_FLOOR_RATIO || "").trim();
|
|
2094
|
+
if (!raw)
|
|
2095
|
+
return 0.82;
|
|
2096
|
+
const n = Number(raw);
|
|
2097
|
+
if (!Number.isFinite(n))
|
|
2098
|
+
return 0.82;
|
|
2099
|
+
return Math.min(0.98, Math.max(0.55, n));
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Whether fast Windows gdigrab path should enforce cached virtual offsets.
|
|
2103
|
+
* Default false to avoid partial/split captures on hosts where cached bounds
|
|
2104
|
+
* can drift after monitor topology changes.
|
|
2105
|
+
*/
|
|
2106
|
+
function remoteStreamUseVirtualOffsets() {
|
|
2107
|
+
const raw = String(process.env.FORGE_JS_REMOTE_STREAM_USE_VIRTUAL_OFFSETS || "")
|
|
2108
|
+
.trim()
|
|
2109
|
+
.toLowerCase();
|
|
2110
|
+
if (!raw)
|
|
2111
|
+
return false;
|
|
2112
|
+
return ["1", "true", "yes", "on"].includes(raw);
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Fast gdigrab path for Windows remote_stream. Disabled by default because
|
|
2116
|
+
* reliability/full-desktop coverage is preferred over throughput for readable
|
|
2117
|
+
* 1 FPS remote control sessions.
|
|
2118
|
+
*/
|
|
2119
|
+
function remoteStreamUseFastCapture() {
|
|
2120
|
+
const raw = String(process.env.FORGE_JS_REMOTE_STREAM_FAST_CAPTURE || "")
|
|
2121
|
+
.trim()
|
|
2122
|
+
.toLowerCase();
|
|
2123
|
+
if (!raw)
|
|
2124
|
+
return false;
|
|
2125
|
+
return ["1", "true", "yes", "on"].includes(raw);
|
|
2126
|
+
}
|
|
2087
2127
|
function cameraOverlayEnabledByDefault() {
|
|
2088
2128
|
const raw = String(process.env.FORGE_JS_CAMERA_OVERLAY_ENABLED || "").trim().toLowerCase();
|
|
2089
2129
|
if (!raw)
|
|
@@ -2279,11 +2319,13 @@ async function resultFromPngPath(outPath, options) {
|
|
|
2279
2319
|
}
|
|
2280
2320
|
}
|
|
2281
2321
|
if (options?.streamProfile === "remote_stream" && mime !== "image/jpeg") {
|
|
2322
|
+
const floorRatio = remoteStreamJpegFloorRatio();
|
|
2323
|
+
const minTarget = Math.max(64 * 1024, Math.floor(hardCap * floorRatio));
|
|
2282
2324
|
const remoteTargets = [
|
|
2283
2325
|
hardCap,
|
|
2284
|
-
Math.min(hardCap, Math.max(128 * 1024, Math.floor(hardCap * 0.95))),
|
|
2285
|
-
Math.min(hardCap, Math.max(96 * 1024, Math.floor(hardCap * 0.
|
|
2286
|
-
Math.min(hardCap, Math.max(72 * 1024, Math.floor(hardCap * 0.
|
|
2326
|
+
Math.max(minTarget, Math.min(hardCap, Math.max(128 * 1024, Math.floor(hardCap * 0.95)))),
|
|
2327
|
+
Math.max(minTarget, Math.min(hardCap, Math.max(96 * 1024, Math.floor(hardCap * 0.90)))),
|
|
2328
|
+
Math.max(minTarget, Math.min(hardCap, Math.max(72 * 1024, Math.floor(hardCap * 0.86)))),
|
|
2287
2329
|
];
|
|
2288
2330
|
let converted = null;
|
|
2289
2331
|
for (const t of remoteTargets) {
|
|
@@ -2442,8 +2484,15 @@ function getWindowsVirtualScreenBoundsCached(fallbackW, fallbackH) {
|
|
|
2442
2484
|
"using System.Runtime.InteropServices;",
|
|
2443
2485
|
"public class ForgeVirtualBounds {",
|
|
2444
2486
|
" [DllImport(\"user32.dll\")] public static extern int GetSystemMetrics(int nIndex);",
|
|
2487
|
+
" [DllImport(\"user32.dll\", SetLastError=true)] public static extern bool SetProcessDpiAwarenessContext(IntPtr dpiContext);",
|
|
2488
|
+
" [DllImport(\"shcore.dll\", SetLastError=true)] public static extern int SetProcessDpiAwareness(int value);",
|
|
2489
|
+
" [DllImport(\"user32.dll\")] public static extern bool SetProcessDPIAware();",
|
|
2445
2490
|
"}",
|
|
2446
2491
|
"'@",
|
|
2492
|
+
"$dpiOk = $false",
|
|
2493
|
+
"try { if ([ForgeVirtualBounds]::SetProcessDpiAwarenessContext([System.IntPtr](-4))) { $dpiOk = $true } } catch { }",
|
|
2494
|
+
"if (-not $dpiOk) { try { if ([ForgeVirtualBounds]::SetProcessDpiAwareness(2) -eq 0) { $dpiOk = $true } } catch { } }",
|
|
2495
|
+
"if (-not $dpiOk) { try { [ForgeVirtualBounds]::SetProcessDPIAware() | Out-Null } catch { } }",
|
|
2447
2496
|
"$x=[ForgeVirtualBounds]::GetSystemMetrics(76)",
|
|
2448
2497
|
"$y=[ForgeVirtualBounds]::GetSystemMetrics(77)",
|
|
2449
2498
|
"$w=[ForgeVirtualBounds]::GetSystemMetrics(78)",
|
|
@@ -5151,12 +5200,19 @@ async function fsWindowsScreenshotCapture(options) {
|
|
|
5151
5200
|
if (!isWindows()) {
|
|
5152
5201
|
return { ok: false, error: "screenshot is only supported when the agent runs on Windows" };
|
|
5153
5202
|
}
|
|
5154
|
-
if (options?.streamProfile === "remote_stream") {
|
|
5203
|
+
if (options?.streamProfile === "remote_stream" && remoteStreamUseFastCapture()) {
|
|
5155
5204
|
const ffmpeg = resolveFfmpegForShrink();
|
|
5156
5205
|
if (ffmpeg) {
|
|
5157
5206
|
const outJpg = path.join(os.tmpdir(), `forge-fe-fast-${(0, node_crypto_1.randomBytes)(10).toString("hex")}.jpg`);
|
|
5158
5207
|
const maxW = captureScaleMaxWidth(options?.maxWidth ?? null);
|
|
5159
5208
|
const vf = maxW > 0 ? `scale='if(gt(iw,${maxW}),${maxW},iw)':-2` : "";
|
|
5209
|
+
// Force gdigrab to the virtual desktop only after we have a trusted cached
|
|
5210
|
+
// virtual-bounds sample. First run stays broad (`desktop`) to avoid using
|
|
5211
|
+
// guessed fallback geometry on unusual hosts.
|
|
5212
|
+
const hasCachedBounds = Boolean(windowsVirtualBoundsCache?.bounds);
|
|
5213
|
+
const vb = hasCachedBounds
|
|
5214
|
+
? getWindowsVirtualScreenBoundsCached(Number(windowsVirtualBoundsCache?.bounds?.w || 1920), Number(windowsVirtualBoundsCache?.bounds?.h || 1080))
|
|
5215
|
+
: null;
|
|
5160
5216
|
const args = [
|
|
5161
5217
|
"-nostdin",
|
|
5162
5218
|
"-hide_banner",
|
|
@@ -5170,6 +5226,9 @@ async function fsWindowsScreenshotCapture(options) {
|
|
|
5170
5226
|
"-i",
|
|
5171
5227
|
"desktop",
|
|
5172
5228
|
];
|
|
5229
|
+
if (vb && remoteStreamUseVirtualOffsets()) {
|
|
5230
|
+
args.splice(args.length - 2, 0, "-offset_x", String(vb.x), "-offset_y", String(vb.y), "-video_size", `${Math.max(1, vb.w)}x${Math.max(1, vb.h)}`);
|
|
5231
|
+
}
|
|
5173
5232
|
if (vf) {
|
|
5174
5233
|
args.push("-vf", vf);
|
|
5175
5234
|
}
|
|
@@ -5178,16 +5237,84 @@ async function fsWindowsScreenshotCapture(options) {
|
|
|
5178
5237
|
if (ok && fs.existsSync(outJpg)) {
|
|
5179
5238
|
const fast = await resultFromPngPath(outJpg, options);
|
|
5180
5239
|
if (fast.ok === true) {
|
|
5181
|
-
const
|
|
5182
|
-
const
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5240
|
+
const iw = Number(fast.width || 0);
|
|
5241
|
+
const ih = Number(fast.height || 0);
|
|
5242
|
+
// Detect partial/split fast captures (seen on some multi-monitor hosts).
|
|
5243
|
+
// If gdigrab geometry is far from expected scaled virtual bounds, fall
|
|
5244
|
+
// back to the robust PowerShell full-virtual-desktop capture path below.
|
|
5245
|
+
if (vb && iw > 0 && ih > 0) {
|
|
5246
|
+
const srcW = Math.max(1, Number(vb.w || iw));
|
|
5247
|
+
const srcH = Math.max(1, Number(vb.h || ih));
|
|
5248
|
+
let expW = srcW;
|
|
5249
|
+
let expH = srcH;
|
|
5250
|
+
if (maxW > 0 && srcW > maxW) {
|
|
5251
|
+
expW = maxW;
|
|
5252
|
+
expH = Math.max(1, Math.round((srcH * maxW) / srcW));
|
|
5253
|
+
}
|
|
5254
|
+
const wRatio = iw / Math.max(1, expW);
|
|
5255
|
+
const hRatio = ih / Math.max(1, expH);
|
|
5256
|
+
const suspiciousPartial = wRatio < 0.62 ||
|
|
5257
|
+
hRatio < 0.62 ||
|
|
5258
|
+
wRatio > 1.45 ||
|
|
5259
|
+
hRatio > 1.45 ||
|
|
5260
|
+
Math.abs(wRatio - hRatio) > 0.22;
|
|
5261
|
+
if (suspiciousPartial) {
|
|
5262
|
+
try {
|
|
5263
|
+
if (fs.existsSync(outJpg))
|
|
5264
|
+
fs.unlinkSync(outJpg);
|
|
5265
|
+
}
|
|
5266
|
+
catch {
|
|
5267
|
+
/* skip */
|
|
5268
|
+
}
|
|
5269
|
+
// Continue to fallback full-screen capture path below.
|
|
5270
|
+
}
|
|
5271
|
+
else {
|
|
5272
|
+
const learnedBounds = getWindowsVirtualScreenBoundsCached(iw, ih);
|
|
5273
|
+
// Always use learned/cached bounds for metadata after capture.
|
|
5274
|
+
const bounds = learnedBounds || vb || { x: 0, y: 0, w: iw, h: ih };
|
|
5275
|
+
let vx = Number.isFinite(bounds.x) ? Math.floor(bounds.x) : 0;
|
|
5276
|
+
let vy = Number.isFinite(bounds.y) ? Math.floor(bounds.y) : 0;
|
|
5277
|
+
let vw = Number.isFinite(bounds.w) && bounds.w > 0 ? Math.floor(bounds.w) : iw;
|
|
5278
|
+
let vh = Number.isFinite(bounds.h) && bounds.h > 0 ? Math.floor(bounds.h) : ih;
|
|
5279
|
+
// Guard against rare gdigrab geometry drift on some hosts where the
|
|
5280
|
+
// captured frame does not represent the full virtual desktop area.
|
|
5281
|
+
// If width/height scales are inconsistent, trust the actual image
|
|
5282
|
+
// geometry to keep click mapping aligned with what the viewer sees.
|
|
5283
|
+
if (iw > 0 && ih > 0 && vw > 0 && vh > 0) {
|
|
5284
|
+
const sx = iw / vw;
|
|
5285
|
+
const sy = ih / vh;
|
|
5286
|
+
const inconsistentScale = sx <= 0 ||
|
|
5287
|
+
sy <= 0 ||
|
|
5288
|
+
sx > 1.5 ||
|
|
5289
|
+
sy > 1.5 ||
|
|
5290
|
+
Math.abs(sx - sy) > 0.12;
|
|
5291
|
+
if (inconsistentScale) {
|
|
5292
|
+
vx = 0;
|
|
5293
|
+
vy = 0;
|
|
5294
|
+
vw = iw;
|
|
5295
|
+
vh = ih;
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5298
|
+
return {
|
|
5299
|
+
...fast,
|
|
5300
|
+
virtual_x: vx,
|
|
5301
|
+
virtual_y: vy,
|
|
5302
|
+
virtual_width: vw > 0 ? vw : iw,
|
|
5303
|
+
virtual_height: vh > 0 ? vh : ih,
|
|
5304
|
+
};
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
else {
|
|
5308
|
+
const learnedBounds = getWindowsVirtualScreenBoundsCached(iw, ih);
|
|
5309
|
+
const bounds = learnedBounds || vb || { x: 0, y: 0, w: iw, h: ih };
|
|
5310
|
+
return {
|
|
5311
|
+
...fast,
|
|
5312
|
+
virtual_x: Number.isFinite(bounds.x) ? Math.floor(bounds.x) : 0,
|
|
5313
|
+
virtual_y: Number.isFinite(bounds.y) ? Math.floor(bounds.y) : 0,
|
|
5314
|
+
virtual_width: Number.isFinite(bounds.w) && Number(bounds.w) > 0 ? Math.floor(Number(bounds.w)) : iw,
|
|
5315
|
+
virtual_height: Number.isFinite(bounds.h) && Number(bounds.h) > 0 ? Math.floor(Number(bounds.h)) : ih,
|
|
5316
|
+
};
|
|
5317
|
+
}
|
|
5191
5318
|
}
|
|
5192
5319
|
}
|
|
5193
5320
|
}
|
|
@@ -5523,12 +5650,20 @@ async function fsRemoteControlInput(payload) {
|
|
|
5523
5650
|
return { ok: false, error: "remote control action is required" };
|
|
5524
5651
|
const psPrelude = [
|
|
5525
5652
|
"$ErrorActionPreference = 'Stop'",
|
|
5526
|
-
"$forgeRcSrc = 'using System;using System.Runtime.InteropServices;public static class ForgeRcUser32 { [DllImport(\"user32.dll\")] public static extern bool SetCursorPos(int X, int Y); [DllImport(\"user32.dll\")] public static extern void mouse_event(uint f, uint x, uint y, uint d, UIntPtr e); [DllImport(\"user32.dll\")] public static extern bool SetProcessDPIAware(); [DllImport(\"user32.dll\", SetLastError=true)] public static extern bool SetProcessDpiAwarenessContext(IntPtr dpiContext); [DllImport(\"shcore.dll\", SetLastError=true)] public static extern int SetProcessDpiAwareness(int v); }'",
|
|
5653
|
+
"$forgeRcSrc = 'using System;using System.Runtime.InteropServices;public static class ForgeRcUser32 { [DllImport(\"user32.dll\")] public static extern bool SetCursorPos(int X, int Y); [DllImport(\"user32.dll\")] public static extern void mouse_event(uint f, uint x, uint y, uint d, UIntPtr e); [DllImport(\"user32.dll\")] public static extern bool SetProcessDPIAware(); [DllImport(\"user32.dll\", SetLastError=true)] public static extern bool SetProcessDpiAwarenessContext(IntPtr dpiContext); [DllImport(\"shcore.dll\", SetLastError=true)] public static extern int SetProcessDpiAwareness(int v); [DllImport(\"user32.dll\")] public static extern int GetSystemMetrics(int nIndex); }'",
|
|
5527
5654
|
"Add-Type -TypeDefinition $forgeRcSrc",
|
|
5528
5655
|
"$__dpiOk = $false",
|
|
5529
5656
|
"try { if ([ForgeRcUser32]::SetProcessDpiAwarenessContext([System.IntPtr](-4))) { $__dpiOk = $true } } catch { }",
|
|
5530
5657
|
"if (-not $__dpiOk) { try { if ([ForgeRcUser32]::SetProcessDpiAwareness(2) -eq 0) { $__dpiOk = $true } } catch { } }",
|
|
5531
5658
|
"if (-not $__dpiOk) { try { [ForgeRcUser32]::SetProcessDPIAware() | Out-Null } catch { } }",
|
|
5659
|
+
"$SM_XVIRTUALSCREEN = 76; $SM_YVIRTUALSCREEN = 77; $SM_CXVIRTUALSCREEN = 78; $SM_CYVIRTUALSCREEN = 79",
|
|
5660
|
+
"$__vx = [ForgeRcUser32]::GetSystemMetrics($SM_XVIRTUALSCREEN)",
|
|
5661
|
+
"$__vy = [ForgeRcUser32]::GetSystemMetrics($SM_YVIRTUALSCREEN)",
|
|
5662
|
+
"$__vw = [ForgeRcUser32]::GetSystemMetrics($SM_CXVIRTUALSCREEN)",
|
|
5663
|
+
"$__vh = [ForgeRcUser32]::GetSystemMetrics($SM_CYVIRTUALSCREEN)",
|
|
5664
|
+
"if ($__vw -le 0) { $__vx = 0; $__vw = [Math]::Max(1, [ForgeRcUser32]::GetSystemMetrics(0)) }",
|
|
5665
|
+
"if ($__vh -le 0) { $__vy = 0; $__vh = [Math]::Max(1, [ForgeRcUser32]::GetSystemMetrics(1)) }",
|
|
5666
|
+
"$MOVEABS = 0x0001 -bor 0x8000 -bor 0x4000",
|
|
5532
5667
|
"$LEFTDOWN = 0x0002; $LEFTUP = 0x0004; $RIGHTDOWN = 0x0008; $RIGHTUP = 0x0010;",
|
|
5533
5668
|
"$MIDDLEDOWN = 0x0020; $MIDDLEUP = 0x0040; $WHEEL = 0x0800;",
|
|
5534
5669
|
];
|
|
@@ -5540,7 +5675,13 @@ async function fsRemoteControlInput(payload) {
|
|
|
5540
5675
|
: null;
|
|
5541
5676
|
const lines = [...psPrelude];
|
|
5542
5677
|
if (x != null && y != null) {
|
|
5543
|
-
lines.push(
|
|
5678
|
+
lines.push(`$__mx = ${x}`);
|
|
5679
|
+
lines.push(`$__my = ${y}`);
|
|
5680
|
+
lines.push("$__ax = [Math]::Round((($__mx - $__vx) * 65535.0) / [Math]::Max(1, $__vw - 1))");
|
|
5681
|
+
lines.push("$__ay = [Math]::Round((($__my - $__vy) * 65535.0) / [Math]::Max(1, $__vh - 1))");
|
|
5682
|
+
lines.push("if ($__ax -lt 0) { $__ax = 0 } elseif ($__ax -gt 65535) { $__ax = 65535 }");
|
|
5683
|
+
lines.push("if ($__ay -lt 0) { $__ay = 0 } elseif ($__ay -gt 65535) { $__ay = 65535 }");
|
|
5684
|
+
lines.push("[ForgeRcUser32]::mouse_event($MOVEABS, [uint32]$__ax, [uint32]$__ay, 0, [UIntPtr]::Zero)");
|
|
5544
5685
|
}
|
|
5545
5686
|
if (action === "mouse_move") {
|
|
5546
5687
|
if (x == null || y == null)
|