Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ pub fn err(s: &str) -> Box<Error> {
Box::<Error + Send + Sync>::from(s)
}

pub enum ClipboardContent {
Utf8(String),
Tiff(Vec<u8>),
// 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
Expand All @@ -29,6 +38,6 @@ pub trait ClipboardProvider: Sized {
fn get_contents(&mut self) -> Result<String, Box<Error>>;
/// Method to set the clipboard contents as a String
fn set_contents(&mut self, String) -> Result<(), Box<Error>>;
// 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<ClipboardContent, Box<Error>>;
}
6 changes: 6 additions & 0 deletions src/nop_clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ limitations under the License.
*/

use common::ClipboardProvider;
use common::ClipboardContent;
use std::error::Error;

pub struct NopClipboardContext;
Expand All @@ -28,6 +29,11 @@ impl ClipboardProvider for NopClipboardContext {
implemented on this platform.");
Ok("".to_string())
}
fn get_binary_contents(&mut self) -> Result<ClipboardContent, Box<Error>> {
println!("Attempting to get the contents of the clipboard, which hasn't yet been \
implemented on this platform.");
Ok(ClipboardContent::__Nonexhaustive)
}
fn set_contents(&mut self, _: String) -> Result<(), Box<Error>> {
println!("Attempting to set the contents of the clipboard, which hasn't yet been \
implemented on this platform.");
Expand Down
49 changes: 49 additions & 0 deletions src/osx_clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,55 @@ impl ClipboardProvider for OSXClipboardContext {
Ok(string_array[0].as_str().to_owned())
}
}
fn get_binary_contents(&mut self) -> Result<ClipboardContent, Box<Error>> {
let string_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSString")) };
unsafe { transmute(cls) }
};
let image_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSImage")) };
unsafe { transmute(cls) }
};
let url_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSURL")) };
unsafe { transmute(cls) }
};
let classes = vec![url_class, image_class, string_class];
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the more "specific" class needs to come first, as readObjectsForClasses will try to match in order

let classes: Id<NSArray<NSObject, Owned>> = NSArray::from_vec(classes);
let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
let contents: Id<NSArray<NSObject>> = unsafe {
let obj: *mut NSArray<NSObject> =
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 {
Err(err("pasteboard#readObjectsForClasses:options: returned empty"))
} else {
let obj = &contents[0];
if obj.is_kind_of(Class::get("NSString").unwrap()) {
let s: &NSString = unsafe { transmute(obj) };
Ok(ClipboardContent::Utf8(s.as_str().to_owned()))
} else if obj.is_kind_of(Class::get("NSImage").unwrap()) {
let tiff: &NSArray<NSObject> = 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(ClipboardContent::Tiff(vec.into()))
} else if obj.is_kind_of(Class::get("NSURL").unwrap()) {
let s: &NSString = unsafe { msg_send![obj, absoluteString] };
Ok(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<Error>> {
let string_array = NSArray::from_vec(vec![NSString::from_str(&data)]);
let _: usize = unsafe { msg_send![self.pasteboard, clearContents] };
Expand Down