From 62ba3693af293f2244b859908d171a3da1eebd14 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Tue, 20 Oct 2026 11:34:24 +0800 Subject: [PATCH] [fix] restore clipboard sync after focus returns, refs #121 --- .../src/gui/application/gui_application.cpp | 16 ++++-- .../clipboard/clipboard_controller.cpp | 49 ++++++++++++++++++- .../features/clipboard/clipboard_controller.h | 6 ++- 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/gui/application/gui_application.cpp b/apps/desktop/src/gui/application/gui_application.cpp index abe4ad1f..60876d49 100644 --- a/apps/desktop/src/gui/application/gui_application.cpp +++ b/apps/desktop/src/gui/application/gui_application.cpp @@ -4382,15 +4382,20 @@ void GuiApplication::SelectStreamTab(int index) { (*ui_->stream)->set_has_frame(false); } std::shared_ptr selected_session; + bool clipboard_resumed = false; { std::lock_guard delivery_lock(audio_delivery_mutex_); std::shared_lock lock(remote_sessions_mutex_); for (auto& [id, props] : remote_sessions_) { if (props) { props->tab_selected_ = id == focused_remote_id_; - props->clipboard_allowed_ = props->tab_selected_ && - props->control_mouse_ && - focus_on_stream_window_; + const bool clipboard_allowed = props->tab_selected_ && + props->control_mouse_ && + focus_on_stream_window_; + if (!props->clipboard_allowed_.exchange(clipboard_allowed) && + clipboard_allowed) { + clipboard_resumed = true; + } } } const auto selected = remote_sessions_.find(selected_remote_id); @@ -4402,6 +4407,11 @@ void GuiApplication::SelectStreamTab(int index) { selected_session->connection_status_.load() == ConnectionStatus::Connected; } + if (clipboard_resumed) { + // Flush a copy made in another local app before the next paste shortcut, + // instead of waiting for the periodic clipboard poll. + clipboard_.HandleLocalUpdate(); + } SyncAudioPlayback(); } diff --git a/apps/desktop/src/gui/features/clipboard/clipboard_controller.cpp b/apps/desktop/src/gui/features/clipboard/clipboard_controller.cpp index 0252fb8f..90a5fece 100644 --- a/apps/desktop/src/gui/features/clipboard/clipboard_controller.cpp +++ b/apps/desktop/src/gui/features/clipboard/clipboard_controller.cpp @@ -30,6 +30,7 @@ uint32_t ClipboardController::event_type() const { return event_type_; } void ClipboardController::Initialize() { last_text_.clear(); + deferred_local_session_.reset(); char *clipboard_text = SDL_GetClipboardText(); if (clipboard_text) { @@ -42,6 +43,7 @@ void ClipboardController::Initialize() { void ClipboardController::Shutdown() { events_enabled_.store(false, std::memory_order_release); + deferred_local_session_.reset(); std::lock_guard lock(pending_mutex_); pending_remote_text_.reset(); } @@ -126,6 +128,7 @@ void ClipboardController::ApplyPendingRemoteText() { // SDL will normally emit SDL_EVENT_CLIPBOARD_UPDATE for this write. Update // the baseline first so that event cannot echo the text back to the sender. last_text_ = std::move(*pending_text); + deferred_local_session_.reset(); } void ClipboardController::HandleLocalUpdate() { @@ -134,6 +137,7 @@ void ClipboardController::HandleLocalUpdate() { } if (!SDL_HasClipboardText()) { last_text_.clear(); + deferred_local_session_.reset(); return; } @@ -146,12 +150,14 @@ void ClipboardController::HandleLocalUpdate() { std::string text(clipboard_text); SDL_free(clipboard_text); if (text == last_text_) { + SendDeferredLocalText(); return; } // Record the value before sending. Duplicate SDL notifications and a // remote write of the same value must not create a clipboard feedback loop. last_text_ = text; + deferred_local_session_.reset(); if (text.empty()) { return; } @@ -163,11 +169,50 @@ void ClipboardController::HandleLocalUpdate() { SendToPeers(text); } +void ClipboardController::SendDeferredLocalText() { + const auto session = deferred_local_session_.lock(); + if (!session) { + return; + } + + std::shared_lock lock(owner_.remote_sessions_mutex_); + const auto current = owner_.remote_sessions_.find(session->remote_id_); + if (current == owner_.remote_sessions_.end() || current->second != session || + session->closing_ || !session->peer_ || !session->connection_established_ || + !session->tab_opened_ || !session->tab_selected_ || + !session->control_mouse_) { + deferred_local_session_.reset(); + return; + } + if (!session->clipboard_allowed_.load()) { + return; + } + + const int ret = SendReliableDataFrame( + session->peer_, last_text_.data(), last_text_.size(), + session->clipboard_label_.c_str()); + if (ret != 0) { + LOG_WARN("Failed to send deferred clipboard data to peer [{}], ret={}", + session->remote_id_, ret); + return; + } + deferred_local_session_.reset(); +} + int ClipboardController::SendToPeers(const std::string &text) { std::shared_lock lock(owner_.remote_sessions_mutex_); for (const auto &[remote_id, props] : owner_.remote_sessions_) { - if (!props || !props->peer_ || !props->connection_established_ || - !props->clipboard_allowed_.load()) { + if (!props || props->closing_ || !props->peer_ || + !props->connection_established_) { + continue; + } + if (!props->clipboard_allowed_.load()) { + // Copying in another local app temporarily releases keyboard control. + // Keep this copy for the same selected stream, without sharing it with + // background tabs or DeskHub previews. + if (props->tab_opened_ && props->tab_selected_ && props->control_mouse_) { + deferred_local_session_ = props; + } continue; } diff --git a/apps/desktop/src/gui/features/clipboard/clipboard_controller.h b/apps/desktop/src/gui/features/clipboard/clipboard_controller.h index e4d4d6dc..f7206625 100644 --- a/apps/desktop/src/gui/features/clipboard/clipboard_controller.h +++ b/apps/desktop/src/gui/features/clipboard/clipboard_controller.h @@ -41,6 +41,7 @@ public: private: int SendToPeers(const std::string &text); + void SendDeferredLocalText(); GuiRuntime &owner_; uint32_t event_type_ = 0; @@ -51,8 +52,11 @@ private: std::weak_ptr pending_session_; uint64_t pending_generation_ = 0; std::string last_text_; + // UI thread only. Retain the destination of a local copy while its stream + // window is unfocused; last_text_ holds the latest value to send on return. + std::weak_ptr deferred_local_session_; }; } // namespace crossdesk -#endif // _CLIPBOARD_CONTROLLER_H_ \ No newline at end of file +#endif // _CLIPBOARD_CONTROLLER_H_