refact: file copy&paste, cross platform (no macOS) (#10671)

* feat: unix, file copy&paste

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact: unix file c&p, check peer version

Signed-off-by: fufesou <linlong1266@gmail.com>

* Update pubspec.yaml

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
This commit is contained in:
fufesou
2025-02-04 20:33:02 +08:00
committed by GitHub
parent a27fa43081
commit fbba8f0b34
42 changed files with 2026 additions and 1778 deletions

View File

@@ -1,18 +1,27 @@
use super::*;
#[cfg(not(target_os = "android"))]
use crate::clipboard::clipboard_listener;
#[cfg(not(target_os = "android"))]
pub use crate::clipboard::{check_clipboard, ClipboardContext, ClipboardSide};
pub use crate::clipboard::{CLIPBOARD_INTERVAL as INTERVAL, CLIPBOARD_NAME as NAME};
#[cfg(windows)]
use crate::ipc::{self, ClipboardFile, ClipboardNonFile, Data};
#[cfg(feature = "unix-file-copy-paste")]
pub use crate::{
clipboard::{check_clipboard_files, FILE_CLIPBOARD_NAME as FILE_NAME},
clipboard_file::unix_file_clip,
};
#[cfg(all(feature = "unix-file-copy-paste", target_os = "linux"))]
use clipboard::platform::unix::fuse::{init_fuse_context, uninit_fuse_context};
#[cfg(not(target_os = "android"))]
use clipboard_master::{CallbackResult, ClipboardHandler};
use clipboard_master::CallbackResult;
#[cfg(target_os = "android")]
use hbb_common::config::{keys, option2bool};
#[cfg(target_os = "android")]
use std::sync::atomic::{AtomicBool, Ordering};
use std::{
io,
sync::mpsc::{channel, RecvTimeoutError, Sender},
sync::mpsc::{channel, RecvTimeoutError},
time::Duration,
};
#[cfg(windows)]
@@ -23,9 +32,7 @@ static CLIPBOARD_SERVICE_OK: AtomicBool = AtomicBool::new(false);
#[cfg(not(target_os = "android"))]
struct Handler {
sp: EmptyExtraFieldService,
ctx: Option<ClipboardContext>,
tx_cb_result: Sender<CallbackResult>,
#[cfg(target_os = "windows")]
stream: Option<ipc::ConnectionTmpl<parity_tokio_ipc::ConnectionClient>>,
#[cfg(target_os = "windows")]
@@ -37,39 +44,51 @@ pub fn is_clipboard_service_ok() -> bool {
CLIPBOARD_SERVICE_OK.load(Ordering::SeqCst)
}
pub fn new() -> GenericService {
let svc = EmptyExtraFieldService::new(NAME.to_owned(), false);
pub fn new(name: String) -> GenericService {
let svc = EmptyExtraFieldService::new(name, false);
GenericService::run(&svc.clone(), run);
svc.sp
}
#[cfg(not(target_os = "android"))]
fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
#[cfg(all(feature = "unix-file-copy-paste", target_os = "linux"))]
let _fuse_call_on_ret = {
if sp.name() == FILE_NAME {
Some(init_fuse_context(false).map(|_| crate::SimpleCallOnReturn {
b: true,
f: Box::new(|| {
uninit_fuse_context(false);
}),
}))
} else {
None
}
};
let (tx_cb_result, rx_cb_result) = channel();
let handler = Handler {
sp: sp.clone(),
ctx: Some(ClipboardContext::new()?),
tx_cb_result,
let ctx = Some(ClipboardContext::new().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?);
clipboard_listener::subscribe(sp.name(), tx_cb_result)?;
let mut handler = Handler {
ctx,
#[cfg(target_os = "windows")]
stream: None,
#[cfg(target_os = "windows")]
rt: None,
};
let (tx_start_res, rx_start_res) = channel();
let h = crate::clipboard::start_clipbard_master_thread(handler, tx_start_res);
let shutdown = match rx_start_res.recv() {
Ok((Some(s), _)) => s,
Ok((None, err)) => {
bail!(err);
}
Err(e) => {
bail!("Failed to create clipboard listener: {}", e);
}
};
while sp.ok() {
match rx_cb_result.recv_timeout(Duration::from_millis(INTERVAL)) {
Ok(CallbackResult::Next) => {
#[cfg(feature = "unix-file-copy-paste")]
if sp.name() == FILE_NAME {
handler.check_clipboard_file();
continue;
}
if let Some(msg) = handler.get_clipboard_msg() {
sp.send(msg);
}
}
Ok(CallbackResult::Stop) => {
log::debug!("Clipboard listener stopped");
break;
@@ -78,36 +97,40 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
bail!("Clipboard listener stopped with error: {}", err);
}
Err(RecvTimeoutError::Timeout) => {}
_ => {}
Err(RecvTimeoutError::Disconnected) => {
log::error!("Clipboard listener disconnected");
break;
}
}
}
shutdown.signal();
h.join().ok();
clipboard_listener::unsubscribe(&sp.name());
Ok(())
}
#[cfg(not(target_os = "android"))]
impl ClipboardHandler for Handler {
fn on_clipboard_change(&mut self) -> CallbackResult {
if self.sp.ok() {
if let Some(msg) = self.get_clipboard_msg() {
self.sp.send(msg);
impl Handler {
#[cfg(feature = "unix-file-copy-paste")]
fn check_clipboard_file(&mut self) {
if let Some(urls) = check_clipboard_files(&mut self.ctx, ClipboardSide::Host, false) {
if !urls.is_empty() {
match clipboard::platform::unix::serv_files::sync_files(&urls) {
Ok(()) => {
// Use `send_data()` here to reuse `handle_file_clip()` in `connection.rs`.
hbb_common::allow_err!(clipboard::send_data(
0,
unix_file_clip::get_format_list()
));
}
Err(e) => {
log::error!("Failed to sync clipboard files: {}", e);
}
}
}
}
CallbackResult::Next
}
fn on_clipboard_error(&mut self, error: io::Error) -> CallbackResult {
self.tx_cb_result
.send(CallbackResult::StopWithError(error))
.ok();
CallbackResult::Next
}
}
#[cfg(not(target_os = "android"))]
impl Handler {
fn get_clipboard_msg(&mut self) -> Option<Message> {
#[cfg(target_os = "windows")]
if crate::common::is_server() && crate::platform::is_root() {
@@ -144,6 +167,7 @@ impl Handler {
}
}
}
check_clipboard(&mut self.ctx, ClipboardSide::Host, false)
}

View File

@@ -1,4 +1,6 @@
use super::{input_service::*, *};
#[cfg(feature = "unix-file-copy-paste")]
use crate::clipboard::try_empty_clipboard_files;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::clipboard::{update_clipboard, ClipboardSide};
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
@@ -6,8 +8,6 @@ use crate::clipboard_file::*;
#[cfg(target_os = "android")]
use crate::keyboard::client::map_key_to_control_key;
#[cfg(target_os = "linux")]
use crate::platform::linux::is_x11;
#[cfg(target_os = "linux")]
use crate::platform::linux_desktop_manager;
#[cfg(any(target_os = "windows", target_os = "linux"))]
use crate::platform::WallPaperRemover;
@@ -441,6 +441,28 @@ impl Connection {
std::thread::spawn(move || Self::handle_input(_rx_input, tx_cloned));
let mut second_timer = crate::rustdesk_interval(time::interval(Duration::from_secs(1)));
#[cfg(feature = "unix-file-copy-paste")]
let rx_clip_holder;
let mut rx_clip;
let _tx_clip: mpsc::UnboundedSender<i32>;
#[cfg(feature = "unix-file-copy-paste")]
{
rx_clip_holder = (
clipboard::get_rx_cliprdr_server(id),
crate::SimpleCallOnReturn {
b: true,
f: Box::new(move || {
clipboard::remove_channel_by_conn_id(id);
}),
},
);
rx_clip = rx_clip_holder.0.lock().await;
}
#[cfg(not(feature = "unix-file-copy-paste"))]
{
(_tx_clip, rx_clip) = mpsc::unbounded_channel::<i32>();
}
loop {
tokio::select! {
// biased; // video has higher priority // causing test_delay_timer failed while transferring big file
@@ -488,6 +510,12 @@ impl Connection {
s.write().unwrap().subscribe(
super::clipboard_service::NAME,
conn.inner.clone(), conn.can_sub_clipboard_service());
#[cfg(feature = "unix-file-copy-paste")]
s.write().unwrap().subscribe(
super::clipboard_service::FILE_NAME,
conn.inner.clone(),
conn.can_sub_file_clipboard_service(),
);
s.write().unwrap().subscribe(
NAME_CURSOR,
conn.inner.clone(), enabled || conn.show_remote_cursor);
@@ -513,6 +541,18 @@ impl Connection {
} else if &name == "file" {
conn.file = enabled;
conn.send_permission(Permission::File, enabled).await;
#[cfg(feature = "unix-file-copy-paste")]
if !enabled {
conn.try_empty_file_clipboard();
}
#[cfg(feature = "unix-file-copy-paste")]
if let Some(s) = conn.server.upgrade() {
s.write().unwrap().subscribe(
super::clipboard_service::FILE_NAME,
conn.inner.clone(),
conn.can_sub_file_clipboard_service(),
);
}
} else if &name == "restart" {
conn.restart = enabled;
conn.send_permission(Permission::Restart, enabled).await;
@@ -527,7 +567,7 @@ impl Connection {
ipc::Data::RawMessage(bytes) => {
allow_err!(conn.stream.send_raw(bytes).await);
}
#[cfg(any(target_os="windows", target_os="linux", target_os = "macos"))]
#[cfg(target_os = "windows")]
ipc::Data::ClipboardFile(clip) => {
allow_err!(conn.stream.send(&clip_2_msg(clip)).await);
}
@@ -740,9 +780,26 @@ impl Connection {
}
}
}
clip_file = rx_clip.recv() => match clip_file {
Some(_clip) => {
#[cfg(feature = "unix-file-copy-paste")]
if crate::is_support_file_copy_paste(&conn.lr.version)
{
conn.handle_file_clip(_clip).await;
}
}
None => {
//
}
},
}
}
#[cfg(feature = "unix-file-copy-paste")]
{
conn.try_empty_file_clipboard();
}
if let Some(video_privacy_conn_id) = privacy_mode::get_privacy_mode_conn_id() {
if video_privacy_conn_id == id {
let _ = Self::turn_off_privacy_to_msg(id);
@@ -1202,15 +1259,20 @@ impl Connection {
);
}
#[cfg(any(
target_os = "windows",
all(
any(target_os = "linux", target_os = "macos"),
feature = "unix-file-copy-paste"
)
))]
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
{
platform_additions.insert("has_file_clipboard".into(), json!(true));
let is_both_windows = cfg!(target_os = "windows")
&& self.lr.my_platform == whoami::Platform::Windows.to_string();
#[cfg(feature = "unix-file-copy-paste")]
let is_unix_and_peer_supported = crate::is_support_file_copy_paste(&self.lr.version);
#[cfg(not(feature = "unix-file-copy-paste"))]
let is_unix_and_peer_supported = false;
// to-do: add file clipboard support for macos
let is_both_macos = cfg!(target_os = "macos")
&& self.lr.my_platform == whoami::Platform::MacOS.to_string();
let has_file_clipboard =
is_both_windows || (is_unix_and_peer_supported && !is_both_macos);
platform_additions.insert("has_file_clipboard".into(), json!(has_file_clipboard));
}
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
@@ -1375,6 +1437,10 @@ impl Connection {
if !self.can_sub_clipboard_service() {
noperms.push(super::clipboard_service::NAME);
}
#[cfg(feature = "unix-file-copy-paste")]
if !self.can_sub_file_clipboard_service() {
noperms.push(super::clipboard_service::FILE_NAME);
}
if !self.audio_enabled() {
noperms.push(super::audio_service::NAME);
}
@@ -1455,11 +1521,18 @@ impl Connection {
self.audio && !self.disable_audio
}
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
fn file_transfer_enabled(&self) -> bool {
self.file && self.enable_file_transfer
}
#[cfg(feature = "unix-file-copy-paste")]
fn can_sub_file_clipboard_service(&self) -> bool {
self.clipboard_enabled()
&& self.file_transfer_enabled()
&& crate::get_builtin_option(keys::OPTION_ONE_WAY_FILE_TRANSFER) != "Y"
}
fn try_start_cm(&mut self, peer_id: String, name: String, authorized: bool) {
self.send_to_cm(ipc::Data::Login {
id: self.inner.id(),
@@ -2113,12 +2186,23 @@ impl Connection {
#[cfg(target_os = "android")]
crate::clipboard::handle_msg_multi_clipboards(_mcb);
}
Some(message::Union::Cliprdr(_clip)) =>
{
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
if let Some(clip) = msg_2_clip(_clip) {
log::debug!("got clipfile from client peer");
self.send_to_cm(ipc::Data::ClipboardFile(clip))
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
Some(message::Union::Cliprdr(clip)) => {
if let Some(clip) = msg_2_clip(clip) {
#[cfg(target_os = "windows")]
{
self.send_to_cm(ipc::Data::ClipboardFile(clip));
}
#[cfg(feature = "unix-file-copy-paste")]
if crate::is_support_file_copy_paste(&self.lr.version) {
if let Some(msg) = unix_file_clip::serve_clip_messages(
ClipboardSide::Host,
clip,
self.inner.id(),
) {
self.send(msg).await;
}
}
}
}
Some(message::Union::FileAction(fa)) => {
@@ -2911,13 +2995,26 @@ impl Connection {
}
}
}
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
if let Ok(q) = o.enable_file_transfer.enum_value() {
if q != BoolOption::NotSet {
self.enable_file_transfer = q == BoolOption::Yes;
#[cfg(target_os = "windows")]
self.send_to_cm(ipc::Data::ClipboardFileEnabled(
self.file_transfer_enabled(),
));
#[cfg(feature = "unix-file-copy-paste")]
if !self.enable_file_transfer {
self.try_empty_file_clipboard();
}
#[cfg(feature = "unix-file-copy-paste")]
if let Some(s) = self.server.upgrade() {
s.write().unwrap().subscribe(
super::clipboard_service::FILE_NAME,
self.inner.clone(),
self.can_sub_file_clipboard_service(),
);
}
}
}
if let Ok(q) = o.disable_clipboard.enum_value() {
@@ -2941,6 +3038,12 @@ impl Connection {
self.inner.clone(),
self.can_sub_clipboard_service(),
);
#[cfg(feature = "unix-file-copy-paste")]
s.write().unwrap().subscribe(
super::clipboard_service::FILE_NAME,
self.inner.clone(),
self.can_sub_file_clipboard_service(),
);
s.write().unwrap().subscribe(
NAME_CURSOR,
self.inner.clone(),
@@ -3330,6 +3433,41 @@ impl Connection {
}
false
}
#[cfg(feature = "unix-file-copy-paste")]
async fn handle_file_clip(&mut self, clip: clipboard::ClipboardFile) {
let is_stopping_allowed = clip.is_stopping_allowed();
let is_keyboard_enabled = self.peer_keyboard_enabled();
let file_transfer_enabled = self.file_transfer_enabled();
let stop = is_stopping_allowed && !file_transfer_enabled;
log::debug!(
"Process clipboard message from clip, stop: {}, is_stopping_allowed: {}, file_transfer_enabled: {}",
stop, is_stopping_allowed, file_transfer_enabled);
if !stop {
use hbb_common::config::keys::OPTION_ONE_WAY_FILE_TRANSFER;
// Note: Code will not reach here if `crate::get_builtin_option(OPTION_ONE_WAY_FILE_TRANSFER) == "Y"` is true.
// Because `file-clipboard` service will not be subscribed.
// But we still check it here to keep the same logic to windows version in `ui_cm_interface.rs`.
if clip.is_beginning_message()
&& crate::get_builtin_option(OPTION_ONE_WAY_FILE_TRANSFER) == "Y"
{
// If one way file transfer is enabled, don't send clipboard file to client
} else {
// Maybe we should end the connection, because copy&paste files causes everything to wait.
allow_err!(
self.stream
.send(&crate::clipboard_file::clip_2_msg(clip))
.await
);
}
}
}
#[inline]
#[cfg(feature = "unix-file-copy-paste")]
fn try_empty_file_clipboard(&mut self) {
try_empty_clipboard_files(ClipboardSide::Host, self.inner.id());
}
}
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {

View File

@@ -21,12 +21,6 @@ pub fn init() {
}
fn map_err_scrap(err: String) -> io::Error {
// to-do: Remove this the following log
log::error!(
"REMOVE ME ===================================== wayland scrap error {}",
&err
);
// to-do: Handle error better, do not restart server
if err.starts_with("Did not receive a reply") {
log::error!("Fatal pipewire error, {}", &err);