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:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user