From c704b44d28677d8ee50dffa187401352412587eb Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Thu, 26 Dec 2024 21:54:36 +0100 Subject: [PATCH 01/22] 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 9284f8e7..ebe92b1b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -104,3 +104,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 ca90d72cde1e498a71baff4f554dca762786c525 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Thu, 26 Dec 2024 23:02:01 +0100 Subject: [PATCH 02/22] 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 2c90bfc298ac24f6080cc2b65fa4b4debe9c86e4 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 10:41:11 +0100 Subject: [PATCH 03/22] 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 9604bba2cd6a868f7d97e2626a7ada6fe4c91e86 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 11:12:09 +0100 Subject: [PATCH 04/22] 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 e033630964dc2675e674c617ad9e140affee97c8 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 11:21:32 +0100 Subject: [PATCH 05/22] Add Android dependencies. --- Cargo.lock | 191 ++++++++++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 4 ++ 2 files changed, 178 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23127c45..6b0cb5c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,9 @@ dependencies = [ "clipboard-win", "env_logger", "image", + "jni", "log", + "ndk-context", "objc2", "objc2-app-kit", "objc2-core-foundation", @@ -32,7 +34,7 @@ dependencies = [ "objc2-foundation", "parking_lot", "percent-encoding", - "windows-sys", + "windows-sys 0.52.0", "wl-clipboard-rs", "x11rb", ] @@ -67,12 +69,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" @@ -88,6 +102,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 = "crc32fast" version = "1.3.2" @@ -123,7 +147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -213,9 +237,31 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.52.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 1.0.69", + "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" @@ -271,6 +317,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 = "nom" version = "7.1.1" @@ -374,7 +426,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -397,7 +449,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -497,7 +549,16 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.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]] @@ -532,13 +593,33 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -582,6 +663,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.12" @@ -664,7 +755,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -673,13 +764,37 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[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]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[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]] @@ -688,28 +803,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[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.52.6" @@ -722,24 +855,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[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.52.6" @@ -756,7 +913,7 @@ dependencies = [ "log", "os_pipe", "rustix", - "thiserror", + "thiserror 2.0.18", "tree_magic_mini", "wayland-backend", "wayland-client", diff --git a/Cargo.toml b/Cargo.toml index 07abd4f0..8378aeb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,10 @@ image = { version = "0.25", optional = true, default-features = false, features parking_lot = "0.12" percent-encoding = "2.3.1" +[target.'cfg(target_os = "android")'.dependencies] +ndk-context = "0.1" +jni = "0.21" + [[example]] name = "get_image" required-features = ["image-data"] From c3c5753d40c23e5eef1f029eb61ecbb285a46949 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 11:44:06 +0100 Subject: [PATCH 06/22] 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 524448a1de5c9c87d36aed7e0a48587457cc5714 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 18:40:48 +0100 Subject: [PATCH 07/22] 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 7c3b688f8b0f8695d9286d41296443474b195107 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 18:51:43 +0100 Subject: [PATCH 08/22] 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 a07f0b8b138b8cf90e97b27cf23ca1ab01749230 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 21:39:42 +0100 Subject: [PATCH 09/22] 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 08406aff2823fddd7d9597d34c338d71b42637df Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 21:51:45 +0100 Subject: [PATCH 10/22] 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 8e26daa6f50d91e9500f19157408c7849af54003 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 21:52:49 +0100 Subject: [PATCH 11/22] 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 37d6abfaca52262ce89fc27df9f9d26a6e0f7c99 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 21:54:30 +0100 Subject: [PATCH 12/22] 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 3759058607b34a36f66847e162d9fc035a1c0c31 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 22:04:05 +0100 Subject: [PATCH 13/22] Fix warnings. --- src/common.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common.rs b/src/common.rs index b5e877ba..3539568b 100644 --- a/src/common.rs +++ b/src/common.rs @@ -149,12 +149,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 { @@ -162,7 +162,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() { @@ -172,6 +172,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 d5c5a86b9d696530c2c9679bc59c8e8fadebdf8b Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Fri, 27 Dec 2024 22:51:17 +0100 Subject: [PATCH 14/22] 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(()) + } } From b41d70158c63b693edc8988107537b33e6992151 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Fri, 12 Sep 2025 20:48:31 +0200 Subject: [PATCH 15/22] fix compilation --- src/platform/android.rs | 22 ++++++++++++++++++++-- src/platform/mod.rs | 1 + 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/platform/android.rs b/src/platform/android.rs index 486d13bd..2f4da533 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -1,11 +1,16 @@ -use std::borrow::Cow; +use std::{ + borrow::Cow, + path::{Path, PathBuf}, +}; use jni::{ objects::{JObject, JString}, AttachGuard, JavaVM, }; -use crate::{Error, ImageData}; +#[cfg(feature = "image-data")] +use crate::common::ImageData; +use crate::Error; impl From for Error { fn from(error: jni::errors::Error) -> Self { @@ -86,9 +91,18 @@ impl<'clipboard> Get<'clipboard> { Ok(text.into()) } + pub(crate) fn html(self) -> Result { + Err(Error::ClipboardNotSupported) + } + + #[cfg(feature = "image-data")] pub(crate) fn image(self) -> Result, Error> { Err(Error::ClipboardNotSupported) } + + pub(crate) fn file_list(self) -> Result, Error> { + Err(Error::ClipboardNotSupported) + } } pub(crate) struct Set<'clipboard> { @@ -133,6 +147,10 @@ impl<'clipboard> Set<'clipboard> { pub(crate) fn image(self, _: ImageData) -> Result<(), Error> { Err(Error::ClipboardNotSupported) } + + pub(crate) fn file_list(self, _: &[impl AsRef]) -> Result<(), Error> { + Err(Error::ClipboardNotSupported) + } } pub(crate) struct Clear<'clipboard> { diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 8625b59a..5180a704 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -18,4 +18,5 @@ pub use osx::*; #[cfg(target_os = "android")] mod android; +#[cfg(target_os = "android")] pub(crate) use android::*; From 8e33708e33a961336945ee8d7b464cbb480ddf67 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Fri, 12 Sep 2025 19:12:22 +0200 Subject: [PATCH 16/22] android: rework implementation, bump MSRV don't store context inside clipboard, adapt code to the latest jni changes --- .github/workflows/test.yml | 2 +- Cargo.lock | 155 ++++++++++------------------------- Cargo.toml | 4 +- src/common.rs | 1 + src/platform/android.rs | 160 ++++++++++++++++++------------------- 5 files changed, 128 insertions(+), 194 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ebe92b1b..8863f191 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: os: [macos-latest, windows-latest, ubuntu-latest] # Latest stable and MSRV. We only run checks with all features enabled # for the MSRV build to keep CI fast, since other configurations should also work. - rust_version: [stable, "1.71.0"] + rust_version: [stable, "1.77.0"] steps: - uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/Cargo.lock b/Cargo.lock index 6b0cb5c3..d727738a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,7 +34,7 @@ dependencies = [ "objc2-foundation", "parking_lot", "percent-encoding", - "windows-sys 0.52.0", + "windows-sys", "wl-clipboard-rs", "x11rb", ] @@ -147,7 +147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -237,30 +237,43 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] name = "jni" version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +source = "git+https://github.com/jni-rs/jni-rs.git?rev=cdca9ea0d41a1d167841dc9e2b161f7f957b2e51#cdca9ea0d41a1d167841dc9e2b161f7f957b2e51" dependencies = [ "cesu8", "cfg-if", "combine", "jni-sys", "log", - "thiserror 1.0.69", + "once_cell", + "paste", + "thiserror", "walkdir", - "windows-sys 0.45.0", ] [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "jpeg-decoder" @@ -415,9 +428,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.15.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "os_pipe" @@ -426,7 +439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -449,9 +462,15 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -549,7 +568,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -593,33 +612,13 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -755,7 +754,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -764,37 +763,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[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]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[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", + "windows-targets", ] [[package]] @@ -803,46 +778,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[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.52.6" @@ -855,48 +812,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[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.52.6" @@ -913,7 +846,7 @@ dependencies = [ "log", "os_pipe", "rustix", - "thiserror 2.0.18", + "thiserror", "tree_magic_mini", "wayland-backend", "wayland-client", diff --git a/Cargo.toml b/Cargo.toml index 8378aeb7..6eb96c50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" readme = "README.md" keywords = ["clipboard", "image"] edition = "2021" -rust-version = "1.71.0" +rust-version = "1.77.0" [features] default = ["image-data"] @@ -89,7 +89,7 @@ percent-encoding = "2.3.1" [target.'cfg(target_os = "android")'.dependencies] ndk-context = "0.1" -jni = "0.21" +jni = { git = "https://github.com/jni-rs/jni-rs.git", rev = "cdca9ea0d41a1d167841dc9e2b161f7f957b2e51" } [[example]] name = "get_image" diff --git a/src/common.rs b/src/common.rs index 3539568b..2c41c607 100644 --- a/src/common.rs +++ b/src/common.rs @@ -90,6 +90,7 @@ impl std::fmt::Debug for Error { } impl Error { + #[cfg(not(target_os = "android"))] pub(crate) fn unknown>(message: M) -> Self { Error::Unknown { description: message.into() } } diff --git a/src/platform/android.rs b/src/platform/android.rs index 2f4da533..e9d7db8a 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -5,7 +5,7 @@ use std::{ use jni::{ objects::{JObject, JString}, - AttachGuard, JavaVM, + Env, JavaVM, }; #[cfg(feature = "image-data")] @@ -18,40 +18,36 @@ impl From for Error { } } -pub(crate) struct Clipboard { - 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 with_clipboard_access(callback: F) -> Result +where + F: FnOnce(&mut Env, JObject) -> Result, +{ + let ctx = ndk_context::android_context(); - fn context(&self) -> JObject { - // SAFETY: Valid pointer guaranteed by the `ndk_context` crate. - unsafe { JObject::from_raw(self.ctx.context().cast()) } - } + let jvm = unsafe { JavaVM::from_raw(ctx.vm().cast()) }; - fn clipboard_manager<'attachment>( - &self, - env: &mut AttachGuard<'attachment>, - ) -> Result, Error> { - let context = self.context(); + jvm.attach_current_thread(|env| { + let context = unsafe { JObject::from_raw(env, ctx.context().cast()) }; let clipboard = env.new_string("clipboard")?; - Ok(env + let clipboard_manager = env .call_method( context, - "getSystemService", - "(Ljava/lang/String;)Ljava/lang/Object;", + c"getSystemService", + c"(Ljava/lang/String;)Ljava/lang/Object;", &[(&clipboard).into()], )? - .l()?) + .l()?; + + callback(env, clipboard_manager) + }) +} + +pub(crate) struct Clipboard(()); + +impl Clipboard { + pub(crate) fn new() -> Result { + Ok(Self(())) } } @@ -65,30 +61,39 @@ impl<'clipboard> Get<'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()) + with_clipboard_access(|env, clipboard_manager| { + if !env.call_method(&clipboard_manager, c"hasPrimaryClip", c"()Z", &[])?.z()? { + return Err(Error::ContentNotAvailable); + } + + let clip = env + .call_method( + clipboard_manager, + c"getPrimaryClip", + c"()Landroid/content/ClipData;", + &[], + )? + .l()?; + + if env.call_method(&clip, c"getItemCount", c"()I", &[])?.i()? == 0 { + return Err(Error::ContentNotAvailable); + } + + let item = env + .call_method( + &clip, + c"getItemAt", + c"(I)Landroid/content/ClipData$Item;", + &[0.into()], + )? + .l()?; + + let char_sequence = + env.call_method(item, c"getText", c"()Ljava/lang/CharSequence;", &[])?.l()?; + let text = env.cast_local::(char_sequence)?.to_string(); + + Ok(text) + }) } pub(crate) fn html(self) -> Result { @@ -115,28 +120,26 @@ impl<'clipboard> Set<'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(()) + with_clipboard_access(|env, clipboard_manager| { + let label = env.new_string("label")?; + let text = env.new_string(text)?; + + let clip_data = env.call_static_method( + c"android/content/ClipData", + c"newPlainText", + c"(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;", + &[(&label).into(), (&text).into()], + )?; + + env.call_method( + clipboard_manager, + c"setPrimaryClip", + c"(Landroid/content/ClipData;)V", + &[(&clip_data).into()], + )?; + + Ok(()) + }) } pub(crate) fn html(self, _: Cow<'_, str>, _: Option>) -> Result<(), Error> { @@ -163,12 +166,9 @@ impl<'clipboard> Clear<'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)?; - - env.call_method(clipboard_manager, "clearPrimaryClip", "()V", &[])?; - - Ok(()) + with_clipboard_access(|env, clipboard_manager| { + env.call_method(clipboard_manager, c"clearPrimaryClip", c"()V", &[])?; + Ok(()) + }) } } From 8cdfefdb628f7cfe17f52a64721b00c79236154e Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Thu, 27 Mar 2025 10:29:12 +0100 Subject: [PATCH 17/22] Android CI workflow --- .github/workflows/test.yml | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8863f191..8bdabc6a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,6 +34,10 @@ jobs: components: clippy - uses: actions/checkout@v4 + - name: Add Android target + if: ${{ matrix.os == 'ubuntu-latest' }} + run: rustup target add aarch64-linux-android + - name: Run `cargo clippy` with no features if: ${{ matrix.rust_version == 'stable' }} run: cargo clippy --verbose --no-default-features -- -D warnings -D clippy::dbg_macro @@ -48,6 +52,15 @@ jobs: - name: Run `cargo clippy` with all features run: cargo clippy --verbose --all-features -- -D warnings -D clippy::dbg_macro + + - name: Run `cargo clippy` with no features on Android + if: ${{ matrix.os == 'ubuntu-latest' }} + run: cargo clippy --target aarch64-linux-android --verbose --no-default-features -- -D warnings -D clippy::dbg_macro + + - name: Run `cargo clippy` with `image-data` feature on Android + if: ${{ matrix.os == 'ubuntu-latest' }} + run: cargo clippy --target aarch64-linux-android --verbose --no-default-features --features image-data -- -D warnings -D clippy::dbg_macro + - name: Run `cargo clippy` with dependency version checks if: ${{ matrix.rust_version == 'stable' }} @@ -104,24 +117,3 @@ 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 0cdbdc2bfece0e7e237b24d4faf7b0949b4a9347 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:46:30 +0200 Subject: [PATCH 18/22] android: suppress dead code warnings --- src/platform/android.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/platform/android.rs b/src/platform/android.rs index e9d7db8a..ccdd6c36 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -52,12 +52,12 @@ impl Clipboard { } 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 new(_clipboard: &'clipboard mut Clipboard) -> Self { + Self { _clipboard } } pub(crate) fn text(self) -> Result { @@ -111,12 +111,12 @@ impl<'clipboard> Get<'clipboard> { } 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 new(_clipboard: &'clipboard mut Clipboard) -> Self { + Self { _clipboard } } pub(crate) fn text(self, text: Cow<'_, str>) -> Result<(), Error> { @@ -157,12 +157,12 @@ impl<'clipboard> Set<'clipboard> { } 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> { From a63eeee9ad8cd50f869aa103c43ba32b47d60931 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Mon, 15 Sep 2025 16:22:46 +0200 Subject: [PATCH 19/22] windows: fix clippy, bump miri --- .github/workflows/test.yml | 2 +- src/platform/windows.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8bdabc6a..3aa785c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -102,7 +102,7 @@ jobs: steps: - uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: nightly-2023-10-08 + toolchain: nightly-2024-01-08 components: miri - name: Checkout diff --git a/src/platform/windows.rs b/src/platform/windows.rs index cb115982..93e9fd5f 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -747,10 +747,9 @@ impl<'clipboard> Set<'clipboard> { let paths: Vec<_> = file_list .iter() .filter_map(|path| { - to_final_path_wide(path.as_ref()).map(|wide| { + to_final_path_wide(path.as_ref()).inspect(|wide| { // Windows uses wchar_t which is 16 bit data_len += wide.len() * std::mem::size_of::(); - wide }) }) .collect(); From 26f718bd8a92c6ff6add5490f8d04083ce666294 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Sun, 27 Jul 2025 21:23:06 +0200 Subject: [PATCH 20/22] android: add example and run it with emulator in CI --- .github/workflows/test.yml | 60 +++++- Cargo.lock | 380 ++++++++++++++++++++++++++++++++++--- Cargo.toml | 12 ++ examples/android.rs | 99 ++++++++++ tools/android_test.sh | 32 ++++ 5 files changed, 555 insertions(+), 28 deletions(-) create mode 100644 examples/android.rs create mode 100755 tools/android_test.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3aa785c6..c1d890b3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,7 +36,7 @@ jobs: - name: Add Android target if: ${{ matrix.os == 'ubuntu-latest' }} - run: rustup target add aarch64-linux-android + run: rustup target add x86_64-linux-android - name: Run `cargo clippy` with no features if: ${{ matrix.rust_version == 'stable' }} @@ -55,12 +55,11 @@ jobs: - name: Run `cargo clippy` with no features on Android if: ${{ matrix.os == 'ubuntu-latest' }} - run: cargo clippy --target aarch64-linux-android --verbose --no-default-features -- -D warnings -D clippy::dbg_macro + run: cargo clippy --target x86_64-linux-android --verbose --no-default-features -- -D warnings -D clippy::dbg_macro - name: Run `cargo clippy` with `image-data` feature on Android if: ${{ matrix.os == 'ubuntu-latest' }} - run: cargo clippy --target aarch64-linux-android --verbose --no-default-features --features image-data -- -D warnings -D clippy::dbg_macro - + run: cargo clippy --target x86_64-linux-android --verbose --no-default-features --features image-data -- -D warnings -D clippy::dbg_macro - name: Run `cargo clippy` with dependency version checks if: ${{ matrix.rust_version == 'stable' }} @@ -117,3 +116,56 @@ jobs: - uses: actions/checkout@v4 - name: Check semver uses: obi1kenobi/cargo-semver-checks-action@v2 + + android_emulator: + needs: clippy + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Install `cargo-apk` + run: cargo install cargo-apk + + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Gradle cache + uses: gradle/actions/setup-gradle@v3 + + - name: AVD cache + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-29 + + - name: create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 29 + arch: x86_64 + target: default + profile: Nexus 6 + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." + + - name: Run android example + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 29 + force-avd-creation: false + arch: x86_64 + target: default + profile: Nexus 6 + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: ./tools/android_test.sh diff --git a/Cargo.lock b/Cargo.lock index d727738a..06e6a7e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,16 +17,46 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.8.0", + "cc", + "cesu8", + "jni 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", + "jni-sys 0.3.0", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + [[package]] name = "arboard" version = "3.6.1" dependencies = [ + "android-activity", "clipboard-win", "env_logger", "image", - "jni", + "jni 0.21.1 (git+https://github.com/jni-rs/jni-rs.git?rev=cdca9ea0d41a1d167841dc9e2b161f7f957b2e51)", "log", + "ndk", "ndk-context", + "ndk-sys", "objc2", "objc2-app-kit", "objc2-core-foundation", @@ -34,7 +64,7 @@ dependencies = [ "objc2-foundation", "parking_lot", "percent-encoding", - "windows-sys", + "windows-sys 0.52.0", "wl-clipboard-rs", "x11rb", ] @@ -80,6 +110,9 @@ name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] [[package]] name = "cesu8" @@ -140,6 +173,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.13" @@ -147,7 +186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -188,12 +227,30 @@ dependencies = [ "windows-link", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "hermit-abi" version = "0.4.0" @@ -226,7 +283,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", ] [[package]] @@ -237,7 +304,23 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.52.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 0.3.0", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", ] [[package]] @@ -248,14 +331,20 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.4.1", "log", "once_cell", "paste", - "thiserror", + "thiserror 2.0.18", "walkdir", ] +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jni-sys" version = "0.4.1" @@ -275,6 +364,16 @@ dependencies = [ "syn", ] +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + [[package]] name = "jpeg-decoder" version = "0.3.1" @@ -311,9 +410,9 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "minimal-lexical" @@ -330,12 +429,36 @@ dependencies = [ "adler", ] +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.8.0", + "jni-sys 0.3.0", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + [[package]] name = "ndk-context" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.0", +] + [[package]] name = "nom" version = "7.1.1" @@ -355,6 +478,28 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "objc2" version = "0.6.0" @@ -439,7 +584,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -462,7 +607,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -484,7 +629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.9.1", ] [[package]] @@ -505,6 +650,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -532,6 +686,18 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + [[package]] name = "redox_syscall" version = "0.5.8" @@ -568,9 +734,15 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "same-file" version = "1.0.6" @@ -586,6 +758,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "smallvec" version = "1.9.0" @@ -612,13 +804,33 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -643,6 +855,36 @@ dependencies = [ "weezl", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap 2.11.4", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + [[package]] name = "tree_magic_mini" version = "3.1.6" @@ -672,6 +914,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wayland-backend" version = "0.3.12" @@ -754,7 +1005,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -763,13 +1014,37 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[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]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[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]] @@ -778,28 +1053,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[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.52.6" @@ -812,30 +1105,69 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + [[package]] name = "wl-clipboard-rs" version = "0.9.3" @@ -846,7 +1178,7 @@ dependencies = [ "log", "os_pipe", "rustix", - "thiserror", + "thiserror 2.0.18", "tree_magic_mini", "wayland-backend", "wayland-client", diff --git a/Cargo.toml b/Cargo.toml index 6eb96c50..e42b99ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,3 +98,15 @@ required-features = ["image-data"] [[example]] name = "set_image" required-features = ["image-data"] + +[[example]] +name = "android" +crate-type = ["cdylib"] + +[target.'cfg(target_os = "android")'.dev-dependencies] +android-activity = { version = "0.6.0", features = [ "native-activity" ] } +ndk = "0.9" +ndk-sys = "0.6" + +[package.metadata.android.sdk] +target_sdk_version = 29 \ No newline at end of file diff --git a/examples/android.rs b/examples/android.rs new file mode 100644 index 00000000..1b38cc14 --- /dev/null +++ b/examples/android.rs @@ -0,0 +1,99 @@ +#[no_mangle] +#[cfg(target_os = "android")] +fn android_main(app: android_activity::AndroidApp) { + use android_activity::{MainEvent, PollEvent}; + use arboard::Clipboard; + + println!("app started"); + + let mut quit = false; + let mut redraw_pending = true; + let mut native_window: Option = None; + + let text = "hello world"; + + let mut ctx = Clipboard::new().unwrap(); + + assert!( + ctx.set_text(text).is_ok(), + "We can write to the clipboard even if we don't have focus" + ); + + assert!(ctx.get_text().is_err(), "We can't read the clipboard if we don't have focus"); + + while !quit { + app.poll_events(Some(std::time::Duration::from_secs(1)) /* timeout */, |event| { + match event { + PollEvent::Wake => {} + PollEvent::Timeout => { + redraw_pending = true; + } + PollEvent::Main(main_event) => { + match main_event { + MainEvent::InitWindow { .. } => { + native_window = app.native_window(); + redraw_pending = true; + } + MainEvent::TerminateWindow { .. } => { + native_window = None; + } + MainEvent::WindowResized { .. } => { + redraw_pending = true; + } + MainEvent::RedrawNeeded { .. } => { + redraw_pending = true; + } + MainEvent::InputAvailable { .. } => { + redraw_pending = true; + } + MainEvent::GainedFocus => { + assert_eq!( + ctx.get_text().unwrap(), + text, + "Since we have focus we can access the clipboard" + ); + + ctx.clear().unwrap(); + assert!(ctx.get_text().is_err()); + + quit = true; + } + _ => { /* ... */ } + } + } + _ => {} + } + + if redraw_pending { + if let Some(native_window) = &native_window { + redraw_pending = false; + + dummy_render(native_window); + } + } + }); + } +} + +/// Post a NOP frame to the window +/// +/// Since this is a bare minimum test app we don't depend +/// on any GPU graphics APIs but we do need to at least +/// convince Android that we're drawing something and are +/// responsive, otherwise it will stop delivering input +/// events to us. +#[cfg(target_os = "android")] +fn dummy_render(native_window: &ndk::native_window::NativeWindow) { + unsafe { + let mut buf: ndk_sys::ANativeWindow_Buffer = std::mem::zeroed(); + let mut rect: ndk_sys::ARect = std::mem::zeroed(); + ndk_sys::ANativeWindow_lock( + native_window.ptr().as_ptr() as _, + &mut buf as _, + &mut rect as _, + ); + // Note: we don't try and touch the buffer since that + // also requires us to handle various buffer formats + ndk_sys::ANativeWindow_unlockAndPost(native_window.ptr().as_ptr() as _); + } +} diff --git a/tools/android_test.sh b/tools/android_test.sh new file mode 100755 index 00000000..dd978b38 --- /dev/null +++ b/tools/android_test.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# This script is used in CI to run a sample Android app +# and verify arboard implementation for the platform. + +# Make sure the package is removed since it may end up in the AVD cache. This causes +# INSTALL_FAILED_UPDATE_INCOMPATIBLE errors when the debug keystore is regenerated, +# as it is not stored/cached on the CI +adb uninstall rust.example.android || true + +cargo apk run --target x86_64-linux-android --example android --no-logcat + +sleep 30 + +adb logcat *:I -d > ~/logcat.log + +if grep 'app started' ~/logcat.log; +then + echo "App running" +else + echo "::error::App not running" + exit 1 +fi + +ERROR_MSG=$(grep -e "thread '.*' panicked at" ~/logcat.log) +if [ -z "${ERROR_MSG}" ]; +then + exit 0 +else + echo "::error::${ERROR_MSG}" + exit 1 +fi From fa1455e471c705e6360d997f74be99ba2b861420 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:51:07 +0200 Subject: [PATCH 21/22] android: check build version --- src/platform/android.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/platform/android.rs b/src/platform/android.rs index ccdd6c36..1596bec6 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -47,7 +47,17 @@ pub(crate) struct Clipboard(()); impl Clipboard { pub(crate) fn new() -> Result { - Ok(Self(())) + with_clipboard_access(|env, _| { + let version_class = env.find_class(c"android/os/Build$VERSION")?; + let build_sdk = env.get_static_field(version_class, c"SDK_INT", c"I")?.i()?; + + // clearPrimaryClip was introduced in this version + if build_sdk >= 28 { + Ok(Self(())) + } else { + Err(Error::ClipboardNotSupported) + } + }) } } From f63f381656af0f403d5de9b52762d6092443d2c1 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Thu, 19 Feb 2026 19:09:04 +0100 Subject: [PATCH 22/22] update to jni 0.22 Signed-off-by: Gae24 <96017547+Gae24@users.noreply.github.com> --- Cargo.lock | 41 +++++++++++++++++++++++++++++----- Cargo.toml | 2 +- src/platform/android.rs | 49 ++++++++++++++++++++++++++--------------- 3 files changed, 68 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06e6a7e1..9c8cef2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,7 @@ dependencies = [ "bitflags 2.8.0", "cc", "cesu8", - "jni 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", + "jni 0.21.1", "jni-sys 0.3.0", "libc", "log", @@ -52,7 +52,7 @@ dependencies = [ "clipboard-win", "env_logger", "image", - "jni 0.21.1 (git+https://github.com/jni-rs/jni-rs.git?rev=cdca9ea0d41a1d167841dc9e2b161f7f957b2e51)", + "jni 0.22.0", "log", "ndk", "ndk-context", @@ -325,18 +325,34 @@ dependencies = [ [[package]] name = "jni" -version = "0.21.1" -source = "git+https://github.com/jni-rs/jni-rs.git?rev=cdca9ea0d41a1d167841dc9e2b161f7f957b2e51#cdca9ea0d41a1d167841dc9e2b161f7f957b2e51" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051ac7b04986c689b0c2e136bd26f992509d4d3e10aff8557ca002d5de7eae39" dependencies = [ "cesu8", "cfg-if", "combine", + "jni-macros", "jni-sys 0.4.1", "log", - "once_cell", "paste", "thiserror 2.0.18", "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf8534090fdcb2891c0b24a46db1a11154747c83f59efd732d8fb75a7b2e48c" +dependencies = [ + "cesu8", + "proc-macro-crate", + "proc-macro2", + "quote", + "rustc_version", + "syn", ] [[package]] @@ -724,6 +740,15 @@ version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.3" @@ -758,6 +783,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde_core" version = "1.0.228" diff --git a/Cargo.toml b/Cargo.toml index e42b99ea..651f71ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,7 +89,7 @@ percent-encoding = "2.3.1" [target.'cfg(target_os = "android")'.dependencies] ndk-context = "0.1" -jni = { git = "https://github.com/jni-rs/jni-rs.git", rev = "cdca9ea0d41a1d167841dc9e2b161f7f957b2e51" } +jni = "0.22" [[example]] name = "get_image" diff --git a/src/platform/android.rs b/src/platform/android.rs index 1596bec6..eae50c9c 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -4,6 +4,7 @@ use std::{ }; use jni::{ + jni_sig, jni_str, objects::{JObject, JString}, Env, JavaVM, }; @@ -33,8 +34,8 @@ where let clipboard_manager = env .call_method( context, - c"getSystemService", - c"(Ljava/lang/String;)Ljava/lang/Object;", + jni_str!("getSystemService"), + jni_sig!("(Ljava/lang/String;)Ljava/lang/Object;"), &[(&clipboard).into()], )? .l()?; @@ -48,8 +49,9 @@ pub(crate) struct Clipboard(()); impl Clipboard { pub(crate) fn new() -> Result { with_clipboard_access(|env, _| { - let version_class = env.find_class(c"android/os/Build$VERSION")?; - let build_sdk = env.get_static_field(version_class, c"SDK_INT", c"I")?.i()?; + let version_class = env.load_class(jni_str!("android/os/Build$VERSION"))?; + let build_sdk = + env.get_static_field(version_class, jni_str!("SDK_INT"), jni_sig!("I"))?.i()?; // clearPrimaryClip was introduced in this version if build_sdk >= 28 { @@ -72,34 +74,43 @@ impl<'clipboard> Get<'clipboard> { pub(crate) fn text(self) -> Result { with_clipboard_access(|env, clipboard_manager| { - if !env.call_method(&clipboard_manager, c"hasPrimaryClip", c"()Z", &[])?.z()? { + if !env + .call_method(&clipboard_manager, jni_str!("hasPrimaryClip"), jni_sig!("()Z"), &[])? + .z()? + { return Err(Error::ContentNotAvailable); } let clip = env .call_method( clipboard_manager, - c"getPrimaryClip", - c"()Landroid/content/ClipData;", + jni_str!("getPrimaryClip"), + jni_sig!("()Landroid/content/ClipData;"), &[], )? .l()?; - if env.call_method(&clip, c"getItemCount", c"()I", &[])?.i()? == 0 { + if env.call_method(&clip, jni_str!("getItemCount"), jni_sig!("()I"), &[])?.i()? == 0 { return Err(Error::ContentNotAvailable); } let item = env .call_method( &clip, - c"getItemAt", - c"(I)Landroid/content/ClipData$Item;", + jni_str!("getItemAt"), + jni_sig!("(I)Landroid/content/ClipData$Item;"), &[0.into()], )? .l()?; - let char_sequence = - env.call_method(item, c"getText", c"()Ljava/lang/CharSequence;", &[])?.l()?; + let char_sequence = env + .call_method( + item, + jni_str!("getText"), + jni_sig!("()Ljava/lang/CharSequence;"), + &[], + )? + .l()?; let text = env.cast_local::(char_sequence)?.to_string(); Ok(text) @@ -135,16 +146,18 @@ impl<'clipboard> Set<'clipboard> { let text = env.new_string(text)?; let clip_data = env.call_static_method( - c"android/content/ClipData", - c"newPlainText", - c"(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;", + jni_str!("android/content/ClipData"), + jni_str!("newPlainText"), + jni_sig!( + "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;" + ), &[(&label).into(), (&text).into()], )?; env.call_method( clipboard_manager, - c"setPrimaryClip", - c"(Landroid/content/ClipData;)V", + jni_str!("setPrimaryClip"), + jni_sig!("(Landroid/content/ClipData;)V"), &[(&clip_data).into()], )?; @@ -177,7 +190,7 @@ impl<'clipboard> Clear<'clipboard> { pub(crate) fn clear(self) -> Result<(), Error> { with_clipboard_access(|env, clipboard_manager| { - env.call_method(clipboard_manager, c"clearPrimaryClip", c"()V", &[])?; + env.call_method(clipboard_manager, jni_str!("clearPrimaryClip"), jni_sig!("()V"), &[])?; Ok(()) }) }