From 2922ebe22aa3a0edb52a183e82efac78b0a7d43a Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sun, 8 Sep 2024 21:13:05 +0800 Subject: [PATCH] Fix/clipboard retry if is occupied (#9293) * fix: clipboard, retry if is occupied Signed-off-by: fufesou * fix: clipboard service, hold runtime to cm ipc Signed-off-by: fufesou * update arboard Signed-off-by: fufesou * refact: log Signed-off-by: fufesou * fix: get formats, return only not Signed-off-by: fufesou --------- Signed-off-by: fufesou --- Cargo.lock | 2 +- src/clipboard.rs | 52 ++++++++++++++++++++++++++------- src/server/clipboard_service.rs | 34 ++++++++++++++++----- src/ui_cm_interface.rs | 1 + 4 files changed, 70 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd0e449e7..b2bd08d43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,7 +224,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arboard" version = "3.4.0" -source = "git+https://github.com/rustdesk-org/arboard#a04bdb1b368a99691822c33bf0f7ed497d6a7a35" +source = "git+https://github.com/rustdesk-org/arboard#747ab2d9b40a5c9c5102051cf3b0bb38b4845e60" dependencies = [ "clipboard-win", "core-graphics 0.23.2", diff --git a/src/clipboard.rs b/src/clipboard.rs index 4e6295db9..e4bd00390 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -1,9 +1,10 @@ use arboard::{ClipboardData, ClipboardFormat}; use clipboard_master::{ClipboardHandler, Master, Shutdown}; -use hbb_common::{log, message_proto::*, ResultType}; +use hbb_common::{bail, log, message_proto::*, ResultType}; use std::{ sync::{mpsc::Sender, Arc, Mutex}, thread::JoinHandle, + time::Duration, }; pub const CLIPBOARD_NAME: &'static str = "clipboard"; @@ -26,6 +27,9 @@ lazy_static::lazy_static! { static ref CLIPBOARD_CTX: Arc>> = Arc::new(Mutex::new(None)); } +const CLIPBOARD_GET_MAX_RETRY: usize = 3; +const CLIPBOARD_GET_RETRY_INTERVAL_DUR: Duration = Duration::from_millis(33); + const SUPPORTED_FORMATS: &[ClipboardFormat] = &[ ClipboardFormat::Text, ClipboardFormat::Html, @@ -151,14 +155,18 @@ pub fn check_clipboard( *ctx = ClipboardContext::new().ok(); } let ctx2 = ctx.as_mut()?; - let content = ctx2.get(side, force); - if let Ok(content) = content { - if !content.is_empty() { - let mut msg = Message::new(); - let clipboards = proto::create_multi_clipboards(content); - msg.set_multi_clipboards(clipboards.clone()); - *LAST_MULTI_CLIPBOARDS.lock().unwrap() = clipboards; - return Some(msg); + match ctx2.get(side, force) { + Ok(content) => { + if !content.is_empty() { + let mut msg = Message::new(); + let clipboards = proto::create_multi_clipboards(content); + msg.set_multi_clipboards(clipboards.clone()); + *LAST_MULTI_CLIPBOARDS.lock().unwrap() = clipboards; + return Some(msg); + } + } + Err(e) => { + log::error!("Failed to get clipboard content. {}", e); } } None @@ -263,9 +271,33 @@ impl ClipboardContext { Ok(ClipboardContext { inner: board }) } + fn get_formats(&mut self, formats: &[ClipboardFormat]) -> ResultType> { + for i in 0..CLIPBOARD_GET_MAX_RETRY { + match self.inner.get_formats(SUPPORTED_FORMATS) { + Ok(data) => { + return Ok(data + .into_iter() + .filter(|c| !matches!(c, arboard::ClipboardData::None)) + .collect()) + } + Err(e) => match e { + arboard::Error::ClipboardOccupied => { + log::debug!("Failed to get clipboard formats, clipboard is occupied, retrying... {}", i + 1); + std::thread::sleep(CLIPBOARD_GET_RETRY_INTERVAL_DUR); + } + _ => { + log::error!("Failed to get clipboard formats, {}", e); + return Err(e.into()); + } + }, + } + } + bail!("Failed to get clipboard formats, clipboard is occupied, {CLIPBOARD_GET_MAX_RETRY} retries failed"); + } + pub fn get(&mut self, side: ClipboardSide, force: bool) -> ResultType> { let _lock = ARBOARD_MTX.lock().unwrap(); - let data = self.inner.get_formats(SUPPORTED_FORMATS)?; + let data = self.get_formats(SUPPORTED_FORMATS)?; if data.is_empty() { return Ok(data); } diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index e70974258..a2a3b3153 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -11,6 +11,8 @@ use std::{ sync::mpsc::{channel, RecvTimeoutError, Sender}, time::Duration, }; +#[cfg(windows)] +use tokio::runtime::Runtime; struct Handler { sp: EmptyExtraFieldService, @@ -18,6 +20,8 @@ struct Handler { tx_cb_result: Sender, #[cfg(target_os = "windows")] stream: Option>, + #[cfg(target_os = "windows")] + rt: Option, } pub fn new() -> GenericService { @@ -34,6 +38,8 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> { tx_cb_result, #[cfg(target_os = "windows")] stream: None, + #[cfg(target_os = "windows")] + rt: None, }; let (tx_start_res, rx_start_res) = channel(); @@ -129,29 +135,41 @@ impl Handler { // 1. the clipboard is not used frequently. // 2. the clipboard handle is sync and will not block the main thread. #[cfg(windows)] - #[tokio::main(flavor = "current_thread")] - async fn read_clipboard_from_cm_ipc(&mut self) -> ResultType> { + fn read_clipboard_from_cm_ipc(&mut self) -> ResultType> { + if self.rt.is_none() { + self.rt = Some(Runtime::new()?); + } + let Some(rt) = &self.rt else { + // unreachable! + bail!("failed to get tokio runtime"); + }; let mut is_sent = false; if let Some(stream) = &mut self.stream { // If previous stream is still alive, reuse it. // If the previous stream is dead, `is_sent` will trigger reconnect. - is_sent = stream.send(&Data::ClipboardNonFile(None)).await.is_ok(); + is_sent = match rt.block_on(stream.send(&Data::ClipboardNonFile(None))) { + Ok(_) => true, + Err(e) => { + log::debug!("Failed to send to cm: {}", e); + false + } + }; } if !is_sent { - let mut stream = crate::ipc::connect(100, "_cm").await?; - stream.send(&Data::ClipboardNonFile(None)).await?; + let mut stream = rt.block_on(crate::ipc::connect(100, "_cm"))?; + rt.block_on(stream.send(&Data::ClipboardNonFile(None)))?; self.stream = Some(stream); } if let Some(stream) = &mut self.stream { loop { - match stream.next_timeout(800).await? { + match rt.block_on(stream.next_timeout(800))? { Some(Data::ClipboardNonFile(Some((err, mut contents)))) => { if !err.is_empty() { bail!("{}", err); } else { if contents.iter().any(|c| c.next_raw) { - match timeout(1000, stream.next_raw()).await { + match rt.block_on(timeout(1000, stream.next_raw())) { Ok(Ok(mut data)) => { for c in &mut contents { if c.next_raw { @@ -168,7 +186,7 @@ impl Handler { Err(e) => { // Reconnect to avoid the next raw data remaining in the buffer. self.stream = None; - log::debug!("failed to get raw clipboard data: {}", e); + log::debug!("Failed to get raw clipboard data: {}", e); } } } diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index c9b46ff1f..49f91a9da 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -520,6 +520,7 @@ impl IpcTaskRunner { } } Err(e) => { + log::debug!("Failed to get clipboard content. {}", e); allow_err!(self.stream.send(&Data::ClipboardNonFile(Some((format!("{}", e), vec![])))).await); } }