Merge branch 'feat/x11/clipboard-file/init' into feat/osx/clipboard-file

Signed-off-by: ClSlaid <cailue@bupt.edu.cn>
This commit is contained in:
ClSlaid
2023-10-30 15:26:03 +08:00
25 changed files with 271 additions and 191 deletions

View File

@@ -9,6 +9,21 @@ build = "build.rs"
[build-dependencies]
cc = "1.0"
[features]
default = []
unix-file-copy-paste = [
"dep:x11rb",
"dep:x11-clipboard",
"dep:rand",
"dep:fuser",
"dep:libc",
"dep:dashmap",
"dep:percent-encoding",
"dep:utf16string",
"dep:once_cell",
"dep:cacao"
]
[dependencies]
thiserror = "1.0"
lazy_static = "1.4"
@@ -18,17 +33,18 @@ hbb_common = { path = "../hbb_common" }
parking_lot = {version = "0.12"}
[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
rand = {version = "0.8"}
fuser = {version = "0.13"}
libc = {version = "0.2"}
dashmap = "5.5"
percent-encoding = "2.3"
utf16string = "0.2"
once_cell = "1.18"
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}
utf16string = {version = "0.2", optional = true}
once_cell = {version = "1.18", optional = true}
[target.'cfg(target_os = "linux")'.dependencies]
x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch"}
x11rb = {version = "0.12", features = ["all-extensions"]}
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"}
cacao = {git="https://github.com/clslaid/cacao", branch = "feat/set-file-urls", optional = true}

View File

@@ -47,6 +47,19 @@ impl ContextSend {
}
}
/// make sure the clipboard context is enabled.
pub fn make_sure_enabled() -> ResultType<()> {
let mut lock = CONTEXT_SEND.addr.lock().unwrap();
if lock.is_some() {
return Ok(());
}
let ctx = crate::create_cliprdr_context(true, false, CLIPBOARD_RESPONSE_WAIT_TIMEOUT_SECS)?;
*lock = Some(ctx);
log::info!("clipboard context for file transfer recreated.");
Ok(())
}
pub fn proc<F: FnOnce(&mut Box<dyn CliprdrServiceContext>) -> ResultType<()>>(
f: F,
) -> ResultType<()> {

View File

@@ -108,6 +108,7 @@ pub enum ClipboardFile {
struct MsgChannel {
peer_id: String,
conn_id: i32,
#[allow(dead_code)]
sender: UnboundedSender<ClipboardFile>,
receiver: Arc<TokioMutex<UnboundedReceiver<ClipboardFile>>>,
}
@@ -193,6 +194,7 @@ pub fn get_rx_cliprdr_server(conn_id: i32) -> Arc<TokioMutex<UnboundedReceiver<C
}
}
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste",))]
#[inline]
fn send_data(conn_id: i32, data: ClipboardFile) {
#[cfg(target_os = "windows")]
@@ -204,7 +206,7 @@ fn send_data(conn_id: i32, data: ClipboardFile) {
send_data_to_channel(conn_id, data);
}
}
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste",))]
#[inline]
fn send_data_to_channel(conn_id: i32, data: ClipboardFile) {
// no need to handle result here
@@ -218,6 +220,7 @@ fn send_data_to_channel(conn_id: i32, data: ClipboardFile) {
}
}
#[cfg(feature = "unix-file-copy-paste")]
#[inline]
fn send_data_to_all(data: ClipboardFile) {
// no need to handle result here

View File

@@ -14,45 +14,53 @@ pub fn create_cliprdr_context(
Ok(boxed)
}
#[cfg(feature = "unix-file-copy-paste")]
#[cfg(any(target_os = "linux", target_os = "macos"))]
/// use FUSE for file pasting on these platforms
pub mod fuse;
#[cfg(feature = "unix-file-copy-paste")]
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub mod unix;
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub fn create_cliprdr_context(
enable_files: bool,
_enable_files: bool,
_enable_others: bool,
response_wait_timeout_secs: u32,
_response_wait_timeout_secs: u32,
) -> crate::ResultType<Box<dyn crate::CliprdrServiceContext>> {
use std::{fs::Permissions, os::unix::prelude::PermissionsExt};
#[cfg(feature = "unix-file-copy-paste")]
{
use std::{fs::Permissions, os::unix::prelude::PermissionsExt};
use hbb_common::{config::APP_NAME, log};
use hbb_common::{config::APP_NAME, log};
if !enable_files {
return Ok(Box::new(DummyCliprdrContext {}) as Box<_>);
if !_enable_files {
return Ok(Box::new(DummyCliprdrContext {}) as Box<_>);
}
let timeout = std::time::Duration::from_secs(_response_wait_timeout_secs as u64);
let app_name = APP_NAME.read().unwrap().clone();
let mnt_path = format!("/tmp/{}/{}", app_name, "cliprdr");
// this function must be called after the main IPC is up
std::fs::create_dir(&mnt_path).ok();
std::fs::set_permissions(&mnt_path, Permissions::from_mode(0o777)).ok();
log::info!("clear previously mounted cliprdr FUSE");
if let Err(e) = std::process::Command::new("umount").arg(&mnt_path).status() {
log::warn!("umount {:?} may fail: {:?}", mnt_path, e);
}
let unix_ctx = unix::ClipboardContext::new(timeout, mnt_path.parse().unwrap())?;
log::debug!("start cliprdr FUSE");
unix_ctx.run().expect("failed to start cliprdr FUSE");
Ok(Box::new(unix_ctx) as Box<_>)
}
let timeout = std::time::Duration::from_secs(response_wait_timeout_secs as u64);
let app_name = APP_NAME.read().unwrap().clone();
let mnt_path = format!("/tmp/{}/{}", app_name, "cliprdr");
// this function must be called after the main IPC is up
std::fs::create_dir(&mnt_path).ok();
std::fs::set_permissions(&mnt_path, Permissions::from_mode(0o777)).ok();
log::info!("clear previously mounted cliprdr FUSE");
if let Err(e) = std::process::Command::new("umount").arg(&mnt_path).status() {
log::warn!("umount {:?} may fail: {:?}", mnt_path, e);
}
let unix_ctx = unix::ClipboardContext::new(timeout, mnt_path.parse().unwrap())?;
log::debug!("start cliprdr FUSE");
unix_ctx.run().expect("failed to start cliprdr FUSE");
Ok(Box::new(unix_ctx) as Box<_>)
#[cfg(not(feature = "unix-file-copy-paste"))]
return Ok(Box::new(DummyCliprdrContext {}) as Box<_>);
}
struct DummyCliprdrContext {}
@@ -73,7 +81,8 @@ 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
#[cfg(any(target_os = "linux", target_os = "macos"))]
const LDAP_EPOCH_DELTA: u64 = 116444772610000000;

View File

@@ -24,7 +24,6 @@ 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;
@@ -34,6 +33,7 @@ pub mod x11;
pub mod ns_clipboard;
pub mod local_file;
#[cfg(target_os = "linux")]
pub mod url;
@@ -68,7 +68,6 @@ fn add_remote_format(local_name: &str, remote_id: i32) {
trait SysClipboard: Send + Sync {
fn start(&self);
fn stop(&self);
fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError>;
fn get_file_list(&self) -> Vec<PathBuf>;
@@ -531,7 +530,7 @@ impl CliprdrServiceContext for ClipboardContext {
if let Some(fuse_handle) = self.fuse_handle.lock().take() {
fuse_handle.join();
}
self.clipboard.stop();
// we don't stop the clipboard, keep listening in case of restart
Ok(())
}

View File

@@ -1,8 +1,4 @@
use std::{
collections::BTreeSet,
path::PathBuf,
sync::atomic::{AtomicBool, Ordering},
};
use std::{collections::BTreeSet, path::PathBuf};
use cacao::pasteboard::{Pasteboard, PasteboardName};
use hbb_common::log;
@@ -28,7 +24,6 @@ fn set_file_list(file_list: &[PathBuf]) -> Result<(), CliprdrError> {
}
pub struct NsPasteboard {
stopped: AtomicBool,
ignore_path: PathBuf,
former_file_list: Mutex<Vec<PathBuf>>,
@@ -37,16 +32,10 @@ pub struct NsPasteboard {
impl NsPasteboard {
pub fn new(ignore_path: &PathBuf) -> Result<Self, CliprdrError> {
Ok(Self {
stopped: AtomicBool::new(false),
ignore_path: ignore_path.to_owned(),
former_file_list: Mutex::new(vec![]),
})
}
#[inline]
fn is_stopped(&self) -> bool {
self.stopped.load(Ordering::Relaxed)
}
}
impl SysClipboard for NsPasteboard {
@@ -56,13 +45,11 @@ impl SysClipboard for NsPasteboard {
}
fn start(&self) {
self.stopped.store(false, Ordering::Relaxed);
{
*self.former_file_list.lock() = vec![];
}
loop {
if self.is_stopped() {
std::thread::sleep(std::time::Duration::from_millis(100));
continue;
}
let file_list = match wait_file_list() {
Some(v) => v,
None => {
@@ -104,10 +91,6 @@ impl SysClipboard for NsPasteboard {
log::debug!("stop listening file related atoms on clipboard");
}
fn stop(&self) {
self.stopped.store(true, Ordering::Relaxed);
}
fn get_file_list(&self) -> Vec<PathBuf> {
self.former_file_list.lock().clone()
}

View File

@@ -1,8 +1,4 @@
use std::{
collections::BTreeSet,
path::PathBuf,
sync::atomic::{AtomicBool, Ordering},
};
use std::{collections::BTreeSet, path::PathBuf};
use hbb_common::log;
use once_cell::sync::OnceCell;
@@ -16,15 +12,11 @@ use super::{encode_path_to_uri, parse_plain_uri_list, SysClipboard};
static X11_CLIPBOARD: OnceCell<Clipboard> = 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))
}
pub struct X11Clipboard {
stop: AtomicBool,
ignore_path: PathBuf,
text_uri_list: Atom,
gnome_copied_files: Atom,
@@ -50,7 +42,6 @@ impl X11Clipboard {
.map_err(|_| CliprdrError::CliprdrInit)?;
Ok(Self {
ignore_path: ignore_path.to_owned(),
stop: AtomicBool::new(false),
text_uri_list,
gnome_copied_files,
nautilus_clipboard,
@@ -64,11 +55,18 @@ impl X11Clipboard {
// NOTE:
// # why not use `load_wait`
// load_wait is likely to wait forever, which is not what we want
let res = get_clip()?.load(clip, target, prop, X11_CLIPBOARD_TIMEOUT);
let res = get_clip()?.load_wait(clip, target, prop);
match res {
Ok(res) => Ok(res),
Err(x11_clipboard::error::Error::UnexpectedType(_)) => Ok(vec![]),
Err(_) => Err(CliprdrError::ClipboardInternalError),
Err(x11_clipboard::error::Error::Timeout) => {
log::debug!("x11 clipboard get content timeout.");
Err(CliprdrError::ClipboardInternalError)
}
Err(e) => {
log::debug!("x11 clipboard get content fail: {:?}", e);
Err(CliprdrError::ClipboardInternalError)
}
}
}
@@ -81,22 +79,12 @@ impl X11Clipboard {
}
fn wait_file_list(&self) -> Result<Option<Vec<PathBuf>>, CliprdrError> {
if self.stop.load(Ordering::Relaxed) {
return Ok(None);
}
let v = self.load(self.text_uri_list)?;
let p = parse_plain_uri_list(v)?;
Ok(Some(p))
}
}
impl X11Clipboard {
#[inline]
fn is_stopped(&self) -> bool {
self.stop.load(Ordering::Relaxed)
}
}
impl SysClipboard for X11Clipboard {
fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError> {
*self.former_file_list.lock() = paths.to_vec();
@@ -114,19 +102,12 @@ impl SysClipboard for X11Clipboard {
.map_err(|_| CliprdrError::ClipboardInternalError)
}
fn stop(&self) {
self.stop.store(true, Ordering::Relaxed);
}
fn start(&self) {
self.stop.store(false, Ordering::Relaxed);
{
// clear cached file list
*self.former_file_list.lock() = vec![];
}
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) => {

View File

@@ -1,4 +1,3 @@
from ast import parse
import os
import optparse
from hashlib import md5
@@ -47,7 +46,7 @@ def write_metadata(md5_table: dict, output_folder: str, exe: str):
f.write((len(path)).to_bytes(length=length_count, byteorder='big'))
f.write(path)
# data length & compressed data
f.write((data_length).to_bytes(
f.write(data_length.to_bytes(
length=length_count, byteorder='big'))
f.write(compressed_data)
# md5 code
@@ -65,6 +64,8 @@ def build_portable(output_folder: str):
# Linux: python3 generate.py -f ../rustdesk-portable-packer/test -o . -e ./test/main.py
# Windows: python3 .\generate.py -f ..\rustdesk\flutter\build\windows\runner\Debug\ -o . -e ..\rustdesk\flutter\build\windows\runner\Debug\rustdesk.exe
if __name__ == '__main__':
parser = optparse.OptionParser()
parser.add_option("-f", "--folder", dest="folder",