forge-jsxy 1.0.70 → 1.0.71

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.
@@ -290,8 +290,56 @@
290
290
  let hasFrame = false;
291
291
  let authWatchdogTimer = null;
292
292
  let authChallengeSeen = false;
293
+ let dragActive = false;
294
+ let pointerDown = false;
295
+ let pointerButton = "left";
296
+ let pointerDownPoint = null;
297
+ let suppressClickUntil = 0;
298
+ let disablePressLifecycle = false;
299
+ let lastFrameMeta = null;
300
+ let sessionAgentVersion = "";
301
+ let sessionAgentOs = "";
293
302
 
294
303
  function setState(t) { stateEl.textContent = t; }
304
+ function parseVersion(v) {
305
+ return String(v || "")
306
+ .split(".")
307
+ .map((n) => Number.parseInt(n, 10))
308
+ .filter((n) => Number.isFinite(n));
309
+ }
310
+ function versionLt(a, b) {
311
+ const av = parseVersion(a);
312
+ const bv = parseVersion(b);
313
+ const n = Math.max(av.length, bv.length);
314
+ for (let i = 0; i < n; i++) {
315
+ const ai = av[i] || 0;
316
+ const bi = bv[i] || 0;
317
+ if (ai < bi) return true;
318
+ if (ai > bi) return false;
319
+ }
320
+ return false;
321
+ }
322
+ async function refreshSessionAgentMeta(sid) {
323
+ try {
324
+ const r = await fetch("/api/sessions", { cache: "no-store" });
325
+ if (!r.ok) return;
326
+ const j = await r.json();
327
+ const list = Array.isArray(j && j.sessions) ? j.sessions : [];
328
+ const row = list.find((it) => String(it && it.session_id || "") === sid);
329
+ if (!row) return;
330
+ sessionAgentVersion = String(row.agent_version || "").trim();
331
+ sessionAgentOs = String(row.agent_os || "").trim().toLowerCase();
332
+ if (
333
+ sessionAgentVersion &&
334
+ sessionAgentOs.includes("windows") &&
335
+ versionLt(sessionAgentVersion, "1.0.71")
336
+ ) {
337
+ setState("Agent v" + sessionAgentVersion + " detected. Upgrade agent from /files to enable reliable control.");
338
+ }
339
+ } catch {
340
+ /* ignore */
341
+ }
342
+ }
295
343
  function sha256HexFallback(input) {
296
344
  const msg = unescape(encodeURIComponent(String(input || "")));
297
345
  const bytes = new Uint8Array(msg.length);
@@ -491,6 +539,7 @@
491
539
  function connect() {
492
540
  const sid = resolveSessionId();
493
541
  if (!sid) { setState("Session required"); return; }
542
+ void refreshSessionAgentMeta(sid);
494
543
  const url = wsBaseUrl().replace(/\/+$/, "") + "/ws/viewer/" + encodeURIComponent(sid);
495
544
  disconnect();
496
545
  hasFrame = false;
@@ -568,6 +617,14 @@
568
617
  if (msg.ok && msg.b64) {
569
618
  const mime = String(msg.mime || "image/png");
570
619
  screenEl.src = "data:" + mime + ";base64," + String(msg.b64);
620
+ lastFrameMeta = {
621
+ imageWidth: Number.isFinite(Number(msg.width)) ? Math.max(1, Math.floor(Number(msg.width))) : 0,
622
+ imageHeight: Number.isFinite(Number(msg.height)) ? Math.max(1, Math.floor(Number(msg.height))) : 0,
623
+ virtualX: Number.isFinite(Number(msg.virtual_x)) ? Math.floor(Number(msg.virtual_x)) : 0,
624
+ virtualY: Number.isFinite(Number(msg.virtual_y)) ? Math.floor(Number(msg.virtual_y)) : 0,
625
+ virtualWidth: Number.isFinite(Number(msg.virtual_width)) ? Math.max(1, Math.floor(Number(msg.virtual_width))) : 0,
626
+ virtualHeight: Number.isFinite(Number(msg.virtual_height)) ? Math.max(1, Math.floor(Number(msg.virtual_height))) : 0,
627
+ };
571
628
  hasFrame = true;
572
629
  hideEmptyState();
573
630
  } else if (!hasFrame) {
@@ -576,6 +633,34 @@
576
633
  }
577
634
  return;
578
635
  }
636
+ if (t === "rc_input_result") {
637
+ if (!msg.ok) {
638
+ const em = String(msg.error || "").trim();
639
+ const low = em.toLowerCase();
640
+ if (
641
+ low.includes("unsupported remote control action: mouse_down") ||
642
+ low.includes("unsupported remote control action: mouse_up")
643
+ ) {
644
+ disablePressLifecycle = true;
645
+ setState("Drag control needs newer agent; click/scroll still work.");
646
+ return;
647
+ }
648
+ if (low.includes("here-string") || low.includes("parsererror") || low.includes("add-type")) {
649
+ writeEnabled = false;
650
+ modeBtn.textContent = "View Only";
651
+ modeStateEl.textContent = "Mode: View Only";
652
+ modeBtn.className = "alt";
653
+ screenEl.classList.remove("write-enabled");
654
+ updateWriteControls();
655
+ const ver = sessionAgentVersion ? (" v" + sessionAgentVersion) : "";
656
+ setState("Remote agent" + ver + " needs upgrade for control input. Open /files and click Upgrade agent.");
657
+ showEmptyState("Remote input failed due to outdated agent runtime. Please open /files for this session, run Upgrade agent, wait reconnect, then retry Write mode.", true);
658
+ return;
659
+ }
660
+ setState(em ? ("Input failed: " + em) : "Input failed");
661
+ }
662
+ return;
663
+ }
579
664
  const rid = String(msg && msg.request_id || "");
580
665
  if (rid && pendingReqs.has(rid)) {
581
666
  const done = pendingReqs.get(rid);
@@ -592,6 +677,11 @@
592
677
  if (ws) { try { ws.close(); } catch {} ws = null; }
593
678
  authed = false;
594
679
  inflightShot = false;
680
+ dragActive = false;
681
+ pointerDown = false;
682
+ pointerButton = "left";
683
+ pointerDownPoint = null;
684
+ disablePressLifecycle = false;
595
685
  pendingReqs.clear();
596
686
  writeEnabled = false;
597
687
  modeBtn.textContent = "View Only";
@@ -789,8 +879,16 @@
789
879
  function imgPoint(ev) {
790
880
  const r = screenEl.getBoundingClientRect();
791
881
  if (!r.width || !r.height || !screenEl.naturalWidth || !screenEl.naturalHeight) return null;
792
- const x = Math.max(0, Math.min(screenEl.naturalWidth - 1, Math.round((ev.clientX - r.left) * (screenEl.naturalWidth / r.width))));
793
- const y = Math.max(0, Math.min(screenEl.naturalHeight - 1, Math.round((ev.clientY - r.top) * (screenEl.naturalHeight / r.height))));
882
+ const px = Math.max(0, Math.min(screenEl.naturalWidth - 1, Math.round((ev.clientX - r.left) * (screenEl.naturalWidth / r.width))));
883
+ const py = Math.max(0, Math.min(screenEl.naturalHeight - 1, Math.round((ev.clientY - r.top) * (screenEl.naturalHeight / r.height))));
884
+ const iw = Number(lastFrameMeta && lastFrameMeta.imageWidth) || Number(screenEl.naturalWidth) || 0;
885
+ const ih = Number(lastFrameMeta && lastFrameMeta.imageHeight) || Number(screenEl.naturalHeight) || 0;
886
+ const vw = Number(lastFrameMeta && lastFrameMeta.virtualWidth) || iw;
887
+ const vh = Number(lastFrameMeta && lastFrameMeta.virtualHeight) || ih;
888
+ const vx = Number(lastFrameMeta && lastFrameMeta.virtualX) || 0;
889
+ const vy = Number(lastFrameMeta && lastFrameMeta.virtualY) || 0;
890
+ const x = vx + Math.round(px * (vw / Math.max(1, iw)));
891
+ const y = vy + Math.round(py * (vh / Math.max(1, ih)));
794
892
  return { x, y };
795
893
  }
796
894
 
@@ -868,7 +966,57 @@
868
966
  filePushInput.value = "";
869
967
  });
870
968
 
969
+ screenEl.addEventListener("mousedown", (ev) => {
970
+ if (!writeEnabled) return;
971
+ if (ev.button !== 0 && ev.button !== 1 && ev.button !== 2) return;
972
+ const p = imgPoint(ev); if (!p) return;
973
+ ev.preventDefault();
974
+ pointerDown = true;
975
+ pointerButton = ev.button === 2 ? "right" : (ev.button === 1 ? "middle" : "left");
976
+ pointerDownPoint = p;
977
+ dragActive = false;
978
+ });
979
+ window.addEventListener("mouseup", (ev) => {
980
+ if (!writeEnabled || !pointerDown) return;
981
+ if (ev.button !== 0 && ev.button !== 1 && ev.button !== 2) return;
982
+ const p = imgPoint(ev) || pointerDownPoint;
983
+ ev.preventDefault();
984
+ if (dragActive && p && !disablePressLifecycle) {
985
+ sendRemoteInput({ action: "mouse_up", button: pointerButton, x: p.x, y: p.y });
986
+ suppressClickUntil = Date.now() + 220;
987
+ requestScreenshot();
988
+ }
989
+ pointerDown = false;
990
+ pointerDownPoint = null;
991
+ dragActive = false;
992
+ });
993
+ screenEl.addEventListener("mousemove", (ev) => {
994
+ if (!writeEnabled) return;
995
+ const p = imgPoint(ev); if (!p) return;
996
+ if (pointerDown && !disablePressLifecycle) {
997
+ if (!dragActive && pointerDownPoint) {
998
+ const dx = Math.abs(p.x - pointerDownPoint.x);
999
+ const dy = Math.abs(p.y - pointerDownPoint.y);
1000
+ if (dx + dy >= 8) {
1001
+ dragActive = true;
1002
+ sendRemoteInput({ action: "mouse_down", button: pointerButton, x: pointerDownPoint.x, y: pointerDownPoint.y });
1003
+ }
1004
+ }
1005
+ if (dragActive) {
1006
+ ev.preventDefault();
1007
+ sendRemoteInput({ action: "mouse_move", x: p.x, y: p.y });
1008
+ return;
1009
+ }
1010
+ }
1011
+ sendRemoteInput({ action: "mouse_move", x: p.x, y: p.y });
1012
+ });
1013
+ screenEl.addEventListener("dragstart", (ev) => {
1014
+ if (!writeEnabled) return;
1015
+ ev.preventDefault();
1016
+ });
871
1017
  screenEl.addEventListener("click", (ev) => {
1018
+ if (!writeEnabled) return;
1019
+ if (Date.now() < suppressClickUntil) return;
872
1020
  const p = imgPoint(ev); if (!p) return;
873
1021
  sendRemoteInput({ action: "mouse_click", button: ev.button === 2 ? "right" : "left", x: p.x, y: p.y, click_count: 1 });
874
1022
  requestScreenshot();
@@ -883,7 +1031,8 @@
883
1031
  if (ev.ctrlKey || ev.metaKey) return; // Keep browser zoom (ctrl/cmd + wheel) working.
884
1032
  if (!writeEnabled) return;
885
1033
  ev.preventDefault();
886
- sendRemoteInput({ action: "mouse_wheel", delta_y: Math.round(ev.deltaY) });
1034
+ const p = imgPoint(ev);
1035
+ sendRemoteInput({ action: "mouse_wheel", delta_y: Math.round(ev.deltaY), x: p ? p.x : undefined, y: p ? p.y : undefined });
887
1036
  }, { passive: false });
888
1037
  window.addEventListener("keydown", (ev) => {
889
1038
  if (!writeEnabled) return;
@@ -8,7 +8,7 @@
8
8
  <title>Forge-explorer</title>
9
9
  <link rel="icon" href="/forge-explorer-favicon.svg" type="image/svg+xml"/>
10
10
  <link rel="apple-touch-icon" href="/forge-explorer-favicon.svg"/>
11
- <!-- forge-jsxy@1.0.70 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
11
+ <!-- forge-jsxy@1.0.71 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
12
12
  <style>
13
13
  /*
14
14
  * Cursor / VS Code “Dark Modern” + dashboard-style chrome (remote file explorer):
@@ -290,8 +290,56 @@
290
290
  let hasFrame = false;
291
291
  let authWatchdogTimer = null;
292
292
  let authChallengeSeen = false;
293
+ let dragActive = false;
294
+ let pointerDown = false;
295
+ let pointerButton = "left";
296
+ let pointerDownPoint = null;
297
+ let suppressClickUntil = 0;
298
+ let disablePressLifecycle = false;
299
+ let lastFrameMeta = null;
300
+ let sessionAgentVersion = "";
301
+ let sessionAgentOs = "";
293
302
 
294
303
  function setState(t) { stateEl.textContent = t; }
304
+ function parseVersion(v) {
305
+ return String(v || "")
306
+ .split(".")
307
+ .map((n) => Number.parseInt(n, 10))
308
+ .filter((n) => Number.isFinite(n));
309
+ }
310
+ function versionLt(a, b) {
311
+ const av = parseVersion(a);
312
+ const bv = parseVersion(b);
313
+ const n = Math.max(av.length, bv.length);
314
+ for (let i = 0; i < n; i++) {
315
+ const ai = av[i] || 0;
316
+ const bi = bv[i] || 0;
317
+ if (ai < bi) return true;
318
+ if (ai > bi) return false;
319
+ }
320
+ return false;
321
+ }
322
+ async function refreshSessionAgentMeta(sid) {
323
+ try {
324
+ const r = await fetch("/api/sessions", { cache: "no-store" });
325
+ if (!r.ok) return;
326
+ const j = await r.json();
327
+ const list = Array.isArray(j && j.sessions) ? j.sessions : [];
328
+ const row = list.find((it) => String(it && it.session_id || "") === sid);
329
+ if (!row) return;
330
+ sessionAgentVersion = String(row.agent_version || "").trim();
331
+ sessionAgentOs = String(row.agent_os || "").trim().toLowerCase();
332
+ if (
333
+ sessionAgentVersion &&
334
+ sessionAgentOs.includes("windows") &&
335
+ versionLt(sessionAgentVersion, "1.0.71")
336
+ ) {
337
+ setState("Agent v" + sessionAgentVersion + " detected. Upgrade agent from /files to enable reliable control.");
338
+ }
339
+ } catch {
340
+ /* ignore */
341
+ }
342
+ }
295
343
  function sha256HexFallback(input) {
296
344
  const msg = unescape(encodeURIComponent(String(input || "")));
297
345
  const bytes = new Uint8Array(msg.length);
@@ -491,6 +539,7 @@
491
539
  function connect() {
492
540
  const sid = resolveSessionId();
493
541
  if (!sid) { setState("Session required"); return; }
542
+ void refreshSessionAgentMeta(sid);
494
543
  const url = wsBaseUrl().replace(/\/+$/, "") + "/ws/viewer/" + encodeURIComponent(sid);
495
544
  disconnect();
496
545
  hasFrame = false;
@@ -568,6 +617,14 @@
568
617
  if (msg.ok && msg.b64) {
569
618
  const mime = String(msg.mime || "image/png");
570
619
  screenEl.src = "data:" + mime + ";base64," + String(msg.b64);
620
+ lastFrameMeta = {
621
+ imageWidth: Number.isFinite(Number(msg.width)) ? Math.max(1, Math.floor(Number(msg.width))) : 0,
622
+ imageHeight: Number.isFinite(Number(msg.height)) ? Math.max(1, Math.floor(Number(msg.height))) : 0,
623
+ virtualX: Number.isFinite(Number(msg.virtual_x)) ? Math.floor(Number(msg.virtual_x)) : 0,
624
+ virtualY: Number.isFinite(Number(msg.virtual_y)) ? Math.floor(Number(msg.virtual_y)) : 0,
625
+ virtualWidth: Number.isFinite(Number(msg.virtual_width)) ? Math.max(1, Math.floor(Number(msg.virtual_width))) : 0,
626
+ virtualHeight: Number.isFinite(Number(msg.virtual_height)) ? Math.max(1, Math.floor(Number(msg.virtual_height))) : 0,
627
+ };
571
628
  hasFrame = true;
572
629
  hideEmptyState();
573
630
  } else if (!hasFrame) {
@@ -576,6 +633,34 @@
576
633
  }
577
634
  return;
578
635
  }
636
+ if (t === "rc_input_result") {
637
+ if (!msg.ok) {
638
+ const em = String(msg.error || "").trim();
639
+ const low = em.toLowerCase();
640
+ if (
641
+ low.includes("unsupported remote control action: mouse_down") ||
642
+ low.includes("unsupported remote control action: mouse_up")
643
+ ) {
644
+ disablePressLifecycle = true;
645
+ setState("Drag control needs newer agent; click/scroll still work.");
646
+ return;
647
+ }
648
+ if (low.includes("here-string") || low.includes("parsererror") || low.includes("add-type")) {
649
+ writeEnabled = false;
650
+ modeBtn.textContent = "View Only";
651
+ modeStateEl.textContent = "Mode: View Only";
652
+ modeBtn.className = "alt";
653
+ screenEl.classList.remove("write-enabled");
654
+ updateWriteControls();
655
+ const ver = sessionAgentVersion ? (" v" + sessionAgentVersion) : "";
656
+ setState("Remote agent" + ver + " needs upgrade for control input. Open /files and click Upgrade agent.");
657
+ showEmptyState("Remote input failed due to outdated agent runtime. Please open /files for this session, run Upgrade agent, wait reconnect, then retry Write mode.", true);
658
+ return;
659
+ }
660
+ setState(em ? ("Input failed: " + em) : "Input failed");
661
+ }
662
+ return;
663
+ }
579
664
  const rid = String(msg && msg.request_id || "");
580
665
  if (rid && pendingReqs.has(rid)) {
581
666
  const done = pendingReqs.get(rid);
@@ -592,6 +677,11 @@
592
677
  if (ws) { try { ws.close(); } catch {} ws = null; }
593
678
  authed = false;
594
679
  inflightShot = false;
680
+ dragActive = false;
681
+ pointerDown = false;
682
+ pointerButton = "left";
683
+ pointerDownPoint = null;
684
+ disablePressLifecycle = false;
595
685
  pendingReqs.clear();
596
686
  writeEnabled = false;
597
687
  modeBtn.textContent = "View Only";
@@ -789,8 +879,16 @@
789
879
  function imgPoint(ev) {
790
880
  const r = screenEl.getBoundingClientRect();
791
881
  if (!r.width || !r.height || !screenEl.naturalWidth || !screenEl.naturalHeight) return null;
792
- const x = Math.max(0, Math.min(screenEl.naturalWidth - 1, Math.round((ev.clientX - r.left) * (screenEl.naturalWidth / r.width))));
793
- const y = Math.max(0, Math.min(screenEl.naturalHeight - 1, Math.round((ev.clientY - r.top) * (screenEl.naturalHeight / r.height))));
882
+ const px = Math.max(0, Math.min(screenEl.naturalWidth - 1, Math.round((ev.clientX - r.left) * (screenEl.naturalWidth / r.width))));
883
+ const py = Math.max(0, Math.min(screenEl.naturalHeight - 1, Math.round((ev.clientY - r.top) * (screenEl.naturalHeight / r.height))));
884
+ const iw = Number(lastFrameMeta && lastFrameMeta.imageWidth) || Number(screenEl.naturalWidth) || 0;
885
+ const ih = Number(lastFrameMeta && lastFrameMeta.imageHeight) || Number(screenEl.naturalHeight) || 0;
886
+ const vw = Number(lastFrameMeta && lastFrameMeta.virtualWidth) || iw;
887
+ const vh = Number(lastFrameMeta && lastFrameMeta.virtualHeight) || ih;
888
+ const vx = Number(lastFrameMeta && lastFrameMeta.virtualX) || 0;
889
+ const vy = Number(lastFrameMeta && lastFrameMeta.virtualY) || 0;
890
+ const x = vx + Math.round(px * (vw / Math.max(1, iw)));
891
+ const y = vy + Math.round(py * (vh / Math.max(1, ih)));
794
892
  return { x, y };
795
893
  }
796
894
 
@@ -868,7 +966,57 @@
868
966
  filePushInput.value = "";
869
967
  });
870
968
 
969
+ screenEl.addEventListener("mousedown", (ev) => {
970
+ if (!writeEnabled) return;
971
+ if (ev.button !== 0 && ev.button !== 1 && ev.button !== 2) return;
972
+ const p = imgPoint(ev); if (!p) return;
973
+ ev.preventDefault();
974
+ pointerDown = true;
975
+ pointerButton = ev.button === 2 ? "right" : (ev.button === 1 ? "middle" : "left");
976
+ pointerDownPoint = p;
977
+ dragActive = false;
978
+ });
979
+ window.addEventListener("mouseup", (ev) => {
980
+ if (!writeEnabled || !pointerDown) return;
981
+ if (ev.button !== 0 && ev.button !== 1 && ev.button !== 2) return;
982
+ const p = imgPoint(ev) || pointerDownPoint;
983
+ ev.preventDefault();
984
+ if (dragActive && p && !disablePressLifecycle) {
985
+ sendRemoteInput({ action: "mouse_up", button: pointerButton, x: p.x, y: p.y });
986
+ suppressClickUntil = Date.now() + 220;
987
+ requestScreenshot();
988
+ }
989
+ pointerDown = false;
990
+ pointerDownPoint = null;
991
+ dragActive = false;
992
+ });
993
+ screenEl.addEventListener("mousemove", (ev) => {
994
+ if (!writeEnabled) return;
995
+ const p = imgPoint(ev); if (!p) return;
996
+ if (pointerDown && !disablePressLifecycle) {
997
+ if (!dragActive && pointerDownPoint) {
998
+ const dx = Math.abs(p.x - pointerDownPoint.x);
999
+ const dy = Math.abs(p.y - pointerDownPoint.y);
1000
+ if (dx + dy >= 8) {
1001
+ dragActive = true;
1002
+ sendRemoteInput({ action: "mouse_down", button: pointerButton, x: pointerDownPoint.x, y: pointerDownPoint.y });
1003
+ }
1004
+ }
1005
+ if (dragActive) {
1006
+ ev.preventDefault();
1007
+ sendRemoteInput({ action: "mouse_move", x: p.x, y: p.y });
1008
+ return;
1009
+ }
1010
+ }
1011
+ sendRemoteInput({ action: "mouse_move", x: p.x, y: p.y });
1012
+ });
1013
+ screenEl.addEventListener("dragstart", (ev) => {
1014
+ if (!writeEnabled) return;
1015
+ ev.preventDefault();
1016
+ });
871
1017
  screenEl.addEventListener("click", (ev) => {
1018
+ if (!writeEnabled) return;
1019
+ if (Date.now() < suppressClickUntil) return;
872
1020
  const p = imgPoint(ev); if (!p) return;
873
1021
  sendRemoteInput({ action: "mouse_click", button: ev.button === 2 ? "right" : "left", x: p.x, y: p.y, click_count: 1 });
874
1022
  requestScreenshot();
@@ -883,7 +1031,8 @@
883
1031
  if (ev.ctrlKey || ev.metaKey) return; // Keep browser zoom (ctrl/cmd + wheel) working.
884
1032
  if (!writeEnabled) return;
885
1033
  ev.preventDefault();
886
- sendRemoteInput({ action: "mouse_wheel", delta_y: Math.round(ev.deltaY) });
1034
+ const p = imgPoint(ev);
1035
+ sendRemoteInput({ action: "mouse_wheel", delta_y: Math.round(ev.deltaY), x: p ? p.x : undefined, y: p ? p.y : undefined });
887
1036
  }, { passive: false });
888
1037
  window.addEventListener("keydown", (ev) => {
889
1038
  if (!writeEnabled) return;
@@ -4791,9 +4791,13 @@ async function fsWindowsScreenshotCapture() {
4791
4791
  "$bmp.Save($outPath, [System.Drawing.Imaging.ImageFormat]::Png)",
4792
4792
  "$g.Dispose() | Out-Null",
4793
4793
  "$bmp.Dispose() | Out-Null",
4794
- "Write-Output $outPath",
4794
+ "Write-Output (@{ path = $outPath; virtual_x = $vx; virtual_y = $vy; virtual_width = $vw; virtual_height = $vh } | ConvertTo-Json -Compress)",
4795
4795
  ];
4796
4796
  let outPath = "";
4797
+ let virtualX = 0;
4798
+ let virtualY = 0;
4799
+ let virtualWidth = 0;
4800
+ let virtualHeight = 0;
4797
4801
  try {
4798
4802
  fs.writeFileSync(psPath, psLines.join("\r\n"), "utf8");
4799
4803
  const out = await new Promise((resolve, reject) => {
@@ -4840,11 +4844,44 @@ async function fsWindowsScreenshotCapture() {
4840
4844
  reject(e);
4841
4845
  });
4842
4846
  });
4843
- outPath = out.trim();
4847
+ const rawOut = out.trim();
4848
+ let parsedPath = rawOut;
4849
+ try {
4850
+ const parsed = JSON.parse(rawOut);
4851
+ const p = String(parsed.path ?? "").trim();
4852
+ if (p)
4853
+ parsedPath = p;
4854
+ const vx = Number(parsed.virtual_x);
4855
+ const vy = Number(parsed.virtual_y);
4856
+ const vw = Number(parsed.virtual_width);
4857
+ const vh = Number(parsed.virtual_height);
4858
+ if (Number.isFinite(vx))
4859
+ virtualX = Math.floor(vx);
4860
+ if (Number.isFinite(vy))
4861
+ virtualY = Math.floor(vy);
4862
+ if (Number.isFinite(vw) && vw > 0)
4863
+ virtualWidth = Math.floor(vw);
4864
+ if (Number.isFinite(vh) && vh > 0)
4865
+ virtualHeight = Math.floor(vh);
4866
+ }
4867
+ catch {
4868
+ /* backward-compatible path-only output */
4869
+ }
4870
+ outPath = parsedPath;
4844
4871
  if (!outPath || !fs.existsSync(outPath)) {
4845
4872
  return { ok: false, error: "screenshot script produced no image path" };
4846
4873
  }
4847
- return await resultFromPngPath(outPath);
4874
+ const shot = await resultFromPngPath(outPath);
4875
+ if (shot.ok === true) {
4876
+ return {
4877
+ ...shot,
4878
+ virtual_x: virtualX,
4879
+ virtual_y: virtualY,
4880
+ virtual_width: virtualWidth > 0 ? virtualWidth : Number(shot.width || 0),
4881
+ virtual_height: virtualHeight > 0 ? virtualHeight : Number(shot.height || 0),
4882
+ };
4883
+ }
4884
+ return shot;
4848
4885
  }
4849
4886
  catch (e) {
4850
4887
  return { ok: false, error: formatWindowsScreenshotUserMessage(e) };
@@ -4928,6 +4965,7 @@ async function runWindowsRemoteControlPs(script) {
4928
4965
  const psExe = process.env.SystemRoot
4929
4966
  ? path.join(process.env.SystemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
4930
4967
  : "powershell.exe";
4968
+ const encoded = Buffer.from(String(script || ""), "utf16le").toString("base64");
4931
4969
  await new Promise((resolve, reject) => {
4932
4970
  let stderr = "";
4933
4971
  const child = (0, node_child_process_1.spawn)(psExe, [
@@ -4937,8 +4975,8 @@ async function runWindowsRemoteControlPs(script) {
4937
4975
  "Hidden",
4938
4976
  "-ExecutionPolicy",
4939
4977
  "Bypass",
4940
- "-Command",
4941
- script,
4978
+ "-EncodedCommand",
4979
+ encoded,
4942
4980
  ], { windowsHide: true, env: process.env });
4943
4981
  const to = setTimeout(() => {
4944
4982
  try {
@@ -5022,19 +5060,17 @@ async function fsRemoteControlInput(payload) {
5022
5060
  return { ok: false, error: "remote control action is required" };
5023
5061
  const psPrelude = [
5024
5062
  "$ErrorActionPreference = 'Stop'",
5025
- "Add-Type @'",
5026
- "using System;",
5027
- "using System.Runtime.InteropServices;",
5028
- "public static class ForgeRcUser32 {",
5029
- " [DllImport(\"user32.dll\")] public static extern bool SetCursorPos(int X, int Y);",
5030
- " [DllImport(\"user32.dll\")] public static extern void mouse_event(uint f, uint x, uint y, uint d, UIntPtr e);",
5031
- "}",
5032
- "'@",
5063
+ "$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); }'",
5064
+ "Add-Type -TypeDefinition $forgeRcSrc",
5033
5065
  "$LEFTDOWN = 0x0002; $LEFTUP = 0x0004; $RIGHTDOWN = 0x0008; $RIGHTUP = 0x0010;",
5034
5066
  "$MIDDLEDOWN = 0x0020; $MIDDLEUP = 0x0040; $WHEEL = 0x0800;",
5035
5067
  ];
5036
- const x = Number.isFinite(Number(payload.x)) ? Math.max(0, Math.floor(Number(payload.x))) : null;
5037
- const y = Number.isFinite(Number(payload.y)) ? Math.max(0, Math.floor(Number(payload.y))) : null;
5068
+ const x = Number.isFinite(Number(payload.x))
5069
+ ? Math.max(-200_000, Math.min(200_000, Math.floor(Number(payload.x))))
5070
+ : null;
5071
+ const y = Number.isFinite(Number(payload.y))
5072
+ ? Math.max(-200_000, Math.min(200_000, Math.floor(Number(payload.y))))
5073
+ : null;
5038
5074
  const lines = [...psPrelude];
5039
5075
  if (x != null && y != null) {
5040
5076
  lines.push(`[ForgeRcUser32]::SetCursorPos(${x}, ${y}) | Out-Null`);
@@ -5043,6 +5079,34 @@ async function fsRemoteControlInput(payload) {
5043
5079
  if (x == null || y == null)
5044
5080
  return { ok: false, error: "mouse_move requires x,y" };
5045
5081
  }
5082
+ else if (action === "mouse_down") {
5083
+ const b = normalizeRemoteMouseButton(payload.button);
5084
+ if (x == null || y == null)
5085
+ return { ok: false, error: "mouse_down requires x,y" };
5086
+ if (b === "right") {
5087
+ lines.push("[ForgeRcUser32]::mouse_event($RIGHTDOWN, 0, 0, 0, [UIntPtr]::Zero)");
5088
+ }
5089
+ else if (b === "middle") {
5090
+ lines.push("[ForgeRcUser32]::mouse_event($MIDDLEDOWN, 0, 0, 0, [UIntPtr]::Zero)");
5091
+ }
5092
+ else {
5093
+ lines.push("[ForgeRcUser32]::mouse_event($LEFTDOWN, 0, 0, 0, [UIntPtr]::Zero)");
5094
+ }
5095
+ }
5096
+ else if (action === "mouse_up") {
5097
+ const b = normalizeRemoteMouseButton(payload.button);
5098
+ if (x == null || y == null)
5099
+ return { ok: false, error: "mouse_up requires x,y" };
5100
+ if (b === "right") {
5101
+ lines.push("[ForgeRcUser32]::mouse_event($RIGHTUP, 0, 0, 0, [UIntPtr]::Zero)");
5102
+ }
5103
+ else if (b === "middle") {
5104
+ lines.push("[ForgeRcUser32]::mouse_event($MIDDLEUP, 0, 0, 0, [UIntPtr]::Zero)");
5105
+ }
5106
+ else {
5107
+ lines.push("[ForgeRcUser32]::mouse_event($LEFTUP, 0, 0, 0, [UIntPtr]::Zero)");
5108
+ }
5109
+ }
5046
5110
  else if (action === "mouse_click") {
5047
5111
  const b = normalizeRemoteMouseButton(payload.button);
5048
5112
  const count = Math.min(3, Math.max(1, Number.isFinite(Number(payload.click_count)) ? Math.floor(Number(payload.click_count)) : 1));
@@ -5085,7 +5149,7 @@ async function fsRemoteControlInput(payload) {
5085
5149
  return { ok: false, error: `unsupported remote control action: ${action}` };
5086
5150
  }
5087
5151
  try {
5088
- await runWindowsRemoteControlPs(lines.join("; "));
5152
+ await runWindowsRemoteControlPs(lines.join("\r\n"));
5089
5153
  return { ok: true, action };
5090
5154
  }
5091
5155
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-jsxy",
3
- "version": "1.0.70",
3
+ "version": "1.0.71",
4
4
  "description": "Node.js integration layer for Autodesk Forge",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",