diff --git a/Cargo.toml b/Cargo.toml index c4778da..ef4b34e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ clipboard-win = "2.1" objc = "0.2" objc_id = "0.1" objc-foundation = "0.1" +libc = "0.2" [target.'cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))'.dependencies] x11-clipboard = "0.3" diff --git a/src/lib.rs b/src/lib.rs index de6169b..18aa2f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,10 +28,12 @@ extern crate clipboard_win; #[cfg(target_os="macos")] #[macro_use] extern crate objc; -#[cfg(target_os="macos")] -extern crate objc_id; -#[cfg(target_os="macos")] +#[cfg(target_os = "macos")] +extern crate libc; +#[cfg(target_os = "macos")] extern crate objc_foundation; +#[cfg(target_os = "macos")] +extern crate objc_id; mod common; pub use common::ClipboardProvider; diff --git a/src/osx_clipboard.rs b/src/osx_clipboard.rs index cf8c0b3..77c6ce9 100644 --- a/src/osx_clipboard.rs +++ b/src/osx_clipboard.rs @@ -15,56 +15,53 @@ limitations under the License. */ use common::*; -use objc::runtime::{Object, Class}; -use objc_foundation::{INSArray, INSString, INSObject}; -use objc_foundation::{NSArray, NSDictionary, NSString, NSObject}; -use objc_id::{Id, Owned}; +use objc::runtime::{Class, Object, Sel}; +use objc_foundation::{INSArray, INSString}; +use objc_foundation::{NSArray, NSString}; +use objc_id::Id; use std::error::Error; -use std::mem::transmute; +use std::ffi::CStr; pub struct OSXClipboardContext { pasteboard: Id, } +#[allow(non_upper_case_globals)] +static NSUTF8StringEncoding: usize = 4; //apple documentation says it is 4 + // required to bring NSPasteboard into the path of the class-resolver #[link(name = "AppKit", kind = "framework")] -extern "C" {} +extern "C" { + pub static NSPasteboardTypeString: Sel; +} impl ClipboardProvider for OSXClipboardContext { - fn new() -> Result> { - let cls = try!(Class::get("NSPasteboard").ok_or(err("Class::get(\"NSPasteboard\")"))); + fn new() -> Result> { + let cls = Class::get("NSPasteboard").ok_or(err("Class::get(\"NSPasteboard\")"))?; let pasteboard: *mut Object = unsafe { msg_send![cls, generalPasteboard] }; if pasteboard.is_null() { return Err(err("NSPasteboard#generalPasteboard returned null")); } let pasteboard: Id = unsafe { Id::from_ptr(pasteboard) }; - Ok(OSXClipboardContext { pasteboard: pasteboard }) + Ok(OSXClipboardContext { + pasteboard: pasteboard, + }) } - fn get_contents(&mut self) -> Result> { - let string_class: Id = { - let cls: Id = unsafe { Id::from_ptr(class("NSString")) }; - unsafe { transmute(cls) } - }; - let classes: Id> = NSArray::from_vec(vec![string_class]); - let options: Id> = NSDictionary::new(); - let string_array: 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 string_array.count() == 0 { - Err(err("pasteboard#readObjectsForClasses:options: returned empty")) + fn get_contents(&mut self) -> Result> { + let string: *mut NSString = + unsafe { msg_send![self.pasteboard, stringForType: NSPasteboardTypeString] }; + if string.is_null() { + Err(err("pasteboard#stringForType returned null")) } else { - Ok(string_array[0].as_str().to_owned()) + let res: String = nsstring_to_rust_string(string).unwrap(); + let _: () = unsafe { msg_send![string, release] }; + Ok(res) } } - fn set_contents(&mut self, data: String) -> Result<(), Box> { + 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] }; - let success: bool = unsafe { msg_send![self.pasteboard, writeObjects:string_array] }; + let success: bool = unsafe { msg_send![self.pasteboard, writeObjects: string_array] }; return if success { Ok(()) } else { @@ -73,10 +70,20 @@ impl ClipboardProvider for OSXClipboardContext { } } -// this is a convenience function that both cocoa-rs and -// glutin define, which seems to depend on the fact that -// Option::None has the same representation as a null pointer -#[inline] -pub fn class(name: &str) -> *mut Class { - unsafe { transmute(Class::get(name)) } +fn nsstring_to_rust_string(nsstring: *mut NSString) -> Result> { + unsafe { + let string_size: usize = + msg_send![nsstring, lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; + //we need +1 because getCString will return null terminated string + let char_ptr = libc::malloc(string_size + 1); + let res: bool = msg_send![nsstring, getCString:char_ptr maxLength:string_size + 1 encoding:NSUTF8StringEncoding]; + if res { + let c_string = CStr::from_ptr(char_ptr as *const i8); + libc::free(char_ptr); + Ok(c_string.to_string_lossy().into_owned()) + } else { + libc::free(char_ptr); + Err(err("Casting from NSString to Rust string has failed")) + } + } }