Merge pull request #6227 from ClSlaid/feat/osx/pasteboard-file
[feat] osx pasteboard file copy and paste support
This commit is contained in:
		
						commit
						7480ead76a
					
				
							
								
								
									
										2462
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2462
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -69,7 +69,7 @@ num_cpus = "1.15" | |||||||
| bytes = { version = "1.4", features = ["serde"] } | bytes = { version = "1.4", features = ["serde"] } | ||||||
| default-net = "0.14" | default-net = "0.14" | ||||||
| wol-rs = "1.0" | wol-rs = "1.0" | ||||||
| flutter_rust_bridge = { version = "1.75", features = ["uuid"], optional = true} | flutter_rust_bridge = { version = "=1.75", features = ["uuid"], optional = true} | ||||||
| errno = "0.3" | errno = "0.3" | ||||||
| rdev = { git = "https://github.com/fufesou/rdev" } | rdev = { git = "https://github.com/fufesou/rdev" } | ||||||
| url = { version = "2.3", features = ["serde"] } | url = { version = "2.3", features = ["serde"] } | ||||||
| @ -164,7 +164,7 @@ winapi = { version = "0.3", features = [ "winnt" ] } | |||||||
| [build-dependencies] | [build-dependencies] | ||||||
| cc = "1.0" | cc = "1.0" | ||||||
| hbb_common = { path = "libs/hbb_common" } | hbb_common = { path = "libs/hbb_common" } | ||||||
| flutter_rust_bridge_codegen = "1.75" | flutter_rust_bridge_codegen = "=1.75" | ||||||
| os-version = "0.2" | os-version = "0.2" | ||||||
| 
 | 
 | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
|  | |||||||
| @ -20,7 +20,8 @@ unix-file-copy-paste = [ | |||||||
| "dep:dashmap", | "dep:dashmap", | ||||||
| "dep:percent-encoding", | "dep:percent-encoding", | ||||||
| "dep:utf16string", | "dep:utf16string", | ||||||
| "dep:once_cell" | "dep:once_cell", | ||||||
|  | "dep:cacao" | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [dependencies] | [dependencies] | ||||||
| @ -32,12 +33,17 @@ hbb_common = { path = "../hbb_common" } | |||||||
| parking_lot = {version = "0.12"} | parking_lot = {version = "0.12"} | ||||||
| 
 | 
 | ||||||
| [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] | [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] | ||||||
| once_cell = {version = "1.18", optional = true} |  | ||||||
| x11rb =  {version = "0.12", features = ["all-extensions"], optional = true} |  | ||||||
| rand = {version = "0.8", optional = true} | rand = {version = "0.8", optional = true} | ||||||
| fuser = {version = "0.13", optional = true} | fuser = {version = "0.13", optional = true} | ||||||
| libc = {version = "0.2", optional = true} | libc = {version = "0.2", optional = true} | ||||||
| dashmap = {version ="5.5", optional = true} | dashmap = {version ="5.5", optional = true} | ||||||
| percent-encoding = {version  ="2.3", optional = true} |  | ||||||
| utf16string = {version = "0.2", optional = true} | utf16string = {version = "0.2", optional = true} | ||||||
|  | once_cell = {version = "1.18", optional = true} | ||||||
|  | 
 | ||||||
|  | [target.'cfg(target_os = "linux")'.dependencies] | ||||||
|  | percent-encoding = {version  ="2.3", optional = true} | ||||||
| x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true} | x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true} | ||||||
|  | x11rb =  {version = "0.12", features = ["all-extensions"], optional = true} | ||||||
|  | 
 | ||||||
|  | [target.'cfg(target_os = "macos")'.dependencies] | ||||||
|  | cacao = {git="https://github.com/clslaid/cacao", branch = "feat/set-file-urls", optional = true} | ||||||
|  | |||||||
| @ -1,13 +1,9 @@ | |||||||
|  | #[cfg(target_os = "windows")] | ||||||
| fn build_c_impl() { | fn build_c_impl() { | ||||||
|     #[cfg(not(target_os = "linux"))] |  | ||||||
|     let mut build = cc::Build::new(); |     let mut build = cc::Build::new(); | ||||||
| 
 | 
 | ||||||
|     #[cfg(target_os = "windows")] |  | ||||||
|     build.file("src/windows/wf_cliprdr.c"); |     build.file("src/windows/wf_cliprdr.c"); | ||||||
|     #[cfg(target_os = "macos")] |  | ||||||
|     build.file("src/OSX/Clipboard.m"); |  | ||||||
| 
 | 
 | ||||||
|     #[cfg(not(target_os = "linux"))] |  | ||||||
|     { |     { | ||||||
|         build.flag_if_supported("-Wno-c++0x-extensions"); |         build.flag_if_supported("-Wno-c++0x-extensions"); | ||||||
|         build.flag_if_supported("-Wno-return-type-c-linkage"); |         build.flag_if_supported("-Wno-return-type-c-linkage"); | ||||||
| @ -30,12 +26,10 @@ fn build_c_impl() { | |||||||
|         build.compile("mycliprdr"); |         build.compile("mycliprdr"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[cfg(target_os = "windows")] |  | ||||||
|     println!("cargo:rerun-if-changed=src/windows/wf_cliprdr.c"); |     println!("cargo:rerun-if-changed=src/windows/wf_cliprdr.c"); | ||||||
|     #[cfg(target_os = "macos")] |  | ||||||
|     println!("cargo:rerun-if-changed=src/OSX/Clipboard.m"); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn main() { | fn main() { | ||||||
|  |     #[cfg(target_os = "windows")] | ||||||
|     build_c_impl(); |     build_c_impl(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,11 +0,0 @@ | |||||||
| #include "../cliprdr.h" |  | ||||||
| 
 |  | ||||||
| void mac_cliprdr_init(CliprdrClientContext *cliprdr) |  | ||||||
| { |  | ||||||
|     (void)cliprdr; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void mac_cliprdr_uninit(CliprdrClientContext *cliprdr) |  | ||||||
| { |  | ||||||
|     (void)cliprdr; |  | ||||||
| } |  | ||||||
| @ -154,6 +154,11 @@ impl fuser::Filesystem for FuseClient { | |||||||
|         let mut server = self.server.lock(); |         let mut server = self.server.lock(); | ||||||
|         server.getattr(req, ino, reply) |         server.getattr(req, ino, reply) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     fn statfs(&mut self, req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyStatfs) { | ||||||
|  |         let mut server = self.server.lock(); | ||||||
|  |         server.statfs(req, ino, reply) | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// fuse server
 | /// fuse server
 | ||||||
| @ -486,6 +491,15 @@ impl fuser::Filesystem for FuseServer { | |||||||
|         let attr = (&entry.attributes).into(); |         let attr = (&entry.attributes).into(); | ||||||
|         reply.attr(&std::time::Duration::default(), &attr) |         reply.attr(&std::time::Duration::default(), &attr) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     fn statfs(&mut self, _req: &fuser::Request<'_>, _ino: u64, reply: fuser::ReplyStatfs) { | ||||||
|  |         let mut blocks = 0; | ||||||
|  |         for file in self.files.iter() { | ||||||
|  |             blocks += file.attributes.size / (BLOCK_SIZE as u64) | ||||||
|  |                 + (file.attributes.size % (BLOCK_SIZE as u64) != 0) as u64; | ||||||
|  |         } | ||||||
|  |         reply.statfs(blocks, 0, 0, 0, 0, BLOCK_SIZE, 512, BLOCK_SIZE) | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl FuseServer { | impl FuseServer { | ||||||
|  | |||||||
| @ -58,6 +58,7 @@ pub fn create_cliprdr_context( | |||||||
| 
 | 
 | ||||||
|         Ok(Box::new(unix_ctx) as Box<_>) |         Ok(Box::new(unix_ctx) as Box<_>) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     #[cfg(not(feature = "unix-file-copy-paste"))] |     #[cfg(not(feature = "unix-file-copy-paste"))] | ||||||
|     return Ok(Box::new(DummyCliprdrContext {}) as Box<_>); |     return Ok(Box::new(DummyCliprdrContext {}) as Box<_>); | ||||||
| } | } | ||||||
| @ -81,6 +82,7 @@ impl CliprdrServiceContext for DummyCliprdrContext { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[cfg(feature = "unix-file-copy-paste")] | #[cfg(feature = "unix-file-copy-paste")] | ||||||
|  | #[cfg(any(target_os = "linux", target_os = "macos"))] | ||||||
| // begin of epoch used by microsoft
 | // begin of epoch used by microsoft
 | ||||||
| // 1601-01-01 00:00:00 + LDAP_EPOCH_DELTA*(100 ns) = 1970-01-01 00:00:00
 | // 1601-01-01 00:00:00 + LDAP_EPOCH_DELTA*(100 ns) = 1970-01-01 00:00:00
 | ||||||
| const LDAP_EPOCH_DELTA: u64 = 116444772610000000; | const LDAP_EPOCH_DELTA: u64 = 116444772610000000; | ||||||
|  | |||||||
| @ -25,8 +25,13 @@ use self::url::{encode_path_to_uri, parse_plain_uri_list}; | |||||||
| use super::fuse::FuseServer; | use super::fuse::FuseServer; | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "linux")] | #[cfg(target_os = "linux")] | ||||||
|  | /// clipboard implementation of x11
 | ||||||
| pub mod x11; | pub mod x11; | ||||||
| 
 | 
 | ||||||
|  | #[cfg(target_os = "macos")] | ||||||
|  | /// clipboard implementation of macos
 | ||||||
|  | pub mod ns_clipboard; | ||||||
|  | 
 | ||||||
| pub mod local_file; | pub mod local_file; | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "linux")] | #[cfg(target_os = "linux")] | ||||||
| @ -76,7 +81,7 @@ fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, Cli | |||||||
|     } |     } | ||||||
|     #[cfg(not(feature = "wayland"))] |     #[cfg(not(feature = "wayland"))] | ||||||
|     { |     { | ||||||
|         pub use x11::*; |         use x11::*; | ||||||
|         let x11_clip = X11Clipboard::new(ignore_path)?; |         let x11_clip = X11Clipboard::new(ignore_path)?; | ||||||
|         Ok(Box::new(x11_clip) as Box<_>) |         Ok(Box::new(x11_clip) as Box<_>) | ||||||
|     } |     } | ||||||
| @ -84,7 +89,9 @@ fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, Cli | |||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "macos")] | #[cfg(target_os = "macos")] | ||||||
| fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, CliprdrError> { | fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, CliprdrError> { | ||||||
|     unimplemented!() |     use ns_clipboard::*; | ||||||
|  |     let ns_pb = NsPasteboard::new(ignore_path)?; | ||||||
|  |     Ok(Box::new(ns_pb) as Box<_>) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
|  | |||||||
							
								
								
									
										97
									
								
								libs/clipboard/src/platform/unix/ns_clipboard.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								libs/clipboard/src/platform/unix/ns_clipboard.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | |||||||
|  | use std::{collections::BTreeSet, path::PathBuf}; | ||||||
|  | 
 | ||||||
|  | use cacao::pasteboard::{Pasteboard, PasteboardName}; | ||||||
|  | use hbb_common::log; | ||||||
|  | use parking_lot::Mutex; | ||||||
|  | 
 | ||||||
|  | use crate::{platform::unix::send_format_list, CliprdrError}; | ||||||
|  | 
 | ||||||
|  | use super::SysClipboard; | ||||||
|  | 
 | ||||||
|  | #[inline] | ||||||
|  | fn wait_file_list() -> Option<Vec<PathBuf>> { | ||||||
|  |     let pb = Pasteboard::named(PasteboardName::General); | ||||||
|  |     pb.get_file_urls() | ||||||
|  |         .ok() | ||||||
|  |         .map(|v| v.into_iter().map(|nsurl| nsurl.pathbuf()).collect()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[inline] | ||||||
|  | fn set_file_list(file_list: &[PathBuf]) -> Result<(), CliprdrError> { | ||||||
|  |     let pb = Pasteboard::named(PasteboardName::General); | ||||||
|  |     pb.set_files(file_list.to_vec()) | ||||||
|  |         .map_err(|_| CliprdrError::ClipboardInternalError) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct NsPasteboard { | ||||||
|  |     ignore_path: PathBuf, | ||||||
|  | 
 | ||||||
|  |     former_file_list: Mutex<Vec<PathBuf>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl NsPasteboard { | ||||||
|  |     pub fn new(ignore_path: &PathBuf) -> Result<Self, CliprdrError> { | ||||||
|  |         Ok(Self { | ||||||
|  |             ignore_path: ignore_path.to_owned(), | ||||||
|  |             former_file_list: Mutex::new(vec![]), | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl SysClipboard for NsPasteboard { | ||||||
|  |     fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError> { | ||||||
|  |         *self.former_file_list.lock() = paths.to_vec(); | ||||||
|  |         set_file_list(paths) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn start(&self) { | ||||||
|  |         { | ||||||
|  |             *self.former_file_list.lock() = vec![]; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         loop { | ||||||
|  |             let file_list = match wait_file_list() { | ||||||
|  |                 Some(v) => v, | ||||||
|  |                 None => { | ||||||
|  |                     std::thread::sleep(std::time::Duration::from_millis(100)); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             let filtered = file_list | ||||||
|  |                 .into_iter() | ||||||
|  |                 .filter(|pb| !pb.starts_with(&self.ignore_path)) | ||||||
|  |                 .collect::<Vec<_>>(); | ||||||
|  | 
 | ||||||
|  |             if filtered.is_empty() { | ||||||
|  |                 std::thread::sleep(std::time::Duration::from_millis(100)); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             { | ||||||
|  |                 let mut former = self.former_file_list.lock(); | ||||||
|  | 
 | ||||||
|  |                 let filtered_st: BTreeSet<_> = filtered.iter().collect(); | ||||||
|  |                 let former_st = former.iter().collect::<BTreeSet<_>>(); | ||||||
|  |                 if filtered_st == former_st { | ||||||
|  |                     std::thread::sleep(std::time::Duration::from_millis(100)); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 *former = filtered; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if let Err(e) = send_format_list(0) { | ||||||
|  |                 log::warn!("failed to send format list: {}", e); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             std::thread::sleep(std::time::Duration::from_millis(100)); | ||||||
|  |         } | ||||||
|  |         log::debug!("stop listening file related atoms on clipboard"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_file_list(&self) -> Vec<PathBuf> { | ||||||
|  |         self.former_file_list.lock().clone() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -479,7 +479,7 @@ impl Connection { | |||||||
|                         ipc::Data::RawMessage(bytes) => { |                         ipc::Data::RawMessage(bytes) => { | ||||||
|                             allow_err!(conn.stream.send_raw(bytes).await); |                             allow_err!(conn.stream.send_raw(bytes).await); | ||||||
|                         } |                         } | ||||||
|                         #[cfg(any(target_os="windows", target_os="linux"))] |                         #[cfg(any(target_os="windows", target_os="linux", target_os = "macos"))] | ||||||
|                         ipc::Data::ClipboardFile(clip) => { |                         ipc::Data::ClipboardFile(clip) => { | ||||||
|                             allow_err!(conn.stream.send(&clip_2_msg(clip)).await); |                             allow_err!(conn.stream.send(&clip_2_msg(clip)).await); | ||||||
|                         } |                         } | ||||||
|  | |||||||
| @ -450,7 +450,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> { | |||||||
|                                     } |                                     } | ||||||
|                                 } |                                 } | ||||||
|                                 Data::ClipboardFileEnabled(_enabled) => { |                                 Data::ClipboardFileEnabled(_enabled) => { | ||||||
|                                     #[cfg(any(target_os= "windows",target_os ="linux"))] |                                     #[cfg(any(target_os= "windows",target_os ="linux", target_os = "macos"))] | ||||||
|                                     { |                                     { | ||||||
|                                         self.file_transfer_enabled_peer = _enabled; |                                         self.file_transfer_enabled_peer = _enabled; | ||||||
|                                     } |                                     } | ||||||
| @ -489,7 +489,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> { | |||||||
|                     } |                     } | ||||||
|                     match &data { |                     match &data { | ||||||
|                         Data::SwitchPermission{name: _name, enabled: _enabled} => { |                         Data::SwitchPermission{name: _name, enabled: _enabled} => { | ||||||
|                             #[cfg(any(target_os="linux", target_os="windows"))] |                             #[cfg(any(target_os="linux", target_os="windows", target_os = "macos"))] | ||||||
|                             if _name == "file" { |                             if _name == "file" { | ||||||
|                                 self.file_transfer_enabled = *_enabled; |                                 self.file_transfer_enabled = *_enabled; | ||||||
|                             } |                             } | ||||||
| @ -512,7 +512,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> { | |||||||
|                             let file_transfer_enabled_peer = self.file_transfer_enabled_peer; |                             let file_transfer_enabled_peer = self.file_transfer_enabled_peer; | ||||||
|                             let stop = is_stopping_allowed && !(is_clipboard_enabled && file_transfer_enabled && file_transfer_enabled_peer); |                             let stop = is_stopping_allowed && !(is_clipboard_enabled && file_transfer_enabled && file_transfer_enabled_peer); | ||||||
|                             log::debug!( |                             log::debug!( | ||||||
|                                 "Process clipboard message from cm, stop: {}, is_stopping_allowed: {}, is_clipboard_enabled: {}, file_transfer_enabled: {}, file_transfer_enabled_peer: {}", |                                 "Process clipboard message from clip, stop: {}, is_stopping_allowed: {}, is_clipboard_enabled: {}, file_transfer_enabled: {}, file_transfer_enabled_peer: {}", | ||||||
|                                 stop, is_stopping_allowed, is_clipboard_enabled, file_transfer_enabled, file_transfer_enabled_peer); |                                 stop, is_stopping_allowed, is_clipboard_enabled, file_transfer_enabled, file_transfer_enabled_peer); | ||||||
|                             if stop { |                             if stop { | ||||||
|                                 ContextSend::set_is_stopped(); |                                 ContextSend::set_is_stopped(); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user