From a9015bcf703e01931c0f7a3ecd633e5fdfddcf6e Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Thu, 4 Jul 2024 21:23:08 +0800 Subject: [PATCH] feat: clipboard svg (#8608) * feat: clipboard svg Signed-off-by: fufesou * fix: is_last_plain, reset on clipboard event Signed-off-by: fufesou --------- Signed-off-by: fufesou --- Cargo.lock | 276 ++++++++++++++++++++++++++++++++++++++++++++++- src/clipboard.rs | 96 +++++++++++------ 2 files changed, 337 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b25f5cb5..a56357f19 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#61b448d8261fb313d67a61d03fc130bd738db396" +source = "git+https://github.com/rustdesk-org/arboard#98a1be0cab8355dd91a629b5dee8b428714cd902" 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" @@ -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/src/clipboard.rs b/src/clipboard.rs index 9dbdfddda..bb20f3b32 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -14,6 +14,7 @@ use hbb_common::{ pub const CLIPBOARD_NAME: &'static str = "clipboard"; pub const CLIPBOARD_INTERVAL: u64 = 333; +const FAKE_SVG_WIDTH: usize = 999999; lazy_static::lazy_static! { pub static ref CONTENT: Arc> = Default::default(); @@ -142,6 +143,13 @@ pub fn check_clipboard( let content = ctx2.get(); if let Ok(content) = content { if !content.is_empty() { + 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); @@ -212,26 +220,26 @@ impl ClipboardData { match self { ClipboardData::Empty => true, ClipboardData::Text(s) => s.is_empty(), - ClipboardData::Image(a, _) => a.bytes.is_empty(), + ClipboardData::Image(a, _) => a.bytes().is_empty(), } } fn from_msg(clipboard: Clipboard) -> Self { - let is_image = clipboard.width > 0 && clipboard.height > 0; + let is_image = clipboard.width > 0; let data = if clipboard.compress { decompress(&clipboard.content) } else { clipboard.content.into() }; if is_image { - ClipboardData::Image( - arboard::ImageData { - bytes: data.into(), - width: clipboard.width as _, - height: clipboard.height as _, - }, - 0, - ) + // We cannot use data.start_with(b" { - let compressed = compress_func(&a.bytes); - let compress = compressed.len() < a.bytes.len(); + let compressed = compress_func(&a.bytes()); + let compress = compressed.len() < a.bytes().len(); let content = if compress { compressed } else { - a.bytes.to_vec() + 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: a.width as _, - height: a.height as _, + width: w as _, + height: h as _, ..Default::default() }); } @@ -285,9 +297,13 @@ 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, _)) => { - a.width == b.width && a.height == b.height && a.bytes == b.bytes - } + (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, } @@ -295,7 +311,12 @@ impl PartialEq for ClipboardData { } #[cfg(not(any(all(target_os = "linux", feature = "unix-file-copy-paste"))))] -pub struct ClipboardContext(arboard::Clipboard, (Arc, u64), Option); +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"))))] #[allow(unreachable_code)] @@ -367,13 +388,18 @@ impl ClipboardContext { shutdown = Some(st); } } - Ok(ClipboardContext(board, (change_count, 0), shutdown)) + Ok(ClipboardContext { + inner: board, + counter: (change_count, 0), + shutdown, + is_last_plain: false, + }) } #[inline] pub fn change_count(&self) -> u64 { - debug_assert!(self.2.is_some()); - self.1 .0.load(Ordering::SeqCst) + debug_assert!(self.shutdown.is_some()); + self.counter.0.load(Ordering::SeqCst) } pub fn get(&mut self) -> ResultType { @@ -381,22 +407,28 @@ impl ClipboardContext { 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.1 .1 { - self.1 .1 = cn; - if let Ok(image) = self.0.get_image() { - if image.width > 0 && image.height > 0 { - return Ok(ClipboardData::image(image)); - } + if cn != self.counter.1 { + self.is_last_plain = false; + self.counter.1 = cn; + 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.clone())); } } - Ok(ClipboardData::Text(self.0.get_text()?)) + Ok(ClipboardData::Text(self.inner.get_text()?)) } fn set(&mut self, data: &ClipboardData) -> ResultType<()> { let _lock = ARBOARD_MTX.lock().unwrap(); match data { - ClipboardData::Text(s) => self.0.set_text(s)?, - ClipboardData::Image(a, _) => self.0.set_image(a.clone())?, + ClipboardData::Text(s) => self.inner.set_text(s)?, + ClipboardData::Image(a, _) => self.inner.set_image(a.clone())?, _ => {} } Ok(()) @@ -405,7 +437,7 @@ impl ClipboardContext { impl Drop for ClipboardContext { fn drop(&mut self) { - if let Some(shutdown) = self.2.take() { + if let Some(shutdown) = self.shutdown.take() { let _ = shutdown.signal(); } }