diff --git a/libs/clipboard/src/platform/linux/mod.rs b/libs/clipboard/src/platform/linux/mod.rs index a006e1705..736b6e740 100644 --- a/libs/clipboard/src/platform/linux/mod.rs +++ b/libs/clipboard/src/platform/linux/mod.rs @@ -221,6 +221,12 @@ impl LocalFile { let wstr: WString = WString::from(&self.name); let name = wstr.as_bytes(); + log::debug!( + "put file to list: name_len {}, name {}", + name.len(), + &self.name + ); + let flags = 0x4064; // flags, 4 bytes @@ -382,7 +388,9 @@ impl ClipboardContext { let prefix = self.fuse_mount_point.clone(); let paths: Vec = paths.iter().cloned().map(|p| prefix.join(p)).collect(); log::debug!("setting clipboard with paths: {:?}", paths); - self.clipboard.set_file_list(&paths) + self.clipboard.set_file_list(&paths)?; + log::debug!("clipboard set, paths: {:?}", paths); + Ok(()) } fn serve_file_contents( @@ -544,8 +552,8 @@ impl ClipboardContext { pub fn serve(&self, conn_id: i32, msg: ClipboardFile) -> Result<(), CliprdrError> { if self.is_stopped() { - log::debug!("cliprdr stopped, skip serving clipboard"); - return Ok(()); + log::debug!("cliprdr stopped, restart it"); + self.run()?; } match msg { ClipboardFile::NotifyCallback { .. } => { diff --git a/libs/clipboard/src/platform/linux/x11.rs b/libs/clipboard/src/platform/linux/x11.rs index 093c3827d..579e7f317 100644 --- a/libs/clipboard/src/platform/linux/x11.rs +++ b/libs/clipboard/src/platform/linux/x11.rs @@ -1,4 +1,5 @@ use std::{ + collections::BTreeSet, path::PathBuf, sync::atomic::{AtomicBool, Ordering}, }; @@ -8,6 +9,7 @@ use hbb_common::{ log, }; use once_cell::sync::OnceCell; +use parking_lot::Mutex; use x11_clipboard::Clipboard; use x11rb::protocol::xproto::Atom; @@ -23,6 +25,9 @@ use super::{encode_path_to_uri, parse_plain_uri_list, SysClipboard}; static X11_CLIPBOARD: OnceCell = OnceCell::new(); +// this is tested on an Arch Linux with X11 +const X11_CLIPBOARD_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(70); + fn get_clip() -> Result<&'static Clipboard, CliprdrError> { X11_CLIPBOARD.get_or_try_init(|| Clipboard::new().map_err(|_| CliprdrError::CliprdrInit)) } @@ -32,6 +37,8 @@ pub struct X11Clipboard { ignore_path: PathBuf, text_uri_list: Atom, gnome_copied_files: Atom, + + former_file_list: Mutex>, } impl X11Clipboard { @@ -50,15 +57,18 @@ impl X11Clipboard { stop: AtomicBool::new(false), text_uri_list, gnome_copied_files, + former_file_list: Mutex::new(vec![]), }) } fn load(&self, target: Atom) -> Result, CliprdrError> { let clip = get_clip()?.setter.atoms.clipboard; let prop = get_clip()?.setter.atoms.property; - log::debug!("try to load clipboard content"); + // NOTE: + // # why not use `load_wait` + // load_wait is likely to wait forever, which is not what we want get_clip()? - .load_wait(clip, target, prop) + .load(clip, target, prop, X11_CLIPBOARD_TIMEOUT) .map_err(|_| CliprdrError::ConversionFailure) } @@ -90,6 +100,8 @@ impl X11Clipboard { impl SysClipboard for X11Clipboard { fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError> { + *self.former_file_list.lock() = paths.to_vec(); + let uri_list: Vec = paths.iter().map(|pb| encode_path_to_uri(pb)).collect(); let uri_list = uri_list.join("\n"); let text_uri_list_data = uri_list.as_bytes().to_vec(); @@ -109,12 +121,22 @@ impl SysClipboard for X11Clipboard { fn start(&self) { self.stop.store(false, Ordering::Relaxed); - while let Ok(sth) = self.wait_file_list() { + loop { + let sth = match self.wait_file_list() { + Ok(sth) => sth, + Err(e) => { + log::warn!("failed to get file list from clipboard: {}", e); + std::thread::sleep(std::time::Duration::from_millis(100)); + continue; + } + }; + if self.is_stopped() { break; } let Some(paths) = sth else { + // just sleep std::thread::sleep(std::time::Duration::from_millis(100)); continue; }; @@ -129,8 +151,20 @@ impl SysClipboard for X11Clipboard { continue; } - // send update to server - log::debug!("clipboard updated: {:?}", filtered); + { + let mut former = self.former_file_list.lock(); + + let filtered_st: BTreeSet<_> = filtered.iter().collect(); + let former_st = former.iter().collect(); + if filtered_st == former_st { + std::thread::sleep(std::time::Duration::from_millis(100)); + continue; + } + + // send update to server + log::debug!("clipboard updated: {:?}", filtered); + *former = filtered; + } if let Err(e) = send_format_list(0) { log::warn!("failed to send format list: {}", e); @@ -138,6 +172,7 @@ impl SysClipboard for X11Clipboard { std::thread::sleep(std::time::Duration::from_millis(100)); } + log::debug!("stop listening file related atoms on clipboard"); } fn send_format_list(&self, conn_id: i32) -> Result<(), CliprdrError> { diff --git a/libs/clipboard/src/platform/mod.rs b/libs/clipboard/src/platform/mod.rs index 604055905..ec2149502 100644 --- a/libs/clipboard/src/platform/mod.rs +++ b/libs/clipboard/src/platform/mod.rs @@ -52,6 +52,8 @@ pub fn create_cliprdr_context( }; let linux_ctx = linux::ClipboardContext::new(timeout, rd_mnt)?; + log::debug!("start cliprdr FUSE"); + linux_ctx.run().expect("failed to start cliprdr FUSE"); Ok(Box::new(linux_ctx) as Box<_>) } diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 3f5df545b..25783ab9a 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -303,7 +303,8 @@ impl Remote { if stop { ContextSend::set_is_stopped(); } else { - allow_err!(peer.send(&crate::clipboard_file::clip_2_msg(clip)).await); + let msg = crate::clipboard_file::clip_2_msg(clip); + allow_err!(peer.send(&msg).await); } } }, diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 53d1252ae..2bbab9dde 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -338,9 +338,11 @@ impl IpcTaskRunner { let _tx_clip; #[cfg(any(target_os = "windows", target_os = "linux"))] if self.conn_id > 0 && is_authorized { + log::debug!("Clipboard is enabled from client peer: type 1"); rx_clip1 = clipboard::get_rx_cliprdr_server(self.conn_id); rx_clip = rx_clip1.lock().await; } else { + log::debug!("Clipboard is enabled from client peer, actually useless: type 2"); let rx_clip2; (_tx_clip, rx_clip2) = unbounded_channel::(); rx_clip1 = Arc::new(TokioMutex::new(rx_clip2)); @@ -480,7 +482,8 @@ impl IpcTaskRunner { } } Some(data) = self.rx.recv() => { - if self.stream.send(&data).await.is_err() { + if let Err(e) = self.stream.send(&data).await { + log::error!("error encountered in IPC task, quitting: {}", e); break; } match &data {