use std::sync::OnceLock;
use frus_widgets::{Brightness, Color, SystemBars};
use jni::objects::{GlobalRef, JClass, JObject, JValue};
use jni::{JNIEnv, JavaVM};
struct Handle {
vm: JavaVM,
class: GlobalRef,
activity: GlobalRef,
}
unsafe impl Send for Handle {}
unsafe impl Sync for Handle {}
static HANDLE: OnceLock<Handle> = OnceLock::new();
pub(crate) fn install(env: &mut JNIEnv, loader: &JObject, activity: &JObject) {
match try_install(env, loader, activity) {
Ok(handle) => {
let _ = HANDLE.set(handle);
}
Err(err) => {
if env.exception_check().unwrap_or(false) {
let _ = env.exception_clear();
}
log::warn!("system bars unavailable ({err}) — the platform's defaults stay");
}
}
}
fn try_install(
env: &mut JNIEnv,
loader: &JObject,
activity: &JObject,
) -> Result<Handle, jni::errors::Error> {
let name = env.new_string("dev.frus.input.FrusSystemBars")?;
let class = env
.call_method(
loader,
"loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
&[JValue::Object(&name)],
)?
.l()?;
Ok(Handle {
vm: env.get_java_vm()?,
class: env.new_global_ref(&class)?,
activity: env.new_global_ref(activity)?,
})
}
fn argb(color: Color) -> i32 {
let byte = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u32;
((byte(color.a) << 24) | (byte(color.r) << 16) | (byte(color.g) << 8) | byte(color.b)) as i32
}
pub(crate) fn apply(bars: SystemBars) -> bool {
let Some(handle) = HANDLE.get() else {
return false;
};
let Ok(mut env) = handle.vm.attach_current_thread_permanently() else {
return false;
};
let class: &JClass = handle.class.as_obj().into();
let called = env.call_static_method(
class,
"apply",
"(Landroid/app/Activity;IIZZ)V",
&[
JValue::Object(handle.activity.as_obj()),
JValue::Int(argb(bars.status_bar_color)),
JValue::Int(argb(bars.navigation_bar_color)),
JValue::Bool((bars.status_bar_icons == Brightness::Dark) as u8),
JValue::Bool((bars.navigation_bar_icons == Brightness::Dark) as u8),
],
);
match called {
Ok(_) => true,
Err(err) => {
if env.exception_check().unwrap_or(false) {
let _ = env.exception_clear();
}
log::warn!("system bars: apply failed ({err})");
false
}
}
}