diff --git a/Cargo.lock b/Cargo.lock index 8d560f16d..887060ddb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,12 +271,11 @@ dependencies = [ [[package]] name = "arboard" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafb29b107435aa276664c1db8954ac27a6e105cdad3c88287a199eb0e313c08" +version = "3.3.1" +source = "git+https://github.com/fufesou/arboard?branch=feat/x11_set_conn_timeout#956b5f8693b4fc7fddd7b8cafbe1111a892b34b1" dependencies = [ "clipboard-win", - "core-graphics 0.22.3", + "core-graphics 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "image", "log", "objc", @@ -284,9 +283,9 @@ dependencies = [ "objc_id", "parking_lot", "thiserror", - "winapi 0.3.9", + "windows-sys 0.48.0", "wl-clipboard-rs", - "x11rb", + "x11rb 0.13.0", ] [[package]] @@ -997,18 +996,16 @@ dependencies = [ "thiserror", "utf16string", "x11-clipboard", - "x11rb", + "x11rb 0.12.0", ] [[package]] name = "clipboard-win" -version = "4.5.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +checksum = "3ec832972fefb8cf9313b45a0d1945e29c9c251f1d4c6eafc5fe2124c02d2e81" dependencies = [ "error-code", - "str-buf", - "winapi 0.3.9", ] [[package]] @@ -1731,7 +1728,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.1", + "libloading 0.7.4", ] [[package]] @@ -1980,13 +1977,9 @@ dependencies = [ [[package]] name = "error-code" -version = "2.3.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" -dependencies = [ - "libc", - "str-buf", -] +checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc" [[package]] name = "evdev" @@ -2467,6 +2460,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -5501,7 +5504,7 @@ dependencies = [ "winres", "wol-rs", "x11-clipboard", - "x11rb", + "x11rb 0.12.0", "zip", ] @@ -5999,12 +6002,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "str-buf" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" - [[package]] name = "strength_reduce" version = "0.2.4" @@ -7785,7 +7782,7 @@ name = "x11-clipboard" version = "0.8.1" source = "git+https://github.com/clslaid/x11-clipboard?branch=feat/store-batch#5fc2e73bc01ada3681159b34cf3ea8f0d14cd904" dependencies = [ - "x11rb", + "x11rb 0.12.0", ] [[package]] @@ -7805,11 +7802,22 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" dependencies = [ - "gethostname", + "gethostname 0.3.0", "nix 0.26.4", "winapi 0.3.9", "winapi-wsapoll", - "x11rb-protocol", + "x11rb-protocol 0.12.0", +] + +[[package]] +name = "x11rb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" +dependencies = [ + "gethostname 0.4.3", + "rustix 0.38.21", + "x11rb-protocol 0.13.0", ] [[package]] @@ -7821,6 +7829,12 @@ dependencies = [ "nix 0.26.4", ] +[[package]] +name = "x11rb-protocol" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" + [[package]] name = "xdg-home" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index d37f694a3..c73642a66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,7 @@ sys-locale = "0.3" enigo = { path = "libs/enigo", features = [ "with_serde" ] } clipboard = { path = "libs/clipboard" } ctrlc = "3.2" -arboard = { version = "3.2", features = ["wayland-data-control"] } +arboard = { git = "https://github.com/fufesou/arboard", branch = "feat/x11_set_conn_timeout", features = ["wayland-data-control"] } system_shutdown = "4.0" qrcode-generator = "4.1" diff --git a/src/client.rs b/src/client.rs index 3f1d4bd70..0a98a9da1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -26,6 +26,8 @@ use hbb_common::tokio; #[cfg(not(feature = "flutter"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::tokio::sync::mpsc::UnboundedSender; +#[cfg(not(any(target_os = "android", target_os = "ios")))] +use hbb_common::tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; use hbb_common::{ allow_err, anyhow::{anyhow, Context}, @@ -65,7 +67,7 @@ use crate::{ #[cfg(not(any(target_os = "android", target_os = "ios")))] use crate::ui_session_interface::SessionPermissionConfig; #[cfg(not(any(target_os = "android", target_os = "ios")))] -use crate::{check_clipboard, ClipboardContext, CLIPBOARD_INTERVAL}; +use crate::{check_clipboard, CLIPBOARD_INTERVAL}; pub use super::lang::*; @@ -717,47 +719,48 @@ impl Client { // // If clipboard update is detected, the text will be sent to all sessions by `send_text_clipboard_msg`. #[cfg(not(any(target_os = "android", target_os = "ios")))] - fn try_start_clipboard(_ctx: Option) { + fn try_start_clipboard(_ctx: Option) -> Option> { let mut clipboard_lock = TEXT_CLIPBOARD_STATE.lock().unwrap(); if clipboard_lock.running { - return; + return None; } + clipboard_lock.running = true; + let (tx, rx) = unbounded_channel(); - match ClipboardContext::new() { - Ok(mut ctx) => { - clipboard_lock.running = true; - // ignore clipboard update before service start - check_clipboard(&mut ctx, Some(&OLD_CLIPBOARD_TEXT)); - std::thread::spawn(move || { - log::info!("Start text clipboard loop"); - loop { - std::thread::sleep(Duration::from_millis(CLIPBOARD_INTERVAL)); - if !TEXT_CLIPBOARD_STATE.lock().unwrap().running { - break; - } + log::info!("Start text clipboard loop"); + std::thread::spawn(move || { + let mut is_sent = false; + let mut ctx = None; + loop { + if !TEXT_CLIPBOARD_STATE.lock().unwrap().running { + break; + } + if !TEXT_CLIPBOARD_STATE.lock().unwrap().is_required { + continue; + } - if !TEXT_CLIPBOARD_STATE.lock().unwrap().is_required { - continue; - } - - if let Some(msg) = check_clipboard(&mut ctx, Some(&OLD_CLIPBOARD_TEXT)) { - #[cfg(feature = "flutter")] - crate::flutter::send_text_clipboard_msg(msg); - #[cfg(not(feature = "flutter"))] - if let Some(ctx) = &_ctx { - if ctx.cfg.is_text_clipboard_required() { - let _ = ctx.tx.send(Data::Message(msg)); - } - } + if let Some(msg) = check_clipboard(&mut ctx, Some(&OLD_CLIPBOARD_TEXT)) { + #[cfg(feature = "flutter")] + crate::flutter::send_text_clipboard_msg(msg); + #[cfg(not(feature = "flutter"))] + if let Some(ctx) = &_ctx { + if ctx.cfg.is_text_clipboard_required() { + let _ = ctx.tx.send(Data::Message(msg)); } } - log::info!("Stop text clipboard loop"); - }); + } + + if !is_sent { + is_sent = true; + tx.send(()).ok(); + } + + std::thread::sleep(Duration::from_millis(CLIPBOARD_INTERVAL)); } - Err(err) => { - log::error!("Failed to start clipboard service of client: {}", err); - } - } + log::info!("Stop text clipboard loop"); + }); + + Some(rx) } #[inline] @@ -1532,8 +1535,7 @@ impl LoginConfigHandler { n += 1; } else if q == "custom" { let config = self.load_config(); - let allow_more = - !crate::using_public_server() || self.direct == Some(true); + let allow_more = !crate::using_public_server() || self.direct == Some(true); let quality = if config.custom_image_quality.is_empty() { 50 } else { diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index ec2fdd3e7..e561b1428 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -17,16 +17,15 @@ use hbb_common::tokio::sync::mpsc::error::TryRecvError; use hbb_common::{ allow_err, config::{PeerConfig, TransferSerde}, - fs, fs::{ - can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult, - RemoveJobMeta, + self, can_enable_overwrite_detection, get_job, get_string, new_send_confirm, + DigestCheckResult, RemoveJobMeta, }, get_time, log, - message_proto::permission_info::Permission, - message_proto::*, + message_proto::{permission_info::Permission, *}, protobuf::Message as _, rendezvous_proto::ConnType, + timeout, tokio::{ self, sync::mpsc, @@ -170,7 +169,8 @@ impl Remote { #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] let mut rx_clip_client = rx_clip_client_lock.lock().await; - let mut status_timer = crate::rustdesk_interval(time::interval(Duration::new(1, 0))); + let mut status_timer = + crate::rustdesk_interval(time::interval(Duration::new(1, 0))); let mut fps_instant = Instant::now(); loop { @@ -1099,15 +1099,20 @@ impl Remote { if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) { #[cfg(feature = "flutter")] #[cfg(not(any(target_os = "android", target_os = "ios")))] - Client::try_start_clipboard(None); + let rx = Client::try_start_clipboard(None); #[cfg(not(feature = "flutter"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] - Client::try_start_clipboard(Some( + let rx = Client::try_start_clipboard(Some( crate::client::ClientClipboardContext { cfg: self.handler.get_permission_config(), tx: self.sender.clone(), }, )); + // To make sure current text clipboard data is updated. + #[cfg(not(any(target_os = "android", target_os = "ios")))] + if let Some(mut rx) = rx { + timeout(common::CLIPBOARD_INTERVAL, rx.recv()).await.ok(); + } #[cfg(not(any(target_os = "android", target_os = "ios")))] if let Some(msg_out) = Client::get_current_text_clipboard_msg() { diff --git a/src/common.rs b/src/common.rs index 1f81d2690..50e9253d6 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, future::Future, sync::{Arc, Mutex, RwLock}, task::Poll, @@ -12,13 +13,6 @@ pub enum GrabState { Exit, } -#[cfg(not(any( - target_os = "android", - target_os = "ios", - all(target_os = "linux", feature = "unix-file-copy-paste") -)))] -pub use arboard::Clipboard as ClipboardContext; - #[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))] static X11_CLIPBOARD: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); @@ -290,14 +284,18 @@ pub fn create_clipboard_msg(content: String) -> Message { #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn check_clipboard( - ctx: &mut ClipboardContext, + ctx: &mut Option, old: Option<&Arc>>, ) -> Option { + if ctx.is_none() { + *ctx = ClipboardContext::new().ok(); + } + let ctx2 = ctx.as_mut()?; let side = if old.is_none() { "host" } else { "client" }; let old = if let Some(old) = old { old } else { &CONTENT }; let content = { let _lock = ARBOARD_MTX.lock().unwrap(); - ctx.get_text() + ctx2.get_text() }; if let Ok(content) = content { if content.len() < 2_000_000 && !content.is_empty() { @@ -1374,9 +1372,7 @@ impl ThrottledInterval { Poll::Pending } } - Poll::Pending => { - Poll::Pending - }, + Poll::Pending => Poll::Pending, } } } @@ -1388,6 +1384,78 @@ pub fn rustdesk_interval(i: Interval) -> ThrottledInterval { ThrottledInterval::new(i) } +#[cfg(not(any( + target_os = "android", + target_os = "ios", + all(target_os = "linux", feature = "unix-file-copy-paste") +)))] +pub struct ClipboardContext(arboard::Clipboard); + +#[cfg(not(any( + target_os = "android", + target_os = "ios", + all(target_os = "linux", feature = "unix-file-copy-paste") +)))] +impl ClipboardContext { + #[inline] + #[cfg(any(target_os = "windows", target_os = "macos"))] + pub fn new() -> ResultType { + Ok(ClipboardContext(arboard::Clipboard::new()?)) + } + + #[cfg(target_os = "linux")] + pub fn new() -> ResultType { + let dur = arboard::Clipboard::get_x11_server_conn_timeout(); + let dur_bak = dur; + let _restore_timeout_on_ret = SimpleCallOnReturn { + b: true, + f: Box::new(move || arboard::Clipboard::set_x11_server_conn_timeout(dur_bak)), + }; + + for i in 1..4 { + arboard::Clipboard::set_x11_server_conn_timeout(dur * i); + match arboard::Clipboard::new() { + Ok(c) => return Ok(ClipboardContext(c)), + Err(arboard::Error::X11ServerConnTimeout) => continue, + Err(err) => return Err(err.into()), + } + } + bail!("Failed to create clipboard context, timeout"); + } + + #[inline] + #[cfg(any(target_os = "windows", target_os = "macos"))] + pub fn get_text(&mut self) -> ResultType { + Ok(self.0.get_text()?) + } + + #[cfg(target_os = "linux")] + pub fn get_text(&mut self) -> ResultType { + let dur = arboard::Clipboard::get_x11_server_conn_timeout(); + let dur_bak = dur; + let _restore_timeout_on_ret = SimpleCallOnReturn { + b: true, + f: Box::new(move || arboard::Clipboard::set_x11_server_conn_timeout(dur_bak)), + }; + + for i in 1..4 { + arboard::Clipboard::set_x11_server_conn_timeout(dur * i); + match self.0.get_text() { + Ok(s) => return Ok(s), + Err(arboard::Error::X11ServerConnTimeout) => continue, + Err(err) => return Err(err.into()), + } + } + bail!("Failed to get text, timeout"); + } + + #[inline] + pub fn set_text<'a, T: Into>>(&mut self, text: T) -> ResultType<()> { + self.0.set_text(text)?; + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -1444,12 +1512,58 @@ mod tests { let dur = Duration::from_secs(1); assert_eq!(dur * 2, Duration::from_secs(2)); - assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.9), Duration::from_millis(900)); - assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.923), Duration::from_millis(923)); - assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.923 * 1e-3), Duration::from_micros(923)); - assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.923 * 1e-6), Duration::from_nanos(923)); - assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.923 * 1e-9), Duration::from_nanos(1)); - assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.5 * 1e-9), Duration::from_nanos(1)); - assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.499 * 1e-9), Duration::from_nanos(0)); + assert_eq!( + Duration::from_secs_f64(dur.as_secs_f64() * 0.9), + Duration::from_millis(900) + ); + assert_eq!( + Duration::from_secs_f64(dur.as_secs_f64() * 0.923), + Duration::from_millis(923) + ); + assert_eq!( + Duration::from_secs_f64(dur.as_secs_f64() * 0.923 * 1e-3), + Duration::from_micros(923) + ); + assert_eq!( + Duration::from_secs_f64(dur.as_secs_f64() * 0.923 * 1e-6), + Duration::from_nanos(923) + ); + assert_eq!( + Duration::from_secs_f64(dur.as_secs_f64() * 0.923 * 1e-9), + Duration::from_nanos(1) + ); + assert_eq!( + Duration::from_secs_f64(dur.as_secs_f64() * 0.5 * 1e-9), + Duration::from_nanos(1) + ); + assert_eq!( + Duration::from_secs_f64(dur.as_secs_f64() * 0.499 * 1e-9), + Duration::from_nanos(0) + ); + } + + #[tokio::test] + #[cfg(not(any( + target_os = "android", + target_os = "ios", + all(target_os = "linux", feature = "unix-file-copy-paste") + )))] + async fn test_clipboard_context() { + #[cfg(target_os = "linux")] + let dur = { + let dur = Duration::from_micros(500); + arboard::Clipboard::set_x11_server_conn_timeout(dur); + dur + }; + + let _ctx = ClipboardContext::new(); + #[cfg(target_os = "linux")] + { + assert_eq!( + arboard::Clipboard::get_x11_server_conn_timeout(), + dur, + "Failed to restore x11 server conn timeout" + ); + } } } diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index be521b152..34e1635eb 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -34,18 +34,16 @@ pub fn new() -> GenericService { } fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> { - if let Some(ctx) = state.ctx.as_mut() { - if let Some(msg) = check_clipboard(ctx, None) { - sp.send(msg); - } - sp.snapshot(|sps| { - let txt = crate::CONTENT.lock().unwrap().clone(); - if !txt.is_empty() { - let msg_out = crate::create_clipboard_msg(txt); - sps.send_shared(Arc::new(msg_out)); - } - Ok(()) - })?; + if let Some(msg) = check_clipboard(&mut state.ctx, None) { + sp.send(msg); } + sp.snapshot(|sps| { + let txt = crate::CONTENT.lock().unwrap().clone(); + if !txt.is_empty() { + let msg_out = crate::create_clipboard_msg(txt); + sps.send_shared(Arc::new(msg_out)); + } + Ok(()) + })?; Ok(()) }