diff --git a/src/client.rs b/src/client.rs index d1b503e43ee5..e6d0d5f08f7d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,5 @@ +#[cfg(not(any(target_os = "android", target_os = "ios")))] +use crate::clipboard::clipboard_listener; use async_trait::async_trait; use bytes::Bytes; #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -174,6 +176,8 @@ pub fn get_key_state(key: enigo::Key) -> bool { } impl Client { + const CLIENT_CLIPBOARD_NAME: &'static str = "client-clipboard"; + /// Start a new connection. pub async fn start( peer: &str, @@ -685,6 +689,8 @@ impl Client { if crate::flutter::sessions::has_sessions_running(ConnType::DEFAULT_CONN) { return; } + #[cfg(not(target_os = "android"))] + clipboard_listener::unsubscribe(Self::CLIENT_CLIPBOARD_NAME); CLIPBOARD_STATE.lock().unwrap().running = false; } @@ -703,40 +709,33 @@ impl Client { } let (tx_cb_result, rx_cb_result) = mpsc::channel(); - let handler = ClientClipboardHandler { - ctx: None, - tx_cb_result, - #[cfg(not(feature = "flutter"))] - client_clip_ctx: _client_clip_ctx, - }; - - let (tx_start_res, rx_start_res) = mpsc::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)) => { - log::error!("{}", err); - return None; - } - Err(e) => { - log::error!("Failed to create clipboard listener: {}", e); - return None; - } - }; + if let Err(e) = + clipboard_listener::subscribe(Self::CLIENT_CLIPBOARD_NAME.to_owned(), tx_cb_result) + { + log::error!("Failed to subscribe clipboard listener: {}", e); + return None; + } clipboard_lock.running = true; - let (tx_started, rx_started) = unbounded_channel(); - log::info!("Start text clipboard loop"); + log::info!("Start client clipboard loop"); std::thread::spawn(move || { - tx_started.send(()).ok(); + let mut handler = ClientClipboardHandler { + ctx: None, + #[cfg(not(feature = "flutter"))] + client_clip_ctx: _client_clip_ctx, + }; + tx_started.send(()).ok(); loop { if !CLIPBOARD_STATE.lock().unwrap().running { break; } match rx_cb_result.recv_timeout(Duration::from_millis(CLIPBOARD_INTERVAL)) { + Ok(CallbackResult::Next) => { + handler.check_clipboard(); + } Ok(CallbackResult::Stop) => { log::debug!("Clipboard listener stopped"); break; @@ -746,12 +745,13 @@ impl Client { break; } Err(RecvTimeoutError::Timeout) => {} - _ => {} + Err(RecvTimeoutError::Disconnected) => { + log::error!("Clipboard listener disconnected"); + break; + } } } - log::info!("Stop text clipboard loop"); - shutdown.signal(); - h.join().ok(); + log::info!("Stop client clipboard loop"); CLIPBOARD_STATE.lock().unwrap().running = false; }); @@ -766,7 +766,7 @@ impl Client { } clipboard_lock.running = true; - log::info!("Start text clipboard loop"); + log::info!("Start client clipboard loop"); std::thread::spawn(move || { loop { if !CLIPBOARD_STATE.lock().unwrap().running { @@ -783,7 +783,7 @@ impl Client { std::thread::sleep(Duration::from_millis(CLIPBOARD_INTERVAL)); } - log::info!("Stop text clipboard loop"); + log::info!("Stop client clipboard loop"); CLIPBOARD_STATE.lock().unwrap().running = false; }); @@ -807,7 +807,6 @@ impl ClipboardState { #[cfg(not(any(target_os = "android", target_os = "ios")))] struct ClientClipboardHandler { ctx: Option, - tx_cb_result: Sender, #[cfg(not(feature = "flutter"))] client_clip_ctx: Option, } @@ -843,6 +842,30 @@ impl ClientClipboardHandler { } } + fn check_clipboard(&mut self) { + if CLIPBOARD_STATE.lock().unwrap().running { + #[cfg(feature = "unix-file-copy-paste")] + if self.is_file_required() { + if let Some(msg) = + check_clipboard_files(&mut self.ctx, ClipboardSide::Client, false) + { + if !msg.is_empty() { + let msg = + crate::clipboard_file::clip_2_msg(unix_file_clip::get_format_list()); + self.send_msg(msg, true); + return; + } + } + } + + if self.is_text_required() { + if let Some(msg) = check_clipboard(&mut self.ctx, ClipboardSide::Client, false) { + self.send_msg(msg, false); + } + } + } + } + #[inline] #[cfg(feature = "flutter")] fn send_msg(&self, msg: Message, _is_file: bool) { @@ -875,41 +898,6 @@ impl ClientClipboardHandler { } } -#[cfg(not(any(target_os = "android", target_os = "ios")))] -impl ClipboardHandler for ClientClipboardHandler { - fn on_clipboard_change(&mut self) -> CallbackResult { - if CLIPBOARD_STATE.lock().unwrap().running { - #[cfg(feature = "unix-file-copy-paste")] - if self.is_file_required() { - if let Some(msg) = - check_clipboard_files(&mut self.ctx, ClipboardSide::Client, false) - { - if !msg.is_empty() { - let msg = - crate::clipboard_file::clip_2_msg(unix_file_clip::get_format_list()); - self.send_msg(msg, true); - return CallbackResult::Next; - } - } - } - - if self.is_text_required() { - if let Some(msg) = check_clipboard(&mut self.ctx, ClipboardSide::Client, false) { - self.send_msg(msg, false); - } - } - } - CallbackResult::Next - } - - fn on_clipboard_error(&mut self, error: io::Error) -> CallbackResult { - self.tx_cb_result - .send(CallbackResult::StopWithError(error)) - .ok(); - CallbackResult::Next - } -} - /// Audio handler for the [`Client`]. #[derive(Default)] pub struct AudioHandler { diff --git a/src/clipboard.rs b/src/clipboard.rs index bbc1d90b2f35..e5ab04aeadf6 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -1,7 +1,5 @@ #[cfg(not(target_os = "android"))] use arboard::{ClipboardData, ClipboardFormat}; -#[cfg(not(target_os = "android"))] -use clipboard_master::{ClipboardHandler, Master, Shutdown}; use hbb_common::{bail, log, message_proto::*, ResultType}; use std::{ sync::{mpsc::Sender, Arc, Mutex}, @@ -441,36 +439,6 @@ impl std::fmt::Display for ClipboardSide { } } -#[cfg(not(target_os = "android"))] -pub fn start_clipbard_master_thread( - handler: impl ClipboardHandler + Send + 'static, - tx_start_res: Sender<(Option, String)>, -) -> JoinHandle<()> { - // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage#:~:text=The%20window%20must%20belong%20to%20the%20current%20thread. - let h = std::thread::spawn(move || match Master::new(handler) { - Ok(mut master) => { - tx_start_res - .send((Some(master.shutdown_channel()), "".to_owned())) - .ok(); - log::debug!("Clipboard listener started"); - if let Err(err) = master.run() { - log::error!("Failed to run clipboard listener: {}", err); - } else { - log::debug!("Clipboard listener stopped"); - } - } - Err(err) => { - tx_start_res - .send(( - None, - format!("Failed to create clipboard listener: {}", err), - )) - .ok(); - } - }); - h -} - pub use proto::get_msg_if_not_support_multi_clip; mod proto { #[cfg(not(target_os = "android"))] @@ -685,3 +653,136 @@ pub fn get_clipboards_msg(client: bool) -> Option { msg.set_multi_clipboards(clipboards); Some(msg) } + +// We need this mod to notify multiple subscribers when the clipboard changes. +// Because only one clipboard master(listener) can tigger the clipboard change event multiple listeners are created on Linux(x11). +// https://github.com/rustdesk-org/clipboard-master/blob/4fb62e5b62fb6350d82b571ec7ba94b3cd466695/src/master/x11.rs#L226 +#[cfg(not(target_os = "android"))] +pub mod clipboard_listener { + use clipboard_master::{CallbackResult, ClipboardHandler, Master, Shutdown}; + use hbb_common::{bail, log, ResultType}; + use std::{ + collections::HashMap, + io, + sync::mpsc::{channel, Sender}, + sync::{Arc, Mutex}, + thread::JoinHandle, + }; + + lazy_static::lazy_static! { + pub static ref CLIPBOARD_LISTENER: Arc> = Default::default(); + } + + struct Handler { + subscribers: Arc>>>, + } + + impl ClipboardHandler for Handler { + fn on_clipboard_change(&mut self) -> CallbackResult { + let sub_lock = self.subscribers.lock().unwrap(); + for tx in sub_lock.values() { + tx.send(CallbackResult::Next).ok(); + } + CallbackResult::Next + } + + fn on_clipboard_error(&mut self, error: io::Error) -> CallbackResult { + let msg = format!("Clipboard listener error: {}", error); + let sub_lock = self.subscribers.lock().unwrap(); + for tx in sub_lock.values() { + tx.send(CallbackResult::StopWithError(io::Error::new( + io::ErrorKind::Other, + msg.clone(), + ))) + .ok(); + } + CallbackResult::Next + } + } + + #[derive(Default)] + pub struct ClipboardListener { + subscribers: Arc>>>, + handle: Option<(Shutdown, JoinHandle<()>)>, + } + + pub fn subscribe(name: String, tx: Sender) -> ResultType<()> { + log::info!("Subscribe clipboard listener: {}", name); + let mut listener_lock = CLIPBOARD_LISTENER.lock().unwrap(); + listener_lock.subscribers.lock().unwrap().insert(name, tx); + + if listener_lock.handle.is_none() { + log::info!("Start clipboard listener thread"); + let handler = Handler { + subscribers: listener_lock.subscribers.clone(), + }; + let (tx_start_res, rx_start_res) = channel(); + let h = 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); + } + }; + listener_lock.handle = Some((shutdown, h)); + log::info!("Clipboard listener thread started"); + } + + log::info!("Clipboard listener subscribed: {}", name); + Ok(()) + } + + pub fn unsubscribe(name: &str) { + log::info!("Unsubscribe clipboard listener: {}", name); + let mut listener_lock = CLIPBOARD_LISTENER.lock().unwrap(); + let is_empty = { + let mut sub_lock = listener_lock.subscribers.lock().unwrap(); + if let Some(tx) = sub_lock.remove(name) { + tx.send(CallbackResult::Stop).ok(); + } + sub_lock.is_empty() + }; + if is_empty { + if let Some((shutdown, h)) = listener_lock.handle.take() { + log::info!("Stop clipboard listener thread"); + shutdown.signal(); + h.join().ok(); + log::info!("Clipboard listener thread stopped"); + } + } + log::info!("Clipboard listener unsubscribed: {}", name); + } + + fn start_clipbard_master_thread( + handler: impl ClipboardHandler + Send + 'static, + tx_start_res: Sender<(Option, String)>, + ) -> JoinHandle<()> { + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage#:~:text=The%20window%20must%20belong%20to%20the%20current%20thread. + let h = std::thread::spawn(move || match Master::new(handler) { + Ok(mut master) => { + tx_start_res + .send((Some(master.shutdown_channel()), "".to_owned())) + .ok(); + log::debug!("Clipboard listener started"); + if let Err(err) = master.run() { + log::error!("Failed to run clipboard listener: {}", err); + } else { + log::debug!("Clipboard listener stopped"); + } + } + Err(err) => { + tx_start_res + .send(( + None, + format!("Failed to create clipboard listener: {}", err), + )) + .ok(); + } + }); + h + } +} diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index 218a9d0b3d99..d0264d636eb5 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -1,10 +1,12 @@ use super::*; -#[cfg(not(target_os = "android"))] -pub use crate::clipboard::{check_clipboard, ClipboardContext, ClipboardSide}; #[cfg(feature = "unix-file-copy-paste")] use crate::clipboard::check_clipboard_files; +#[cfg(not(target_os = "android"))] +use crate::clipboard::clipboard_listener; #[cfg(feature = "unix-file-copy-paste")] pub use crate::clipboard::FILE_CLIPBOARD_NAME as FILE_NAME; +#[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}; @@ -59,7 +61,7 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> { let (tx_cb_result, rx_cb_result) = channel(); let ctx = Some(ClipboardContext::new().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?); - clipboard_listener::subscribe(sp.name(), tx_cb_result.clone())?; + clipboard_listener::subscribe(sp.name(), tx_cb_result)?; let mut handler = Handler { ctx, #[cfg(target_os = "windows")] @@ -88,7 +90,10 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> { bail!("Clipboard listener stopped with error: {}", err); } Err(RecvTimeoutError::Timeout) => {} - _ => {} + Err(RecvTimeoutError::Disconnected) => { + log::error!("Clipboard listener disconnected"); + break; + } } } @@ -252,97 +257,3 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> { CLIPBOARD_SERVICE_OK.store(false, Ordering::SeqCst); Ok(()) } - -// We need this mod to notify multiple subscribers when the clipboard changes. -// Because only one clipboard master(listener) can tigger the clipboard change event multiple listeners are created on Linux(x11). -// https://github.com/rustdesk-org/clipboard-master/blob/4fb62e5b62fb6350d82b571ec7ba94b3cd466695/src/master/x11.rs#L226 -#[cfg(not(target_os = "android"))] -mod clipboard_listener { - use clipboard_master::{CallbackResult, ClipboardHandler, Shutdown}; - use hbb_common::{bail, ResultType}; - use std::{ - collections::HashMap, - io, - sync::mpsc::{channel, Sender}, - sync::{Arc, Mutex}, - thread::JoinHandle, - }; - - lazy_static::lazy_static! { - pub static ref CLIPBOARD_LISTENER: Arc> = Default::default(); - } - - struct Handler { - subscribers: Arc>>>, - } - - impl ClipboardHandler for Handler { - fn on_clipboard_change(&mut self) -> CallbackResult { - let sub_lock = self.subscribers.lock().unwrap(); - for tx in sub_lock.values() { - tx.send(CallbackResult::Next).ok(); - } - CallbackResult::Next - } - - fn on_clipboard_error(&mut self, error: io::Error) -> CallbackResult { - let msg = format!("Clipboard listener error: {}", error); - let sub_lock = self.subscribers.lock().unwrap(); - for tx in sub_lock.values() { - tx.send(CallbackResult::StopWithError(io::Error::new( - io::ErrorKind::Other, - msg.clone(), - ))) - .ok(); - } - CallbackResult::Next - } - } - - #[derive(Default)] - pub struct ClipboardListener { - subscribers: Arc>>>, - handle: Option<(Shutdown, JoinHandle<()>)>, - } - - pub fn subscribe(name: String, tx: Sender) -> ResultType<()> { - let mut listener_lock = CLIPBOARD_LISTENER.lock().unwrap(); - listener_lock.subscribers.lock().unwrap().insert(name, tx); - - if listener_lock.handle.is_none() { - let handler = Handler { - subscribers: listener_lock.subscribers.clone(), - }; - 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); - } - }; - listener_lock.handle = Some((shutdown, h)); - } - - Ok(()) - } - - pub fn unsubscribe(name: &str) { - let mut listener_lock = CLIPBOARD_LISTENER.lock().unwrap(); - let is_empty = { - let mut sub_lock = listener_lock.subscribers.lock().unwrap(); - sub_lock.remove(name); - sub_lock.is_empty() - }; - if is_empty { - if let Some((shutdown, h)) = listener_lock.handle.take() { - shutdown.signal(); - h.join().ok(); - } - } - } -} diff --git a/src/server/connection.rs b/src/server/connection.rs index c6de8d24dc4b..e67c02e5141f 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -8,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; diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 88e557f9a2c7..1aa4130deb0f 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -1151,7 +1151,7 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver