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:
parent
1e6944b380
commit
2922ebe22a
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user