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]]
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",

View File

@ -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<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] = &[
ClipboardFormat::Text,
ClipboardFormat::Html,
@ -151,8 +155,8 @@ pub fn check_clipboard(
*ctx = ClipboardContext::new().ok();
}
let ctx2 = ctx.as_mut()?;
let content = ctx2.get(side, force);
if let Ok(content) = content {
match ctx2.get(side, force) {
Ok(content) => {
if !content.is_empty() {
let mut msg = Message::new();
let clipboards = proto::create_multi_clipboards(content);
@ -161,6 +165,10 @@ pub fn check_clipboard(
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<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>> {
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);
}

View File

@ -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<CallbackResult>,
#[cfg(target_os = "windows")]
stream: Option<ipc::ConnectionTmpl<parity_tokio_ipc::ConnectionClient>>,
#[cfg(target_os = "windows")]
rt: Option<Runtime>,
}
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<Vec<ClipboardNonFile>> {
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;
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);
}
}
}

View File

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