Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added android support #87

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
14 changes: 9 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
[package]
name = "clipboard"
version = "0.5.0"
authors = ["Avi Weinstock <[email protected]>"]
description = "rust-clipboard is a cross-platform library for getting and setting the contents of the OS-level clipboard."
repository = "https://github.com/aweinstock314/rust-clipboard"
license = "MIT / Apache-2.0"
keywords = ["clipboard"]
license = "MIT / Apache-2.0"
name = "clipboard"
repository = "https://github.com/aweinstock314/rust-clipboard"
version = "0.5.0"

[target.'cfg(windows)'.dependencies]
clipboard-win = "2.1"

[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2"
objc_id = "0.1"
objc-foundation = "0.1"
objc_id = "0.1"

[target.'cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))'.dependencies]
x11-clipboard = "0.3"

[target.'cfg(target_os = "android")'.dependencies]
jni = "0.19"
ndk-glue = {version = "0.6"}
110 changes: 110 additions & 0 deletions src/android_clipboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use common::ClipboardProvider;
use jni::objects::JString;
use std::{error::Error, ffi::CStr};

pub struct AndroidClipboardContext;

impl ClipboardProvider for AndroidClipboardContext {
fn new() -> Result<Self, Box<dyn Error>> {
Ok(AndroidClipboardContext)
}

fn get_contents(&mut self) -> Result<String, Box<dyn Error>> {
let ctx = ndk_glue::native_activity();

let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?;
let env = vm.attach_current_thread()?;
let class_ctxt = env.find_class("android/content/Context")?;
let cb = env.get_static_field(class_ctxt, "CLIPBOARD_SERVICE", "Ljava/lang/String;")?;
let cb_manager = env
.call_method(
ctx.activity(),
"getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;",
&[cb],
)?
.l()
.unwrap();

let clip_data = env
.call_method(
cb_manager,
"getPrimaryClip",
"()Landroid/content/ClipData;",
&[],
)?
.l()
.unwrap();

//return Ok(format!("{:?}", clip_data));

let item = env
.call_method(
clip_data,
"getItemAt",
"(I)Landroid/content/ClipData$Item;",
&[0i32.into()],
)?
.l()
.unwrap();

let char_seq = env
.call_method(item, "getText", "()Ljava/lang/CharSequence;", &[])?
.l()?;

let string = env
.call_method(char_seq, "toString", "()Ljava/lang/String;", &[])?
.l()
.unwrap();

let jstring = JString::from(string.into_inner());

let ptr = env.get_string_utf_chars(jstring)?;
let s;
unsafe {
s = CStr::from_ptr(ptr).to_owned().into_string()?;
}
env.release_string_utf_chars(jstring, ptr)?;
Ok(s)
}

#[allow(deprecated)]
fn set_contents(&mut self, text: String) -> Result<(), Box<dyn Error>> {
let ctx = ndk_glue::native_activity();

let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?;
let env = vm.attach_current_thread()?;
let class_ctxt = env.find_class("android/content/Context")?;
let cb = env.get_static_field(class_ctxt, "CLIPBOARD_SERVICE", "Ljava/lang/String;")?;
let cb_manager = env
.call_method(
ctx.activity(),
"getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;",
&[cb],
)?
.l()?;

let class_clip_data = env.find_class("android/content/ClipData")?;

let clip_data = env.call_static_method(
class_clip_data,
"newPlainText",
"(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;",
&[
env.new_string("text").unwrap().into(),
env.new_string(text).unwrap().into(),
],
)?;

env.call_method(
cb_manager,
"setPrimaryClip",
"(Landroid/content/ClipData;)V",
&[clip_data],
)?
.v()?;

Ok(())
}
}
10 changes: 5 additions & 5 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ limitations under the License.

use std::error::Error;

pub fn err(s: &str) -> Box<Error> {
Box::<Error + Send + Sync>::from(s)
pub fn err(s: &str) -> Box<dyn Error> {
Box::<dyn Error + Send + Sync>::from(s)
}

/// Trait for clipboard access
pub trait ClipboardProvider: Sized {
/// Create a context with which to access the clipboard
// TODO: consider replacing Box<Error> with an associated type?
fn new() -> Result<Self, Box<Error>>;
fn new() -> Result<Self, Box<dyn Error>>;
/// Method to get the clipboard contents as a String
fn get_contents(&mut self) -> Result<String, Box<Error>>;
fn get_contents(&mut self) -> Result<String, Box<dyn Error>>;
/// Method to set the clipboard contents as a String
fn set_contents(&mut self, String) -> Result<(), Box<Error>>;
fn set_contents(&mut self, text: String) -> Result<(), Box<dyn Error>>;
// TODO: come up with some platform-agnostic API for richer types
// than just strings (c.f. issue #31)
}
45 changes: 33 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,43 +19,64 @@ limitations under the License.
#![crate_type = "dylib"]
#![crate_type = "rlib"]

#[cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))]
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))
))]
extern crate x11_clipboard as x11_clipboard_crate;

#[cfg(windows)]
extern crate clipboard_win;

#[cfg(target_os="macos")]
#[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 objc_foundation;
#[cfg(target_os = "macos")]
extern crate objc_id;

mod common;
pub use common::ClipboardProvider;

#[cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))]
#[cfg(target_os = "android")]
extern crate jni;
#[cfg(target_os = "android")]
pub mod android_clipboard;

#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))
))]
pub mod x11_clipboard;

#[cfg(windows)]
pub mod windows_clipboard;

#[cfg(target_os="macos")]
#[cfg(target_os = "macos")]
pub mod osx_clipboard;

pub mod nop_clipboard;

#[cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))]
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))
))]
pub type ClipboardContext = x11_clipboard::X11ClipboardContext;
#[cfg(windows)]
pub type ClipboardContext = windows_clipboard::WindowsClipboardContext;
#[cfg(target_os="macos")]
#[cfg(target_os = "macos")]
pub type ClipboardContext = osx_clipboard::OSXClipboardContext;
#[cfg(target_os="android")]
pub type ClipboardContext = nop_clipboard::NopClipboardContext; // TODO: implement AndroidClipboardContext (see #52)
#[cfg(not(any(unix, windows, target_os="macos", target_os="android", target_os="emscripten")))]
#[cfg(target_os = "android")]
pub type ClipboardContext = android_clipboard::AndroidClipboardContext;
//pub type ClipboardContext = nop_clipboard::NopClipboardContext; // TODO: implement AndroidClipboardContext (see #52)
#[cfg(not(any(
unix,
windows,
target_os = "macos",
target_os = "android",
target_os = "emscripten"
)))]
pub type ClipboardContext = nop_clipboard::NopClipboardContext;

#[test]
Expand Down
18 changes: 11 additions & 7 deletions src/nop_clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,21 @@ use std::error::Error;
pub struct NopClipboardContext;

impl ClipboardProvider for NopClipboardContext {
fn new() -> Result<NopClipboardContext, Box<Error>> {
fn new() -> Result<NopClipboardContext, Box<dyn Error>> {
Ok(NopClipboardContext)
}
fn get_contents(&mut self) -> Result<String, Box<Error>> {
println!("Attempting to get the contents of the clipboard, which hasn't yet been \
implemented on this platform.");
fn get_contents(&mut self) -> Result<String, Box<dyn Error>> {
println!(
"Attempting to get the contents of the clipboard, which hasn't yet been \
implemented on this platform."
);
Ok("".to_string())
}
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.");
fn set_contents(&mut self, _: String) -> Result<(), Box<dyn Error>> {
println!(
"Attempting to set the contents of the clipboard, which hasn't yet been \
implemented on this platform."
);
Ok(())
}
}
26 changes: 16 additions & 10 deletions src/osx_clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ 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::runtime::{Class, Object};
use objc_foundation::{INSArray, INSObject, INSString};
use objc_foundation::{NSArray, NSDictionary, NSObject, NSString};
use objc_id::{Id, Owned};
use std::error::Error;
use std::mem::transmute;
Expand All @@ -31,16 +31,18 @@ pub struct OSXClipboardContext {
extern "C" {}

impl ClipboardProvider for OSXClipboardContext {
fn new() -> Result<OSXClipboardContext, Box<Error>> {
fn new() -> Result<OSXClipboardContext, Box<dyn Error>> {
let cls = try!(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>> {
fn get_contents(&mut self) -> Result<String, Box<dyn Error>> {
let string_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSString")) };
unsafe { transmute(cls) }
Expand All @@ -51,20 +53,24 @@ impl ClipboardProvider for OSXClipboardContext {
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"));
return Err(err(
"pasteboard#readObjectsForClasses:options: returned null",
));
}
Id::from_ptr(obj)
};
if string_array.count() == 0 {
Err(err("pasteboard#readObjectsForClasses:options: returned empty"))
Err(err(
"pasteboard#readObjectsForClasses:options: returned empty",
))
} else {
Ok(string_array[0].as_str().to_owned())
}
}
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 Down
6 changes: 3 additions & 3 deletions src/windows_clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ use std::error::Error;
pub struct WindowsClipboardContext;

impl ClipboardProvider for WindowsClipboardContext {
fn new() -> Result<Self, Box<Error>> {
fn new() -> Result<Self, Box<dyn Error>> {
Ok(WindowsClipboardContext)
}
fn get_contents(&mut self) -> Result<String, Box<Error>> {
fn get_contents(&mut self) -> Result<String, Box<dyn Error>> {
Ok(get_clipboard_string()?)
}
fn set_contents(&mut self, data: String) -> Result<(), Box<Error>> {
fn set_contents(&mut self, data: String) -> Result<(), Box<dyn Error>> {
Ok(set_clipboard_string(&data)?)
}
}