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
|
|
793
|
-
const
|
|
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
|
-
|
|
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.
|
|
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
|
|
793
|
-
const
|
|
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
|
-
|
|
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;
|
package/dist/fsProtocol.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
"-
|
|
4941
|
-
|
|
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
|
-
"
|
|
5026
|
-
"
|
|
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))
|
|
5037
|
-
|
|
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) {
|