From 67ea7ea6b32cdd3d918ce446dcf6d9406e7504a4 Mon Sep 17 00:00:00 2001 From: Keith Smith Date: Sat, 26 Sep 2026 15:38:58 -0600 Subject: [PATCH] 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 --- CMakeLists.txt | 2 +- docs/PROGRESS.md | 5 +++++ src/session_tab.cpp | 39 +++++++++++++++++++++++++++------------ src/session_tab.h | 7 +++++++ 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a364eac..8f9a7ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ 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_REQUIRED ON) diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index cf9ae20..20804a4 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -242,6 +242,11 @@ Delivered: 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 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 used to abort the connection generically; `kAnnouncedEncodings` is now the single source of truth for what `SetEncodings` announces and what diff --git a/src/session_tab.cpp b/src/session_tab.cpp index a00a4a3..2f0237f 100644 --- a/src/session_tab.cpp +++ b/src/session_tab.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -799,6 +800,26 @@ void SessionTab::onBackendRemoteClipboardTextChanged(const QString& text) } 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()) { return; @@ -814,22 +835,16 @@ void SessionTab::onSystemClipboardChanged() 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); appendEvent(QStringLiteral("Clipboard: sending %1 characters to remote.").arg(text.size())); } +void SessionTab::showEvent(QShowEvent* event) +{ + QWidget::showEvent(event); + pushLocalClipboardToRemote(); +} + void SessionTab::setupUi() { auto* rootLayout = new QVBoxLayout(this); diff --git a/src/session_tab.h b/src/session_tab.h index 7321a2a..e96c4c0 100644 --- a/src/session_tab.h +++ b/src/session_tab.h @@ -24,6 +24,7 @@ class QComboBox; class QLabel; class QPushButton; class QScrollArea; +class QShowEvent; class KodoTerm; struct SessionUiPreferences @@ -99,6 +100,11 @@ signals: void requestSetClipboardText(const QString& text); 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: void onBackendStateChanged(SessionState state, const QString& message); void onBackendEventLogged(const QString& message); @@ -166,6 +172,7 @@ private: std::function)> callback, bool maskInput = true); void hidePasswordPrompt(); + void pushLocalClipboardToRemote(); bool validateProfileForConnect(); void appendEvent(const QString& message); void setState(SessionState state, const QString& message);