RDP session fixes: correct keyboard scancodes, clipboard sync, cursor shapes

Fix RDP keyboard input using FreeRDP's authoritative X11-keycode-to-scancode
table instead of ad hoc bit math, which misread punctuation keys as unrelated
letter keys (e.g. apostrophe as B) because X11 keycode numbering only
coincidentally overlaps PC/AT scancodes.

Add bidirectional clipboard sync (CF_UNICODETEXT) over the cliprdr channel,
and RDP pointer/cursor shape sync so the local cursor reflects what the
remote OS wants displayed (resize handles, text I-beam, etc.) instead of
staying a static arrow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 14:53:03 -06:00
co-authored by Claude Sonnet 5
parent 27cc3a3bb2
commit 793fdd9366
8 changed files with 654 additions and 27 deletions
+77
View File
@@ -19,6 +19,7 @@
#include <QPlainTextEdit>
#include <QApplication>
#include <QClipboard>
#include <QMimeData>
#include <QComboBox>
#include <QProcessEnvironment>
#include <QPushButton>
@@ -74,6 +75,8 @@ SessionTab::SessionTab(const Profile& profile,
m_terminalFontPointSize(preferences.terminalFontPointSize > 0
? preferences.terminalFontPointSize
: 0),
m_clipboardSyncSupported(profile.protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive)
== 0),
m_sshTerminal(nullptr),
m_rdpDisplay(nullptr),
m_terminalOutput(nullptr),
@@ -193,6 +196,11 @@ SessionTab::SessionTab(const Profile& profile,
m_backend,
&SessionBackend::sendMouseWheelEvent,
Qt::QueuedConnection);
connect(this,
&SessionTab::requestSetClipboardText,
m_backend,
&SessionBackend::setClipboardText,
Qt::QueuedConnection);
connect(m_backend,
&SessionBackend::stateChanged,
@@ -237,10 +245,49 @@ SessionTab::SessionTab(const Profile& profile,
}
},
Qt::QueuedConnection);
connect(m_backend,
&SessionBackend::remoteClipboardTextChanged,
this,
&SessionTab::onBackendRemoteClipboardTextChanged,
Qt::QueuedConnection);
connect(m_backend,
&SessionBackend::cursorImageChanged,
this,
[this](const QImage& image, const QPoint& hotspot) {
if (m_rdpDisplay != nullptr) {
m_rdpDisplay->setCursorImage(image, hotspot);
}
},
Qt::QueuedConnection);
connect(m_backend,
&SessionBackend::cursorHidden,
this,
[this]() {
if (m_rdpDisplay != nullptr) {
m_rdpDisplay->setCursorHidden();
}
},
Qt::QueuedConnection);
connect(m_backend,
&SessionBackend::cursorReset,
this,
[this]() {
if (m_rdpDisplay != nullptr) {
m_rdpDisplay->setCursorDefault();
}
},
Qt::QueuedConnection);
m_backendThread->start();
}
if (m_clipboardSyncSupported) {
connect(QApplication::clipboard(),
&QClipboard::dataChanged,
this,
&SessionTab::onSystemClipboardChanged);
}
setState(SessionState::Disconnected, QStringLiteral("Ready to connect."));
QTimer::singleShot(0, this, &SessionTab::connectSession);
}
@@ -615,6 +662,36 @@ void SessionTab::onBackendHostKeyConfirmationRequested(const QString& prompt)
emit requestHostKeyConfirmation(reply == QMessageBox::Yes);
}
void SessionTab::onBackendRemoteClipboardTextChanged(const QString& text)
{
if (text == m_lastSyncedClipboardText) {
return;
}
m_lastSyncedClipboardText = text;
QApplication::clipboard()->setText(text);
}
void SessionTab::onSystemClipboardChanged()
{
if (!m_clipboardSyncSupported || m_state != SessionState::Connected) {
return;
}
const QClipboard* clipboard = QApplication::clipboard();
if (!clipboard->mimeData()->hasText()) {
return;
}
const QString text = clipboard->text();
if (text == m_lastSyncedClipboardText) {
return;
}
m_lastSyncedClipboardText = text;
emit requestSetClipboardText(text);
}
void SessionTab::setupUi()
{
auto* rootLayout = new QVBoxLayout(this);