diff --git a/libs/clipboard/src/platform/fuse.rs b/libs/clipboard/src/platform/fuse.rs index f641b2cc3..cf0263309 100644 --- a/libs/clipboard/src/platform/fuse.rs +++ b/libs/clipboard/src/platform/fuse.rs @@ -181,7 +181,7 @@ impl FuseServer { } impl FuseServer { - pub fn serve(&mut self, reply: ClipboardFile) -> Result<(), CliprdrError> { + pub fn serve(&self, reply: ClipboardFile) -> Result<(), CliprdrError> { self.tx.send(reply).map_err(|e| { log::error!("failed to serve cliprdr reply from endpoint: {:?}", e); CliprdrError::ClipboardInternalError @@ -190,6 +190,15 @@ impl FuseServer { } } +impl FuseServer { + pub fn load_file_list(&mut self, files: Vec) -> Result<(), CliprdrError> { + let tree = FuseNode::build_tree(files)?; + self.files = tree; + self.generation.fetch_add(1, Ordering::Relaxed); + Ok(()) + } +} + impl fuser::Filesystem for FuseServer { fn init( &mut self, @@ -499,165 +508,11 @@ impl FuseServer { paths } - /// fetch file list from remote - fn sync_file_system( - &mut self, - conn_id: i32, - file_group_format_id: i32, - _file_contents_format_id: i32, - ) -> Result { - let resp = self.send_sync_fs_request(conn_id, file_group_format_id, self.timeout)?; - let descs = match resp { - ClipboardFile::FormatDataResponse { - msg_flags, - format_data, - } => { - if msg_flags != 0x1 { - log::error!("clipboard FUSE server: received unexpected response flags"); - return Err(CliprdrError::ClipboardInternalError); - } - let descs = FileDescription::parse_file_descriptors(format_data, conn_id)?; - - descs - } - _ => { - log::error!("clipboard FUSE server: received unexpected response type"); - - return Err(CliprdrError::ClipboardInternalError); - } - }; - - let mut new_tree = FuseNode::build_tree(descs)?; - let res = new_tree - .iter_mut() - .filter(|f_node| f_node.is_file() && f_node.attributes.size == 0) - .try_for_each(|f_node| self.sync_node_size(f_node)); - - if let Err(err) = res { - log::error!( - "clipboard FUSE server: failed to fetch file size: {:?}", - err - ); - - return Err(CliprdrError::ClipboardInternalError); - } - - // replace current file system - self.files = new_tree; - self.generation.fetch_add(1, Ordering::Relaxed); - - Ok(true) - } - - fn send_sync_fs_request( - &self, - conn_id: i32, - file_group_format_id: i32, - timeout: std::time::Duration, - ) -> Result { - // request file list - let data = ClipboardFile::FormatDataRequest { - requested_format_id: file_group_format_id, - }; - send_data(conn_id, data); - self.rx.recv_timeout(timeout).map_err(|e| { - log::error!("failed to receive file list from channel: {:?}", e); - CliprdrError::ClipboardInternalError - }) - } - - pub fn update_files( - &mut self, - conn_id: i32, - file_group_format_id: i32, - file_contents_format_id: i32, - ) -> Result { - self.sync_file_system(conn_id, file_group_format_id, file_contents_format_id) - } - /// allocate a new file descriptor fn alloc_fd(&self) -> u64 { self.file_handle_counter.fetch_add(1, Ordering::Relaxed) } - // synchronize metadata with remote - fn sync_node_size(&self, node: &mut FuseNode) -> Result<(), std::io::Error> { - log::debug!( - "syncing metadata for {:?} on stream: {}", - node.name, - node.stream_id - ); - - let request = ClipboardFile::FileContentsRequest { - stream_id: node.stream_id, - list_index: node.inode as i32 - 2, // list index at least 2 - dw_flags: 1, - - n_position_low: 0, - n_position_high: 0, - cb_requested: 8, - have_clip_data_id: false, - clip_data_id: 0, - }; - - send_data(node.conn_id, request); - - log::debug!( - "waiting for metadata sync reply for {:?} on channel {}", - node.name, - node.conn_id - ); - - let reply = self - .rx - .recv_timeout(self.timeout) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::TimedOut, e))?; - log::debug!( - "got metadata sync reply for {:?} on channel {}", - node.name, - node.conn_id - ); - - let size = match reply { - ClipboardFile::FileContentsResponse { - msg_flags, - stream_id, - requested_data, - } => { - if stream_id != node.stream_id { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "stream id mismatch", - )); - } - if msg_flags & 1 == 0 { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "failure request", - )); - } - if requested_data.len() != 8 { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "invalid data length", - )); - } - let little_endian_value = u64::from_le_bytes(requested_data.try_into().unwrap()); - little_endian_value - } - _ => { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "invalid reply", - )); - } - }; - log::debug!("got metadata sync reply for {:?}: size {}", node.name, size); - - node.attributes.size = size; - Ok(()) - } - fn read_node( &self, node: &FuseNode, @@ -916,6 +771,7 @@ impl FuseNode { } } + #[allow(unused)] pub fn is_file(&self) -> bool { self.attributes.kind == FileType::File } diff --git a/libs/clipboard/src/platform/linux/mod.rs b/libs/clipboard/src/platform/linux/mod.rs index 736b6e740..73448d25f 100644 --- a/libs/clipboard/src/platform/linux/mod.rs +++ b/libs/clipboard/src/platform/linux/mod.rs @@ -14,10 +14,12 @@ use hbb_common::{ log, }; use lazy_static::lazy_static; -use parking_lot::{Mutex, RwLock}; +use parking_lot::Mutex; use utf16string::WString; -use crate::{send_data, ClipboardFile, CliprdrError, CliprdrServiceContext}; +use crate::{ + platform::fuse::FileDescription, send_data, ClipboardFile, CliprdrError, CliprdrServiceContext, +}; use super::{fuse::FuseServer, LDAP_EPOCH_DELTA}; @@ -44,13 +46,11 @@ fn add_remote_format(local_name: &str, remote_id: i32) { } trait SysClipboard: Send + Sync { - fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError>; - fn stop(&self); fn start(&self); - /// send to 0 will send to all channels - fn send_format_list(&self, conn_id: i32) -> Result<(), CliprdrError>; - /// send to 0 will send to all channels - fn send_file_list(&self, conn_id: i32) -> Result<(), CliprdrError>; + fn stop(&self); + + fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError>; + fn get_file_list(&self) -> Result, CliprdrError>; } fn get_sys_clipboard(ignore_path: &PathBuf) -> Result, CliprdrError> { @@ -312,7 +312,6 @@ pub struct ClipboardContext { fuse_server: Arc>, - file_list: RwLock>, clipboard: Arc, } @@ -328,13 +327,11 @@ impl ClipboardContext { let clipboard = get_sys_clipboard(&fuse_mount_point)?; let clipboard = Arc::from(clipboard) as Arc<_>; - let file_list = RwLock::new(vec![]); Ok(Self { fuse_mount_point, fuse_server, fuse_handle: Mutex::new(None), - file_list, clipboard, }) } @@ -379,10 +376,6 @@ impl ClipboardContext { Ok(()) } - pub fn stop(&self) -> Result<(), CliprdrError> { - self.set_is_stopped() - } - /// set clipboard data from file list pub fn set_clipboard(&self, paths: &[PathBuf]) -> Result<(), CliprdrError> { let prefix = self.fuse_mount_point.clone(); @@ -404,7 +397,7 @@ impl ClipboardContext { stream_id, file_idx, } => { - let file_list = self.file_list.read(); + let file_list = self.clipboard.get_file_list()?; let Some(file) = file_list.get(file_idx) else { log::error!( "invalid file index {} requested from conn: {}", @@ -436,7 +429,7 @@ impl ClipboardContext { offset, length, } => { - let file_list = self.file_list.read(); + let file_list = self.clipboard.get_file_list()?; let Some(file) = file_list.get(file_idx) else { log::error!( "invalid file index {} requested from conn: {}", @@ -523,33 +516,6 @@ impl ClipboardContext { self.fuse_handle.lock().is_none() } - pub fn set_is_stopped(&self) -> Result<(), CliprdrError> { - if self.is_stopped() { - log::debug!("cliprdr already stopped"); - return Ok(()); - } - // unmount the fuse - if let Some(fuse_handle) = self.fuse_handle.lock().take() { - fuse_handle.join(); - } - self.clipboard.stop(); - Ok(()) - } - - pub fn empty_clipboard(&self, conn_id: i32) -> Result { - // gc all files, the clipboard is going to shutdown - if self.is_stopped() { - log::debug!("cliprdr stopped, skip emptying clipboard"); - return Ok(true); - } - - self.fuse_server.lock().update_files( - conn_id, - FILEDESCRIPTOR_FORMAT_ID, - FILECONTENTS_FORMAT_ID, - ) - } - pub fn serve(&self, conn_id: i32, msg: ClipboardFile) -> Result<(), CliprdrError> { if self.is_stopped() { log::debug!("cliprdr stopped, restart it"); @@ -562,9 +528,8 @@ impl ClipboardContext { ClipboardFile::MonitorReady => { log::debug!("server_monitor_ready called"); - // ignore capabilities for now + self.send_file_list(conn_id)?; - self.clipboard.send_file_list(0)?; Ok(()) } @@ -595,17 +560,19 @@ impl ClipboardContext { add_remote_format(FILECONTENTS_FORMAT_NAME, file_contents_id); add_remote_format(FILEDESCRIPTORW_FORMAT_NAME, file_descriptor_id); - self.fuse_server.lock().update_files( - conn_id, - file_descriptor_id, - file_contents_id, - )?; + + // sync file system from peer + let data = ClipboardFile::FormatDataRequest { + requested_format_id: file_descriptor_id, + }; + send_data(conn_id, data); + Ok(()) } ClipboardFile::FormatListResponse { msg_flags } => { log::debug!("server_format_list_response called"); if msg_flags != 0x1 { - self.clipboard.send_format_list(conn_id) + send_format_list(conn_id) } else { Ok(()) } @@ -625,7 +592,7 @@ impl ClipboardContext { }; if format == FILEDESCRIPTORW_FORMAT_NAME { - self.clipboard.send_file_list(conn_id)?; + self.send_file_list(conn_id)?; } else if format == FILECONTENTS_FORMAT_NAME { log::error!( "try to read file contents with FormatDataRequest from conn={}", @@ -642,13 +609,27 @@ impl ClipboardContext { } Ok(()) } - ClipboardFile::FormatDataResponse { .. } => { - // we don't know its corresponding request, no resend can be performed + ClipboardFile::FormatDataResponse { + msg_flags, + format_data, + } => { log::debug!("server_format_data_response called"); - let mut fuse_server = self.fuse_server.lock(); - fuse_server.serve(msg)?; - let paths = fuse_server.list_root(); + if msg_flags != 0x1 { + resp_format_data_failure(conn_id); + return Ok(()); + } + + // this must be a file descriptor format data + let files = FileDescription::parse_file_descriptors(format_data.into(), conn_id)?; + + let paths = { + let mut fuse_guard = self.fuse_server.lock(); + fuse_guard.load_file_list(files)?; + + fuse_guard.list_root() + }; + self.set_clipboard(&paths)?; Ok(()) } @@ -693,16 +674,30 @@ impl ClipboardContext { } } } + + fn send_file_list(&self, conn_id: i32) -> Result<(), CliprdrError> { + let file_list = self.clipboard.get_file_list()?; + let paths = file_list.into_iter().map(|lf| lf.path).collect(); + + send_file_list(paths, conn_id) + } } impl CliprdrServiceContext for ClipboardContext { fn set_is_stopped(&mut self) -> Result<(), CliprdrError> { - self.stop() + // unmount the fuse + if let Some(fuse_handle) = self.fuse_handle.lock().take() { + fuse_handle.join(); + } + self.clipboard.stop(); + Ok(()) } + fn empty_clipboard(&mut self, _conn_id: i32) -> Result { self.clipboard.set_file_list(&[])?; Ok(true) } + fn server_clip_file(&mut self, conn_id: i32, msg: ClipboardFile) -> Result<(), CliprdrError> { self.serve(conn_id, msg) } @@ -715,3 +710,43 @@ fn resp_format_data_failure(conn_id: i32) { }; send_data(conn_id, data) } + +fn send_format_list(conn_id: i32) -> Result<(), CliprdrError> { + log::debug!("send format list to remote, conn={}", conn_id); + let fd_format_name = get_local_format(FILEDESCRIPTOR_FORMAT_ID) + .unwrap_or(FILEDESCRIPTORW_FORMAT_NAME.to_string()); + let fc_format_name = + get_local_format(FILECONTENTS_FORMAT_ID).unwrap_or(FILECONTENTS_FORMAT_NAME.to_string()); + let format_list = ClipboardFile::FormatList { + format_list: vec![ + (FILEDESCRIPTOR_FORMAT_ID, fd_format_name), + (FILECONTENTS_FORMAT_ID, fc_format_name), + ], + }; + + send_data(conn_id, format_list); + log::debug!("format list to remote dispatched, conn={}", conn_id); + Ok(()) +} + +fn send_file_list(paths: Vec, conn_id: i32) -> Result<(), CliprdrError> { + log::debug!("send file list to remote, conn={}", conn_id); + let files = construct_file_list(paths.as_slice())?; + + let mut data = BytesMut::with_capacity(4 + 592 * files.len()); + data.put_u32_le(paths.len() as u32); + for file in files.iter() { + data.put(file.as_bin().as_slice()); + } + + let format_data = data.to_vec(); + + send_data( + conn_id, + ClipboardFile::FormatDataResponse { + msg_flags: 1, + format_data, + }, + ); + Ok(()) +} diff --git a/libs/clipboard/src/platform/linux/x11.rs b/libs/clipboard/src/platform/linux/x11.rs index 579e7f317..e7e0f846a 100644 --- a/libs/clipboard/src/platform/linux/x11.rs +++ b/libs/clipboard/src/platform/linux/x11.rs @@ -4,21 +4,15 @@ use std::{ sync::atomic::{AtomicBool, Ordering}, }; -use hbb_common::{ - bytes::{BufMut, BytesMut}, - log, -}; +use hbb_common::log; use once_cell::sync::OnceCell; use parking_lot::Mutex; use x11_clipboard::Clipboard; use x11rb::protocol::xproto::Atom; use crate::{ - platform::linux::{ - construct_file_list, FILECONTENTS_FORMAT_ID, FILECONTENTS_FORMAT_NAME, - FILEDESCRIPTORW_FORMAT_NAME, FILEDESCRIPTOR_FORMAT_ID, - }, - send_data, ClipboardFile, CliprdrError, + platform::linux::{construct_file_list, send_format_list}, + CliprdrError, }; use super::{encode_path_to_uri, parse_plain_uri_list, SysClipboard}; @@ -175,89 +169,8 @@ impl SysClipboard for X11Clipboard { log::debug!("stop listening file related atoms on clipboard"); } - fn send_format_list(&self, conn_id: i32) -> Result<(), CliprdrError> { - if self.is_stopped() { - log::debug!("clipboard stopped, skip sending"); - return Ok(()); - } - - let Some(paths) = self.wait_file_list()? else { - log::debug!("no files in format list, skip sending"); - return Ok(()); - }; - - let filtered: Vec<_> = paths - .into_iter() - .filter(|pb| !pb.starts_with(&self.ignore_path)) - .collect(); - - if filtered.is_empty() { - log::debug!("no files in format list, skip sending"); - return Ok(()); - } - - send_format_list(conn_id) - } - - fn send_file_list(&self, conn_id: i32) -> Result<(), CliprdrError> { - if self.is_stopped() { - log::debug!("clipboard stopped, skip sending"); - return Ok(()); - } - let Some(paths) = self.wait_file_list()? else { - log::debug!("no files in format list, skip sending"); - return Ok(()); - }; - - let filtered: Vec<_> = paths - .into_iter() - .filter(|pb| !pb.starts_with(&self.ignore_path)) - .collect(); - - if filtered.is_empty() { - log::debug!("no files in format list, skip sending"); - return Ok(()); - } - - send_file_list(filtered, conn_id) + fn get_file_list(&self) -> Result, CliprdrError> { + let paths = { self.former_file_list.lock().clone() }; + construct_file_list(&paths) } } - -fn send_format_list(conn_id: i32) -> Result<(), CliprdrError> { - log::debug!("send format list to remote, conn={}", conn_id); - let format_list = ClipboardFile::FormatList { - format_list: vec![ - ( - FILEDESCRIPTOR_FORMAT_ID, - FILEDESCRIPTORW_FORMAT_NAME.to_string(), - ), - (FILECONTENTS_FORMAT_ID, FILECONTENTS_FORMAT_NAME.to_string()), - ], - }; - - send_data(conn_id, format_list); - log::debug!("format list to remote dispatched, conn={}", conn_id); - Ok(()) -} - -fn send_file_list(paths: Vec, conn_id: i32) -> Result<(), CliprdrError> { - log::debug!("send file list to remote, conn={}", conn_id); - let files = construct_file_list(paths.as_slice())?; - - let mut data = BytesMut::with_capacity(4 + 592 * files.len()); - data.put_u32_le(paths.len() as u32); - for file in files.iter() { - data.put(file.as_bin().as_slice()); - } - - let format_data = data.to_vec(); - - send_data( - conn_id, - ClipboardFile::FormatDataResponse { - msg_flags: 1, - format_data, - }, - ); - Ok(()) -} diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 35bd35f68..0e6963d65 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -317,6 +317,7 @@ impl Remote { if stop { ContextSend::set_is_stopped(); } else { + log::debug!("Send system clipboard message to remote"); let msg = crate::clipboard_file::clip_2_msg(clip); allow_err!(peer.send(&msg).await); } @@ -1714,6 +1715,7 @@ impl Remote { #[cfg(any(target_os = "windows", target_os = "linux"))] fn handle_cliprdr_msg(&self, clip: hbb_common::message_proto::Cliprdr) { + log::debug!("handling cliprdr msg from server peer"); #[cfg(feature = "flutter")] if let Some(hbb_common::message_proto::cliprdr::Union::FormatList(_)) = &clip.union { if self.client_conn_id @@ -1723,20 +1725,23 @@ impl Remote { } } - if let Some(clip) = crate::clipboard_file::msg_2_clip(clip) { - let is_stopping_allowed = clip.is_stopping_allowed_from_peer(); - let file_transfer_enabled = self.handler.lc.read().unwrap().enable_file_transfer.v; - let stop = is_stopping_allowed && !file_transfer_enabled; - log::debug!( + let Some(clip) = crate::clipboard_file::msg_2_clip(clip) else { + log::warn!("failed to decode cliprdr msg from server peer"); + return; + }; + + let is_stopping_allowed = clip.is_stopping_allowed_from_peer(); + let file_transfer_enabled = self.handler.lc.read().unwrap().enable_file_transfer.v; + let stop = is_stopping_allowed && !file_transfer_enabled; + log::debug!( "Process clipboard message from server peer, stop: {}, is_stopping_allowed: {}, file_transfer_enabled: {}", stop, is_stopping_allowed, file_transfer_enabled); - if !stop { - let _ = ContextSend::proc(|context| -> ResultType<()> { - context - .server_clip_file(self.client_conn_id, clip) - .map_err(|e| e.into()) - }); - } + if !stop { + let _ = ContextSend::proc(|context| -> ResultType<()> { + context + .server_clip_file(self.client_conn_id, clip) + .map_err(|e| e.into()) + }); } } } diff --git a/src/server/connection.rs b/src/server/connection.rs index 39dae9868..5c93e4de5 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -480,6 +480,7 @@ impl Connection { } #[cfg(any(target_os="windows", target_os="linux"))] ipc::Data::ClipboardFile(clip) => { + log::debug!("got clipfile from rx_from_cm, send to stream: {:?}", clip); allow_err!(conn.stream.send(&clip_2_msg(clip)).await); } ipc::Data::PrivacyModeState((_, state)) => { @@ -1785,10 +1786,11 @@ impl Connection { update_clipboard(_cb, None); } } - Some(message::Union::Cliprdr(_clip)) => - { + Some(message::Union::Cliprdr(_clip)) => { + log::debug!("got cliprdr file from connection:{:?}", _clip); #[cfg(any(target_os = "windows", target_os = "linux"))] if let Some(clip) = msg_2_clip(_clip) { + log::debug!("send cliprdr file from connection to cm"); self.send_to_cm(ipc::Data::ClipboardFile(clip)) } } diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 85cd44f1d..53020cf1a 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -425,7 +425,7 @@ impl IpcTaskRunner { } #[cfg(not(any(target_os = "android", target_os = "ios")))] Data::ClipboardFile(_clip) => { - #[cfg(any(windows, linux))] + #[cfg(any(target_os = "windows", target_os="linux"))] { let is_stopping_allowed = _clip.is_stopping_allowed_from_peer(); let is_clipboard_enabled = ContextSend::is_enabled();