Sync local clipboard to a tab when it becomes visible

Only the visible tab syncs the clipboard, but a tab in the background
missed local clipboard changes and kept a stale remote clipboard when
switched to, so with two RDP/VNC sessions only the one last copied in
worked. A tab now pushes the current local clipboard to its remote on
show.

Bump version to v2026.9.26.2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ksmithandClaude Sonnet 5 committed 2026-09-26 15:38:58 -06:00
1 parent 0f821feeed
commit 6a68ed0211
4 files changed
+40 -13

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.21) cmake_minimum_required(VERSION 3.21)
project(OrbitHub VERSION 2026.9.26 LANGUAGES CXX) project(OrbitHub VERSION 2026.9.26.2 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
+5
View File
@@ -242,6 +242,11 @@ Delivered:
and not-yet-ready-channel copies are logged to the session event log and not-yet-ready-channel copies are logged to the session event log
(with a line per copy sent/received) instead of silently dropped. Not (with a line per copy sent/received) instead of silently dropped. Not
reproduced locally; needs confirmation from real use reproduced locally; needs confirmation from real use
- Follow-up to the clipboard fix above: restricting sync to the visible
tab left a tab that had been in the background with a stale remote
clipboard when switched to (with two RDP sessions only the one last
copied in worked). A tab now pushes the current local clipboard to its
remote whenever it becomes visible
- Robustness fix: an unrecognized `FramebufferUpdate` rectangle encoding - Robustness fix: an unrecognized `FramebufferUpdate` rectangle encoding
used to abort the connection generically; `kAnnouncedEncodings` is now used to abort the connection generically; `kAnnouncedEncodings` is now
the single source of truth for what `SetEncodings` announces and what the single source of truth for what `SetEncodings` announces and what
+27 -12
View File
@@ -28,6 +28,7 @@
#include <QComboBox> #include <QComboBox>
#include <QProcessEnvironment> #include <QProcessEnvironment>
#include <QPushButton> #include <QPushButton>
#include <QShowEvent>
#include <QScrollArea> #include <QScrollArea>
#include <QThread> #include <QThread>
#include <QTimer> #include <QTimer>
@@ -799,6 +800,26 @@ void SessionTab::onBackendRemoteClipboardTextChanged(const QString& text)
} }
void SessionTab::onSystemClipboardChanged() void SessionTab::onSystemClipboardChanged()
{
const QClipboard* clipboard = QApplication::clipboard();
const QString text = clipboard->mimeData()->hasText() ? clipboard->text() : QString();
// Suppress only the change we caused ourselves, and only briefly. The
// old "same as last synced text" check had no expiry: once a send was
// lost (channel not ready, remote clipboard changed to something we
// never saw) copying that same text again did nothing, indefinitely.
constexpr qint64 kEchoWindowMs = 1000;
const bool isEcho = !m_pendingClipboardEcho.isEmpty() && text == m_pendingClipboardEcho
&& QDateTime::currentMSecsSinceEpoch() - m_pendingClipboardEchoMs < kEchoWindowMs;
m_pendingClipboardEcho.clear();
if (isEcho) {
return;
}
pushLocalClipboardToRemote();
}
void SessionTab::pushLocalClipboardToRemote()
{ {
if (!m_clipboardSyncSupported || m_state != SessionState::Connected || !isVisible()) { if (!m_clipboardSyncSupported || m_state != SessionState::Connected || !isVisible()) {
return; return;
@@ -814,22 +835,16 @@ void SessionTab::onSystemClipboardChanged()
return; return;
} }
// Suppress only the change we caused ourselves, and only briefly. The
// old "same as last synced text" check had no expiry: once a send was
// lost (channel not ready, remote clipboard changed to something we
// never saw) copying that same text again did nothing, indefinitely.
constexpr qint64 kEchoWindowMs = 1000;
const bool isEcho = !m_pendingClipboardEcho.isEmpty() && text == m_pendingClipboardEcho
&& QDateTime::currentMSecsSinceEpoch() - m_pendingClipboardEchoMs < kEchoWindowMs;
m_pendingClipboardEcho.clear();
if (isEcho) {
return;
}
emit requestSetClipboardText(text); emit requestSetClipboardText(text);
appendEvent(QStringLiteral("Clipboard: sending %1 characters to remote.").arg(text.size())); appendEvent(QStringLiteral("Clipboard: sending %1 characters to remote.").arg(text.size()));
} }
void SessionTab::showEvent(QShowEvent* event)
{
QWidget::showEvent(event);
pushLocalClipboardToRemote();
}
void SessionTab::setupUi() void SessionTab::setupUi()
{ {
auto* rootLayout = new QVBoxLayout(this); auto* rootLayout = new QVBoxLayout(this);
+7
View File
@@ -24,6 +24,7 @@ class QComboBox;
class QLabel; class QLabel;
class QPushButton; class QPushButton;
class QScrollArea; class QScrollArea;
class QShowEvent;
class KodoTerm; class KodoTerm;
struct SessionUiPreferences struct SessionUiPreferences
@@ -99,6 +100,11 @@ signals:
void requestSetClipboardText(const QString& text); void requestSetClipboardText(const QString& text);
void requestProvideUsername(const QString& username); void requestProvideUsername(const QString& username);
protected:
// A tab that wasn't in front while the local clipboard changed missed
// that change (only the visible tab syncs), so catch it up on show.
void showEvent(QShowEvent* event) override;
private slots: private slots:
void onBackendStateChanged(SessionState state, const QString& message); void onBackendStateChanged(SessionState state, const QString& message);
void onBackendEventLogged(const QString& message); void onBackendEventLogged(const QString& message);
@@ -166,6 +172,7 @@ private:
std::function<void(std::optional<QString>)> callback, std::function<void(std::optional<QString>)> callback,
bool maskInput = true); bool maskInput = true);
void hidePasswordPrompt(); void hidePasswordPrompt();
void pushLocalClipboardToRemote();
bool validateProfileForConnect(); bool validateProfileForConnect();
void appendEvent(const QString& message); void appendEvent(const QString& message);
void setState(SessionState state, const QString& message); void setState(SessionState state, const QString& message);