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,13 +1,3 @@
use std::{
collections::HashMap,
ffi::c_void,
num::NonZeroI64,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, RwLock,
},
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::clipboard::{update_clipboard, ClipboardSide};
#[cfg(not(any(target_os = "ios")))]
@@ -20,7 +10,9 @@ use crate::{
common::get_default_sound_input,
ui_session_interface::{InvokeUiSession, Session},
};
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(feature = "unix-file-copy-paste")]
use crate::{clipboard::try_empty_clipboard_files, clipboard_file::unix_file_clip};
#[cfg(target_os = "windows")]
use clipboard::ContextSend;
use crossbeam_queue::ArrayQueue;
#[cfg(not(target_os = "ios"))]
@@ -44,9 +36,18 @@ use hbb_common::{
},
Stream,
};
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
use hbb_common::{tokio::sync::Mutex as TokioMutex, ResultType};
use scrap::CodecFormat;
use std::{
collections::HashMap,
ffi::c_void,
num::NonZeroI64,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, RwLock,
},
};
pub struct Remote<T: InvokeUiSession> {
handler: Session<T>,
@@ -63,7 +64,7 @@ pub struct Remote<T: InvokeUiSession> {
last_update_jobs_status: (Instant, HashMap<i32, u64>),
is_connected: bool,
first_frame: bool,
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
client_conn_id: i32, // used for file clipboard
data_count: Arc<AtomicUsize>,
video_format: CodecFormat,
@@ -107,7 +108,7 @@ impl<T: InvokeUiSession> Remote<T> {
last_update_jobs_status: (Instant::now(), Default::default()),
is_connected: false,
first_frame: false,
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
client_conn_id: 0,
data_count: Arc::new(AtomicUsize::new(0)),
video_format: CodecFormat::Unknown,
@@ -122,7 +123,7 @@ impl<T: InvokeUiSession> Remote<T> {
}
pub async fn io_loop(&mut self, key: &str, token: &str, round: u32) {
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(target_os = "windows")]
let _file_clip_context_holder = {
// `is_port_forward()` will not reach here, but we still check it for clarity.
if !self.handler.is_file_transfer() && !self.handler.is_port_forward() {
@@ -175,26 +176,33 @@ impl<T: InvokeUiSession> Remote<T> {
}
// just build for now
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
#[cfg(not(any(target_os = "windows", feature = "unix-file-copy-paste")))]
let (_tx_holder, mut rx_clip_client) = mpsc::unbounded_channel::<i32>();
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
let (_tx_holder, rx) = mpsc::unbounded_channel();
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
let mut rx_clip_client_lock = Arc::new(TokioMutex::new(rx));
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
let mut rx_clip_client_holder = (Arc::new(TokioMutex::new(rx)), None);
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
{
let is_conn_not_default = self.handler.is_file_transfer()
|| self.handler.is_port_forward()
|| self.handler.is_rdp();
if !is_conn_not_default {
log::debug!("get cliprdr client for conn_id {}", self.client_conn_id);
(self.client_conn_id, rx_clip_client_lock) =
(self.client_conn_id, rx_clip_client_holder.0) =
clipboard::get_rx_cliprdr_client(&self.handler.get_id());
log::debug!("get cliprdr client for conn_id {}", self.client_conn_id);
let client_conn_id = self.client_conn_id;
rx_clip_client_holder.1 = Some(crate::SimpleCallOnReturn {
b: true,
f: Box::new(move || {
clipboard::remove_channel_by_conn_id(client_conn_id);
}),
});
};
}
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
let mut rx_clip_client = rx_clip_client_lock.lock().await;
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
let mut rx_clip_client = rx_clip_client_holder.0.lock().await;
let mut status_timer =
crate::rustdesk_interval(time::interval(Duration::new(1, 0)));
@@ -242,8 +250,8 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
_msg = rx_clip_client.recv() => {
#[cfg(any(target_os="windows", target_os="linux", target_os = "macos"))]
self.handle_local_clipboard_msg(&mut peer, _msg).await;
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
self.handle_local_clipboard_msg(&mut peer, _msg).await;
}
_ = self.timer.tick() => {
if last_recv_time.elapsed() >= SEC30 {
@@ -323,18 +331,13 @@ impl<T: InvokeUiSession> Remote<T> {
Client::try_stop_clipboard();
}
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
if _set_disconnected_ok {
let conn_id = self.client_conn_id;
log::debug!("try empty cliprdr for conn_id {}", conn_id);
let _ = ContextSend::proc(|context| -> ResultType<()> {
context.empty_clipboard(conn_id)?;
Ok(())
});
crate::clipboard::try_empty_clipboard_files(ClipboardSide::Client, self.client_conn_id);
}
}
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
async fn handle_local_clipboard_msg(
&self,
peer: &mut crate::client::FramedStream,
@@ -365,8 +368,12 @@ impl<T: InvokeUiSession> Remote<T> {
view_only, stop, is_stopping_allowed, server_file_transfer_enabled, file_transfer_enabled
);
if stop {
ContextSend::set_is_stopped();
#[cfg(target_os = "windows")]
{
ContextSend::set_is_stopped();
}
} else {
#[cfg(target_os = "windows")]
if let Err(e) = ContextSend::make_sure_enabled() {
log::error!("failed to restart clipboard context: {}", e);
// to-do: Show msgbox with "Don't show again" option
@@ -509,7 +516,7 @@ impl<T: InvokeUiSession> Remote<T> {
.handle_login_from_ui(os_username, os_password, password, remember, peer)
.await;
}
#[cfg(not(feature = "flutter"))]
#[cfg(all(target_os = "windows", not(feature = "flutter")))]
Data::ToggleClipboardFile => {
self.check_clipboard_file_context();
}
@@ -1221,7 +1228,7 @@ impl<T: InvokeUiSession> Remote<T> {
let peer_platform = pi.platform.clone();
self.set_peer_info(&pi);
self.handler.handle_peer_info(pi);
#[cfg(not(feature = "flutter"))]
#[cfg(all(target_os = "windows", not(feature = "flutter")))]
self.check_clipboard_file_context();
if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) {
#[cfg(feature = "flutter")]
@@ -1233,6 +1240,10 @@ impl<T: InvokeUiSession> Remote<T> {
crate::client::ClientClipboardContext {
cfg: self.handler.get_permission_config(),
tx: self.sender.clone(),
#[cfg(feature = "unix-file-copy-paste")]
is_file_supported: crate::is_support_file_copy_paste(
&peer_version,
),
},
));
// To make sure current text clipboard data is updated.
@@ -1264,6 +1275,9 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(not(target_os = "ios"))]
crate::flutter::update_text_clipboard_required();
#[cfg(all(feature = "flutter", feature = "unix-file-copy-paste"))]
crate::flutter::update_file_clipboard_required();
// on connection established client
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -1317,9 +1331,9 @@ impl<T: InvokeUiSession> Remote<T> {
crate::clipboard::handle_msg_multi_clipboards(_mcb);
}
}
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
Some(message::Union::Cliprdr(clip)) => {
self.handle_cliprdr_msg(clip);
self.handle_cliprdr_msg(clip, peer).await;
}
Some(message::Union::FileResponse(fr)) => {
match fr.union {
@@ -1484,6 +1498,8 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(feature = "flutter")]
#[cfg(not(target_os = "ios"))]
crate::flutter::update_text_clipboard_required();
#[cfg(all(feature = "flutter", feature = "unix-file-copy-paste"))]
crate::flutter::update_file_clipboard_required();
self.handler.set_permission("keyboard", p.enabled);
}
Ok(Permission::Clipboard) => {
@@ -1502,7 +1518,16 @@ impl<T: InvokeUiSession> Remote<T> {
if !p.enabled && self.handler.is_file_transfer() {
return true;
}
#[cfg(all(feature = "flutter", feature = "unix-file-copy-paste"))]
crate::flutter::update_file_clipboard_required();
self.handler.set_permission("file", p.enabled);
#[cfg(feature = "unix-file-copy-paste")]
if !p.enabled {
try_empty_clipboard_files(
ClipboardSide::Client,
self.client_conn_id,
);
}
}
Ok(Permission::Restart) => {
self.handler.set_permission("restart", p.enabled);
@@ -1922,24 +1947,19 @@ impl<T: InvokeUiSession> Remote<T> {
true
}
#[cfg(not(feature = "flutter"))]
#[cfg(all(target_os = "windows", not(feature = "flutter")))]
fn check_clipboard_file_context(&self) {
#[cfg(any(
target_os = "windows",
all(
feature = "unix-file-copy-paste",
any(target_os = "linux", target_os = "macos")
)
))]
{
let enabled = *self.handler.server_file_transfer_enabled.read().unwrap()
&& self.handler.lc.read().unwrap().enable_file_copy_paste.v;
ContextSend::enable(enabled);
}
let enabled = *self.handler.server_file_transfer_enabled.read().unwrap()
&& self.handler.lc.read().unwrap().enable_file_copy_paste.v;
ContextSend::enable(enabled);
}
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
fn handle_cliprdr_msg(&self, clip: hbb_common::message_proto::Cliprdr) {
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
async fn handle_cliprdr_msg(
&self,
clip: hbb_common::message_proto::Cliprdr,
_peer: &mut Stream,
) {
log::debug!("handling cliprdr msg from server peer");
#[cfg(feature = "flutter")]
if let Some(hbb_common::message_proto::cliprdr::Union::FormatList(_)) = &clip.union {
@@ -1956,20 +1976,34 @@ impl<T: InvokeUiSession> Remote<T> {
};
let is_stopping_allowed = clip.is_beginning_message();
let file_transfer_enabled = self.handler.lc.read().unwrap().enable_file_copy_paste.v;
let file_transfer_enabled = self.handler.is_file_clipboard_required();
let stop = is_stopping_allowed && !file_transfer_enabled;
log::debug!(
"Process clipboard message from server peer, stop: {}, is_stopping_allowed: {}, file_transfer_enabled: {}",
stop, is_stopping_allowed, file_transfer_enabled);
if !stop {
#[cfg(target_os = "windows")]
if let Err(e) = ContextSend::make_sure_enabled() {
log::error!("failed to restart clipboard context: {}", e);
};
let _ = ContextSend::proc(|context| -> ResultType<()> {
context
.server_clip_file(self.client_conn_id, clip)
.map_err(|e| e.into())
});
#[cfg(target_os = "windows")]
{
let _ = ContextSend::proc(|context| -> ResultType<()> {
context
.server_clip_file(self.client_conn_id, clip)
.map_err(|e| e.into())
});
}
#[cfg(feature = "unix-file-copy-paste")]
if crate::is_support_file_copy_paste_num(self.handler.lc.read().unwrap().version) {
if let Some(msg) = unix_file_clip::serve_clip_messages(
ClipboardSide::Client,
clip,
self.client_conn_id,
) {
allow_err!(_peer.send(&msg).await);
}
}
}
}