#![allow(clippy::await_holding_lock)] use std::sync::MutexGuard; use anyhow::Result; use jni::objects::{JClass, JObject, JString, JValue}; use jni::signature::{JavaType, Primitive}; use jni::JNIEnv; pub use jni; use crate as drpc; mod jvm_types { pub const BOOLEAN: &str = "java/lang/Boolean"; pub const INTEGER: &str = "java/lang/Integer"; pub const LONG: &str = "java/lang/Long"; pub const STRING: &str = "java/lang/String"; } macro_rules! signature { (bool) => { "Z" }; (i32) => { "I" }; (i64) => { "J" }; (class $path:expr) => { const_format::formatcp!("L{};", $path) }; } const PATH_DISCORD_RPC: &str = "com/discord/rpc/DiscordRPC"; lazy_static::lazy_static! { static ref RUNTIME: tokio::runtime::Runtime = tokio::runtime::Runtime::new().expect("unable to create tokio runtime"); } fn is_null<'b, O>(env: JNIEnv, ref1: O) -> jni::errors::Result where O: Into>, { env.is_same_object(ref1, JObject::null()) } // taking the [`JNIEnv`] as a reference is needed to satisfy the lifetime checker. fn get_client<'a>(env: &'a JNIEnv<'a>, obj: JObject<'a>) -> Result> { if !env.is_instance_of(obj, PATH_DISCORD_RPC)? { bail!("not an instance of DiscordRPC"); } let client = env.get_rust_field::<_, _, drpc::Client>(obj, "handle")?; Ok(client) } // TODO: method to destory afterwards. #[inline(always)] pub fn jni_create<'a>(env: JNIEnv<'a>, _class: JClass) -> Result> { let client = drpc::Client::default(); let jobj = env.alloc_object(PATH_DISCORD_RPC)?; env.set_rust_field(jobj, "handle", client)?; Ok(jobj) } #[inline(always)] pub fn jni_connect(env: JNIEnv, obj: JObject, client_id: JString) -> Result<()> { let client = get_client(&env, obj)?; let client_id = env.get_string(client_id)?.to_str()?.parse::()?; if let Some(current_client_id) = client.client_id() { if current_client_id != client_id { RUNTIME.block_on(async { client.disconnect().await }); } } RUNTIME.block_on(async { client.connect(client_id).await })?; Ok(()) } #[inline(always)] pub fn jni_disconnect(env: JNIEnv, obj: JObject) -> Result<()> { let client = get_client(&env, obj)?; RUNTIME.block_on(async { client.disconnect().await }); Ok(()) } #[inline(always)] pub fn jni_set_activity(env: JNIEnv, obj: JObject, j_activity: JObject) -> Result<()> { let client = get_client(&env, obj)?; let activity = jobject_to_activity(env, j_activity)?; RUNTIME.block_on(async { client.set_activity(activity).await })?; Ok(()) } #[inline(always)] pub fn jni_clear_activity(env: JNIEnv, obj: JObject) -> Result<()> { let client = get_client(&env, obj)?; RUNTIME.block_on(async { client.clear_activity().await })?; Ok(()) } fn jobject_to_activity(env: JNIEnv, jobject: JObject) -> Result { let j_state = env.get_field(jobject, "state", signature!(class jvm_types::STRING))?; let j_details = env.get_field(jobject, "details", signature!(class jvm_types::STRING))?; let j_instance = env.get_field(jobject, "instance", signature!(bool))?; let j_timestamps = env.get_field( jobject, "timestamps", signature!(class "com/discord/rpc/ActivityTimestamps"), )?; let j_assets = env.get_field(jobject, "assets", signature!(class "com/discord/rpc/ActivityAssets"))?; let j_party = env.get_field(jobject, "party", signature!(class "com/discord/rpc/ActivityParty"))?; let j_secrets = env.get_field(jobject, "secrets", signature!(class "com/discord/rpc/ActivitySecrets"))?; let mut activity = drpc::models::Activity::new(); if let JValue::Object(obj) = j_state { if !is_null(env, obj)? { activity = activity.state(env.get_string(obj.into())?); } } if let JValue::Object(obj) = j_details { if !is_null(env, obj)? { activity = activity.details(env.get_string(obj.into())?); } } if let JValue::Bool(b) = j_instance { if b != 0 { activity = activity.instance(true); } } if let JValue::Object(obj) = j_timestamps { if !is_null(env, obj)? { let timestamps = jobject_to_activity_timestamps(env, obj)?; activity = activity.timestamps(|_| timestamps); } } if let JValue::Object(obj) = j_assets { if !is_null(env, obj)? { let assets = jobject_to_activity_assets(env, obj)?; activity = activity.assets(|_| assets); } } if let JValue::Object(obj) = j_party { if !is_null(env, obj)? { let party = jobject_to_activity_party(env, obj)?; activity = activity.party(|_| party); } } if let JValue::Object(obj) = j_secrets { if !is_null(env, obj)? { let secrets = jobject_to_activity_secrets(env, obj)?; activity = activity.secrets(|_| secrets); } } Ok(activity) } fn jobject_to_activity_timestamps( env: JNIEnv, jobject: JObject, ) -> Result { let j_start = env.get_field(jobject, "start", signature!(class jvm_types::LONG))?; let j_end = env.get_field(jobject, "end", signature!(class jvm_types::LONG))?; let mut timestamps = drpc::models::ActivityTimestamps::new(); if let JValue::Object(obj) = j_start { if !is_null(env, obj)? { if let JValue::Long(l) = env.call_method_unchecked( obj, (obj, "longValue", "()J"), JavaType::Primitive(Primitive::Long), &[], )? { timestamps = timestamps.start(l as u64); } } } if let JValue::Object(obj) = j_end { if !is_null(env, obj)? { if let JValue::Long(l) = env.call_method_unchecked( obj, (obj, "longValue", "()J"), JavaType::Primitive(Primitive::Long), &[], )? { timestamps = timestamps.end(l as u64); } } } Ok(timestamps) } fn jobject_to_activity_assets( env: JNIEnv, jobject: JObject, ) -> Result { let j_lrg_img = env.get_field(jobject, "largeImage", signature!(class jvm_types::STRING))?; let j_lrg_txt = env.get_field(jobject, "largeText", signature!(class jvm_types::STRING))?; let j_sml_img = env.get_field(jobject, "smallImage", signature!(class jvm_types::STRING))?; let j_sml_txt = env.get_field(jobject, "smallText", signature!(class jvm_types::STRING))?; let mut assets = drpc::models::ActivityAssets::new(); if let JValue::Object(obj) = j_lrg_img { if !is_null(env, obj)? { assets = assets.large_image(env.get_string(obj.into())?); } } if let JValue::Object(obj) = j_lrg_txt { if !is_null(env, obj)? { assets = assets.large_text(env.get_string(obj.into())?); } } if let JValue::Object(obj) = j_sml_img { if !is_null(env, obj)? { assets = assets.small_image(env.get_string(obj.into())?); } } if let JValue::Object(obj) = j_sml_txt { if !is_null(env, obj)? { assets = assets.small_text(env.get_string(obj.into())?); } } Ok(assets) } fn jobject_to_activity_party(env: JNIEnv, jobject: JObject) -> Result { let j_id = env.get_field(jobject, "id", signature!(i32))?; let j_min_size = env.get_field(jobject, "minSize", signature!(i32))?; let j_max_size = env.get_field(jobject, "maxSize", signature!(i32))?; let mut party = drpc::models::ActivityParty::new(); if let JValue::Int(l) = j_id { party = party.id(l as u32); } if let (JValue::Int(l1), JValue::Int(l2)) = (j_min_size, j_max_size) { party = party.size((l1 as u32, l2 as u32)); } Ok(party) } fn jobject_to_activity_secrets( env: JNIEnv, jobject: JObject, ) -> Result { let j_join = env.get_field(jobject, "join", signature!(class jvm_types::STRING))?; let j_spectate = env.get_field(jobject, "spectate", signature!(class jvm_types::STRING))?; let j_game = env.get_field(jobject, "game", signature!(class jvm_types::STRING))?; let mut secrets = drpc::models::ActivitySecrets::new(); if let JValue::Object(obj) = j_join { if !is_null(env, obj)? { secrets = secrets.join(env.get_string(obj.into())?); } } if let JValue::Object(obj) = j_spectate { if !is_null(env, obj)? { secrets = secrets.spectate(env.get_string(obj.into())?); } } if let JValue::Object(obj) = j_game { if !is_null(env, obj)? { secrets = secrets.game(env.get_string(obj.into())?); } } Ok(secrets) }