Add VNC clipboard sync

RFB's ServerCutText/ClientCutText messages are much simpler than RDP's
CLIPRDR channel (no format-list/format-request negotiation -- text is
just sent directly in both directions), so ServerCutText now emits
remoteClipboardTextChanged instead of being discarded, and
VncSessionBackend overrides setClipboardText to send ClientCutText
immediately. Extends session_tab.cpp's clipboard-sync gate to cover
VNC alongside RDP; the QClipboard wiring itself was already
protocol-agnostic. Latin-1 only, per RFB's wire format -- no Unicode
clipboard extension is in scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 20:31:03 -06:00
co-authored by Claude Sonnet 5
parent 35d4daec7f
commit 3dd894407a
4 changed files with 134 additions and 5 deletions
+3 -2
View File
@@ -77,8 +77,9 @@ SessionTab::SessionTab(const Profile& profile,
m_terminalFontPointSize(preferences.terminalFontPointSize > 0
? preferences.terminalFontPointSize
: 0),
m_clipboardSyncSupported(profile.protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive)
== 0),
m_clipboardSyncSupported(
profile.protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0
|| profile.protocol.compare(QStringLiteral("VNC"), Qt::CaseInsensitive) == 0),
m_sshTerminal(nullptr),
m_rdpDisplay(nullptr),
m_vncDisplay(nullptr),
+28 -2
View File
@@ -263,6 +263,16 @@ void VncSessionBackend::sendMouseWheelEvent(int x, int y, int deltaX, int deltaY
}
}
void VncSessionBackend::setClipboardText(const QString& text)
{
if (m_socket->state() != QAbstractSocket::ConnectedState) {
return;
}
// Unlike RDP's CLIPRDR (list -> server request -> response), RFB's
// ClientCutText has no negotiation: send it immediately.
sendClientCutText(text);
}
void VncSessionBackend::onSocketConnected()
{
emit eventLogged(QStringLiteral("TCP connection established; waiting for VNC handshake."));
@@ -465,6 +475,19 @@ void VncSessionBackend::sendWheelClick(quint8 wheelBit)
m_socket->write(release);
}
void VncSessionBackend::sendClientCutText(const QString& text)
{
// Latin-1 only, matching ServerCutText's decode above -- RFB has no
// Unicode clipboard extension in scope here.
const QByteArray latin1 = text.toLatin1();
QByteArray msg;
msg.append(kMsgClientCutText);
msg.append(3, char(0)); // padding
appendU32BE(msg, static_cast<quint32>(latin1.size()));
msg.append(latin1);
m_socket->write(msg);
}
void VncSessionBackend::finishHandshakeIntoRunningState()
{
emit remoteDesktopSizeChanged(m_framebuffer.width(), m_framebuffer.height());
@@ -895,10 +918,13 @@ void VncSessionBackend::processReceiveBuffer()
if (!haveBytes(static_cast<int>(m_pendingLength))) {
return;
}
// Clipboard sync is out of scope for this pass; consume and
// discard so the byte stream stays in sync.
// RFB's ServerCutText is Latin-1 only (RFC 6143 SS7.5.4) -- no
// Unicode extension is implemented, unlike RDP's CLIPRDR.
const QString text =
QString::fromLatin1(m_recvBuffer.left(static_cast<int>(m_pendingLength)));
m_recvBuffer.remove(0, static_cast<int>(m_pendingLength));
m_rfbState = RfbState::WaitingServerMessageType;
emit remoteClipboardTextChanged(text);
break;
}
}
+4 -1
View File
@@ -21,7 +21,8 @@ class QTcpSocket;
// Scope (see plan / issue #3 for the full rationale): standard VNC
// Authentication (security type 2) and no-auth (type 1) only -- not
// Apple's Screen Sharing scheme (type 30). Raw + CopyRect encodings only.
// No dynamic resize, no remote cursor shape sync, no clipboard sync.
// No dynamic resize, no remote cursor shape sync. Clipboard sync (Latin-1
// only, per RFB's ServerCutText/ClientCutText) is supported.
class VncSessionBackend : public SessionBackend
{
Q_OBJECT
@@ -52,6 +53,7 @@ public slots:
void sendMouseMoveEvent(int x, int y) override;
void sendMouseButtonEvent(int x, int y, int button, bool pressed) override;
void sendMouseWheelEvent(int x, int y, int deltaX, int deltaY) override;
void setClipboardText(const QString& text) override;
private slots:
void onSocketConnected();
@@ -129,6 +131,7 @@ private:
void onRectangleFinished();
void sendPointerEvent();
void sendWheelClick(quint8 wheelBit);
void sendClientCutText(const QString& text);
};
#endif