android: rework implementation, bump MSRV

don't store context inside clipboard, adapt code to the latest jni changes
This commit is contained in:
Gae24 2025-09-12 19:12:22 +02:00
parent a6b5bad60f
commit 73427f0dec
5 changed files with 158 additions and 165 deletions

View file

@ -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:

163
Cargo.lock generated
View file

@ -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]]
@ -243,30 +243,44 @@ 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?branch=master#71c7f17ed2131cdfd6639b5cf7fdaa92b8443753"
dependencies = [
"cesu8",
"cfg-if",
"combine",
"jni-sys",
"log",
"thiserror",
"once_cell",
"paste",
"static_assertions",
"thiserror 2.0.16",
"walkdir",
"windows-sys 0.45.0",
]
[[package]]
name = "jni-sys"
version = "0.3.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
checksum = "c30a312d782b8d56a1e0897d45c1af33f31f9b4a4d13d31207a8675e0223b818"
dependencies = [
"jni-sys-macros",
]
[[package]]
name = "jni-sys-macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c199962dfd5610ced8eca382606e349f7940a4ac7d867b58a046123411cbb4"
dependencies = [
"quote",
"syn 1.0.100",
]
[[package]]
name = "jpeg-decoder"
@ -421,9 +435,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.15.0"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "os_pipe"
@ -432,7 +446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys",
]
[[package]]
@ -458,6 +472,12 @@ dependencies = [
"windows-targets 0.52.6",
]
[[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"
@ -494,9 +514,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.78"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
@ -555,7 +575,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys",
]
[[package]]
@ -579,6 +599,12 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "1.0.100"
@ -590,6 +616,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.10.1"
@ -599,7 +636,7 @@ dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys 0.52.0",
"windows-sys",
]
[[package]]
@ -617,7 +654,16 @@ version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.35",
]
[[package]]
name = "thiserror"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
"thiserror-impl 2.0.16",
]
[[package]]
@ -628,7 +674,18 @@ checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.100",
]
[[package]]
name = "thiserror-impl"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
@ -753,16 +810,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
"windows-sys",
]
[[package]]
@ -774,21 +822,6 @@ 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",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
@ -820,12 +853,6 @@ dependencies = [
"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.48.0"
@ -838,12 +865,6 @@ 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.48.0"
@ -856,12 +877,6 @@ 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.48.0"
@ -880,12 +895,6 @@ 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.48.0"
@ -898,12 +907,6 @@ 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.48.0"
@ -916,12 +919,6 @@ 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.48.0"
@ -934,12 +931,6 @@ 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.48.0"
@ -963,7 +954,7 @@ dependencies = [
"os_pipe",
"rustix",
"tempfile",
"thiserror",
"thiserror 1.0.35",
"tree_magic_mini",
"wayland-backend",
"wayland-client",

View file

@ -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", branch = "master" }
[[example]]
name = "get_image"

View file

@ -90,6 +90,7 @@ impl std::fmt::Debug for Error {
}
impl Error {
#[cfg(not(target_os = "android"))]
pub(crate) fn unknown<M: Into<String>>(message: M) -> Self {
Error::Unknown { description: message.into() }
}

View file

@ -5,7 +5,8 @@ use std::{
use jni::{
objects::{JObject, JString},
AttachGuard, JavaVM,
strings::JNIString,
Env, JavaVM,
};
#[cfg(feature = "image-data")]
@ -18,40 +19,36 @@ impl From<jni::errors::Error> for Error {
}
}
pub(crate) struct Clipboard {
ctx: ndk_context::AndroidContext,
fn with_clipboard_access<F, T>(callback: F) -> Result<T, Error>
where
F: FnOnce(&mut Env, JObject) -> Result<T, Error>,
{
let ctx = ndk_context::android_context();
let jvm = unsafe { JavaVM::from_raw(ctx.vm().cast()) };
jvm.attach_current_thread(|env| {
let context = unsafe { JObject::from_raw(ctx.context().cast()) };
let clipboard = env.new_string(c"clipboard")?;
let clipboard_manager = env
.call_method(
context,
c"getSystemService",
c"(Ljava/lang/String;)Ljava/lang/Object;",
&[(&clipboard).into()],
)?
.l()?;
callback(env, clipboard_manager)
})
}
pub(crate) struct Clipboard(());
impl Clipboard {
pub(crate) fn new() -> Result<Self, Error> {
Ok(Self { ctx: ndk_context::android_context() })
}
fn vm(&self) -> Result<JavaVM, jni::errors::Error> {
// 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<JObject<'attachment>, 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()?)
Ok(Self(()))
}
}
@ -65,30 +62,39 @@ impl<'clipboard> Get<'clipboard> {
}
pub(crate) fn text(self) -> Result<String, Error> {
let vm = self.clipboard.vm()?;
let mut env = vm.attach_current_thread()?;
let clipboard_manager = self.clipboard.clipboard_manager(&mut env)?;
with_clipboard_access(|env, clipboard_manager| {
if !env.call_method(&clipboard_manager, c"hasPrimaryClip", c"()Z", &[])?.z()? {
return Err(Error::ContentNotAvailable);
}
if !env.call_method(&clipboard_manager, "hasPrimaryClip", "()Z", &[])?.z()? {
return Err(Error::ContentNotAvailable);
}
let clip = env
.call_method(
clipboard_manager,
c"getPrimaryClip",
c"()Landroid/content/ClipData;",
&[],
)?
.l()?;
let clip = env
.call_method(clipboard_manager, "getPrimaryClip", "()Landroid/content/ClipData;", &[])?
.l()?;
if env.call_method(&clip, c"getItemCount", c"()I", &[])?.i()? == 0 {
return Err(Error::ContentNotAvailable);
}
if env.call_method(&clip, "getItemCount", "()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 item = env
.call_method(&clip, "getItemAt", "(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::<JString>(char_sequence)?.to_string();
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())
Ok(text)
})
}
pub(crate) fn html(self) -> Result<String, Error> {
@ -115,28 +121,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)?;
with_clipboard_access(|env, clipboard_manager| {
let label = env.new_string(c"label")?;
let text = env.new_string(JNIString::from(text))?;
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()],
)?;
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,
c"setPrimaryClip",
c"(Landroid/content/ClipData;)V",
&[(&clip_data).into()],
)?;
env.call_method(
clipboard_manager,
"setPrimaryClip",
"(Landroid/content/ClipData;)V",
&[(&clip_data).into()],
)?;
Ok(())
Ok(())
})
}
pub(crate) fn html(self, _: Cow<'_, str>, _: Option<Cow<'_, str>>) -> Result<(), Error> {
@ -163,12 +167,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(())
})
}
}