Skip to content

Commit 4b91bfe

Browse files
authored
Implement Get::html() for all platforms (#163)
* implement get html operation Signed-off-by: Gae24 <[email protected]>
1 parent e458e1a commit 4b91bfe

File tree

7 files changed

+92
-24
lines changed

7 files changed

+92
-24
lines changed

examples/set_html.rs examples/set_get_html.rs

+3
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ consectetur adipiscing elit."#;
1515

1616
ctx.set_html(html, Some(alt_text)).unwrap();
1717
thread::sleep(Duration::from_secs(5));
18+
19+
let success = ctx.get().html().unwrap() == html;
20+
println!("Set and Get html operations were successful: {success}");
1821
}

src/lib.rs

+23
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@ impl Get<'_> {
192192
pub fn image(self) -> Result<ImageData<'static>, Error> {
193193
self.platform.image()
194194
}
195+
196+
/// Completes the "get" operation by fetching HTML from the clipboard.
197+
pub fn html(self) -> Result<String, Error> {
198+
self.platform.html()
199+
}
195200
}
196201

197202
/// A builder for an operation that sets a value to the clipboard.
@@ -322,6 +327,24 @@ mod tests {
322327
ctx.set_html(html, Some(alt_text)).unwrap();
323328
assert_eq!(ctx.get_text().unwrap(), alt_text);
324329
}
330+
{
331+
let mut ctx = Clipboard::new().unwrap();
332+
333+
let html = "<b>hello</b> <i>world</i>!";
334+
335+
ctx.set().html(html, None).unwrap();
336+
337+
if cfg!(target_os = "macos") {
338+
// Copying HTML on macOS adds wrapper content to work around
339+
// historical platform bugs. We control this wrapper, so we are
340+
// able to check that the full user data still appears and at what
341+
// position in the final copy contents.
342+
let content = ctx.get().html().unwrap();
343+
assert!(content.ends_with(&format!("{html}</body></html>")));
344+
} else {
345+
assert_eq!(ctx.get().html().unwrap(), html);
346+
}
347+
}
325348
#[cfg(feature = "image-data")]
326349
{
327350
let mut ctx = Clipboard::new().unwrap();

src/platform/linux/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ impl<'clipboard> Get<'clipboard> {
122122
Clipboard::WlDataControl(clipboard) => clipboard.get_image(self.selection),
123123
}
124124
}
125+
126+
pub(crate) fn html(self) -> Result<String, Error> {
127+
match self.clipboard {
128+
Clipboard::X11(clipboard) => clipboard.get_html(self.selection),
129+
#[cfg(feature = "wayland-data-control")]
130+
Clipboard::WlDataControl(clipboard) => clipboard.get_html(self.selection),
131+
}
132+
}
125133
}
126134

127135
/// Linux-specific extensions to the [`Get`](super::Get) builder.

src/platform/linux/wayland.rs

+14-4
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,12 @@ impl Clipboard {
5353
Ok(Self {})
5454
}
5555

56-
pub(crate) fn get_text(&mut self, selection: LinuxClipboardKind) -> Result<String, Error> {
57-
use wl_clipboard_rs::paste::MimeType;
58-
59-
let result = get_contents(selection.try_into()?, Seat::Unspecified, MimeType::Text);
56+
fn string_for_mime(
57+
&mut self,
58+
selection: LinuxClipboardKind,
59+
mime: paste::MimeType,
60+
) -> Result<String, Error> {
61+
let result = get_contents(selection.try_into()?, Seat::Unspecified, mime);
6062
match result {
6163
Ok((mut pipe, _)) => {
6264
let mut contents = vec![];
@@ -74,6 +76,10 @@ impl Clipboard {
7476
}
7577
}
7678

79+
pub(crate) fn get_text(&mut self, selection: LinuxClipboardKind) -> Result<String, Error> {
80+
self.string_for_mime(selection, paste::MimeType::Text)
81+
}
82+
7783
pub(crate) fn set_text(
7884
&self,
7985
text: Cow<'_, str>,
@@ -91,6 +97,10 @@ impl Clipboard {
9197
Ok(())
9298
}
9399

100+
pub(crate) fn get_html(&mut self, selection: LinuxClipboardKind) -> Result<String, Error> {
101+
self.string_for_mime(selection, paste::MimeType::Specific("text/html"))
102+
}
103+
94104
pub(crate) fn set_html(
95105
&self,
96106
html: Cow<'_, str>,

src/platform/linux/x11.rs

+6
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,12 @@ impl Clipboard {
885885
self.inner.write(data, selection, wait)
886886
}
887887

888+
pub(crate) fn get_html(&self, selection: LinuxClipboardKind) -> Result<String> {
889+
let formats = [self.inner.atoms.HTML];
890+
let result = self.inner.read(&formats, selection)?;
891+
String::from_utf8(result.bytes).map_err(|_| Error::ConversionFailure)
892+
}
893+
888894
pub(crate) fn set_html(
889895
&self,
890896
html: Cow<'_, str>,

src/platform/osx.rs

+25-20
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,27 @@ impl Clipboard {
121121
unsafe { self.pasteboard.clearContents() };
122122
}
123123

124+
fn string_from_type(&self, type_: &'static NSString) -> Result<String, Error> {
125+
// XXX: There does not appear to be an alternative for obtaining text without the need for
126+
// autorelease behavior.
127+
autoreleasepool(|_| {
128+
// XXX: We explicitly use `pasteboardItems` and not `stringForType` since the latter will concat
129+
// multiple strings, if present, into one and return it instead of reading just the first which is `arboard`'s
130+
// historical behavior.
131+
let contents = unsafe { self.pasteboard.pasteboardItems() }.ok_or_else(|| {
132+
Error::Unknown { description: String::from("NSPasteboard#pasteboardItems errored") }
133+
})?;
134+
135+
for item in contents {
136+
if let Some(string) = unsafe { item.stringForType(type_) } {
137+
return Ok(string.to_string());
138+
}
139+
}
140+
141+
Err(Error::ContentNotAvailable)
142+
})
143+
}
144+
124145
// fn get_binary_contents(&mut self) -> Result<Option<ClipboardContent>, Box<dyn std::error::Error>> {
125146
// let string_class: Id<NSObject> = {
126147
// let cls: Id<Class> = unsafe { Id::from_ptr(class("NSString")) };
@@ -182,27 +203,11 @@ impl<'clipboard> Get<'clipboard> {
182203
}
183204

184205
pub(crate) fn text(self) -> Result<String, Error> {
185-
// XXX: There does not appear to be an alternative for obtaining text without the need for
186-
// autorelease behavior.
187-
autoreleasepool(|_| {
188-
// XXX: We explicitly use `pasteboardItems` and not `stringForType` since the latter will concat
189-
// multiple strings, if present, into one and return it instead of reading just the first which is `arboard`'s
190-
// historical behavior.
191-
let contents =
192-
unsafe { self.clipboard.pasteboard.pasteboardItems() }.ok_or_else(|| {
193-
Error::Unknown {
194-
description: String::from("NSPasteboard#pasteboardItems errored"),
195-
}
196-
})?;
197-
198-
for item in contents {
199-
if let Some(string) = unsafe { item.stringForType(NSPasteboardTypeString) } {
200-
return Ok(string.to_string());
201-
}
202-
}
206+
unsafe { self.clipboard.string_from_type(NSPasteboardTypeString) }
207+
}
203208

204-
Err(Error::ContentNotAvailable)
205-
})
209+
pub(crate) fn html(self) -> Result<String, Error> {
210+
unsafe { self.clipboard.string_from_type(NSPasteboardTypeHTML) }
206211
}
207212

208213
#[cfg(feature = "image-data")]

src/platform/windows.rs

+13
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,19 @@ impl<'clipboard> Get<'clipboard> {
567567
String::from_utf16(&out[..bytes_read]).map_err(|_| Error::ConversionFailure)
568568
}
569569

570+
pub(crate) fn html(self) -> Result<String, Error> {
571+
let _clipboard_assertion = self.clipboard?;
572+
573+
let format = clipboard_win::register_format("HTML Format")
574+
.ok_or_else(|| Error::unknown("unable to register HTML format"))?;
575+
576+
let mut out: Vec<u8> = Vec::new();
577+
clipboard_win::raw::get_html(format.get(), &mut out)
578+
.map_err(|_| Error::unknown("failed to read clipboard string"))?;
579+
580+
String::from_utf8(out).map_err(|_| Error::ConversionFailure)
581+
}
582+
570583
#[cfg(feature = "image-data")]
571584
pub(crate) fn image(self) -> Result<ImageData<'static>, Error> {
572585
const FORMAT: u32 = clipboard_win::formats::CF_DIBV5;

0 commit comments

Comments
 (0)