Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
8 changes: 5 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
77 changes: 42 additions & 35 deletions src/osx_clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object>,
}

#[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<OSXClipboardContext, Box<Error>> {
let cls = try!(Class::get("NSPasteboard").ok_or(err("Class::get(\"NSPasteboard\")")));
fn new() -> Result<OSXClipboardContext, Box<dyn Error>> {
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<Object> = unsafe { Id::from_ptr(pasteboard) };
Ok(OSXClipboardContext { pasteboard: pasteboard })
Ok(OSXClipboardContext {
pasteboard: pasteboard,
})
}
fn get_contents(&mut self) -> Result<String, Box<Error>> {
let string_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSString")) };
unsafe { transmute(cls) }
};
let classes: Id<NSArray<NSObject, Owned>> = NSArray::from_vec(vec![string_class]);
let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
let string_array: Id<NSArray<NSString>> = unsafe {
let obj: *mut NSArray<NSString> =
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<String, Box<dyn Error>> {
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<Error>> {
fn set_contents(&mut self, data: String) -> Result<(), Box<dyn Error>> {
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 {
Expand All @@ -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<String, Box<dyn Error>> {
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"))
}
}
}