From d42f5e6547275053590bc7b8acd2a51ce2be1cb2 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Fri, 11 Oct 2024 19:50:06 +0200 Subject: [PATCH 1/7] implement get html operation Signed-off-by: Gae24 <96017547+Gae24@users.noreply.github.com> --- src/lib.rs | 5 ++++ src/platform/linux/mod.rs | 8 ++++++ src/platform/linux/wayland.rs | 21 +++++++++++++++ src/platform/linux/x11.rs | 6 +++++ src/platform/osx.rs | 48 ++++++++++++++++++++--------------- src/platform/windows.rs | 9 +++++++ 6 files changed, 77 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ee752f6..81d4f51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -192,6 +192,11 @@ impl Get<'_> { pub fn image(self) -> Result, Error> { self.platform.image() } + + /// Completes the "get" operation by fetching HTML from the clipboard. + pub fn html(self) -> Result { + self.platform.html() + } } /// A builder for an operation that sets a value to the clipboard. diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index d8207e3..2a6b972 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -122,6 +122,14 @@ impl<'clipboard> Get<'clipboard> { Clipboard::WlDataControl(clipboard) => clipboard.get_image(self.selection), } } + + pub(crate) fn html(self) -> Result { + match self.clipboard { + Clipboard::X11(clipboard) => clipboard.get_html(self.selection), + #[cfg(feature = "wayland-data-control")] + Clipboard::WlDataControl(clipboard) => clipboard.get_html(self.selection), + } + } } /// Linux-specific extensions to the [`Get`](super::Get) builder. diff --git a/src/platform/linux/wayland.rs b/src/platform/linux/wayland.rs index b3916a6..9fbecd3 100644 --- a/src/platform/linux/wayland.rs +++ b/src/platform/linux/wayland.rs @@ -91,6 +91,27 @@ impl Clipboard { Ok(()) } + pub(crate) fn get_html(&mut self, selection: LinuxClipboardKind) -> Result { + use wl_clipboard_rs::paste::MimeType; + + let result = get_contents(selection.try_into()?, Seat::Unspecified, MimeType::Specific("text/html")); + match result { + Ok((mut pipe, _)) => { + let mut contents = vec![]; + pipe.read_to_end(&mut contents).map_err(into_unknown)?; + String::from_utf8(contents).map_err(|_| Error::ConversionFailure) + } + + Err(PasteError::ClipboardEmpty) | Err(PasteError::NoMimeType) => { + Err(Error::ContentNotAvailable) + } + + Err(PasteError::PrimarySelectionUnsupported) => Err(Error::ClipboardNotSupported), + + Err(err) => Err(Error::Unknown { description: err.to_string() }), + } + } + pub(crate) fn set_html( &self, html: Cow<'_, str>, diff --git a/src/platform/linux/x11.rs b/src/platform/linux/x11.rs index 406b43f..c221c2d 100644 --- a/src/platform/linux/x11.rs +++ b/src/platform/linux/x11.rs @@ -885,6 +885,12 @@ impl Clipboard { self.inner.write(data, selection, wait) } + pub(crate) fn get_html(&self, selection: LinuxClipboardKind) -> Result { + let formats = [self.inner.atoms.HTML]; + let result = self.inner.read(&formats, selection)?; + String::from_utf8(result.bytes).map_err(|_| Error::ConversionFailure) + } + pub(crate) fn set_html( &self, html: Cow<'_, str>, diff --git a/src/platform/osx.rs b/src/platform/osx.rs index ec5f881..5e47348 100644 --- a/src/platform/osx.rs +++ b/src/platform/osx.rs @@ -121,6 +121,30 @@ impl Clipboard { unsafe { self.pasteboard.clearContents() }; } + fn string_from_type(&self, type: &'static NSString) -> Result { + // XXX: There does not appear to be an alternative for obtaining text without the need for + // autorelease behavior. + autoreleasepool(|_| { + // XXX: We explicitly use `pasteboardItems` and not `stringForType` since the latter will concat + // multiple strings, if present, into one and return it instead of reading just the first which is `arboard`'s + // historical behavior. + let contents = + unsafe { self.pasteboard.pasteboardItems() }.ok_or_else(|| { + Error::Unknown { + description: String::from("NSPasteboard#pasteboardItems errored"), + } + })?; + + for item in contents { + if let Some(string) = unsafe { item.stringForType(type) } { + return Ok(string.to_string()); + } + } + + Err(Error::ContentNotAvailable) + }) + } + // fn get_binary_contents(&mut self) -> Result, Box> { // let string_class: Id = { // let cls: Id = unsafe { Id::from_ptr(class("NSString")) }; @@ -182,27 +206,11 @@ impl<'clipboard> Get<'clipboard> { } pub(crate) fn text(self) -> Result { - // XXX: There does not appear to be an alternative for obtaining text without the need for - // autorelease behavior. - autoreleasepool(|_| { - // XXX: We explicitly use `pasteboardItems` and not `stringForType` since the latter will concat - // multiple strings, if present, into one and return it instead of reading just the first which is `arboard`'s - // historical behavior. - let contents = - unsafe { self.clipboard.pasteboard.pasteboardItems() }.ok_or_else(|| { - Error::Unknown { - description: String::from("NSPasteboard#pasteboardItems errored"), - } - })?; - - for item in contents { - if let Some(string) = unsafe { item.stringForType(NSPasteboardTypeString) } { - return Ok(string.to_string()); - } - } + self.clipboard.string_from_type(NSPasteboardTypeString) + } - Err(Error::ContentNotAvailable) - }) + pub(crate) fn html(self) -> Result { + self.clipboard.string_from_type(NSPasteboardTypeHTML) } #[cfg(feature = "image-data")] diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 6544b92..e81beb5 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -567,6 +567,15 @@ impl<'clipboard> Get<'clipboard> { String::from_utf16(&out[..bytes_read]).map_err(|_| Error::ConversionFailure) } + pub(crate) fn html(self) -> Result { + let format = clipboard_win::register_format("HTML Format") + .ok_or_else(|| Error::unknown("unable to register HTML format"))?; + let mut out: Vec = Vec::new(); + clipboard_win::raw::get_html(format.get(), &mut out) + .map_err(|_| Error::unknown("failed to read clipboard string"))?; + String::from_utf8(out).map_err(|_| Error::ConversionFailure) + } + #[cfg(feature = "image-data")] pub(crate) fn image(self) -> Result, Error> { const FORMAT: u32 = clipboard_win::formats::CF_DIBV5; From aea50128002d73505fd2a917af0f71b3a9a8e5ab Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Sat, 12 Oct 2024 19:30:51 +0200 Subject: [PATCH 2/7] add test for html() get operation --- examples/{set_html.rs => set_get_html.rs} | 3 +++ src/lib.rs | 9 +++++++++ 2 files changed, 12 insertions(+) rename examples/{set_html.rs => set_get_html.rs} (78%) diff --git a/examples/set_html.rs b/examples/set_get_html.rs similarity index 78% rename from examples/set_html.rs rename to examples/set_get_html.rs index 125f6b1..d05a674 100644 --- a/examples/set_html.rs +++ b/examples/set_get_html.rs @@ -15,4 +15,7 @@ consectetur adipiscing elit."#; ctx.set_html(html, Some(alt_text)).unwrap(); thread::sleep(Duration::from_secs(5)); + + let success = ctx.get().html().unwrap() == html; + println!("Set and Get html operations were successful: {success}"); } diff --git a/src/lib.rs b/src/lib.rs index 81d4f51..eb3c1bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -327,6 +327,15 @@ mod tests { ctx.set_html(html, Some(alt_text)).unwrap(); assert_eq!(ctx.get_text().unwrap(), alt_text); } + { + let mut ctx = Clipboard::new().unwrap(); + + let html = "hello world!"; + + ctx.set().html(html, None).unwrap(); + + assert_eq!(ctx.get().html().unwrap(), html); + } #[cfg(feature = "image-data")] { let mut ctx = Clipboard::new().unwrap(); From 326ede71f7f5e0aafb1bb2ea6b46d948dd3f789d Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Sun, 13 Oct 2024 12:51:13 +0200 Subject: [PATCH 3/7] cleanup wayland code --- src/platform/linux/wayland.rs | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/platform/linux/wayland.rs b/src/platform/linux/wayland.rs index 9fbecd3..1ea780a 100644 --- a/src/platform/linux/wayland.rs +++ b/src/platform/linux/wayland.rs @@ -53,10 +53,8 @@ impl Clipboard { Ok(Self {}) } - pub(crate) fn get_text(&mut self, selection: LinuxClipboardKind) -> Result { - use wl_clipboard_rs::paste::MimeType; - - let result = get_contents(selection.try_into()?, Seat::Unspecified, MimeType::Text); + fn string_for_mime(&mut self, selection: LinuxClipboardKind, mime: paste::MimeType) -> Result { + let result = get_contents(selection.try_into()?, Seat::Unspecified, mime); match result { Ok((mut pipe, _)) => { let mut contents = vec![]; @@ -74,6 +72,10 @@ impl Clipboard { } } + pub(crate) fn get_text(&mut self, selection: LinuxClipboardKind) -> Result { + self.string_for_mime(selection, paste::MimeType::Text) + } + pub(crate) fn set_text( &self, text: Cow<'_, str>, @@ -92,24 +94,7 @@ impl Clipboard { } pub(crate) fn get_html(&mut self, selection: LinuxClipboardKind) -> Result { - use wl_clipboard_rs::paste::MimeType; - - let result = get_contents(selection.try_into()?, Seat::Unspecified, MimeType::Specific("text/html")); - match result { - Ok((mut pipe, _)) => { - let mut contents = vec![]; - pipe.read_to_end(&mut contents).map_err(into_unknown)?; - String::from_utf8(contents).map_err(|_| Error::ConversionFailure) - } - - Err(PasteError::ClipboardEmpty) | Err(PasteError::NoMimeType) => { - Err(Error::ContentNotAvailable) - } - - Err(PasteError::PrimarySelectionUnsupported) => Err(Error::ClipboardNotSupported), - - Err(err) => Err(Error::Unknown { description: err.to_string() }), - } + self.string_for_mime(selection, paste::MimeType::Specific("text/html")) } pub(crate) fn set_html( From 09588349e70587b7a7d9a28b47985487ea2d952a Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Thu, 17 Oct 2024 08:40:44 +0200 Subject: [PATCH 4/7] fix fmt errors --- examples/set_get_html.rs | 2 +- src/platform/linux/mod.rs | 2 +- src/platform/linux/wayland.rs | 6 +++++- src/platform/osx.rs | 9 +++------ src/platform/windows.rs | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/examples/set_get_html.rs b/examples/set_get_html.rs index d05a674..96ab58a 100644 --- a/examples/set_get_html.rs +++ b/examples/set_get_html.rs @@ -15,7 +15,7 @@ consectetur adipiscing elit."#; ctx.set_html(html, Some(alt_text)).unwrap(); thread::sleep(Duration::from_secs(5)); - + let success = ctx.get().html().unwrap() == html; println!("Set and Get html operations were successful: {success}"); } diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 2a6b972..4ce13c1 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -128,7 +128,7 @@ impl<'clipboard> Get<'clipboard> { Clipboard::X11(clipboard) => clipboard.get_html(self.selection), #[cfg(feature = "wayland-data-control")] Clipboard::WlDataControl(clipboard) => clipboard.get_html(self.selection), - } + } } } diff --git a/src/platform/linux/wayland.rs b/src/platform/linux/wayland.rs index 1ea780a..9cd12bf 100644 --- a/src/platform/linux/wayland.rs +++ b/src/platform/linux/wayland.rs @@ -53,7 +53,11 @@ impl Clipboard { Ok(Self {}) } - fn string_for_mime(&mut self, selection: LinuxClipboardKind, mime: paste::MimeType) -> Result { + fn string_for_mime( + &mut self, + selection: LinuxClipboardKind, + mime: paste::MimeType, + ) -> Result { let result = get_contents(selection.try_into()?, Seat::Unspecified, mime); match result { Ok((mut pipe, _)) => { diff --git a/src/platform/osx.rs b/src/platform/osx.rs index 5e47348..5a33728 100644 --- a/src/platform/osx.rs +++ b/src/platform/osx.rs @@ -128,12 +128,9 @@ impl Clipboard { // XXX: We explicitly use `pasteboardItems` and not `stringForType` since the latter will concat // multiple strings, if present, into one and return it instead of reading just the first which is `arboard`'s // historical behavior. - let contents = - unsafe { self.pasteboard.pasteboardItems() }.ok_or_else(|| { - Error::Unknown { - description: String::from("NSPasteboard#pasteboardItems errored"), - } - })?; + let contents = unsafe { self.pasteboard.pasteboardItems() }.ok_or_else(|| { + Error::Unknown { description: String::from("NSPasteboard#pasteboardItems errored") } + })?; for item in contents { if let Some(string) = unsafe { item.stringForType(type) } { diff --git a/src/platform/windows.rs b/src/platform/windows.rs index e81beb5..19e1e82 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -572,7 +572,7 @@ impl<'clipboard> Get<'clipboard> { .ok_or_else(|| Error::unknown("unable to register HTML format"))?; let mut out: Vec = Vec::new(); clipboard_win::raw::get_html(format.get(), &mut out) - .map_err(|_| Error::unknown("failed to read clipboard string"))?; + .map_err(|_| Error::unknown("failed to read clipboard string"))?; String::from_utf8(out).map_err(|_| Error::ConversionFailure) } From f9b466bd1944e0bca5a209a1fca3bc8386064959 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Thu, 17 Oct 2024 08:54:31 +0200 Subject: [PATCH 5/7] fix compilation on macOS --- src/platform/osx.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/osx.rs b/src/platform/osx.rs index 5a33728..1df7222 100644 --- a/src/platform/osx.rs +++ b/src/platform/osx.rs @@ -121,7 +121,7 @@ impl Clipboard { unsafe { self.pasteboard.clearContents() }; } - fn string_from_type(&self, type: &'static NSString) -> Result { + fn string_from_type(&self, type_: &'static NSString) -> Result { // XXX: There does not appear to be an alternative for obtaining text without the need for // autorelease behavior. autoreleasepool(|_| { @@ -133,7 +133,7 @@ impl Clipboard { })?; for item in contents { - if let Some(string) = unsafe { item.stringForType(type) } { + if let Some(string) = unsafe { item.stringForType(type_) } { return Ok(string.to_string()); } } @@ -203,11 +203,11 @@ impl<'clipboard> Get<'clipboard> { } pub(crate) fn text(self) -> Result { - self.clipboard.string_from_type(NSPasteboardTypeString) + unsafe { self.clipboard.string_from_type(NSPasteboardTypeString) } } pub(crate) fn html(self) -> Result { - self.clipboard.string_from_type(NSPasteboardTypeHTML) + unsafe { self.clipboard.string_from_type(NSPasteboardTypeHTML) } } #[cfg(feature = "image-data")] From d445228527ff90e676e0370127265fbeac0e923d Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:24:18 +0200 Subject: [PATCH 6/7] macOS: extract html --- src/platform/osx.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/platform/osx.rs b/src/platform/osx.rs index 1df7222..1e605fa 100644 --- a/src/platform/osx.rs +++ b/src/platform/osx.rs @@ -207,7 +207,8 @@ impl<'clipboard> Get<'clipboard> { } pub(crate) fn html(self) -> Result { - unsafe { self.clipboard.string_from_type(NSPasteboardTypeHTML) } + let html = unsafe { self.clipboard.string_from_type(NSPasteboardTypeHTML) }?; + extract_html(html).ok_or(Error::ConversionFailure) } #[cfg(feature = "image-data")] @@ -352,6 +353,18 @@ fn add_clipboard_exclusions(clipboard: &mut Clipboard, exclude_from_history: boo } } +fn extract_html(html: String) -> Option { + let start_tag = ""; + let end_tag = ""; + + // Locate the start index of the tag + let start_index = html.find(start_tag)? + start_tag.len(); + // Locate the end index of the tag + let end_index = html.find(end_tag)?; + + Some(html[start_index..end_index].to_string()) +} + /// Apple-specific extensions to the [`Set`](crate::Set) builder. pub trait SetExtApple: private::Sealed { /// Excludes the data which will be set on the clipboard from being added to From 52ce71413ad8ecbf15856ab60b765c3081b4e59e Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Sat, 19 Oct 2024 10:42:30 +0200 Subject: [PATCH 7/7] fix windows --- src/platform/windows.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 19e1e82..79dca54 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -568,11 +568,15 @@ impl<'clipboard> Get<'clipboard> { } pub(crate) fn html(self) -> Result { + let _clipboard_assertion = self.clipboard?; + let format = clipboard_win::register_format("HTML Format") .ok_or_else(|| Error::unknown("unable to register HTML format"))?; + let mut out: Vec = Vec::new(); clipboard_win::raw::get_html(format.get(), &mut out) .map_err(|_| Error::unknown("failed to read clipboard string"))?; + String::from_utf8(out).map_err(|_| Error::ConversionFailure) }