Fix/arboard clipboard context timeout (#7217)

* Fix. Set custom timeout for arboard clipboard

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* fix build

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* Do not block tokio runtime

Signed-off-by: fufesou <shuanglongchen@yeah.net>

---------

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou
2024-02-21 22:05:27 +08:00
committed by GitHub
parent c690d5e940
commit 25afdda2b2
6 changed files with 237 additions and 104 deletions

View File

@@ -26,6 +26,8 @@ use hbb_common::tokio;
#[cfg(not(feature = "flutter"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::tokio::sync::mpsc::UnboundedSender;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
use hbb_common::{
allow_err,
anyhow::{anyhow, Context},
@@ -65,7 +67,7 @@ use crate::{
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_session_interface::SessionPermissionConfig;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::{check_clipboard, ClipboardContext, CLIPBOARD_INTERVAL};
use crate::{check_clipboard, CLIPBOARD_INTERVAL};
pub use super::lang::*;
@@ -717,47 +719,48 @@ impl Client {
//
// If clipboard update is detected, the text will be sent to all sessions by `send_text_clipboard_msg`.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn try_start_clipboard(_ctx: Option<ClientClipboardContext>) {
fn try_start_clipboard(_ctx: Option<ClientClipboardContext>) -> Option<UnboundedReceiver<()>> {
let mut clipboard_lock = TEXT_CLIPBOARD_STATE.lock().unwrap();
if clipboard_lock.running {
return;
return None;
}
clipboard_lock.running = true;
let (tx, rx) = unbounded_channel();
match ClipboardContext::new() {
Ok(mut ctx) => {
clipboard_lock.running = true;
// ignore clipboard update before service start
check_clipboard(&mut ctx, Some(&OLD_CLIPBOARD_TEXT));
std::thread::spawn(move || {
log::info!("Start text clipboard loop");
loop {
std::thread::sleep(Duration::from_millis(CLIPBOARD_INTERVAL));
if !TEXT_CLIPBOARD_STATE.lock().unwrap().running {
break;
}
log::info!("Start text clipboard loop");
std::thread::spawn(move || {
let mut is_sent = false;
let mut ctx = None;
loop {
if !TEXT_CLIPBOARD_STATE.lock().unwrap().running {
break;
}
if !TEXT_CLIPBOARD_STATE.lock().unwrap().is_required {
continue;
}
if !TEXT_CLIPBOARD_STATE.lock().unwrap().is_required {
continue;
}
if let Some(msg) = check_clipboard(&mut ctx, Some(&OLD_CLIPBOARD_TEXT)) {
#[cfg(feature = "flutter")]
crate::flutter::send_text_clipboard_msg(msg);
#[cfg(not(feature = "flutter"))]
if let Some(ctx) = &_ctx {
if ctx.cfg.is_text_clipboard_required() {
let _ = ctx.tx.send(Data::Message(msg));
}
}
if let Some(msg) = check_clipboard(&mut ctx, Some(&OLD_CLIPBOARD_TEXT)) {
#[cfg(feature = "flutter")]
crate::flutter::send_text_clipboard_msg(msg);
#[cfg(not(feature = "flutter"))]
if let Some(ctx) = &_ctx {
if ctx.cfg.is_text_clipboard_required() {
let _ = ctx.tx.send(Data::Message(msg));
}
}
log::info!("Stop text clipboard loop");
});
}
if !is_sent {
is_sent = true;
tx.send(()).ok();
}
std::thread::sleep(Duration::from_millis(CLIPBOARD_INTERVAL));
}
Err(err) => {
log::error!("Failed to start clipboard service of client: {}", err);
}
}
log::info!("Stop text clipboard loop");
});
Some(rx)
}
#[inline]
@@ -1532,8 +1535,7 @@ impl LoginConfigHandler {
n += 1;
} else if q == "custom" {
let config = self.load_config();
let allow_more =
!crate::using_public_server() || self.direct == Some(true);
let allow_more = !crate::using_public_server() || self.direct == Some(true);
let quality = if config.custom_image_quality.is_empty() {
50
} else {

View File

@@ -17,16 +17,15 @@ use hbb_common::tokio::sync::mpsc::error::TryRecvError;
use hbb_common::{
allow_err,
config::{PeerConfig, TransferSerde},
fs,
fs::{
can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult,
RemoveJobMeta,
self, can_enable_overwrite_detection, get_job, get_string, new_send_confirm,
DigestCheckResult, RemoveJobMeta,
},
get_time, log,
message_proto::permission_info::Permission,
message_proto::*,
message_proto::{permission_info::Permission, *},
protobuf::Message as _,
rendezvous_proto::ConnType,
timeout,
tokio::{
self,
sync::mpsc,
@@ -170,7 +169,8 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
let mut rx_clip_client = rx_clip_client_lock.lock().await;
let mut status_timer = crate::rustdesk_interval(time::interval(Duration::new(1, 0)));
let mut status_timer =
crate::rustdesk_interval(time::interval(Duration::new(1, 0)));
let mut fps_instant = Instant::now();
loop {
@@ -1099,15 +1099,20 @@ impl<T: InvokeUiSession> Remote<T> {
if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) {
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Client::try_start_clipboard(None);
let rx = Client::try_start_clipboard(None);
#[cfg(not(feature = "flutter"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Client::try_start_clipboard(Some(
let rx = Client::try_start_clipboard(Some(
crate::client::ClientClipboardContext {
cfg: self.handler.get_permission_config(),
tx: self.sender.clone(),
},
));
// To make sure current text clipboard data is updated.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Some(mut rx) = rx {
timeout(common::CLIPBOARD_INTERVAL, rx.recv()).await.ok();
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Some(msg_out) = Client::get_current_text_clipboard_msg() {

View File

@@ -1,4 +1,5 @@
use std::{
borrow::Cow,
future::Future,
sync::{Arc, Mutex, RwLock},
task::Poll,
@@ -12,13 +13,6 @@ pub enum GrabState {
Exit,
}
#[cfg(not(any(
target_os = "android",
target_os = "ios",
all(target_os = "linux", feature = "unix-file-copy-paste")
)))]
pub use arboard::Clipboard as ClipboardContext;
#[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))]
static X11_CLIPBOARD: once_cell::sync::OnceCell<x11_clipboard::Clipboard> =
once_cell::sync::OnceCell::new();
@@ -290,14 +284,18 @@ pub fn create_clipboard_msg(content: String) -> Message {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn check_clipboard(
ctx: &mut ClipboardContext,
ctx: &mut Option<ClipboardContext>,
old: Option<&Arc<Mutex<String>>>,
) -> Option<Message> {
if ctx.is_none() {
*ctx = ClipboardContext::new().ok();
}
let ctx2 = ctx.as_mut()?;
let side = if old.is_none() { "host" } else { "client" };
let old = if let Some(old) = old { old } else { &CONTENT };
let content = {
let _lock = ARBOARD_MTX.lock().unwrap();
ctx.get_text()
ctx2.get_text()
};
if let Ok(content) = content {
if content.len() < 2_000_000 && !content.is_empty() {
@@ -1374,9 +1372,7 @@ impl ThrottledInterval {
Poll::Pending
}
}
Poll::Pending => {
Poll::Pending
},
Poll::Pending => Poll::Pending,
}
}
}
@@ -1388,6 +1384,78 @@ pub fn rustdesk_interval(i: Interval) -> ThrottledInterval {
ThrottledInterval::new(i)
}
#[cfg(not(any(
target_os = "android",
target_os = "ios",
all(target_os = "linux", feature = "unix-file-copy-paste")
)))]
pub struct ClipboardContext(arboard::Clipboard);
#[cfg(not(any(
target_os = "android",
target_os = "ios",
all(target_os = "linux", feature = "unix-file-copy-paste")
)))]
impl ClipboardContext {
#[inline]
#[cfg(any(target_os = "windows", target_os = "macos"))]
pub fn new() -> ResultType<ClipboardContext> {
Ok(ClipboardContext(arboard::Clipboard::new()?))
}
#[cfg(target_os = "linux")]
pub fn new() -> ResultType<ClipboardContext> {
let dur = arboard::Clipboard::get_x11_server_conn_timeout();
let dur_bak = dur;
let _restore_timeout_on_ret = SimpleCallOnReturn {
b: true,
f: Box::new(move || arboard::Clipboard::set_x11_server_conn_timeout(dur_bak)),
};
for i in 1..4 {
arboard::Clipboard::set_x11_server_conn_timeout(dur * i);
match arboard::Clipboard::new() {
Ok(c) => return Ok(ClipboardContext(c)),
Err(arboard::Error::X11ServerConnTimeout) => continue,
Err(err) => return Err(err.into()),
}
}
bail!("Failed to create clipboard context, timeout");
}
#[inline]
#[cfg(any(target_os = "windows", target_os = "macos"))]
pub fn get_text(&mut self) -> ResultType<String> {
Ok(self.0.get_text()?)
}
#[cfg(target_os = "linux")]
pub fn get_text(&mut self) -> ResultType<String> {
let dur = arboard::Clipboard::get_x11_server_conn_timeout();
let dur_bak = dur;
let _restore_timeout_on_ret = SimpleCallOnReturn {
b: true,
f: Box::new(move || arboard::Clipboard::set_x11_server_conn_timeout(dur_bak)),
};
for i in 1..4 {
arboard::Clipboard::set_x11_server_conn_timeout(dur * i);
match self.0.get_text() {
Ok(s) => return Ok(s),
Err(arboard::Error::X11ServerConnTimeout) => continue,
Err(err) => return Err(err.into()),
}
}
bail!("Failed to get text, timeout");
}
#[inline]
pub fn set_text<'a, T: Into<Cow<'a, str>>>(&mut self, text: T) -> ResultType<()> {
self.0.set_text(text)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1444,12 +1512,58 @@ mod tests {
let dur = Duration::from_secs(1);
assert_eq!(dur * 2, Duration::from_secs(2));
assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.9), Duration::from_millis(900));
assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.923), Duration::from_millis(923));
assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.923 * 1e-3), Duration::from_micros(923));
assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.923 * 1e-6), Duration::from_nanos(923));
assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.923 * 1e-9), Duration::from_nanos(1));
assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.5 * 1e-9), Duration::from_nanos(1));
assert_eq!(Duration::from_secs_f64(dur.as_secs_f64() * 0.499 * 1e-9), Duration::from_nanos(0));
assert_eq!(
Duration::from_secs_f64(dur.as_secs_f64() * 0.9),
Duration::from_millis(900)
);
assert_eq!(
Duration::from_secs_f64(dur.as_secs_f64() * 0.923),
Duration::from_millis(923)
);
assert_eq!(
Duration::from_secs_f64(dur.as_secs_f64() * 0.923 * 1e-3),
Duration::from_micros(923)
);
assert_eq!(
Duration::from_secs_f64(dur.as_secs_f64() * 0.923 * 1e-6),
Duration::from_nanos(923)
);
assert_eq!(
Duration::from_secs_f64(dur.as_secs_f64() * 0.923 * 1e-9),
Duration::from_nanos(1)
);
assert_eq!(
Duration::from_secs_f64(dur.as_secs_f64() * 0.5 * 1e-9),
Duration::from_nanos(1)
);
assert_eq!(
Duration::from_secs_f64(dur.as_secs_f64() * 0.499 * 1e-9),
Duration::from_nanos(0)
);
}
#[tokio::test]
#[cfg(not(any(
target_os = "android",
target_os = "ios",
all(target_os = "linux", feature = "unix-file-copy-paste")
)))]
async fn test_clipboard_context() {
#[cfg(target_os = "linux")]
let dur = {
let dur = Duration::from_micros(500);
arboard::Clipboard::set_x11_server_conn_timeout(dur);
dur
};
let _ctx = ClipboardContext::new();
#[cfg(target_os = "linux")]
{
assert_eq!(
arboard::Clipboard::get_x11_server_conn_timeout(),
dur,
"Failed to restore x11 server conn timeout"
);
}
}
}

View File

@@ -34,18 +34,16 @@ pub fn new() -> GenericService {
}
fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> {
if let Some(ctx) = state.ctx.as_mut() {
if let Some(msg) = check_clipboard(ctx, None) {
sp.send(msg);
}
sp.snapshot(|sps| {
let txt = crate::CONTENT.lock().unwrap().clone();
if !txt.is_empty() {
let msg_out = crate::create_clipboard_msg(txt);
sps.send_shared(Arc::new(msg_out));
}
Ok(())
})?;
if let Some(msg) = check_clipboard(&mut state.ctx, None) {
sp.send(msg);
}
sp.snapshot(|sps| {
let txt = crate::CONTENT.lock().unwrap().clone();
if !txt.is_empty() {
let msg_out = crate::create_clipboard_msg(txt);
sps.send_shared(Arc::new(msg_out));
}
Ok(())
})?;
Ok(())
}