[fix] restore clipboard sync after focus returns, refs #121

This commit is contained in:
dijunkun
2026-10-20 11:34:24 +08:00
parent 4e5bf66734
commit 62ba3693af
3 changed files with 65 additions and 6 deletions
@@ -4382,15 +4382,20 @@ void GuiApplication::SelectStreamTab(int index) {
(*ui_->stream)->set_has_frame(false);
}
std::shared_ptr<RemoteSession> 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();
}
@@ -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<std::mutex> 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;
}
@@ -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<gui_detail::RemoteSession> 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<gui_detail::RemoteSession> deferred_local_session_;
};
} // namespace crossdesk
#endif // _CLIPBOARD_CONTROLLER_H_
#endif // _CLIPBOARD_CONTROLLER_H_