Fix RDP/VNC clipboard sync randomly stopping

Local copies were de-duplicated against the last synced text with no
expiry, so one lost send made re-copying the same text a no-op; every
open tab listened to the system clipboard, letting background sessions
overwrite it and forwarding one tab's update to all others; and RDP
never sent the initial Format List MS-RDPECLIP requires after Monitor
Ready, which the server can re-send mid-session (e.g. rdpclip.exe
restart), leaving sync dead until reconnect. Announce failures are now
logged to the event log instead of silently dropped.

Bump version to v2026.9.26.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ksmithandClaude Sonnet 5 committed 2026-09-26 14:57:48 -06:00
1 parent 34c16cbfa8
commit 0f821feeed
5 files changed
+84 -9

No files matched your search

+32 -1
View File
@@ -2000,6 +2000,10 @@ void RdpSessionBackend::sendClipboardTextToRemote(rdp_freerdp* instance, const Q
}
if (cliprdrContext == nullptr || cliprdrContext->ClientFormatList == nullptr) {
// Kept in m_pendingLocalClipboardText and announced once the channel
// reports MonitorReady (see onCliprdrMonitorReady()).
emit eventLogged(QStringLiteral(
"RDP clipboard: channel not ready; copied text will be sent once it is."));
return;
}
@@ -2012,7 +2016,11 @@ void RdpSessionBackend::sendClipboardTextToRemote(rdp_freerdp* instance, const Q
formatList.formats = &format;
const UINT rc = cliprdrContext->ClientFormatList(cliprdrContext, &formatList);
Q_UNUSED(rc);
if (rc != CHANNEL_RC_OK) {
emit eventLogged(
QStringLiteral("RDP clipboard: failed to announce copied text to the server (0x%1).")
.arg(rc, 0, 16));
}
#endif
}
@@ -2324,6 +2332,29 @@ void RdpSessionBackend::onCliprdrMonitorReady()
}
emit eventLogged(QStringLiteral("RDP clipboard channel ready."));
QString pendingText;
{
std::lock_guard<std::mutex> guard(m_cliprdrMutex);
pendingText = m_pendingLocalClipboardText;
}
if (!pendingText.isEmpty()) {
sendClipboardTextToRemote(nullptr, pendingText);
} else if (cliprdrContext->ClientFormatList != nullptr) {
// MS-RDPECLIP requires the client to answer Monitor Ready with its
// capabilities *and* an initial Format List (empty is fine); the
// server doesn't treat the channel as initialized until it has
// both. The server can send Monitor Ready again mid-session -- e.g.
// when rdpclip.exe restarts -- and without this the clipboard
// stayed dead until the session was reconnected.
CLIPRDR_FORMAT_LIST emptyList = {};
const UINT rc = cliprdrContext->ClientFormatList(cliprdrContext, &emptyList);
if (rc != CHANNEL_RC_OK) {
emit eventLogged(
QStringLiteral("RDP clipboard: initial format list failed (0x%1).")
.arg(rc, 0, 16));
}
}
#endif
}