fix: support ext_data_control_v1 Wayland protocol for clipboard sync
KDE Plasma 6.5+ dropped the zwlr_data_control_v1 protocol in favor of the standardized ext_data_control_v1 protocol. This broke clipboard sync between Linux Wayland hosts and remote peers — the first copy/paste works but subsequent clipboard changes are never forwarded. Root cause: Both clipboard-master (change detection) and wl-clipboard-rs (clipboard read/write) only supported zwlr_data_control_v1. On compositors that only expose ext_data_control_v1 (Plasma 6.5+), clipboard-master fell back to X11 mode and wl-clipboard-rs failed silently. Changes: - Update wl-clipboard-rs 0.9.0 -> 0.9.3 (adds ext_data_control_v1 support with automatic fallback to zwlr) - Update wayland-protocols-wlr 0.3.3 -> 0.3.9 - Switch clipboard-master to a fork that supports both ext_data_control_v1 (preferred) and zwlr_data_control_v1 (fallback) for change detection - Add polling fallback on Linux Wayland: on recv timeout, re-read clipboard to catch changes that the event-driven listener may have missed - Add protobuf-level deduplication to avoid sending identical clipboard content on each polling cycle Fixes #13338 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c0da4a6645
commit
74a3502702
4 changed files with 62 additions and 27 deletions
34
Cargo.lock
generated
34
Cargo.lock
generated
|
|
@ -1324,7 +1324,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "clipboard-master"
|
||||
version = "4.0.0-beta.6"
|
||||
source = "git+https://github.com/rustdesk-org/clipboard-master#ddc39f00a6211959489ae683aa6ae6eedf03a809"
|
||||
source = "git+https://github.com/night-hood/clipboard-master?branch=fix/ext-data-control-v1#c2e9f129cfd9179ace1a340d193aaa9556c2ed71"
|
||||
dependencies = [
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
|
|
@ -2282,7 +2282,7 @@ dependencies = [
|
|||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2694,7 +2694,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6673,7 +6673,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -7457,7 +7457,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -7514,7 +7514,7 @@ dependencies = [
|
|||
"security-framework 3.5.1",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -9733,9 +9733,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wayland-protocols-wlr"
|
||||
version = "0.3.3"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953"
|
||||
checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"wayland-backend",
|
||||
|
|
@ -10408,15 +10408,6 @@ dependencies = [
|
|||
"windows-targets 0.53.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
|
|
@ -10838,16 +10829,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wl-clipboard-rs"
|
||||
version = "0.9.0"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4de22eebb1d1e2bad2d970086e96da0e12cde0b411321e5b0f7b2a1f876aa26f"
|
||||
checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"os_pipe",
|
||||
"rustix 0.38.34",
|
||||
"tempfile",
|
||||
"thiserror 1.0.61",
|
||||
"rustix 1.1.2",
|
||||
"thiserror 2.0.17",
|
||||
"tree_magic_mini",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ clipboard = { path = "libs/clipboard" }
|
|||
ctrlc = "3.2"
|
||||
# arboard = { version = "3.4", features = ["wayland-data-control"] }
|
||||
arboard = { git = "https://github.com/rustdesk-org/arboard", features = ["wayland-data-control"] }
|
||||
clipboard-master = { git = "https://github.com/rustdesk-org/clipboard-master" }
|
||||
clipboard-master = { git = "https://github.com/night-hood/clipboard-master", branch = "fix/ext-data-control-v1" }
|
||||
portable-pty = { git = "https://github.com/rustdesk-org/wezterm", branch = "rustdesk/pty_based_0.8.1", package = "portable-pty" }
|
||||
|
||||
system_shutdown = "4.0"
|
||||
|
|
|
|||
|
|
@ -986,6 +986,7 @@ impl Client {
|
|||
std::thread::spawn(move || {
|
||||
let mut handler = ClientClipboardHandler {
|
||||
ctx: None,
|
||||
last_sent_clipboard_sig: None,
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
client_clip_ctx: _client_clip_ctx,
|
||||
};
|
||||
|
|
@ -1007,7 +1008,12 @@ impl Client {
|
|||
log::error!("Clipboard listener stopped with error: {}", err);
|
||||
break;
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => {}
|
||||
Err(RecvTimeoutError::Timeout) => {
|
||||
#[cfg(target_os = "linux")]
|
||||
if !crate::platform::linux::is_x11() {
|
||||
handler.check_clipboard();
|
||||
}
|
||||
}
|
||||
Err(RecvTimeoutError::Disconnected) => {
|
||||
log::error!("Clipboard listener disconnected");
|
||||
break;
|
||||
|
|
@ -1070,12 +1076,25 @@ impl ClipboardState {
|
|||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
struct ClientClipboardHandler {
|
||||
ctx: Option<crate::clipboard::ClipboardContext>,
|
||||
last_sent_clipboard_sig: Option<Vec<u8>>,
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
client_clip_ctx: Option<ClientClipboardContext>,
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
impl ClientClipboardHandler {
|
||||
fn should_send_msg(&mut self, msg: &Message) -> bool {
|
||||
use hbb_common::protobuf::Message as _;
|
||||
let Ok(sig) = msg.write_to_bytes() else {
|
||||
return true;
|
||||
};
|
||||
if self.last_sent_clipboard_sig.as_ref() == Some(&sig) {
|
||||
return false;
|
||||
}
|
||||
self.last_sent_clipboard_sig = Some(sig);
|
||||
true
|
||||
}
|
||||
|
||||
fn is_text_required(&self) -> bool {
|
||||
#[cfg(feature = "flutter")]
|
||||
{
|
||||
|
|
@ -1132,7 +1151,7 @@ impl ClientClipboardHandler {
|
|||
}
|
||||
|
||||
if let Some(msg) = check_clipboard(&mut self.ctx, ClipboardSide::Client, false) {
|
||||
if self.is_text_required() {
|
||||
if self.is_text_required() && self.should_send_msg(&msg) {
|
||||
self.send_msg(msg, false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ use std::{
|
|||
};
|
||||
#[cfg(windows)]
|
||||
use tokio::runtime::Runtime;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use hbb_common::protobuf::Message as _;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
static CLIPBOARD_SERVICE_OK: AtomicBool = AtomicBool::new(false);
|
||||
|
|
@ -33,6 +35,7 @@ static CLIPBOARD_SERVICE_OK: AtomicBool = AtomicBool::new(false);
|
|||
#[cfg(not(target_os = "android"))]
|
||||
struct Handler {
|
||||
ctx: Option<ClipboardContext>,
|
||||
last_clipboard_sig: Option<Vec<u8>>,
|
||||
#[cfg(target_os = "windows")]
|
||||
stream: Option<ipc::ConnectionTmpl<parity_tokio_ipc::ConnectionClient>>,
|
||||
#[cfg(target_os = "windows")]
|
||||
|
|
@ -71,6 +74,7 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
|||
clipboard_listener::subscribe(sp.name(), tx_cb_result)?;
|
||||
let mut handler = Handler {
|
||||
ctx,
|
||||
last_clipboard_sig: None,
|
||||
#[cfg(target_os = "windows")]
|
||||
stream: None,
|
||||
#[cfg(target_os = "windows")]
|
||||
|
|
@ -86,7 +90,9 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
|||
continue;
|
||||
}
|
||||
if let Some(msg) = handler.get_clipboard_msg() {
|
||||
sp.send(msg);
|
||||
if handler.should_send_clipboard_msg(&msg) {
|
||||
sp.send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(CallbackResult::Stop) => {
|
||||
|
|
@ -96,7 +102,16 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
|||
Ok(CallbackResult::StopWithError(err)) => {
|
||||
bail!("Clipboard listener stopped with error: {}", err);
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => {}
|
||||
Err(RecvTimeoutError::Timeout) => {
|
||||
#[cfg(target_os = "linux")]
|
||||
if !crate::platform::linux::is_x11() && sp.name() == NAME {
|
||||
if let Some(msg) = handler.get_clipboard_msg() {
|
||||
if handler.should_send_clipboard_msg(&msg) {
|
||||
sp.send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(RecvTimeoutError::Disconnected) => {
|
||||
log::error!("Clipboard listener disconnected");
|
||||
break;
|
||||
|
|
@ -111,6 +126,17 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
|||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
impl Handler {
|
||||
fn should_send_clipboard_msg(&mut self, msg: &Message) -> bool {
|
||||
let Ok(sig) = msg.write_to_bytes() else {
|
||||
return true;
|
||||
};
|
||||
if self.last_clipboard_sig.as_ref() == Some(&sig) {
|
||||
return false;
|
||||
}
|
||||
self.last_clipboard_sig = Some(sig);
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(feature = "unix-file-copy-paste")]
|
||||
fn check_clipboard_file(&mut self) {
|
||||
if let Some(urls) = check_clipboard_files(&mut self.ctx, ClipboardSide::Host, false) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue