diff --git a/Cargo.lock b/Cargo.lock index bb905da1a..cfc0f8560 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,7 +224,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arboard" version = "3.4.0" -source = "git+https://github.com/rustdesk-org/arboard#58d2b6a5c3af3d1aa15481ddff1bf70551f35a48" +source = "git+https://github.com/rustdesk-org/arboard#27b4e503caa70ec6306e5270461429f2cf907ad6" dependencies = [ "clipboard-win", "core-graphics 0.23.2", @@ -234,11 +234,24 @@ dependencies = [ "objc2-app-kit", "objc2-foundation", "parking_lot", + "resvg", "windows-sys 0.48.0", "wl-clipboard-rs", "x11rb 0.13.1", ] +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "async-broadcast" version = "0.5.1" @@ -987,9 +1000,9 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "5.4.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad" dependencies = [ "error-code", ] @@ -1513,6 +1526,12 @@ dependencies = [ "dasp_sample", ] +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + [[package]] name = "dbus" version = "0.9.7" @@ -1672,7 +1691,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.4", + "libloading 0.7.4", ] [[package]] @@ -2062,6 +2081,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "flume" version = "0.11.0" @@ -2118,6 +2143,29 @@ dependencies = [ "libm", ] +[[package]] +name = "fontconfig-parser" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a595cb550439a117696039dfc69830492058211b771a2a165379f2a1a53d84d" +dependencies = [ + "roxmltree 0.19.0", +] + +[[package]] +name = "fontdb" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -3165,6 +3213,12 @@ dependencies = [ "tiff", ] +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + [[package]] name = "impersonate_system" version = "0.1.0" @@ -3408,6 +3462,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "kurbo" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" +dependencies = [ + "arrayvec", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -3713,6 +3777,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -4659,9 +4732,15 @@ version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" dependencies = [ - "siphasher", + "siphasher 0.2.3", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.5" @@ -5335,6 +5414,31 @@ dependencies = [ "winreg 0.50.0", ] +[[package]] +name = "resvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944d052815156ac8fa77eaac055220e95ba0b01fa8887108ca710c03805d9051" +dependencies = [ + "gif", + "jpeg-decoder", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", +] + +[[package]] +name = "rgb" +version = "0.8.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7439be6844e40133eda024efd85bf07f59d0dd2f59b10c00dd6cfb92cc5c741" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.17.8" @@ -5359,6 +5463,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + [[package]] name = "rpassword" version = "2.1.0" @@ -5737,6 +5853,22 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.6.0", + "bytemuck", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.18" @@ -6027,12 +6159,27 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + [[package]] name = "siphasher" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -6042,6 +6189,15 @@ dependencies = [ "autocfg 1.3.0", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -6112,6 +6268,15 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + [[package]] name = "strsim" version = "0.8.0" @@ -6173,6 +6338,16 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "svgtypes" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fae3064df9b89391c9a76a0425a69d124aee9c5c28455204709e72c39868a43c" +dependencies = [ + "kurbo", + "siphasher 1.0.1", +] + [[package]] name = "syn" version = "0.15.44" @@ -6512,6 +6687,32 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if 1.0.0", + "log", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinyvec" version = "1.6.1" @@ -6803,6 +7004,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + [[package]] name = "typenum" version = "1.17.0" @@ -6875,6 +7082,18 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + +[[package]] +name = "unicode-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -6890,12 +7109,30 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + +[[package]] +name = "unicode-script" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" + [[package]] name = "unicode-segmentation" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + [[package]] name = "unicode-width" version = "0.1.13" @@ -6958,6 +7195,33 @@ dependencies = [ "log", ] +[[package]] +name = "usvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree 0.20.0", + "rustybuzz", + "simplecss", + "siphasher 1.0.1", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + [[package]] name = "utf16string" version = "0.2.0" @@ -7956,6 +8220,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "zbus" version = "3.15.2" diff --git a/Cargo.toml b/Cargo.toml index ef1da853a..0c54897e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,7 +90,7 @@ enigo = { path = "libs/enigo", features = [ "with_serde" ] } clipboard = { path = "libs/clipboard" } ctrlc = "3.2" # arboard = { version = "3.4.0", features = ["wayland-data-control"] } -arboard = { git = "https://github.com/rustdesk-org/arboard", features = ["wayland-data-control"] } +arboard = { git = "https://github.com/rustdesk-org/arboard", features = ["wayland-data-control", "image-data"] } clipboard-master = { git = "https://github.com/rustdesk-org/clipboard-master" } system_shutdown = "4.0" diff --git a/libs/clipboard/src/platform/mod.rs b/libs/clipboard/src/platform/mod.rs index 5db271129..2be4ce809 100644 --- a/libs/clipboard/src/platform/mod.rs +++ b/libs/clipboard/src/platform/mod.rs @@ -1,4 +1,3 @@ -#[cfg(any(target_os = "linux", target_os = "macos"))] use crate::{CliprdrError, CliprdrServiceContext}; #[cfg(target_os = "windows")] @@ -64,10 +63,8 @@ pub fn create_cliprdr_context( return Ok(Box::new(DummyCliprdrContext {}) as Box<_>); } -#[cfg(any(target_os = "linux", target_os = "macos"))] struct DummyCliprdrContext {} -#[cfg(any(target_os = "linux", target_os = "macos"))] impl CliprdrServiceContext for DummyCliprdrContext { fn set_is_stopped(&mut self) -> Result<(), CliprdrError> { Ok(()) diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index d7a8cf0a7..f346a7228 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -81,7 +81,6 @@ message LoginRequest { uint64 session_id = 10; string version = 11; OSLogin os_login = 12; - string my_platform = 13; } message Auth2FA { @@ -316,25 +315,13 @@ message Hash { string challenge = 2; } -enum ClipboardFormat { - Text = 0; - Rtf = 1; - Html = 2; - ImageRgba = 21; - ImagePng = 22; - ImageSvg = 23; -} - message Clipboard { bool compress = 1; bytes content = 2; int32 width = 3; int32 height = 4; - ClipboardFormat format = 5; } -message MultiClipboards { repeated Clipboard clipboards = 1; } - enum FileType { Dir = 0; DirLink = 2; @@ -829,6 +816,5 @@ message Message { PeerInfo peer_info = 25; PointerDeviceEvent pointer_device_event = 26; Auth2FA auth_2fa = 27; - MultiClipboards multi_clipboards = 28; } } diff --git a/src/client.rs b/src/client.rs index 4357c0074..4c3f53b03 100644 --- a/src/client.rs +++ b/src/client.rs @@ -65,7 +65,7 @@ use crate::{ }; #[cfg(not(any(target_os = "android", target_os = "ios")))] -use crate::clipboard::{check_clipboard, ClipboardSide, CLIPBOARD_INTERVAL}; +use crate::clipboard::{check_clipboard, CLIPBOARD_INTERVAL}; #[cfg(not(feature = "flutter"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] use crate::ui_session_interface::SessionPermissionConfig; @@ -136,11 +136,18 @@ lazy_static::lazy_static! { #[cfg(not(any(target_os = "android", target_os = "ios")))] lazy_static::lazy_static! { static ref ENIGO: Arc> = Arc::new(Mutex::new(enigo::Enigo::new())); + static ref OLD_CLIPBOARD_DATA: Arc> = Default::default(); static ref TEXT_CLIPBOARD_STATE: Arc> = Arc::new(Mutex::new(TextClipboardState::new())); } const PUBLIC_SERVER: &str = "public"; +#[inline] +#[cfg(not(any(target_os = "android", target_os = "ios")))] +pub fn get_old_clipboard_text() -> Arc> { + OLD_CLIPBOARD_DATA.clone() +} + #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn get_key_state(key: enigo::Key) -> bool { use enigo::KeyboardControllable; @@ -712,9 +719,7 @@ impl Client { // // If clipboard update is detected, the text will be sent to all sessions by `send_text_clipboard_msg`. #[cfg(not(any(target_os = "android", target_os = "ios")))] - fn try_start_clipboard( - client_clip_ctx: Option, - ) -> Option> { + fn try_start_clipboard(_ctx: Option) -> Option> { let mut clipboard_lock = TEXT_CLIPBOARD_STATE.lock().unwrap(); if clipboard_lock.running { return None; @@ -735,8 +740,15 @@ impl Client { continue; } - if let Some(msg) = check_clipboard(&mut ctx, ClipboardSide::Client) { - Self::send_msg(&client_clip_ctx, msg); + if let Some(msg) = check_clipboard(&mut ctx, Some(OLD_CLIPBOARD_DATA.clone())) { + #[cfg(feature = "flutter")] + crate::flutter::send_text_clipboard_msg(msg); + #[cfg(not(feature = "flutter"))] + if let Some(ctx) = &_ctx { + if ctx.cfg.is_text_clipboard_required() { + let _ = ctx.tx.send(Data::Message(msg)); + } + } } if !is_sent { @@ -753,39 +765,15 @@ impl Client { } #[inline] - #[cfg(feature = "flutter")] #[cfg(not(any(target_os = "android", target_os = "ios")))] - fn send_msg(_ctx: &Option, msg: Message) { - crate::flutter::send_text_clipboard_msg(msg); - } - - #[cfg(not(feature = "flutter"))] - #[cfg(not(any(target_os = "android", target_os = "ios")))] - fn send_msg(ctx: &Option, msg: Message) { - if let Some(ctx) = &ctx { - if ctx.cfg.is_text_clipboard_required() { - if let Some(pi) = ctx.cfg.lc.read().unwrap().peer_info.as_ref() { - if let Some(message::Union::MultiClipboards(multi_clipboards)) = &msg.union { - if let Some(msg_out) = crate::clipboard::get_msg_if_not_support_multi_clip( - &pi.version, - &pi.platform, - multi_clipboards, - ) { - let _ = ctx.tx.send(Data::Message(msg_out)); - return; - } - } - } - let _ = ctx.tx.send(Data::Message(msg)); - } + fn get_current_clipboard_msg() -> Option { + let data = &*OLD_CLIPBOARD_DATA.lock().unwrap(); + if data.is_empty() { + None + } else { + Some(data.create_msg()) } } - - #[inline] - #[cfg(not(any(target_os = "android", target_os = "ios")))] - fn get_current_clipboard_msg(peer_version: &str, peer_platform: &str) -> Option { - crate::clipboard::get_cache_msg(peer_version, peer_platform) - } } #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -2035,16 +2023,11 @@ impl LoginConfigHandler { if display_name.is_empty() { display_name = crate::username(); } - #[cfg(not(target_os = "android"))] - let my_platform = whoami::platform().to_string(); - #[cfg(target_os = "android")] - let my_platform = "Android".into(); let mut lr = LoginRequest { username: pure_id, password: password.into(), my_id, my_name: display_name, - my_platform, option: self.get_option_message(true).into(), session_id: self.session_id, version: crate::VERSION.to_string(), diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index ac630cda9..19074bbd1 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -41,7 +41,7 @@ use crate::client::{ new_voice_call_request, Client, MediaData, MediaSender, QualityStatus, MILLI1, SEC30, }; #[cfg(not(any(target_os = "android", target_os = "ios")))] -use crate::clipboard::{update_clipboard, ClipboardSide, CLIPBOARD_INTERVAL}; +use crate::clipboard::{update_clipboard, CLIPBOARD_INTERVAL}; use crate::common::{get_default_sound_input, set_sound_input}; use crate::ui_session_interface::{InvokeUiSession, Session}; #[cfg(not(any(target_os = "ios")))] @@ -1118,8 +1118,6 @@ impl Remote { } } Some(login_response::Union::PeerInfo(pi)) => { - let peer_version = pi.version.clone(); - let peer_platform = pi.platform.clone(); self.handler.handle_peer_info(pi); self.check_clipboard_file_context(); if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) { @@ -1141,9 +1139,7 @@ impl Remote { } #[cfg(not(any(target_os = "android", target_os = "ios")))] - if let Some(msg_out) = - Client::get_current_clipboard_msg(&peer_version, &peer_platform) - { + if let Some(msg_out) = Client::get_current_clipboard_msg() { let sender = self.sender.clone(); let permission_config = self.handler.get_permission_config(); tokio::spawn(async move { @@ -1184,7 +1180,7 @@ impl Remote { Some(message::Union::Clipboard(cb)) => { if !self.handler.lc.read().unwrap().disable_clipboard.v { #[cfg(not(any(target_os = "android", target_os = "ios")))] - update_clipboard(vec![cb], ClipboardSide::Client); + update_clipboard(cb, Some(crate::client::get_old_clipboard_text())); #[cfg(any(target_os = "android", target_os = "ios"))] { let content = if cb.compress { @@ -1198,12 +1194,6 @@ impl Remote { } } } - Some(message::Union::MultiClipboards(_mcb)) => { - if !self.handler.lc.read().unwrap().disable_clipboard.v { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - update_clipboard(_mcb.clipboards, ClipboardSide::Client); - } - } #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] Some(message::Union::Cliprdr(clip)) => { self.handle_cliprdr_msg(clip); diff --git a/src/clipboard.rs b/src/clipboard.rs index e0cf85afa..0db9f59c1 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -3,32 +3,24 @@ use std::sync::{ Arc, Mutex, }; -use arboard::{ClipboardData, ClipboardFormat}; use clipboard_master::{CallbackResult, ClipboardHandler, Master, Shutdown}; -use hbb_common::{log, message_proto::*, ResultType}; +use hbb_common::{ + allow_err, + compress::{compress as compress_func, decompress}, + log, + message_proto::*, + ResultType, +}; pub const CLIPBOARD_NAME: &'static str = "clipboard"; pub const CLIPBOARD_INTERVAL: u64 = 333; - -// This format is used to store the flag in the clipboard. -const RUSTDESK_CLIPBOARD_OWNER_FORMAT: &'static str = "dyn.com.rustdesk.owner"; +const FAKE_SVG_WIDTH: usize = 999999; lazy_static::lazy_static! { + pub static ref CONTENT: Arc> = Default::default(); static ref ARBOARD_MTX: Arc> = Arc::new(Mutex::new(())); - // cache the clipboard msg - static ref LAST_MULTI_CLIPBOARDS: Arc> = Arc::new(Mutex::new(MultiClipboards::new())); } -const SUPPORTED_FORMATS: &[ClipboardFormat] = &[ - ClipboardFormat::Text, - ClipboardFormat::Html, - ClipboardFormat::Rtf, - ClipboardFormat::ImageRgba, - ClipboardFormat::ImagePng, - ClipboardFormat::ImageSvg, - ClipboardFormat::Special(RUSTDESK_CLIPBOARD_OWNER_FORMAT), -]; - #[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))] static X11_CLIPBOARD: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); @@ -134,40 +126,58 @@ impl ClipboardContext { } } -pub fn check_clipboard(ctx: &mut Option, side: ClipboardSide) -> Option { +pub fn check_clipboard( + ctx: &mut Option, + old: Option>>, +) -> Option { if ctx.is_none() { *ctx = ClipboardContext::new(true).ok(); } let ctx2 = ctx.as_mut()?; - let content = ctx2.get(side); + let side = if old.is_none() { "host" } else { "client" }; + let old = if let Some(old) = old { + old + } else { + CONTENT.clone() + }; + let content = ctx2.get(); if let Ok(content) = content { if !content.is_empty() { - let mut msg = Message::new(); - let clipboards = proto::create_multi_clipboards(content); - msg.set_multi_clipboards(clipboards.clone()); - *LAST_MULTI_CLIPBOARDS.lock().unwrap() = clipboards; - return Some(msg); + if matches!(content, ClipboardData::Text(_)) { + // Skip the text if the last content is image-svg/html + if ctx2.is_last_plain { + return None; + } + } + + let changed = content != *old.lock().unwrap(); + if changed { + log::info!("{} update found on {}", CLIPBOARD_NAME, side); + let msg = content.create_msg(); + *old.lock().unwrap() = content; + return Some(msg); + } } } None } -fn update_clipboard_(multi_clipboards: Vec, side: ClipboardSide) { - let mut to_update_data = proto::from_multi_clipbards(multi_clipboards); - if to_update_data.is_empty() { +fn update_clipboard_(clipboard: Clipboard, old: Option>>) { + let content = ClipboardData::from_msg(clipboard); + if content.is_empty() { return; } match ClipboardContext::new(false) { Ok(mut ctx) => { - to_update_data.push(ClipboardData::Special(( - RUSTDESK_CLIPBOARD_OWNER_FORMAT.to_owned(), - side.get_owner_data(), - ))); - if let Err(e) = ctx.set(&to_update_data) { - log::debug!("Failed to set clipboard: {}", e); + let side = if old.is_none() { "host" } else { "client" }; + let old = if let Some(old) = old { + old } else { - log::debug!("{} updated on {}", CLIPBOARD_NAME, side); - } + CONTENT.clone() + }; + allow_err!(ctx.set(&content)); + *old.lock().unwrap() = content; + log::debug!("{} updated on {}", CLIPBOARD_NAME, side); } Err(err) => { log::error!("Failed to create clipboard context: {}", err); @@ -175,17 +185,137 @@ fn update_clipboard_(multi_clipboards: Vec, side: ClipboardSide) { } } -pub fn update_clipboard(multi_clipboards: Vec, side: ClipboardSide) { +pub fn update_clipboard(clipboard: Clipboard, old: Option>>) { std::thread::spawn(move || { - update_clipboard_(multi_clipboards, side); + update_clipboard_(clipboard, old); }); } +#[derive(Clone)] +pub enum ClipboardData { + Text(String), + Image(arboard::ImageData<'static>, u64), + Empty, +} + +impl Default for ClipboardData { + fn default() -> Self { + ClipboardData::Empty + } +} + +impl ClipboardData { + fn image(image: arboard::ImageData<'static>) -> ClipboardData { + let hash = 0; + /* + use std::hash::{DefaultHasher, Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + image.bytes.hash(&mut hasher); + let hash = hasher.finish(); + */ + ClipboardData::Image(image, hash) + } + + pub fn is_empty(&self) -> bool { + match self { + ClipboardData::Empty => true, + ClipboardData::Text(s) => s.is_empty(), + ClipboardData::Image(a, _) => a.bytes().is_empty(), + } + } + + fn from_msg(clipboard: Clipboard) -> Self { + let is_image = clipboard.width > 0; + let data = if clipboard.compress { + decompress(&clipboard.content) + } else { + clipboard.content.into() + }; + if is_image { + // We cannot use data.start_with(b" Message { + let mut msg = Message::new(); + + match self { + ClipboardData::Text(s) => { + let compressed = compress_func(s.as_bytes()); + let compress = compressed.len() < s.as_bytes().len(); + let content = if compress { + compressed + } else { + s.clone().into_bytes() + }; + msg.set_clipboard(Clipboard { + compress, + content: content.into(), + ..Default::default() + }); + } + ClipboardData::Image(a, _) => { + let compressed = compress_func(&a.bytes()); + let compress = compressed.len() < a.bytes().len(); + let content = if compress { + compressed + } else { + a.bytes().to_vec() + }; + let (w, h) = match a { + arboard::ImageData::Rgba(a) => (a.width, a.height), + arboard::ImageData::Svg(_) => (FAKE_SVG_WIDTH as _, 0 as _), + }; + msg.set_clipboard(Clipboard { + compress, + content: content.into(), + width: w as _, + height: h as _, + ..Default::default() + }); + } + _ => {} + } + msg + } +} + +impl PartialEq for ClipboardData { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (ClipboardData::Text(a), ClipboardData::Text(b)) => a == b, + (ClipboardData::Image(a, _), ClipboardData::Image(b, _)) => match (a, b) { + (arboard::ImageData::Rgba(a), arboard::ImageData::Rgba(b)) => { + a.width == b.width && a.height == b.height && a.bytes == b.bytes + } + (arboard::ImageData::Svg(a), arboard::ImageData::Svg(b)) => a == b, + _ => false, + }, + (ClipboardData::Empty, ClipboardData::Empty) => true, + _ => false, + } + } +} + #[cfg(not(any(all(target_os = "linux", feature = "unix-file-copy-paste"))))] pub struct ClipboardContext { inner: arboard::Clipboard, counter: (Arc, u64), shutdown: Option, + is_last_plain: bool, } #[cfg(not(any(all(target_os = "linux", feature = "unix-file-copy-paste"))))] @@ -262,6 +392,7 @@ impl ClipboardContext { inner: board, counter: (change_count, 0), shutdown, + is_last_plain: false, }) } @@ -271,29 +402,35 @@ impl ClipboardContext { self.counter.0.load(Ordering::SeqCst) } - pub fn get(&mut self, side: ClipboardSide) -> ResultType> { + pub fn get(&mut self) -> ResultType { let cn = self.change_count(); let _lock = ARBOARD_MTX.lock().unwrap(); + // only for image for the time being, + // because I do not want to change behavior of text clipboard for the time being if cn != self.counter.1 { + self.is_last_plain = false; self.counter.1 = cn; - let data = self.inner.get_formats(SUPPORTED_FORMATS)?; - if !data.is_empty() { - for c in data.iter() { - if let ClipboardData::Special((_, d)) = c { - if side.is_owner(d) { - return Ok(vec![]); - } - } - } + if let Ok(image) = self.inner.get_image() { + // Both text and image svg may be set by some applications + // But we only want to send the svg content. + // + // We can't call `get_text()` and store current text in `old` in outer scope, + // because it may be updated later than svg. + // Then the text will still be sent and replace the image svg content. + self.is_last_plain = matches!(image, arboard::ImageData::Svg(_)); + return Ok(ClipboardData::image(image)); } - return Ok(data); } - Ok(vec![]) + Ok(ClipboardData::Text(self.inner.get_text()?)) } - fn set(&mut self, data: &[ClipboardData]) -> ResultType<()> { + fn set(&mut self, data: &ClipboardData) -> ResultType<()> { let _lock = ARBOARD_MTX.lock().unwrap(); - self.inner.set_formats(data)?; + match data { + ClipboardData::Text(s) => self.inner.set_text(s)?, + ClipboardData::Image(a, _) => self.inner.set_image(a.clone())?, + _ => {} + } Ok(()) } } @@ -305,210 +442,3 @@ impl Drop for ClipboardContext { } } } - -pub fn is_support_multi_clipboard(peer_version: &str, peer_platform: &str) -> bool { - use hbb_common::get_version_number; - get_version_number(peer_version) >= get_version_number("1.2.7") - && !["", "Android", &whoami::Platform::Ios.to_string()].contains(&peer_platform) -} - -pub fn get_cache_msg(peer_version: &str, peer_platform: &str) -> Option { - let multi_clipboards = LAST_MULTI_CLIPBOARDS.lock().unwrap().clone(); - if multi_clipboards.clipboards.is_empty() { - return None; - } - - let mut msg = Message::new(); - if is_support_multi_clipboard(peer_version, peer_platform) { - msg.set_multi_clipboards(multi_clipboards); - } else { - for clipboard in multi_clipboards.clipboards.iter() { - if clipboard.format.enum_value() == Ok(hbb_common::message_proto::ClipboardFormat::Text) - { - msg.set_clipboard(clipboard.clone()); - break; - } - } - } - Some(msg) -} - -pub fn reset_cache() { - *LAST_MULTI_CLIPBOARDS.lock().unwrap() = MultiClipboards::new(); -} - -#[derive(PartialEq, Eq, Clone, Copy)] -pub enum ClipboardSide { - Host, - Client, -} - -impl ClipboardSide { - // 01: the clipboard is owned by the host - // 10: the clipboard is owned by the client - fn get_owner_data(&self) -> Vec { - match self { - ClipboardSide::Host => vec![0b01], - ClipboardSide::Client => vec![0b10], - } - } - - fn is_owner(&self, data: &[u8]) -> bool { - if data.len() == 0 { - return false; - } - match self { - ClipboardSide::Host => data[0] & 0b01 == 0b01, - ClipboardSide::Client => data[0] & 0b10 == 0b10, - } - } -} - -impl std::fmt::Display for ClipboardSide { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ClipboardSide::Host => write!(f, "host"), - ClipboardSide::Client => write!(f, "client"), - } - } -} - -pub use proto::get_msg_if_not_support_multi_clip; -mod proto { - use arboard::ClipboardData; - use hbb_common::{ - compress::{compress as compress_func, decompress}, - message_proto::{Clipboard, ClipboardFormat, Message, MultiClipboards}, - }; - - fn plain_to_proto(s: String, format: ClipboardFormat) -> Clipboard { - let compressed = compress_func(s.as_bytes()); - let compress = compressed.len() < s.as_bytes().len(); - let content = if compress { - compressed - } else { - s.bytes().collect::>() - }; - Clipboard { - compress, - content: content.into(), - format: format.into(), - ..Default::default() - } - } - - fn image_to_proto(a: arboard::ImageData) -> Clipboard { - match &a { - arboard::ImageData::Rgba(rgba) => { - let compressed = compress_func(&a.bytes()); - let compress = compressed.len() < a.bytes().len(); - let content = if compress { - compressed - } else { - a.bytes().to_vec() - }; - Clipboard { - compress, - content: content.into(), - width: rgba.width as _, - height: rgba.height as _, - format: ClipboardFormat::ImageRgba.into(), - ..Default::default() - } - } - arboard::ImageData::Png(png) => Clipboard { - compress: false, - content: png.to_owned().to_vec().into(), - format: ClipboardFormat::ImagePng.into(), - ..Default::default() - }, - arboard::ImageData::Svg(_) => { - let compressed = compress_func(&a.bytes()); - let compress = compressed.len() < a.bytes().len(); - let content = if compress { - compressed - } else { - a.bytes().to_vec() - }; - Clipboard { - compress, - content: content.into(), - format: ClipboardFormat::ImageSvg.into(), - ..Default::default() - } - } - } - } - - fn clipboard_data_to_proto(data: ClipboardData) -> Option { - let d = match data { - ClipboardData::Text(s) => plain_to_proto(s, ClipboardFormat::Text), - ClipboardData::Rtf(s) => plain_to_proto(s, ClipboardFormat::Rtf), - ClipboardData::Html(s) => plain_to_proto(s, ClipboardFormat::Html), - ClipboardData::Image(a) => image_to_proto(a), - _ => return None, - }; - Some(d) - } - - pub fn create_multi_clipboards(vec_data: Vec) -> MultiClipboards { - MultiClipboards { - clipboards: vec_data - .into_iter() - .filter_map(clipboard_data_to_proto) - .collect(), - ..Default::default() - } - } - - fn from_clipboard(clipboard: Clipboard) -> Option { - let data = if clipboard.compress { - decompress(&clipboard.content) - } else { - clipboard.content.into() - }; - match clipboard.format.enum_value() { - Ok(ClipboardFormat::Text) => String::from_utf8(data).ok().map(ClipboardData::Text), - Ok(ClipboardFormat::Rtf) => String::from_utf8(data).ok().map(ClipboardData::Rtf), - Ok(ClipboardFormat::Html) => String::from_utf8(data).ok().map(ClipboardData::Html), - Ok(ClipboardFormat::ImageRgba) => Some(ClipboardData::Image(arboard::ImageData::rgba( - clipboard.width as _, - clipboard.height as _, - data.into(), - ))), - Ok(ClipboardFormat::ImagePng) => { - Some(ClipboardData::Image(arboard::ImageData::png(data.into()))) - } - Ok(ClipboardFormat::ImageSvg) => Some(ClipboardData::Image(arboard::ImageData::svg( - std::str::from_utf8(&data).unwrap_or_default(), - ))), - _ => None, - } - } - - pub fn from_multi_clipbards(multi_clipboards: Vec) -> Vec { - multi_clipboards - .into_iter() - .filter_map(from_clipboard) - .collect() - } - - pub fn get_msg_if_not_support_multi_clip( - version: &str, - platform: &str, - multi_clipboards: &MultiClipboards, - ) -> Option { - if crate::clipboard::is_support_multi_clipboard(version, platform) { - return None; - } - let mut msg = Message::new(); - // Find the first text clipboard and send it. - for clipboard in multi_clipboards.clipboards.iter() { - if clipboard.format.enum_value() == Ok(ClipboardFormat::Text) { - msg.set_clipboard(clipboard.clone()); - break; - } - } - Some(msg) - } -} diff --git a/src/flutter.rs b/src/flutter.rs index 773abb3f2..d1e44008b 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -1256,19 +1256,6 @@ pub fn update_text_clipboard_required() { pub fn send_text_clipboard_msg(msg: Message) { for s in sessions::get_sessions() { if s.is_text_clipboard_required() { - // Check if the client supports multi clipboards - if let Some(message::Union::MultiClipboards(multi_clipboards)) = &msg.union { - let version = s.ui_handler.peer_info.read().unwrap().version.clone(); - let platform = s.ui_handler.peer_info.read().unwrap().platform.clone(); - if let Some(msg_out) = crate::clipboard::get_msg_if_not_support_multi_clip( - &version, - &platform, - multi_clipboards, - ) { - s.send(Data::Message(msg_out)); - continue; - } - } s.send(Data::Message(msg.clone())); } } diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index 7faa2de3d..eeeea4999 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -1,7 +1,7 @@ use super::*; pub use crate::clipboard::{ - check_clipboard, get_cache_msg, ClipboardContext, ClipboardSide, - CLIPBOARD_INTERVAL as INTERVAL, CLIPBOARD_NAME as NAME, + check_clipboard, ClipboardContext, CLIPBOARD_INTERVAL as INTERVAL, CLIPBOARD_NAME as NAME, + CONTENT, }; #[derive(Default)] @@ -11,7 +11,7 @@ struct State { impl super::service::Reset for State { fn reset(&mut self) { - crate::clipboard::reset_cache(); + *CONTENT.lock().unwrap() = Default::default(); self.ctx = None; } @@ -34,14 +34,14 @@ pub fn new() -> GenericService { } fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> { - if let Some(msg) = check_clipboard(&mut state.ctx, ClipboardSide::Host) { + if let Some(msg) = check_clipboard(&mut state.ctx, None) { sp.send(msg); } sp.snapshot(|sps| { - // Just create a message with multi clipboards here - // The actual peer version and peer platform will be checked again before sending. - if let Some(msg) = get_cache_msg("1.2.7", "Windows") { - sps.send_shared(Arc::new(msg)); + let data = CONTENT.lock().unwrap().clone(); + if !data.is_empty() { + let msg_out = data.create_msg(); + sps.send_shared(Arc::new(msg_out)); } Ok(()) })?; diff --git a/src/server/connection.rs b/src/server/connection.rs index 26533fcf4..cc43650e0 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1,6 +1,6 @@ use super::{input_service::*, *}; #[cfg(not(any(target_os = "android", target_os = "ios")))] -use crate::clipboard::{update_clipboard, ClipboardSide}; +use crate::clipboard::update_clipboard; #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] use crate::clipboard_file::*; #[cfg(target_os = "android")] @@ -685,19 +685,8 @@ impl Connection { msg = Arc::new(new_msg); } } - Some(message::Union::MultiClipboards(_multi_clipboards)) => { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - if let Some(msg_out) = crate::clipboard::get_msg_if_not_support_multi_clip(&conn.lr.version, &conn.lr.my_platform, _multi_clipboards) { - if let Err(err) = conn.stream.send(&msg_out).await { - conn.on_close(&err.to_string(), false).await; - break; - } - continue; - } - } _ => {} } - let msg: &Message = &msg; if let Err(err) = conn.stream.send(msg).await { conn.on_close(&err.to_string(), false).await; @@ -2064,14 +2053,7 @@ impl Connection { { #[cfg(not(any(target_os = "android", target_os = "ios")))] if self.clipboard { - update_clipboard(vec![_cb], ClipboardSide::Host); - } - } - Some(message::Union::MultiClipboards(_mcb)) => - { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - if self.clipboard { - update_clipboard(_mcb.clipboards, ClipboardSide::Host); + update_clipboard(_cb, None); } } Some(message::Union::Cliprdr(_clip)) =>