feat: clipboard svg (#8608)

* feat: clipboard svg

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: is_last_plain, reset on clipboard event

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou 2024-07-04 21:23:08 +08:00 committed by GitHub
parent f8f2686267
commit a9015bcf70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 337 additions and 35 deletions

276
Cargo.lock generated
View File

@ -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"

View File

@ -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<Mutex<ClipboardData>> = 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"<svg") to check if it is svg image
// because svg image starts with other bytes
let img = if clipboard.height == 0 && clipboard.width as usize == FAKE_SVG_WIDTH {
arboard::ImageData::svg(std::str::from_utf8(&data).unwrap_or_default())
} else {
arboard::ImageData::rgba(clipboard.width as _, clipboard.height as _, data.into())
};
ClipboardData::Image(img, 0)
} else {
if let Ok(content) = String::from_utf8(data) {
ClipboardData::Text(content)
@ -260,18 +268,22 @@ impl ClipboardData {
});
}
ClipboardData::Image(a, _) => {
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<AtomicU64>, u64), Option<Shutdown>);
pub struct ClipboardContext {
inner: arboard::Clipboard,
counter: (Arc<AtomicU64>, u64),
shutdown: Option<Shutdown>,
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<ClipboardData> {
@ -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();
}
}