Feature: Web remote desktop bidirectional text clipboard sync

Add keyboard-shortcut (Ctrl+C / Ctrl+X / Ctrl+V) text clipboard transfer
between the browser and the remote desktop, reusing the clipboard protocol
that already exists in the native viewer (COMMAND_SCREEN_GET_CLIPBOARD=24,
COMMAND_SCREEN_SET_CLIPBOARD=25, TOKEN_CLIPBOARD_TEXT=118) so the client
needs no change.

Local -> remote: pressing Ctrl+V reads the browser clipboard, pushes it to
the remote clipboard (UTF-8 -> GBK, matching the client's CF_TEXT/ANSI
handling) and then synthesizes a remote Ctrl+V so the pasted text is the
newly delivered one.

Remote -> local: Ctrl+C or Ctrl+X is forwarded to the remote as usual and,
after a short delay, the remote clipboard is pulled back and written to the
browser clipboard for a later local Ctrl+V.

Both the MFC and Go servers get the same three clipboard signaling paths
(clipboard_set, clipboard_get, clipboard_text) so the shared single-page
frontend behaves identically on either backend.

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxianganddeepseek-v4-pro committed 2026-10-17 19:52:38 +02:00
1 parent e6b4fe3a85
commit 0c04f7516b
10 files changed
+290 -12

No files matched your search

+53
View File
@@ -1365,6 +1365,7 @@
<script>
let ws = null, token = null, decoder = null, devices = [], currentDevice = null;
let frameCount = 0, lastFrameTime = 0, fps = 0, pingInterval = null;
let skipNextCtrlVKeyup = false; // 剪贴板桥:抑制 pasteToRemote 合成的 Ctrl+V 物理 keyup
// FPS 计数:原始风格——decoder.onOutput 里 ++,每经过 1 秒采样一次。
// 简单直接,与本次会话改动前一致。
// 网络流量统计:handleBinaryFrame 累加,每 1 秒钟 renderStats 读出
@@ -1863,6 +1864,12 @@
applyRemoteCursor(msg.index);
}
break;
case 'clipboard_text':
// 远程剪贴板文本回传:写回浏览器(即本机)剪贴板
if (msg.text) {
try { navigator.clipboard.writeText(msg.text); } catch (e) {}
}
break;
case 'device_offline':
// Only handle if this is the device we're currently viewing
if (!token) break;
@@ -3714,6 +3721,28 @@
}
}
// 本地剪贴板 → 远程:读浏览器剪贴板,先 clipboard_set 到远程,再合成一次完整 Ctrl+V。
// 合成按键放在 readText 的 then 里,保证 clipboard_set 先于 Ctrl+V 到达(同一条 WS 有序)。
function pasteToRemote() {
const press = () => { sendKey(86, true, false); sendKey(86, false, false); };
navigator.clipboard.readText().then(function(text) {
if (text && ws && ws.readyState === WebSocket.OPEN && token) {
ws.send(JSON.stringify({ cmd: 'clipboard_set', token, text }));
}
press();
}).catch(function() { press(); }); // 读剪贴板被拒/为空:仍执行一次普通远程粘贴
}
// 远程剪贴板 → 本地:等远程完成拷贝(启发式延时)后请求远程剪贴板,回包写回浏览器剪贴板。
// 不保证 100% 命中,失败用户二次 Ctrl+C 即可。
function scheduleCopyFromRemote() {
setTimeout(function() {
if (ws && ws.readyState === WebSocket.OPEN && token) {
ws.send(JSON.stringify({ cmd: 'clipboard_get', token }));
}
}, 250);
}
function showTouchIndicator(x, y) {
touchIndicator.style.left = x + 'px';
touchIndicator.style.top = y + 'px';
@@ -4198,14 +4227,38 @@
// controlling the remote — see the F11 handler above. Skip here
// to let that one handle it.
if (e.key === 'F11' && !controlEnabled) return;
// 文本剪贴板桥(secure context 下 navigator.clipboard 可用)。
// Ctrl/Cmd+V:把本地剪贴板文本先灌到远程剪贴板,再合成一次 Ctrl+V 粘贴。
// Ctrl/Cmd+C / Ctrl/Cmd+X:照常转发给远程(远程完成拷贝/剪切),随后延迟拉取远程剪贴板回本地。
if (controlEnabled && (e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) {
if (e.keyCode === 86) { // Ctrl/Cmd+V
e.preventDefault();
skipNextCtrlVKeyup = true;
pasteToRemote();
return;
}
// e.keyCode === 67(C)/88(X) 时落到下面的 sendKey 原样转发,转发后再延迟拉取
}
e.preventDefault();
sendKey(e.keyCode, true, e.altKey);
if (controlEnabled && (e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && (e.keyCode === 67 || e.keyCode === 88)) {
scheduleCopyFromRemote();
}
});
document.addEventListener('keyup', function(e) {
if (!document.getElementById('screen-page').classList.contains('active')) return;
if (e.target.tagName === 'INPUT') return;
if (e.key === 'F11' && !controlEnabled) return;
// 由 pasteToRemote 合成的完整 Ctrl+V 按下:物理 keyup 不再转发,避免 down/up 乱序
if (e.keyCode === 86 && skipNextCtrlVKeyup) {
skipNextCtrlVKeyup = false;
e.preventDefault();
return;
}
e.preventDefault();
sendKey(e.keyCode, false, e.altKey);
});