Merge pull request #6227 from ClSlaid/feat/osx/pasteboard-file

[feat] osx pasteboard file copy and paste support
This commit is contained in:
RustDesk 2023-10-30 22:52:35 +08:00 committed by GitHub
commit 7480ead76a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1490 additions and 1143 deletions

2462
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -69,7 +69,7 @@ num_cpus = "1.15"
bytes = { version = "1.4", features = ["serde"] }
default-net = "0.14"
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"
rdev = { git = "https://github.com/fufesou/rdev" }
url = { version = "2.3", features = ["serde"] }
@ -164,7 +164,7 @@ winapi = { version = "0.3", features = [ "winnt" ] }
[build-dependencies]
cc = "1.0"
hbb_common = { path = "libs/hbb_common" }
flutter_rust_bridge_codegen = "1.75"
flutter_rust_bridge_codegen = "=1.75"
os-version = "0.2"
[dev-dependencies]

View File

@ -20,7 +20,8 @@ unix-file-copy-paste = [
"dep:dashmap",
"dep:percent-encoding",
"dep:utf16string",
"dep:once_cell"
"dep:once_cell",
"dep:cacao"
]
[dependencies]
@ -32,12 +33,17 @@ hbb_common = { path = "../hbb_common" }
parking_lot = {version = "0.12"}
[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}
fuser = {version = "0.13", optional = true}
libc = {version = "0.2", optional = true}
dashmap = {version ="5.5", optional = true}
percent-encoding = {version ="2.3", 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}
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}

View File

@ -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();
}

View File

@ -1,11 +0,0 @@
#include "../cliprdr.h"
void mac_cliprdr_init(CliprdrClientContext *cliprdr)
{
(void)cliprdr;
}
void mac_cliprdr_uninit(CliprdrClientContext *cliprdr)
{
(void)cliprdr;
}

View File

@ -154,6 +154,11 @@ impl fuser::Filesystem for FuseClient {
let mut server = self.server.lock();
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
@ -486,6 +491,15 @@ impl fuser::Filesystem for FuseServer {
let attr = (&entry.attributes).into();
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 {

View File

@ -58,6 +58,7 @@ pub fn create_cliprdr_context(
Ok(Box::new(unix_ctx) as Box<_>)
}
#[cfg(not(feature = "unix-file-copy-paste"))]
return Ok(Box::new(DummyCliprdrContext {}) as Box<_>);
}
@ -81,6 +82,7 @@ impl CliprdrServiceContext for DummyCliprdrContext {
}
#[cfg(feature = "unix-file-copy-paste")]
#[cfg(any(target_os = "linux", target_os = "macos"))]
// begin of epoch used by microsoft
// 1601-01-01 00:00:00 + LDAP_EPOCH_DELTA*(100 ns) = 1970-01-01 00:00:00
const LDAP_EPOCH_DELTA: u64 = 116444772610000000;

View File

@ -25,8 +25,13 @@ use self::url::{encode_path_to_uri, parse_plain_uri_list};
use super::fuse::FuseServer;
#[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;
#[cfg(target_os = "linux")]
@ -76,7 +81,7 @@ fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, Cli
}
#[cfg(not(feature = "wayland"))]
{
pub use x11::*;
use x11::*;
let x11_clip = X11Clipboard::new(ignore_path)?;
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")]
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)]

View 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()
}
}

View File

@ -479,7 +479,7 @@ impl Connection {
ipc::Data::RawMessage(bytes) => {
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) => {
allow_err!(conn.stream.send(&clip_2_msg(clip)).await);
}

View File

@ -450,7 +450,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
}
}
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;
}
@ -489,7 +489,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
}
match &data {
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" {
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 stop = is_stopping_allowed && !(is_clipboard_enabled && file_transfer_enabled && file_transfer_enabled_peer);
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);
if stop {
ContextSend::set_is_stopped();