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]]
|
[[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",
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user