feat(part): implement fuse support for linux clipboard

Signed-off-by: 蔡略 <cailue@bupt.edu.cn>
This commit is contained in:
蔡略
2023-09-04 15:38:53 +08:00
parent c25d648321
commit 4f7036a405
8 changed files with 1702 additions and 22 deletions

View File

@@ -15,3 +15,14 @@ lazy_static = "1.4"
serde = "1.0"
serde_derive = "1.0"
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"}
rayon = {version = "1.7"}
dashmap = "5.5"
percent-encoding = "2.3"
utf16string = "0.2"
x11-clipboard = "0.8"

View File

@@ -44,11 +44,9 @@ impl ContextSend {
}
}
}
} else {
if let Some(_clp) = lock.take() {
*lock = None;
log::info!("clipboard context for file transfer destroyed.");
}
} else if let Some(_clp) = lock.take() {
*lock = None;
log::info!("clipboard context for file transfer destroyed.");
}
}

View File

@@ -49,8 +49,14 @@ pub enum CliprdrError {
CliprdrOutOfMemory,
#[error("cliprdr internal error")]
ClipboardInternalError,
#[error("cliprdr occupied")]
ClipboardOccupied,
#[error("content not available")]
ContentNotAvailable,
#[error("conversion failure")]
ConversionFailure,
#[error("unknown cliprdr error")]
Unknown(u32),
Unknown { description: String },
}
#[derive(Debug, Serialize, Deserialize, Clone)]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,135 @@
use std::{
path::{Path, PathBuf},
time::Duration,
};
use crate::CliprdrError;
use super::fuse::{self, FuseServer};
#[cfg(not(feature = "wayland"))]
pub mod x11;
trait SysClipboard {
fn wait_file_list(&self) -> Result<Vec<PathBuf>, CliprdrError>;
fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError>;
}
fn get_sys_clipboard() -> Box<dyn SysClipboard> {
#[cfg(feature = "wayland")]
{
unimplemented!()
}
#[cfg(not(feature = "wayland"))]
{
pub use x11::*;
X11Clipboard::new()
}
}
// on x11, path will be encode as
// "/home/rustdesk/pictures/🖼️.png" -> "file:///home/rustdesk/pictures/%F0%9F%96%BC%EF%B8%8F.png"
// url encode and decode is needed
const ENCODE_SET: percent_encoding::AsciiSet = percent_encoding::CONTROLS.add(b' ').remove(b'/');
fn encode_path_to_uri(path: &PathBuf) -> String {
let encoded = percent_encoding::percent_encode(path.to_str().unwrap().as_bytes(), &ENCODE_SET)
.to_string();
format!("file://{}", encoded)
}
fn parse_uri_to_path(encoded_uri: &str) -> Result<PathBuf, CliprdrError> {
let encoded_path = encoded_uri.trim_start_matches("file://");
let path_str = percent_encoding::percent_decode_str(encoded_path)
.decode_utf8()
.map_err(|_| CliprdrError::ConversionFailure)?;
let path_str = path_str.to_string();
Ok(Path::new(&path_str).to_path_buf())
}
#[cfg(test)]
mod uri_test {
#[test]
fn test_conversion() {
let path = std::path::PathBuf::from("/home/rustdesk/pictures/🖼️.png");
let uri = super::encode_path_to_uri(&path);
assert_eq!(
uri,
"file:///home/rustdesk/pictures/%F0%9F%96%BC%EF%B8%8F.png"
);
let convert_back = super::parse_uri_to_path(&uri).unwrap();
assert_eq!(path, convert_back);
}
}
// helper parse function
// convert 'text/uri-list' data to a list of valid Paths
// # Note
// - none utf8 data will lead to error
fn parse_plain_uri_list(v: Vec<u8>) -> Result<Vec<PathBuf>, CliprdrError> {
let text = String::from_utf8(v).map_err(|_| CliprdrError::ConversionFailure)?;
parse_uri_list(&text)
}
// helper parse function
// convert "x-special/gnome-copied-files", "x-special/x-kde-cutselection" and "x-special/nautilus-clipboard" data to a list of valid Paths
// # Note
// - none utf8 data will lead to error
fn parse_de_uri_list(v: Vec<u8>) -> Result<Vec<PathBuf>, CliprdrError> {
let text = String::from_utf8(v).map_err(|_| CliprdrError::ConversionFailure)?;
let plain_list = text
.trim_start_matches("copy\n")
.trim_start_matches("cut\n");
parse_uri_list(plain_list)
}
// helper parse function
// convert 'text/uri-list' data to a list of valid Paths
// # Note
// - none utf8 data will lead to error
fn parse_uri_list(text: &str) -> Result<Vec<PathBuf>, CliprdrError> {
let mut list = Vec::new();
for line in text.lines() {
let decoded = parse_uri_to_path(line)?;
list.push(decoded)
}
Ok(list)
}
#[derive(Debug)]
pub struct ClipboardContext {
pub stop: bool,
pub fuse_mount_point: PathBuf,
pub fuse_server: FuseServer,
pub file_list: HashSet<PathBuf>,
pub clipboard: Clipboard,
pub bkg_session: fuser::BackgroundSession,
}
impl ClipboardContext {
fn new(timeout: Duration, mount_path: PathBuf) -> Result<Self, CliprdrError> {
// assert mount path exists
let mountpoint = mount_path
.canonicalize()
.map_err(|e| CliprdrError::Unknown {
description: format!("invalid mount point: {:?}", e),
})?;
let fuse_server = FuseServer::new(timeout);
let mnt_opts = [
fuser::MountOption::FSName("clipboard".to_string()),
fuser::MountOption::NoAtime,
fuser::MountOption::RO,
fuser::MountOption::NoExec,
];
let bkg_session = fuser::spawn_mount2(fuse_server, mountpoint, &mnt_opts).map_err(|e| {
CliprdrError::Unknown {
description: format!("failed to mount fuse: {:?}", e),
}
})?;
log::debug!("mounting clipboard fuse to {}", mount_path.display());
}
}

View File

@@ -0,0 +1,7 @@
use super::SysClipboard;
pub struct X11Clipboard {}
impl SysClipboard for X11Clipboard {
todo!()
}

View File

@@ -1,3 +1,5 @@
use parking_lot::{Condvar, Mutex};
#[cfg(target_os = "windows")]
pub mod windows;
#[cfg(target_os = "windows")]
@@ -8,3 +10,17 @@ pub fn create_cliprdr_context(
) -> crate::ResultType<Box<dyn crate::CliprdrServiceContext>> {
windows::create_cliprdr_context(enable_files, enable_others, response_wait_timeout_secs)
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
/// use FUSE for file pasting on these platforms
pub mod fuse;
#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(target_os = "linux")]
pub fn create_cliprdr_context(
enable_files: bool,
enable_others: bool,
response_wait_timeout_secs: u32,
) -> crate::ResultType<Box<dyn crate::CliprdrServiceContext>> {
unimplemented!()
}