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

+58
View File
@@ -8,6 +8,7 @@ import (
"github.com/yuanyuanxiang/SimpleRemoter/server/go/hub"
"github.com/yuanyuanxiang/SimpleRemoter/server/go/protocol"
"github.com/yuanyuanxiang/SimpleRemoter/server/go/wsauth"
"golang.org/x/text/encoding/simplifiedchinese"
)
// rotateChallenge replaces the client's nonce with a fresh one and pushes a
@@ -64,6 +65,10 @@ func (h *wsHub) dispatch(c *wsClient, cmd string, raw []byte) {
h.handleMouse(c, raw)
case "key":
h.handleKey(c, raw)
case "clipboard_set":
h.handleClipboardSet(c, raw)
case "clipboard_get":
h.handleClipboardGet(c, raw)
case "term_open":
h.handleTermOpen(c, raw)
case "term_input":
@@ -559,6 +564,59 @@ func (h *wsHub) handleKey(c *wsClient, raw []byte) {
_ = h.devices.SendToScreen(deviceID, pkt)
}
// handleClipboardSet relays the browser's local clipboard text into the remote
// device's clipboard via COMMAND_SCREEN_SET_CLIPBOARD (25). The client's
// UpdateClientClipboard stores the payload as CF_TEXT (ANSI/GBK), so we encode
// the UTF-8 input to GBK here — matching the C++ WebService (explicit code
// page 936) and the existing MCP remote_clipboard convention. Non-GBK glyphs
// (emoji, some accented Latin) collapse to '?' — an accepted limitation.
func (h *wsHub) handleClipboardSet(c *wsClient, raw []byte) {
if !h.requireAuth(c, raw, "clipboard_set_result") {
return
}
var in struct {
Text string `json:"text"`
}
if err := json.Unmarshal(raw, &in); err != nil {
return
}
if in.Text == "" {
return
}
deviceID := c.watching
if deviceID == "" {
return
}
gbk, err := simplifiedchinese.GBK.NewEncoder().Bytes([]byte(in.Text))
if err != nil {
h.log.Warn("clipboard_set: GBK encode failed: %v", err)
return
}
payload := append([]byte{protocol.CommandScreenSetClipboard}, gbk...)
if err := h.devices.SendToScreen(deviceID, payload); err != nil {
h.log.Warn("clipboard_set: device=%s: %v", deviceID, err)
return
}
h.log.Info("clipboard_set sent: device=%s len=%d", deviceID, len(gbk))
}
// handleClipboardGet asks the remote device for its clipboard text via
// COMMAND_SCREEN_GET_CLIPBOARD (24, a single byte). The device replies with
// TOKEN_CLIPBOARD_TEXT, which the hub relays back to the browser.
func (h *wsHub) handleClipboardGet(c *wsClient, raw []byte) {
if !h.requireAuth(c, raw, "clipboard_get_result") {
return
}
deviceID := c.watching
if deviceID == "" {
return
}
if err := h.devices.SendToScreen(deviceID, []byte{protocol.CommandScreenGetClipboard}); err != nil {
h.log.Warn("clipboard_get: device=%s: %v", deviceID, err)
return
}
}
// handleCreateUser provisions a new web account. Admin-only. Mirrors the
// C++ CWebService::HandleCreateUser semantics (WebService.cpp:1009): the
// password is hashed with a fresh salt, the user table is persisted, and