Fix/clipboard retry if is occupied (#9293)

* fix: clipboard, retry if is occupied

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: clipboard service, hold runtime to cm ipc

Signed-off-by: fufesou <linlong1266@gmail.com>

* update arboard

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact: log

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: get formats, return only not

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou 2024-09-08 21:13:05 +08:00 committed by GitHub
parent 1e6944b380
commit 2922ebe22a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 70 additions and 19 deletions

2
Cargo.lock generated
View File

@ -224,7 +224,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]] [[package]]
name = "arboard" name = "arboard"
version = "3.4.0" version = "3.4.0"
source = "git+https://github.com/rustdesk-org/arboard#a04bdb1b368a99691822c33bf0f7ed497d6a7a35" source = "git+https://github.com/rustdesk-org/arboard#747ab2d9b40a5c9c5102051cf3b0bb38b4845e60"
dependencies = [ dependencies = [
"clipboard-win", "clipboard-win",
"core-graphics 0.23.2", "core-graphics 0.23.2",

View File

@ -1,9 +1,10 @@
use arboard::{ClipboardData, ClipboardFormat}; use arboard::{ClipboardData, ClipboardFormat};
use clipboard_master::{ClipboardHandler, Master, Shutdown}; use clipboard_master::{ClipboardHandler, Master, Shutdown};
use hbb_common::{log, message_proto::*, ResultType}; use hbb_common::{bail, log, message_proto::*, ResultType};
use std::{ use std::{
sync::{mpsc::Sender, Arc, Mutex}, sync::{mpsc::Sender, Arc, Mutex},
thread::JoinHandle, thread::JoinHandle,
time::Duration,
}; };
pub const CLIPBOARD_NAME: &'static str = "clipboard"; pub const CLIPBOARD_NAME: &'static str = "clipboard";
@ -26,6 +27,9 @@ lazy_static::lazy_static! {
static ref CLIPBOARD_CTX: Arc<Mutex<Option<ClipboardContext>>> = Arc::new(Mutex::new(None)); static ref CLIPBOARD_CTX: Arc<Mutex<Option<ClipboardContext>>> = 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] = &[ const SUPPORTED_FORMATS: &[ClipboardFormat] = &[
ClipboardFormat::Text, ClipboardFormat::Text,
ClipboardFormat::Html, ClipboardFormat::Html,
@ -151,14 +155,18 @@ pub fn check_clipboard(
*ctx = ClipboardContext::new().ok(); *ctx = ClipboardContext::new().ok();
} }
let ctx2 = ctx.as_mut()?; let ctx2 = ctx.as_mut()?;
let content = ctx2.get(side, force); match ctx2.get(side, force) {
if let Ok(content) = content { Ok(content) => {
if !content.is_empty() { if !content.is_empty() {
let mut msg = Message::new(); let mut msg = Message::new();
let clipboards = proto::create_multi_clipboards(content); let clipboards = proto::create_multi_clipboards(content);
msg.set_multi_clipboards(clipboards.clone()); msg.set_multi_clipboards(clipboards.clone());
*LAST_MULTI_CLIPBOARDS.lock().unwrap() = clipboards; *LAST_MULTI_CLIPBOARDS.lock().unwrap() = clipboards;
return Some(msg); return Some(msg);
}
}
Err(e) => {
log::error!("Failed to get clipboard content. {}", e);
} }
} }
None None
@ -263,9 +271,33 @@ impl ClipboardContext {
Ok(ClipboardContext { inner: board }) Ok(ClipboardContext { inner: board })
} }
fn get_formats(&mut self, formats: &[ClipboardFormat]) -> ResultType<Vec<ClipboardData>> {
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<Vec<ClipboardData>> { pub fn get(&mut self, side: ClipboardSide, force: bool) -> ResultType<Vec<ClipboardData>> {
let _lock = ARBOARD_MTX.lock().unwrap(); 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() { if data.is_empty() {
return Ok(data); return Ok(data);
} }

View File

@ -11,6 +11,8 @@ use std::{
sync::mpsc::{channel, RecvTimeoutError, Sender}, sync::mpsc::{channel, RecvTimeoutError, Sender},
time::Duration, time::Duration,
}; };
#[cfg(windows)]
use tokio::runtime::Runtime;
struct Handler { struct Handler {
sp: EmptyExtraFieldService, sp: EmptyExtraFieldService,
@ -18,6 +20,8 @@ struct Handler {
tx_cb_result: Sender<CallbackResult>, tx_cb_result: Sender<CallbackResult>,
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
stream: Option<ipc::ConnectionTmpl<parity_tokio_ipc::ConnectionClient>>, stream: Option<ipc::ConnectionTmpl<parity_tokio_ipc::ConnectionClient>>,
#[cfg(target_os = "windows")]
rt: Option<Runtime>,
} }
pub fn new() -> GenericService { pub fn new() -> GenericService {
@ -34,6 +38,8 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
tx_cb_result, tx_cb_result,
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
stream: None, stream: None,
#[cfg(target_os = "windows")]
rt: None,
}; };
let (tx_start_res, rx_start_res) = channel(); let (tx_start_res, rx_start_res) = channel();
@ -129,29 +135,41 @@ impl Handler {
// 1. the clipboard is not used frequently. // 1. the clipboard is not used frequently.
// 2. the clipboard handle is sync and will not block the main thread. // 2. the clipboard handle is sync and will not block the main thread.
#[cfg(windows)] #[cfg(windows)]
#[tokio::main(flavor = "current_thread")] fn read_clipboard_from_cm_ipc(&mut self) -> ResultType<Vec<ClipboardNonFile>> {
async fn read_clipboard_from_cm_ipc(&mut self) -> ResultType<Vec<ClipboardNonFile>> { 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; let mut is_sent = false;
if let Some(stream) = &mut self.stream { if let Some(stream) = &mut self.stream {
// If previous stream is still alive, reuse it. // If previous stream is still alive, reuse it.
// If the previous stream is dead, `is_sent` will trigger reconnect. // 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 { if !is_sent {
let mut stream = crate::ipc::connect(100, "_cm").await?; let mut stream = rt.block_on(crate::ipc::connect(100, "_cm"))?;
stream.send(&Data::ClipboardNonFile(None)).await?; rt.block_on(stream.send(&Data::ClipboardNonFile(None)))?;
self.stream = Some(stream); self.stream = Some(stream);
} }
if let Some(stream) = &mut self.stream { if let Some(stream) = &mut self.stream {
loop { loop {
match stream.next_timeout(800).await? { match rt.block_on(stream.next_timeout(800))? {
Some(Data::ClipboardNonFile(Some((err, mut contents)))) => { Some(Data::ClipboardNonFile(Some((err, mut contents)))) => {
if !err.is_empty() { if !err.is_empty() {
bail!("{}", err); bail!("{}", err);
} else { } else {
if contents.iter().any(|c| c.next_raw) { 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)) => { Ok(Ok(mut data)) => {
for c in &mut contents { for c in &mut contents {
if c.next_raw { if c.next_raw {
@ -168,7 +186,7 @@ impl Handler {
Err(e) => { Err(e) => {
// Reconnect to avoid the next raw data remaining in the buffer. // Reconnect to avoid the next raw data remaining in the buffer.
self.stream = None; self.stream = None;
log::debug!("failed to get raw clipboard data: {}", e); log::debug!("Failed to get raw clipboard data: {}", e);
} }
} }
} }

View File

@ -520,6 +520,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
} }
} }
Err(e) => { Err(e) => {
log::debug!("Failed to get clipboard content. {}", e);
allow_err!(self.stream.send(&Data::ClipboardNonFile(Some((format!("{}", e), vec![])))).await); allow_err!(self.stream.send(&Data::ClipboardNonFile(Some((format!("{}", e), vec![])))).await);
} }
} }