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:
1 parent
e6b4fe3a85
commit
0c04f7516b
10 files changed
+290
-12
No files matched your search
@@ -1126,6 +1126,10 @@ VOID CScreenSpyDlg::OnReceiveComplete()
|
||||
case TOKEN_CLIPBOARD_TEXT: {
|
||||
Buffer str = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(1);
|
||||
UpdateServerClipboard(str.c_str(), str.length());
|
||||
// 反向回传:远程剪贴板文本(UTF-8)→ Web 客户端
|
||||
if (m_bIsWebSession && WebService().IsRunning()) {
|
||||
WebService().NotifyClipboardText(m_ClientID, std::string(str.c_str(), str.length()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TOKEN_BITMAPINFO: {
|
||||
|
||||
@@ -393,6 +393,10 @@ void CWebService::ServerThread(int port) {
|
||||
HandleMouse(ws_ptr, msg);
|
||||
} else if (cmd == "key") {
|
||||
HandleKey(ws_ptr, msg);
|
||||
} else if (cmd == "clipboard_set") {
|
||||
HandleClipboardSet(ws_ptr, msg);
|
||||
} else if (cmd == "clipboard_get") {
|
||||
HandleClipboardGet(ws_ptr, token);
|
||||
} else if (cmd == "rdp_reset") {
|
||||
HandleRdpReset(ws_ptr, token);
|
||||
} else if (cmd == "audio_toggle") {
|
||||
@@ -929,6 +933,93 @@ void CWebService::HandleKey(void* ws_ptr, const std::string& msg) {
|
||||
ctx->Send2Client(szData, length);
|
||||
}
|
||||
|
||||
void CWebService::HandleClipboardSet(void* ws_ptr, const std::string& msg) {
|
||||
// Parse JSON
|
||||
Json::Value root;
|
||||
Json::Reader reader;
|
||||
if (!reader.parse(msg, root)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string token = root.get("token", "").asString();
|
||||
std::string text = root.get("text", "").asString();
|
||||
|
||||
if (text.empty()) return;
|
||||
|
||||
// Validate token
|
||||
std::string username, role;
|
||||
if (!ValidateToken(token, username, role)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get device being watched
|
||||
uint64_t device_id = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_ClientsMutex);
|
||||
auto it = m_Clients.find(ws_ptr);
|
||||
if (it != m_Clients.end()) {
|
||||
device_id = it->second.watch_device_id;
|
||||
it->second.last_activity = (uint64_t)time(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (device_id == 0) return;
|
||||
|
||||
// Get screen context
|
||||
CONTEXT_OBJECT* ctx = GetScreenContext(device_id);
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
// UTF-8 → UTF-16 → GBK(936)。客户端 UpdateClientClipboard 以 CF_TEXT(ANSI/GBK) 落剪贴板,
|
||||
// 故显式用 936 而非 CP_ACP,避免服务端 locale 非中文时出错。
|
||||
int wlen = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), (int)text.size(), nullptr, 0);
|
||||
if (wlen <= 0) return;
|
||||
std::wstring wide(wlen, L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), (int)text.size(), &wide[0], wlen);
|
||||
|
||||
int glen = WideCharToMultiByte(936, 0, wide.c_str(), wlen, nullptr, 0, nullptr, nullptr);
|
||||
if (glen <= 0) return;
|
||||
|
||||
// 命令字节 + GBK 文本
|
||||
std::vector<BYTE> szData(1 + glen);
|
||||
szData[0] = COMMAND_SCREEN_SET_CLIPBOARD;
|
||||
WideCharToMultiByte(936, 0, wide.c_str(), wlen, (char*)szData.data() + 1, glen, nullptr, nullptr);
|
||||
|
||||
ctx->Send2Client(szData.data(), (ULONG)szData.size());
|
||||
}
|
||||
|
||||
void CWebService::HandleClipboardGet(void* ws_ptr, const std::string& token) {
|
||||
// Validate token
|
||||
std::string username, role;
|
||||
if (!ValidateToken(token, username, role)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get device being watched
|
||||
uint64_t device_id = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_ClientsMutex);
|
||||
auto it = m_Clients.find(ws_ptr);
|
||||
if (it != m_Clients.end()) {
|
||||
device_id = it->second.watch_device_id;
|
||||
it->second.last_activity = (uint64_t)time(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (device_id == 0) return;
|
||||
|
||||
// Get screen context
|
||||
CONTEXT_OBJECT* ctx = GetScreenContext(device_id);
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 单字节命令,客户端以 SendClientClipboard → TOKEN_CLIPBOARD_TEXT 回包
|
||||
BYTE bToken = COMMAND_SCREEN_GET_CLIPBOARD;
|
||||
ctx->Send2Client(&bToken, sizeof(BYTE));
|
||||
}
|
||||
|
||||
void CWebService::HandleRdpReset(void* ws_ptr, const std::string& token) {
|
||||
std::string username, role;
|
||||
if (!ValidateToken(token, username, role)) {
|
||||
@@ -1711,6 +1802,26 @@ void CWebService::BroadcastCursor(uint64_t device_id, uint8_t cursor_index) {
|
||||
}
|
||||
}
|
||||
|
||||
void CWebService::NotifyClipboardText(uint64_t device_id, const std::string& text) {
|
||||
if (m_bStopping) return;
|
||||
|
||||
Json::Value res;
|
||||
res["cmd"] = "clipboard_text";
|
||||
res["id"] = device_id;
|
||||
res["text"] = text;
|
||||
|
||||
Json::StreamWriterBuilder builder;
|
||||
builder["indentation"] = "";
|
||||
std::string json = Json::writeString(builder, res);
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_ClientsMutex);
|
||||
for (auto& [ws_ptr, client] : m_Clients) {
|
||||
if (client.watch_device_id == device_id) {
|
||||
SendText(ws_ptr, json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CWebService::StartRemoteDesktop(uint64_t device_id) {
|
||||
if (!m_pParentDlg) return false;
|
||||
|
||||
|
||||
@@ -160,6 +160,9 @@ public:
|
||||
// Cursor change notification (called from ScreenSpyDlg)
|
||||
void BroadcastCursor(uint64_t device_id, uint8_t cursor_index);
|
||||
|
||||
// 远程剪贴板文本回传通知(called from ScreenSpyDlg,payload 为 UTF-8)
|
||||
void NotifyClipboardText(uint64_t device_id, const std::string& text);
|
||||
|
||||
// Get count of web clients watching a device
|
||||
int GetWebClientCount(uint64_t device_id);
|
||||
|
||||
@@ -187,6 +190,8 @@ private:
|
||||
void HandlePing(void* ws_ptr, const std::string& token);
|
||||
void HandleMouse(void* ws_ptr, const std::string& msg);
|
||||
void HandleKey(void* ws_ptr, const std::string& msg);
|
||||
void HandleClipboardSet(void* ws_ptr, const std::string& msg);
|
||||
void HandleClipboardGet(void* ws_ptr, const std::string& token);
|
||||
void HandleRdpReset(void* ws_ptr, const std::string& token);
|
||||
void HandleAudioToggle(void* ws_ptr, const std::string& token);
|
||||
|
||||
|
||||
Reference in new issue
Block a user