diff --git a/src/common.rs b/src/common.rs index 92f80d5..260b562 100644 --- a/src/common.rs +++ b/src/common.rs @@ -20,6 +20,15 @@ pub fn err(s: &str) -> Box { Box::::from(s) } +pub enum ClipboardContent { + Utf8(String), + Tiff(Vec), + // TODO: extend this enum by more types + // Url, RichText, .... + #[doc(hidden)] + __Nonexhaustive, +} + /// Trait for clipboard access pub trait ClipboardProvider: Sized { /// Create a context with which to access the clipboard @@ -29,6 +38,6 @@ pub trait ClipboardProvider: Sized { fn get_contents(&mut self) -> Result>; /// Method to set the clipboard contents as a String fn set_contents(&mut self, String) -> Result<(), Box>; - // TODO: come up with some platform-agnostic API for richer types - // than just strings (c.f. issue #31) + /// Method to get clipboard contents not necessarily string + fn get_binary_contents(&mut self) -> Result, Box>; } diff --git a/src/lib.rs b/src/lib.rs index e0d52ec..a25df66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ extern crate objc_foundation; mod common; pub use common::ClipboardProvider; +pub use common::ClipboardContent; #[cfg(all(unix, not(any(target_os="macos", target_os="android"))))] pub mod x11_clipboard; diff --git a/src/nop_clipboard.rs b/src/nop_clipboard.rs index 75a2913..825b01c 100644 --- a/src/nop_clipboard.rs +++ b/src/nop_clipboard.rs @@ -15,6 +15,7 @@ limitations under the License. */ use common::ClipboardProvider; +use common::ClipboardContent; use std::error::Error; pub struct NopClipboardContext; @@ -28,6 +29,11 @@ impl ClipboardProvider for NopClipboardContext { implemented on this platform."); Ok("".to_string()) } + fn get_binary_contents(&mut self) -> Result, Box> { + println!("Attempting to get the contents of the clipboard, which hasn't yet been \ + implemented on this platform."); + Ok(None) + } fn set_contents(&mut self, _: String) -> Result<(), Box> { println!("Attempting to set the contents of the clipboard, which hasn't yet been \ implemented on this platform."); diff --git a/src/osx_clipboard.rs b/src/osx_clipboard.rs index cf8c0b3..c3e1547 100644 --- a/src/osx_clipboard.rs +++ b/src/osx_clipboard.rs @@ -61,6 +61,55 @@ impl ClipboardProvider for OSXClipboardContext { Ok(string_array[0].as_str().to_owned()) } } + fn get_binary_contents(&mut self) -> Result, Box> { + let string_class: Id = { + let cls: Id = unsafe { Id::from_ptr(class("NSString")) }; + unsafe { transmute(cls) } + }; + let image_class: Id = { + let cls: Id = unsafe { Id::from_ptr(class("NSImage")) }; + unsafe { transmute(cls) } + }; + let url_class: Id = { + let cls: Id = unsafe { Id::from_ptr(class("NSURL")) }; + unsafe { transmute(cls) } + }; + let classes = vec![url_class, image_class, string_class]; + let classes: Id> = NSArray::from_vec(classes); + let options: Id> = NSDictionary::new(); + let contents: Id> = unsafe { + let obj: *mut NSArray = + msg_send![self.pasteboard, readObjectsForClasses:&*classes options:&*options]; + if obj.is_null() { + return Err(err("pasteboard#readObjectsForClasses:options: returned null")); + } + Id::from_ptr(obj) + }; + if contents.count() == 0 { + Ok(None) + } else { + let obj = &contents[0]; + if obj.is_kind_of(Class::get("NSString").unwrap()) { + let s: &NSString = unsafe { transmute(obj) }; + Ok(Some(ClipboardContent::Utf8(s.as_str().to_owned()))) + } else if obj.is_kind_of(Class::get("NSImage").unwrap()) { + let tiff: &NSArray = unsafe { msg_send![obj, TIFFRepresentation] }; + let len: usize = unsafe { msg_send![tiff, length] }; + let bytes: *const u8 = unsafe { msg_send![tiff, bytes] }; + let vec = unsafe { std::slice::from_raw_parts(bytes, len) }; + // Here we copy the entire &[u8] into a new owned `Vec` + // Is there another way that doesn't copy multiple megabytes? + Ok(Some(ClipboardContent::Tiff(vec.into()))) + } else if obj.is_kind_of(Class::get("NSURL").unwrap()) { + let s: &NSString = unsafe { msg_send![obj, absoluteString] }; + Ok(Some(ClipboardContent::Utf8(s.as_str().to_owned()))) + } else { + // let cls: &Class = unsafe { msg_send![obj, class] }; + // println!("{}", cls.name()); + Err(err("pasteboard#readObjectsForClasses:options: returned unknown class")) + } + } + } fn set_contents(&mut self, data: String) -> Result<(), Box> { let string_array = NSArray::from_vec(vec![NSString::from_str(&data)]); let _: usize = unsafe { msg_send![self.pasteboard, clearContents] };