diff --git a/libs/clipboard/Cargo.toml b/libs/clipboard/Cargo.toml index 3de02f768..138dc8287 100644 --- a/libs/clipboard/Cargo.toml +++ b/libs/clipboard/Cargo.toml @@ -18,12 +18,17 @@ hbb_common = { path = "../hbb_common" } parking_lot = {version = "0.12"} [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] -once_cell = "1.18" -x11rb = {version = "0.12", features = ["all-extensions"]} rand = {version = "0.8"} fuser = {version = "0.13"} libc = {version = "0.2"} dashmap = "5.5" percent-encoding = "2.3" utf16string = "0.2" + +[target.'cfg(target_os = "linux")'.dependencies] x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch"} +once_cell = "1.18" +x11rb = {version = "0.12", features = ["all-extensions"]} + +[target.'cfg(target_os = "macos")'.dependencies] +cacao = {git="https://github.com/clslaid/cacao", branch = "feat/set-file-urls"} diff --git a/libs/clipboard/build.rs b/libs/clipboard/build.rs index d0da933f4..3902eaa40 100644 --- a/libs/clipboard/build.rs +++ b/libs/clipboard/build.rs @@ -1,13 +1,9 @@ +#[cfg(target_os = "windows")] fn build_c_impl() { - #[cfg(not(target_os = "linux"))] let mut build = cc::Build::new(); - #[cfg(target_os = "windows")] 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-return-type-c-linkage"); @@ -30,12 +26,10 @@ fn build_c_impl() { build.compile("mycliprdr"); } - #[cfg(target_os = "windows")] 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() { + #[cfg(target_os = "windows")] build_c_impl(); } diff --git a/libs/clipboard/src/OSX/Clipboard.m b/libs/clipboard/src/OSX/Clipboard.m deleted file mode 100644 index 658579ce0..000000000 --- a/libs/clipboard/src/OSX/Clipboard.m +++ /dev/null @@ -1,11 +0,0 @@ -#include "../cliprdr.h" - -void mac_cliprdr_init(CliprdrClientContext *cliprdr) -{ - (void)cliprdr; -} - -void mac_cliprdr_uninit(CliprdrClientContext *cliprdr) -{ - (void)cliprdr; -} diff --git a/libs/clipboard/src/platform/mod.rs b/libs/clipboard/src/platform/mod.rs index 8bc1d0ea6..7a4082b95 100644 --- a/libs/clipboard/src/platform/mod.rs +++ b/libs/clipboard/src/platform/mod.rs @@ -19,7 +19,7 @@ pub fn create_cliprdr_context( pub mod fuse; #[cfg(any(target_os = "linux", target_os = "macos"))] pub mod unix; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "macos"))] pub fn create_cliprdr_context( enable_files: bool, _enable_others: bool, @@ -48,11 +48,11 @@ pub fn create_cliprdr_context( log::warn!("umount {:?} may fail: {:?}", mnt_path, e); } - let linux_ctx = unix::ClipboardContext::new(timeout, mnt_path.parse().unwrap())?; + let unix_ctx = unix::ClipboardContext::new(timeout, mnt_path.parse().unwrap())?; log::debug!("start cliprdr FUSE"); - linux_ctx.run().expect("failed to start cliprdr FUSE"); + unix_ctx.run().expect("failed to start cliprdr FUSE"); - Ok(Box::new(linux_ctx) as Box<_>) + Ok(Box::new(unix_ctx) as Box<_>) } struct DummyCliprdrContext {} diff --git a/libs/clipboard/src/platform/unix/mod.rs b/libs/clipboard/src/platform/unix/mod.rs index b74c583c8..93ffb61b3 100644 --- a/libs/clipboard/src/platform/unix/mod.rs +++ b/libs/clipboard/src/platform/unix/mod.rs @@ -24,8 +24,14 @@ use self::url::{encode_path_to_uri, parse_plain_uri_list}; use super::fuse::FuseServer; #[cfg(not(feature = "wayland"))] +#[cfg(target_os = "linux")] +/// clipboard implementation of x11 pub mod x11; +#[cfg(target_os = "macos")] +/// clipboard implementation of macos +pub mod ns_clipboard; + pub mod local_file; pub mod url; @@ -66,6 +72,7 @@ trait SysClipboard: Send + Sync { fn get_file_list(&self) -> Vec; } +#[cfg(target_os = "linux")] fn get_sys_clipboard(ignore_path: &PathBuf) -> Result, CliprdrError> { #[cfg(feature = "wayland")] { @@ -73,12 +80,19 @@ fn get_sys_clipboard(ignore_path: &PathBuf) -> Result, Cli } #[cfg(not(feature = "wayland"))] { - pub use x11::*; + use x11::*; let x11_clip = X11Clipboard::new(ignore_path)?; Ok(Box::new(x11_clip) as Box<_>) } } +#[cfg(target_os = "macos")] +fn get_sys_clipboard(ignore_path: &PathBuf) -> Result, CliprdrError> { + use ns_clipboard::*; + let ns_pb = NSPasteboard::new(ignore_path)?; + Ok(Box::new(ns_pb) as Box<_>) +} + #[derive(Debug)] enum FileContentsRequest { Size { diff --git a/libs/clipboard/src/platform/unix/ns_clipboard.rs b/libs/clipboard/src/platform/unix/ns_clipboard.rs new file mode 100644 index 000000000..210471417 --- /dev/null +++ b/libs/clipboard/src/platform/unix/ns_clipboard.rs @@ -0,0 +1,113 @@ +use std::{ + collections::BTreeSet, + path::PathBuf, + sync::atomic::{AtomicBool, Ordering}, +}; + +use cacao::pasteboard::{Pasteboard, PasteboardName}; +use parking_lot::Mutex; + +use crate::{platform::unix::send_format_list, CliprdrError}; + +use super::SysClipboard; + +pub struct NsPasteboard { + stopped: AtomicBool, + pasteboard: Pasteboard, + ignore_path: PathBuf, + + former_file_list: Mutex>, +} + +impl NsPasteboard { + pub fn new(ignore_path: &PathBuf) -> Result { + let pasteboard = Pasteboard::named(PasteboardName::General); + Ok(Self { + stopped: AtomicBool::new(false), + ignore_path: ignore_path.to_owned(), + pasteboard, + former_file_list: Mutex::new(vec![]), + }) + } + + fn wait_file_list(&self) -> Option> { + self.pasteboard + .get_file_urls() + .ok() + .map(|v| v.into_iter().map(|nsurl| nsurl.to_path_buf()).collect()) + } + + #[inline] + fn is_stopped(&self) -> bool { + self.stopped.load(Ordering::Relaxed) + } +} + +impl SysClipboard for NsPasteboard { + fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError> { + *self.former_file_list.lock() = paths.to_vec(); + let uri_list: Vec = paths.iter().map(encode_path_to_uri).collect(); + let uri_list = uri_list.join("\n"); + let uri_list = uri_list.as_bytes().to_vec(); + self.pasteboard + .set_file_urls(uri_list) + .map_err(|_| CliprdrError::ClipboardInternalError) + } + + fn start(&self) { + self.stopped.store(false, Ordering::Relaxed); + + loop { + if self.is_stopped() { + std::thread::sleep(std::time::Duration::from_millis(100)); + continue; + } + let file_list = match self.wait_file_list() { + Some(v) => v, + None => { + std::thread::sleep(std::time::Duration::from_millis(100)); + continue; + } + }; + + let filtered = paths + .into_iter() + .filter(|pb| !pb.starts_with(&self.ignore_path)) + .collect::>(); + + 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::>(); + 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 stop(&self) { + self.stopped.store(true, Ordering::Relaxed); + } + + fn get_file_list(&self) -> Vec { + self.former_file_list.lock().clone() + } +} diff --git a/libs/clipboard/src/platform/unix/x11.rs b/libs/clipboard/src/platform/unix/x11.rs index e137deac2..20ca02049 100644 --- a/libs/clipboard/src/platform/unix/x11.rs +++ b/libs/clipboard/src/platform/unix/x11.rs @@ -122,6 +122,11 @@ impl SysClipboard for X11Clipboard { self.stop.store(false, Ordering::Relaxed); loop { + if self.is_stopped() { + std::thread::sleep(std::time::Duration::from_millis(100)); + continue; + } + let sth = match self.wait_file_list() { Ok(sth) => sth, Err(e) => { @@ -131,11 +136,6 @@ impl SysClipboard for X11Clipboard { } }; - if self.is_stopped() { - std::thread::sleep(std::time::Duration::from_millis(100)); - continue; - } - let Some(paths) = sth else { // just sleep std::thread::sleep(std::time::Duration::from_millis(100));