From ac714e65b4a89a5248baf7b0bcf90ed5e99936f6 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Thu, 26 Dec 2024 21:54:36 +0100 Subject: [PATCH 01/14] Android CI build. --- .github/workflows/test.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 27adbf9b..022653f9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -98,3 +98,24 @@ jobs: - uses: actions/checkout@v4 - name: Check semver uses: obi1kenobi/cargo-semver-checks-action@v2 + + android: + needs: clippy + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: actions-rust/setup-rust@v1 + with: + toolchain: stable + override: true + - name: Setup Java JDK + uses: actions/setup-java@v4.5.0 + with: + java-version: '17' + distribution: 'temurin' + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + - name: Install Cargo NDK + run: cargo install cargo-ndk + - name: Run tests + run: cargo ndk --target aarch64-linux-android build From f5edaec023893831f809711abe5fb9116164e0a3 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Thu, 26 Dec 2024 23:02:01 +0100 Subject: [PATCH 02/14] skeleton Android mod --- src/platform/android.rs | 66 +++++++++++++++++++++++++++++++++++++++++ src/platform/mod.rs | 5 ++++ 2 files changed, 71 insertions(+) create mode 100644 src/platform/android.rs diff --git a/src/platform/android.rs b/src/platform/android.rs new file mode 100644 index 00000000..81a96257 --- /dev/null +++ b/src/platform/android.rs @@ -0,0 +1,66 @@ +use std::borrow::Cow; + +use crate::{Error, ImageData}; + +pub(crate) struct Clipboard {} + +impl Clipboard { + pub(crate) fn new() -> Result { + Ok(Self {}) + } +} + +pub(crate) struct Get<'clipboard> { + clipboard: &'clipboard Clipboard, +} + +impl<'clipboard> Get<'clipboard> { + pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { + Self { clipboard } + } + + pub(crate) fn text(self) -> Result { + Err(Error::ClipboardNotSupported) + } + + pub(crate) fn image(self) -> Result, Error> { + Err(Error::ClipboardNotSupported) + } +} + +pub(crate) struct Set<'clipboard> { + clipboard: &'clipboard mut Clipboard, +} + +impl<'clipboard> Set<'clipboard> { + pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { + Self { clipboard } + } + + pub(crate) fn text(self, _: Cow<'_, str>) -> Result<(), Error> { + Err(Error::ClipboardNotSupported) + } + + pub(crate) fn html(self, _: Cow<'_, str>, _: Option>) -> Result<(), Error> { + Err(Error::ClipboardNotSupported) + } + + #[cfg(feature = "image-data")] + pub(crate) fn image(self, _: ImageData) -> Result<(), Error> { + Err(Error::ClipboardNotSupported) + } +} + +pub(crate) struct Clear<'clipboard> { + clipboard: &'clipboard mut Clipboard, +} + +impl<'clipboard> Clear<'clipboard> { + pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { + Self { clipboard } + } + + pub(crate) fn clear(self) -> Result<(), Error> { + Err(Error::ClipboardNotSupported) + } +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 268eb47e..38d130e6 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -15,3 +15,8 @@ pub use windows::*; mod osx; #[cfg(target_os = "macos")] pub use osx::*; + +#[cfg(target_os = "android")] +mod android; +#[cfg(target_os = "android")] +pub use android::*; From ef30aa9402347041a3f571a6b600869fe9292246 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 10:41:11 +0100 Subject: [PATCH 03/14] Do not pub use anything as there is nothing to pub use --- src/platform/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 38d130e6..f36574a7 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -18,5 +18,3 @@ pub use osx::*; #[cfg(target_os = "android")] mod android; -#[cfg(target_os = "android")] -pub use android::*; From 3b46069199cfcd49a3dd5172acb030986aa6f25c Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 11:12:09 +0100 Subject: [PATCH 04/14] Fix visibility --- src/platform/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/mod.rs b/src/platform/mod.rs index f36574a7..8625b59a 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -18,3 +18,4 @@ pub use osx::*; #[cfg(target_os = "android")] mod android; +pub(crate) use android::*; From ad697efe958017d8b84adf14731dcc38a948a867 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 11:21:32 +0100 Subject: [PATCH 05/14] Add Android dependencies. --- Cargo.lock | 173 +++++++++++++++++++++++++++++++++++++++++++++++------ Cargo.toml | 4 ++ 2 files changed, 159 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97bdca1d..07c3205d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,12 +25,14 @@ dependencies = [ "core-graphics", "env_logger", "image", + "jni", "log", + "ndk-context", "objc2", "objc2-app-kit", "objc2-foundation", "parking_lot", - "windows-sys", + "windows-sys 0.48.0", "wl-clipboard-rs", "x11rb", ] @@ -80,12 +82,24 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.0" @@ -101,6 +115,16 @@ dependencies = [ "error-code", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -197,7 +221,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -278,7 +302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ "libc", - "windows-targets", + "windows-targets 0.48.0", ] [[package]] @@ -330,9 +354,31 @@ checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb" dependencies = [ "hermit-abi", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jpeg-decoder" version = "0.3.1" @@ -358,7 +404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -413,6 +459,12 @@ dependencies = [ "adler", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "nix" version = "0.26.4" @@ -513,7 +565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ae859aa07428ca9a929b936690f8b12dc5f11dd8c6992a18ca93919f28bc177" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -536,7 +588,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.0", ] [[package]] @@ -636,7 +688,16 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", ] [[package]] @@ -689,7 +750,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -752,6 +813,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wayland-backend" version = "0.3.2" @@ -837,7 +908,16 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] [[package]] @@ -846,7 +926,22 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -855,51 +950,93 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index bbce191e..29d9386a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,10 @@ wl-clipboard-rs = { version = "0.8", optional = true } image = { version = "0.25", optional = true, default-features = false, features = ["png"] } parking_lot = "0.12" +[target.'cfg(target_os = "android")'.dependencies] +ndk-context = "0.1" +jni = "0.21" + [[example]] name = "get_image" required-features = ["image-data"] From 18a44da0c855f27057fe962bf1ff78306e96a7cc Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 11:44:06 +0100 Subject: [PATCH 06/14] Implement getting text. --- src/platform/android.rs | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/platform/android.rs b/src/platform/android.rs index 81a96257..442cd238 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -1,7 +1,15 @@ use std::borrow::Cow; +use jni::objects::{JObject, JString}; + use crate::{Error, ImageData}; +impl From for Error { + fn from(error: jni::errors::Error) -> Self { + Error::Unknown { description: error.to_string() } + } +} + pub(crate) struct Clipboard {} impl Clipboard { @@ -20,7 +28,36 @@ impl<'clipboard> Get<'clipboard> { } pub(crate) fn text(self) -> Result { - Err(Error::ClipboardNotSupported) + let ctx = ndk_context::android_context(); + let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }.unwrap(); + let context = unsafe { JObject::from_raw(ctx.context().cast()) }; + let mut env = vm.attach_current_thread().unwrap(); + + let clipboard = env.new_string("clipboard")?; + + let clipboard_manager = env + .call_method( + context, + "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;", + &[(&clipboard).into()], + )? + .l()?; + + let clip = env + .call_method(clipboard_manager, "getPrimaryClip", "()Landroid/content/ClipData;", &[])? + .l()?; + + let item = env + .call_method(clip, "getItemAt", "(I)Landroid/content/ClipData$Item;", &[0.into()])? + .l()?; + + let text = env.call_method(item, "getText", "()Ljava/lang/CharSequence;", &[])?.l()?; + + let text = JString::from(text); + let text = env.get_string(&text)?; + + Ok(text.into()) } pub(crate) fn image(self) -> Result, Error> { From f74072fe8b9e2589efe8550eb04826d67a101076 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 18:40:48 +0100 Subject: [PATCH 07/14] setting text --- src/platform/android.rs | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/platform/android.rs b/src/platform/android.rs index 442cd238..0d1a6e8b 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -74,8 +74,41 @@ impl<'clipboard> Set<'clipboard> { Self { clipboard } } - pub(crate) fn text(self, _: Cow<'_, str>) -> Result<(), Error> { - Err(Error::ClipboardNotSupported) + pub(crate) fn text(self, text: Cow<'_, str>) -> Result<(), Error> { + let ctx = ndk_context::android_context(); + let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }.unwrap(); + let context = unsafe { JObject::from_raw(ctx.context().cast()) }; + let mut env = vm.attach_current_thread().unwrap(); + + let clipboard = env.new_string("clipboard")?; + + let clipboard_manager = env + .call_method( + context, + "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;", + &[(&clipboard).into()], + )? + .l()?; + + let label = env.new_string("label")?; + let text = env.new_string(text)?; + + let clip_data = env.call_static_method( + "android/content/ClipData", + "newPlainText", + "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;", + &[(&label).into(), (&text).into()], + )?; + + env.call_method( + clipboard_manager, + "setPrimaryClip", + "(Landroid/content/ClipData;)V", + &[(&clip_data).into()], + )?; + + Ok(()) } pub(crate) fn html(self, _: Cow<'_, str>, _: Option>) -> Result<(), Error> { From 26522274688a487433078ba639c7eb5d5288fc2e Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 18:51:43 +0100 Subject: [PATCH 08/14] clear clipboard --- src/platform/android.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/platform/android.rs b/src/platform/android.rs index 0d1a6e8b..31d0955d 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -131,6 +131,24 @@ impl<'clipboard> Clear<'clipboard> { } pub(crate) fn clear(self) -> Result<(), Error> { - Err(Error::ClipboardNotSupported) + let ctx = ndk_context::android_context(); + let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }.unwrap(); + let context = unsafe { JObject::from_raw(ctx.context().cast()) }; + let mut env = vm.attach_current_thread().unwrap(); + + let clipboard = env.new_string("clipboard")?; + + let clipboard_manager = env + .call_method( + context, + "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;", + &[(&clipboard).into()], + )? + .l()?; + + env.call_method(clipboard_manager, "clearPrimaryClip", "()V", &[])?; + + Ok(()) } } From 9891c718a42576dbd6ae4e456f1800ef3211a4bb Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 21:39:42 +0100 Subject: [PATCH 09/14] helper functions --- src/platform/android.rs | 42 ++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/platform/android.rs b/src/platform/android.rs index 31d0955d..b7185233 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -1,6 +1,9 @@ use std::borrow::Cow; -use jni::objects::{JObject, JString}; +use jni::{ + objects::{JObject, JString}, + JavaVM, +}; use crate::{Error, ImageData}; @@ -10,11 +13,23 @@ impl From for Error { } } -pub(crate) struct Clipboard {} +pub(crate) struct Clipboard { + ctx: ndk_context::AndroidContext, +} impl Clipboard { pub(crate) fn new() -> Result { - Ok(Self {}) + Ok(Self { ctx: ndk_context::android_context() }) + } + + fn vm(&self) -> Result { + // SAFETY: Valid pointer guaranteed by the `ndk_context` crate. + unsafe { jni::JavaVM::from_raw(self.ctx.vm().cast()) } + } + + fn context(&self) -> JObject { + // SAFETY: Valid pointer guaranteed by the `ndk_context` crate. + unsafe { JObject::from_raw(self.ctx.context().cast()) } } } @@ -28,10 +43,9 @@ impl<'clipboard> Get<'clipboard> { } pub(crate) fn text(self) -> Result { - let ctx = ndk_context::android_context(); - let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }.unwrap(); - let context = unsafe { JObject::from_raw(ctx.context().cast()) }; - let mut env = vm.attach_current_thread().unwrap(); + let vm = self.clipboard.vm()?; + let context = self.clipboard.context(); + let mut env = vm.attach_current_thread()?; let clipboard = env.new_string("clipboard")?; @@ -75,10 +89,9 @@ impl<'clipboard> Set<'clipboard> { } pub(crate) fn text(self, text: Cow<'_, str>) -> Result<(), Error> { - let ctx = ndk_context::android_context(); - let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }.unwrap(); - let context = unsafe { JObject::from_raw(ctx.context().cast()) }; - let mut env = vm.attach_current_thread().unwrap(); + let vm = self.clipboard.vm()?; + let context = self.clipboard.context(); + let mut env = vm.attach_current_thread()?; let clipboard = env.new_string("clipboard")?; @@ -131,10 +144,9 @@ impl<'clipboard> Clear<'clipboard> { } pub(crate) fn clear(self) -> Result<(), Error> { - let ctx = ndk_context::android_context(); - let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }.unwrap(); - let context = unsafe { JObject::from_raw(ctx.context().cast()) }; - let mut env = vm.attach_current_thread().unwrap(); + let vm = self.clipboard.vm()?; + let context = self.clipboard.context(); + let mut env = vm.attach_current_thread()?; let clipboard = env.new_string("clipboard")?; From c72f2d0261d85d054796e9d17bddc57d4c889669 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 21:51:45 +0100 Subject: [PATCH 10/14] clipboard_manager fn --- src/platform/android.rs | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/platform/android.rs b/src/platform/android.rs index b7185233..0a47b130 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use jni::{ objects::{JObject, JString}, - JavaVM, + AttachGuard, JavaVM, }; use crate::{Error, ImageData}; @@ -31,6 +31,23 @@ impl Clipboard { // SAFETY: Valid pointer guaranteed by the `ndk_context` crate. unsafe { JObject::from_raw(self.ctx.context().cast()) } } + + fn clipboard_manager<'attachment>( + &self, + env: &mut AttachGuard<'attachment>, + ) -> Result, Error> { + let context = self.context(); + let clipboard = env.new_string("clipboard")?; + + Ok(env + .call_method( + context, + "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;", + &[(&clipboard).into()], + )? + .l()?) + } } pub(crate) struct Get<'clipboard> { @@ -44,19 +61,8 @@ impl<'clipboard> Get<'clipboard> { pub(crate) fn text(self) -> Result { let vm = self.clipboard.vm()?; - let context = self.clipboard.context(); let mut env = vm.attach_current_thread()?; - - let clipboard = env.new_string("clipboard")?; - - let clipboard_manager = env - .call_method( - context, - "getSystemService", - "(Ljava/lang/String;)Ljava/lang/Object;", - &[(&clipboard).into()], - )? - .l()?; + let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; let clip = env .call_method(clipboard_manager, "getPrimaryClip", "()Landroid/content/ClipData;", &[])? @@ -67,10 +73,8 @@ impl<'clipboard> Get<'clipboard> { .l()?; let text = env.call_method(item, "getText", "()Ljava/lang/CharSequence;", &[])?.l()?; - let text = JString::from(text); let text = env.get_string(&text)?; - Ok(text.into()) } From c4391bb77c37cada173964bb53bb767956fb4612 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 21:52:49 +0100 Subject: [PATCH 11/14] use clipboard_manager fn --- src/platform/android.rs | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/src/platform/android.rs b/src/platform/android.rs index 0a47b130..9f659026 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -94,19 +94,8 @@ impl<'clipboard> Set<'clipboard> { pub(crate) fn text(self, text: Cow<'_, str>) -> Result<(), Error> { let vm = self.clipboard.vm()?; - let context = self.clipboard.context(); let mut env = vm.attach_current_thread()?; - - let clipboard = env.new_string("clipboard")?; - - let clipboard_manager = env - .call_method( - context, - "getSystemService", - "(Ljava/lang/String;)Ljava/lang/Object;", - &[(&clipboard).into()], - )? - .l()?; + let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; let label = env.new_string("label")?; let text = env.new_string(text)?; @@ -149,19 +138,8 @@ impl<'clipboard> Clear<'clipboard> { pub(crate) fn clear(self) -> Result<(), Error> { let vm = self.clipboard.vm()?; - let context = self.clipboard.context(); let mut env = vm.attach_current_thread()?; - - let clipboard = env.new_string("clipboard")?; - - let clipboard_manager = env - .call_method( - context, - "getSystemService", - "(Ljava/lang/String;)Ljava/lang/Object;", - &[(&clipboard).into()], - )? - .l()?; + let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; env.call_method(clipboard_manager, "clearPrimaryClip", "()V", &[])?; From 6d55bee907b1afca483a7746cfc98fac4a50ae0a Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 21:54:30 +0100 Subject: [PATCH 12/14] convert to spaces --- src/platform/android.rs | 224 ++++++++++++++++++++-------------------- 1 file changed, 112 insertions(+), 112 deletions(-) diff --git a/src/platform/android.rs b/src/platform/android.rs index 9f659026..a15e5992 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -1,148 +1,148 @@ use std::borrow::Cow; use jni::{ - objects::{JObject, JString}, - AttachGuard, JavaVM, + objects::{JObject, JString}, + AttachGuard, JavaVM, }; use crate::{Error, ImageData}; impl From for Error { - fn from(error: jni::errors::Error) -> Self { - Error::Unknown { description: error.to_string() } - } + fn from(error: jni::errors::Error) -> Self { + Error::Unknown { description: error.to_string() } + } } pub(crate) struct Clipboard { - ctx: ndk_context::AndroidContext, + ctx: ndk_context::AndroidContext, } impl Clipboard { - pub(crate) fn new() -> Result { - Ok(Self { ctx: ndk_context::android_context() }) - } - - fn vm(&self) -> Result { - // SAFETY: Valid pointer guaranteed by the `ndk_context` crate. - unsafe { jni::JavaVM::from_raw(self.ctx.vm().cast()) } - } - - fn context(&self) -> JObject { - // SAFETY: Valid pointer guaranteed by the `ndk_context` crate. - unsafe { JObject::from_raw(self.ctx.context().cast()) } - } - - fn clipboard_manager<'attachment>( - &self, - env: &mut AttachGuard<'attachment>, - ) -> Result, Error> { - let context = self.context(); - let clipboard = env.new_string("clipboard")?; - - Ok(env - .call_method( - context, - "getSystemService", - "(Ljava/lang/String;)Ljava/lang/Object;", - &[(&clipboard).into()], - )? - .l()?) - } + pub(crate) fn new() -> Result { + Ok(Self { ctx: ndk_context::android_context() }) + } + + fn vm(&self) -> Result { + // SAFETY: Valid pointer guaranteed by the `ndk_context` crate. + unsafe { jni::JavaVM::from_raw(self.ctx.vm().cast()) } + } + + fn context(&self) -> JObject { + // SAFETY: Valid pointer guaranteed by the `ndk_context` crate. + unsafe { JObject::from_raw(self.ctx.context().cast()) } + } + + fn clipboard_manager<'attachment>( + &self, + env: &mut AttachGuard<'attachment>, + ) -> Result, Error> { + let context = self.context(); + let clipboard = env.new_string("clipboard")?; + + Ok(env + .call_method( + context, + "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;", + &[(&clipboard).into()], + )? + .l()?) + } } pub(crate) struct Get<'clipboard> { - clipboard: &'clipboard Clipboard, + clipboard: &'clipboard Clipboard, } impl<'clipboard> Get<'clipboard> { - pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { - Self { clipboard } - } - - pub(crate) fn text(self) -> Result { - let vm = self.clipboard.vm()?; - let mut env = vm.attach_current_thread()?; - let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; - - let clip = env - .call_method(clipboard_manager, "getPrimaryClip", "()Landroid/content/ClipData;", &[])? - .l()?; - - let item = env - .call_method(clip, "getItemAt", "(I)Landroid/content/ClipData$Item;", &[0.into()])? - .l()?; - - let text = env.call_method(item, "getText", "()Ljava/lang/CharSequence;", &[])?.l()?; - let text = JString::from(text); - let text = env.get_string(&text)?; - Ok(text.into()) - } - - pub(crate) fn image(self) -> Result, Error> { - Err(Error::ClipboardNotSupported) - } + pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { + Self { clipboard } + } + + pub(crate) fn text(self) -> Result { + let vm = self.clipboard.vm()?; + let mut env = vm.attach_current_thread()?; + let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; + + let clip = env + .call_method(clipboard_manager, "getPrimaryClip", "()Landroid/content/ClipData;", &[])? + .l()?; + + let item = env + .call_method(clip, "getItemAt", "(I)Landroid/content/ClipData$Item;", &[0.into()])? + .l()?; + + let text = env.call_method(item, "getText", "()Ljava/lang/CharSequence;", &[])?.l()?; + let text = JString::from(text); + let text = env.get_string(&text)?; + Ok(text.into()) + } + + pub(crate) fn image(self) -> Result, Error> { + Err(Error::ClipboardNotSupported) + } } pub(crate) struct Set<'clipboard> { - clipboard: &'clipboard mut Clipboard, + clipboard: &'clipboard mut Clipboard, } impl<'clipboard> Set<'clipboard> { - pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { - Self { clipboard } - } - - pub(crate) fn text(self, text: Cow<'_, str>) -> Result<(), Error> { - let vm = self.clipboard.vm()?; - let mut env = vm.attach_current_thread()?; - let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; - - let label = env.new_string("label")?; - let text = env.new_string(text)?; - - let clip_data = env.call_static_method( - "android/content/ClipData", - "newPlainText", - "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;", - &[(&label).into(), (&text).into()], - )?; - - env.call_method( - clipboard_manager, - "setPrimaryClip", - "(Landroid/content/ClipData;)V", - &[(&clip_data).into()], - )?; - - Ok(()) - } - - pub(crate) fn html(self, _: Cow<'_, str>, _: Option>) -> Result<(), Error> { - Err(Error::ClipboardNotSupported) - } - - #[cfg(feature = "image-data")] - pub(crate) fn image(self, _: ImageData) -> Result<(), Error> { - Err(Error::ClipboardNotSupported) - } + pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { + Self { clipboard } + } + + pub(crate) fn text(self, text: Cow<'_, str>) -> Result<(), Error> { + let vm = self.clipboard.vm()?; + let mut env = vm.attach_current_thread()?; + let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; + + let label = env.new_string("label")?; + let text = env.new_string(text)?; + + let clip_data = env.call_static_method( + "android/content/ClipData", + "newPlainText", + "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;", + &[(&label).into(), (&text).into()], + )?; + + env.call_method( + clipboard_manager, + "setPrimaryClip", + "(Landroid/content/ClipData;)V", + &[(&clip_data).into()], + )?; + + Ok(()) + } + + pub(crate) fn html(self, _: Cow<'_, str>, _: Option>) -> Result<(), Error> { + Err(Error::ClipboardNotSupported) + } + + #[cfg(feature = "image-data")] + pub(crate) fn image(self, _: ImageData) -> Result<(), Error> { + Err(Error::ClipboardNotSupported) + } } pub(crate) struct Clear<'clipboard> { - clipboard: &'clipboard mut Clipboard, + clipboard: &'clipboard mut Clipboard, } impl<'clipboard> Clear<'clipboard> { - pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { - Self { clipboard } - } + pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { + Self { clipboard } + } - pub(crate) fn clear(self) -> Result<(), Error> { - let vm = self.clipboard.vm()?; - let mut env = vm.attach_current_thread()?; - let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; + pub(crate) fn clear(self) -> Result<(), Error> { + let vm = self.clipboard.vm()?; + let mut env = vm.attach_current_thread()?; + let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; - env.call_method(clipboard_manager, "clearPrimaryClip", "()V", &[])?; + env.call_method(clipboard_manager, "clearPrimaryClip", "()V", &[])?; - Ok(()) - } + Ok(()) + } } From e8ef9bb9d1ee4d9c3d195e72b01420b08eb51c8a Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 22:04:05 +0100 Subject: [PATCH 13/14] Fix warnings. --- src/common.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common.rs b/src/common.rs index ab00b9c7..a4568a3d 100644 --- a/src/common.rs +++ b/src/common.rs @@ -150,12 +150,12 @@ impl ImageData<'_> { } } -#[cfg(any(windows, all(unix, not(target_os = "macos"))))] +#[cfg(any(windows, all(unix, not(any(target_os = "macos", target_os = "android")))))] pub(crate) struct ScopeGuard { callback: Option, } -#[cfg(any(windows, all(unix, not(target_os = "macos"))))] +#[cfg(any(windows, all(unix, not(any(target_os = "macos", target_os = "android")))))] impl ScopeGuard { #[cfg_attr(all(windows, not(feature = "image-data")), allow(dead_code))] pub(crate) fn new(callback: F) -> Self { @@ -163,7 +163,7 @@ impl ScopeGuard { } } -#[cfg(any(windows, all(unix, not(target_os = "macos"))))] +#[cfg(any(windows, all(unix, not(any(target_os = "macos", target_os = "android")))))] impl Drop for ScopeGuard { fn drop(&mut self) { if let Some(callback) = self.callback.take() { @@ -173,6 +173,7 @@ impl Drop for ScopeGuard { } /// Common trait for sealing platform extension traits. +#[cfg(not(target_os = "android"))] pub(crate) mod private { pub trait Sealed {} From 76fa105f1c7623ceb9cb08436dda1563f5401c3a Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 22:51:17 +0100 Subject: [PATCH 14/14] better error handling --- src/platform/android.rs | 232 +++++++++++++++++++++------------------- 1 file changed, 120 insertions(+), 112 deletions(-) diff --git a/src/platform/android.rs b/src/platform/android.rs index a15e5992..486d13bd 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -1,148 +1,156 @@ use std::borrow::Cow; use jni::{ - objects::{JObject, JString}, - AttachGuard, JavaVM, + objects::{JObject, JString}, + AttachGuard, JavaVM, }; use crate::{Error, ImageData}; impl From for Error { - fn from(error: jni::errors::Error) -> Self { - Error::Unknown { description: error.to_string() } - } + fn from(error: jni::errors::Error) -> Self { + Error::Unknown { description: error.to_string() } + } } pub(crate) struct Clipboard { - ctx: ndk_context::AndroidContext, + ctx: ndk_context::AndroidContext, } impl Clipboard { - pub(crate) fn new() -> Result { - Ok(Self { ctx: ndk_context::android_context() }) - } - - fn vm(&self) -> Result { - // SAFETY: Valid pointer guaranteed by the `ndk_context` crate. - unsafe { jni::JavaVM::from_raw(self.ctx.vm().cast()) } - } - - fn context(&self) -> JObject { - // SAFETY: Valid pointer guaranteed by the `ndk_context` crate. - unsafe { JObject::from_raw(self.ctx.context().cast()) } - } - - fn clipboard_manager<'attachment>( - &self, - env: &mut AttachGuard<'attachment>, - ) -> Result, Error> { - let context = self.context(); - let clipboard = env.new_string("clipboard")?; - - Ok(env - .call_method( - context, - "getSystemService", - "(Ljava/lang/String;)Ljava/lang/Object;", - &[(&clipboard).into()], - )? - .l()?) - } + pub(crate) fn new() -> Result { + Ok(Self { ctx: ndk_context::android_context() }) + } + + fn vm(&self) -> Result { + // SAFETY: Valid pointer guaranteed by the `ndk_context` crate. + unsafe { jni::JavaVM::from_raw(self.ctx.vm().cast()) } + } + + fn context(&self) -> JObject { + // SAFETY: Valid pointer guaranteed by the `ndk_context` crate. + unsafe { JObject::from_raw(self.ctx.context().cast()) } + } + + fn clipboard_manager<'attachment>( + &self, + env: &mut AttachGuard<'attachment>, + ) -> Result, Error> { + let context = self.context(); + let clipboard = env.new_string("clipboard")?; + + Ok(env + .call_method( + context, + "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;", + &[(&clipboard).into()], + )? + .l()?) + } } pub(crate) struct Get<'clipboard> { - clipboard: &'clipboard Clipboard, + clipboard: &'clipboard Clipboard, } impl<'clipboard> Get<'clipboard> { - pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { - Self { clipboard } - } - - pub(crate) fn text(self) -> Result { - let vm = self.clipboard.vm()?; - let mut env = vm.attach_current_thread()?; - let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; - - let clip = env - .call_method(clipboard_manager, "getPrimaryClip", "()Landroid/content/ClipData;", &[])? - .l()?; - - let item = env - .call_method(clip, "getItemAt", "(I)Landroid/content/ClipData$Item;", &[0.into()])? - .l()?; - - let text = env.call_method(item, "getText", "()Ljava/lang/CharSequence;", &[])?.l()?; - let text = JString::from(text); - let text = env.get_string(&text)?; - Ok(text.into()) - } - - pub(crate) fn image(self) -> Result, Error> { - Err(Error::ClipboardNotSupported) - } + pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { + Self { clipboard } + } + + pub(crate) fn text(self) -> Result { + let vm = self.clipboard.vm()?; + let mut env = vm.attach_current_thread()?; + let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; + + if !env.call_method(&clipboard_manager, "hasPrimaryClip", "()Z", &[])?.z()? { + return Err(Error::ContentNotAvailable); + } + + let clip = env + .call_method(clipboard_manager, "getPrimaryClip", "()Landroid/content/ClipData;", &[])? + .l()?; + + if env.call_method(&clip, "getItemCount", "()I", &[])?.i()? == 0 { + return Err(Error::ContentNotAvailable); + } + + let item = env + .call_method(&clip, "getItemAt", "(I)Landroid/content/ClipData$Item;", &[0.into()])? + .l()?; + + let text = env.call_method(item, "getText", "()Ljava/lang/CharSequence;", &[])?.l()?; + let text = JString::from(text); + let text = env.get_string(&text)?; + Ok(text.into()) + } + + pub(crate) fn image(self) -> Result, Error> { + Err(Error::ClipboardNotSupported) + } } pub(crate) struct Set<'clipboard> { - clipboard: &'clipboard mut Clipboard, + clipboard: &'clipboard mut Clipboard, } impl<'clipboard> Set<'clipboard> { - pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { - Self { clipboard } - } - - pub(crate) fn text(self, text: Cow<'_, str>) -> Result<(), Error> { - let vm = self.clipboard.vm()?; - let mut env = vm.attach_current_thread()?; - let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; - - let label = env.new_string("label")?; - let text = env.new_string(text)?; - - let clip_data = env.call_static_method( - "android/content/ClipData", - "newPlainText", - "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;", - &[(&label).into(), (&text).into()], - )?; - - env.call_method( - clipboard_manager, - "setPrimaryClip", - "(Landroid/content/ClipData;)V", - &[(&clip_data).into()], - )?; - - Ok(()) - } - - pub(crate) fn html(self, _: Cow<'_, str>, _: Option>) -> Result<(), Error> { - Err(Error::ClipboardNotSupported) - } - - #[cfg(feature = "image-data")] - pub(crate) fn image(self, _: ImageData) -> Result<(), Error> { - Err(Error::ClipboardNotSupported) - } + pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { + Self { clipboard } + } + + pub(crate) fn text(self, text: Cow<'_, str>) -> Result<(), Error> { + let vm = self.clipboard.vm()?; + let mut env = vm.attach_current_thread()?; + let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; + + let label = env.new_string("label")?; + let text = env.new_string(text)?; + + let clip_data = env.call_static_method( + "android/content/ClipData", + "newPlainText", + "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;", + &[(&label).into(), (&text).into()], + )?; + + env.call_method( + clipboard_manager, + "setPrimaryClip", + "(Landroid/content/ClipData;)V", + &[(&clip_data).into()], + )?; + + Ok(()) + } + + pub(crate) fn html(self, _: Cow<'_, str>, _: Option>) -> Result<(), Error> { + Err(Error::ClipboardNotSupported) + } + + #[cfg(feature = "image-data")] + pub(crate) fn image(self, _: ImageData) -> Result<(), Error> { + Err(Error::ClipboardNotSupported) + } } pub(crate) struct Clear<'clipboard> { - clipboard: &'clipboard mut Clipboard, + clipboard: &'clipboard mut Clipboard, } impl<'clipboard> Clear<'clipboard> { - pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { - Self { clipboard } - } + pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { + Self { clipboard } + } - pub(crate) fn clear(self) -> Result<(), Error> { - let vm = self.clipboard.vm()?; - let mut env = vm.attach_current_thread()?; - let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; + pub(crate) fn clear(self) -> Result<(), Error> { + let vm = self.clipboard.vm()?; + let mut env = vm.attach_current_thread()?; + let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?; - env.call_method(clipboard_manager, "clearPrimaryClip", "()V", &[])?; + env.call_method(clipboard_manager, "clearPrimaryClip", "()V", &[])?; - Ok(()) - } + Ok(()) + } }