From b2b23a7be3576653725e89b4b0d8fd0b9996cf66 Mon Sep 17 00:00:00 2001 From: FancyFlame Date: Sat, 30 Apr 2022 00:40:25 +0800 Subject: [PATCH 1/2] add android support --- Cargo.toml | 14 ++++--- src/android_clipboard.rs | 91 ++++++++++++++++++++++++++++++++++++++++ src/common.rs | 10 ++--- src/lib.rs | 45 ++++++++++++++------ src/nop_clipboard.rs | 18 ++++---- src/osx_clipboard.rs | 26 +++++++----- src/windows_clipboard.rs | 6 +-- 7 files changed, 168 insertions(+), 42 deletions(-) create mode 100644 src/android_clipboard.rs diff --git a/Cargo.toml b/Cargo.toml index c4778da..9eee65c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,23 @@ [package] -name = "clipboard" -version = "0.5.0" authors = ["Avi Weinstock "] 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"} diff --git a/src/android_clipboard.rs b/src/android_clipboard.rs new file mode 100644 index 0000000..bba9c91 --- /dev/null +++ b/src/android_clipboard.rs @@ -0,0 +1,91 @@ +use common::ClipboardProvider; +use jni::objects::JString; +use std::{error::Error, ffi::CStr}; + +pub struct AndroidClipboardContext; + +impl ClipboardProvider for AndroidClipboardContext { + fn new() -> Result> { + Ok(AndroidClipboardContext) + } + + fn get_contents(&mut self) -> Result> { + 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 clip_data = env + .call_method( + cb_manager, + "getPrimaryClip", + "()Landroid/content/ClipData;", + &[], + )? + .l()?; + + let string = env + .call_method(clip_data, "toString", "()Ljava/lang/String;", &[])? + .l()?; + + 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> { + 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(()) + } +} diff --git a/src/common.rs b/src/common.rs index 92f80d5..fe3695a 100644 --- a/src/common.rs +++ b/src/common.rs @@ -16,19 +16,19 @@ limitations under the License. use std::error::Error; -pub fn err(s: &str) -> Box { - Box::::from(s) +pub fn err(s: &str) -> Box { + Box::::from(s) } /// Trait for clipboard access pub trait ClipboardProvider: Sized { /// Create a context with which to access the clipboard // TODO: consider replacing Box with an associated type? - fn new() -> Result>; + fn new() -> Result>; /// Method to get the clipboard contents as a String - fn get_contents(&mut self) -> Result>; + fn get_contents(&mut self) -> Result>; /// Method to set the clipboard contents as a String - fn set_contents(&mut self, String) -> Result<(), Box>; + fn set_contents(&mut self, text: String) -> Result<(), Box>; // TODO: come up with some platform-agnostic API for richer types // than just strings (c.f. issue #31) } diff --git a/src/lib.rs b/src/lib.rs index de6169b..31830a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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] diff --git a/src/nop_clipboard.rs b/src/nop_clipboard.rs index 75a2913..04b31bb 100644 --- a/src/nop_clipboard.rs +++ b/src/nop_clipboard.rs @@ -20,17 +20,21 @@ use std::error::Error; pub struct NopClipboardContext; impl ClipboardProvider for NopClipboardContext { - fn new() -> Result> { + fn new() -> Result> { Ok(NopClipboardContext) } - fn get_contents(&mut self) -> Result> { - println!("Attempting to get the contents of the clipboard, which hasn't yet been \ - implemented on this platform."); + fn get_contents(&mut self) -> Result> { + 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> { - 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> { + println!( + "Attempting to set the contents of the clipboard, which hasn't yet been \ + implemented on this platform." + ); Ok(()) } } diff --git a/src/osx_clipboard.rs b/src/osx_clipboard.rs index cf8c0b3..ccd1b68 100644 --- a/src/osx_clipboard.rs +++ b/src/osx_clipboard.rs @@ -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; @@ -31,16 +31,18 @@ pub struct OSXClipboardContext { extern "C" {} impl ClipboardProvider for OSXClipboardContext { - fn new() -> Result> { + fn new() -> Result> { 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 = unsafe { Id::from_ptr(pasteboard) }; - Ok(OSXClipboardContext { pasteboard: pasteboard }) + Ok(OSXClipboardContext { + pasteboard: pasteboard, + }) } - fn get_contents(&mut self) -> Result> { + fn get_contents(&mut self) -> Result> { let string_class: Id = { let cls: Id = unsafe { Id::from_ptr(class("NSString")) }; unsafe { transmute(cls) } @@ -51,20 +53,24 @@ impl ClipboardProvider for OSXClipboardContext { let obj: *mut NSArray = 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> { + 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 { diff --git a/src/windows_clipboard.rs b/src/windows_clipboard.rs index f534407..9cd4bc3 100644 --- a/src/windows_clipboard.rs +++ b/src/windows_clipboard.rs @@ -22,13 +22,13 @@ use std::error::Error; pub struct WindowsClipboardContext; impl ClipboardProvider for WindowsClipboardContext { - fn new() -> Result> { + fn new() -> Result> { Ok(WindowsClipboardContext) } - fn get_contents(&mut self) -> Result> { + fn get_contents(&mut self) -> Result> { Ok(get_clipboard_string()?) } - fn set_contents(&mut self, data: String) -> Result<(), Box> { + fn set_contents(&mut self, data: String) -> Result<(), Box> { Ok(set_clipboard_string(&data)?) } } From dc66211702bef5324447f25b678a8a65f4485358 Mon Sep 17 00:00:00 2001 From: FancyFlame Date: Sat, 30 Apr 2022 12:13:17 +0800 Subject: [PATCH 2/2] changed get_content method for android --- src/android_clipboard.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/android_clipboard.rs b/src/android_clipboard.rs index bba9c91..d89ae81 100644 --- a/src/android_clipboard.rs +++ b/src/android_clipboard.rs @@ -23,7 +23,8 @@ impl ClipboardProvider for AndroidClipboardContext { "(Ljava/lang/String;)Ljava/lang/Object;", &[cb], )? - .l()?; + .l() + .unwrap(); let clip_data = env .call_method( @@ -32,11 +33,29 @@ impl ClipboardProvider for AndroidClipboardContext { "()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(clip_data, "toString", "()Ljava/lang/String;", &[])? - .l()?; + .call_method(char_seq, "toString", "()Ljava/lang/String;", &[])? + .l() + .unwrap(); let jstring = JString::from(string.into_inner());