use openxr_sys::pfn::EnumerateEnvironmentBlendModes;
use openxr_sys::{EnvironmentBlendMode, Instance, Result, SystemId, ViewConfigurationType};
use std::ffi::OsString;
use std::fs::File;
use std::path::Path;
use std::path::PathBuf;
use std::{cell::RefCell, rc::Rc};
use crate::sk::SkInfo;
use crate::system::{Backend, BackendOpenXR, BackendXRType, Log};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathEntry {
File(OsString),
Dir(OsString),
}
pub fn get_shaders_source_dir() -> String {
std::env::var("SK_RUST_SHADERS_SOURCE_DIR").unwrap_or("shaders_src".into())
}
pub fn get_shaders_sks_dir() -> String {
std::env::var("SK_RUST_SHADERS_SKS_DIR").unwrap_or("shaders".into())
}
pub fn get_assets_dir() -> String {
std::env::var("SK_RUST_ASSETS_DIR").unwrap_or("assets".into())
}
#[cfg(target_os = "android")]
pub fn get_assets(
sk_info: &Option<Rc<RefCell<SkInfo>>>,
sub_dir: PathBuf,
file_extensions: &Vec<String>,
) -> Vec<PathEntry> {
use std::ffi::CString;
if sk_info.is_none() {
Log::err("get_assets, sk_info is None");
return vec![];
}
let sk_i = sk_info.as_ref().unwrap().borrow_mut();
let app = sk_i.get_android_app();
let mut exts = vec![];
for extension in file_extensions {
let extension = extension[1..].to_string();
exts.push(OsString::from(extension));
}
let mut vec = vec![];
if let Ok(cstring) = CString::new(sub_dir.to_str().unwrap_or("Error!!!")) {
if let Some(asset_dir) = app.asset_manager().open_dir(cstring.as_c_str()) {
for entry in asset_dir {
if let Ok(entry_string) = entry.into_string() {
let path = PathBuf::from(entry_string.clone());
if exts.is_empty() {
if let Some(file_name) = path.file_name() {
vec.push(PathEntry::File(file_name.into()))
} else {
Log::err(format!("get_assets, path {:?} don't have a file_name", path));
}
} else if let Some(extension) = path.extension() {
if exts.contains(&extension.to_os_string()) {
if let Some(file_name) = path.file_name() {
vec.push(PathEntry::File(file_name.into()))
}
}
}
}
}
}
}
vec
}
#[cfg(not(target_os = "android"))]
pub fn get_assets(
_sk_info: &Option<Rc<RefCell<SkInfo>>>,
sub_dir: PathBuf,
file_extensions: &Vec<String>,
) -> Vec<PathEntry> {
use std::{env, fs::read_dir};
let sub_dir = sub_dir.to_str().unwrap_or("");
let mut exts = vec![];
for extension in file_extensions {
let extension = extension[1..].to_string();
exts.push(OsString::from(extension));
}
let path_text = env::current_dir().unwrap().to_owned().join(get_assets_dir());
let path_asset = path_text.join(sub_dir);
let mut vec = vec![];
if path_asset.exists() {
if path_asset.is_dir() {
match read_dir(&path_asset) {
Ok(read_dir) => {
for file in read_dir.flatten() {
let path = file.path();
if file.path().is_file() {
if exts.is_empty() {
vec.push(PathEntry::File(file.file_name()))
} else if let Some(extension) = path.extension()
&& (exts.is_empty() || exts.contains(&extension.to_os_string()))
{
vec.push(PathEntry::File(file.file_name()))
}
}
}
}
Err(err) => {
Log::diag(format!("Unable to read {path_asset:?}: {err}"));
}
}
} else {
Log::diag(format!("{path_asset:?} is not a dir"));
}
} else {
Log::diag(format!("{path_asset:?} do not exists"));
}
vec
}
#[cfg(target_os = "android")]
pub fn get_internal_path(sk_info: &Option<Rc<RefCell<SkInfo>>>) -> Option<PathBuf> {
if sk_info.is_none() {
Log::err("get_internal_path, sk_info is None");
return None;
}
let sk_i = sk_info.as_ref().unwrap().borrow_mut();
let app = sk_i.get_android_app();
app.internal_data_path()
}
#[cfg(not(target_os = "android"))]
pub fn get_internal_path(_sk_info: &Option<Rc<RefCell<SkInfo>>>) -> Option<PathBuf> {
None
}
#[cfg(target_os = "android")]
pub fn get_external_path(sk_info: &Option<Rc<RefCell<SkInfo>>>) -> Option<PathBuf> {
if sk_info.is_none() {
Log::err("get_external_path, sk_info is None");
return None;
}
let sk_i = sk_info.as_ref().unwrap().borrow_mut();
let app = sk_i.get_android_app();
app.external_data_path()
}
#[cfg(not(target_os = "android"))]
pub fn get_external_path(_sk_info: &Option<Rc<RefCell<SkInfo>>>) -> Option<PathBuf> {
use std::env;
let path_assets = env::current_dir().unwrap().join(get_assets_dir());
Some(path_assets)
}
#[cfg(target_os = "android")]
pub fn open_asset(sk_info: &Option<Rc<RefCell<SkInfo>>>, asset_path: impl AsRef<Path>) -> Option<File> {
use std::ffi::CString;
if sk_info.is_none() {
Log::err("open_asset, sk_info is None");
return None;
}
let sk_i = sk_info.as_ref().unwrap().borrow_mut();
let app = sk_i.get_android_app();
if let Ok(cstring) = CString::new(asset_path.as_ref().to_str().unwrap_or("Error!!!")) {
if let Some(asset) = app.asset_manager().open(cstring.as_c_str()) {
if let Ok(o_file_desc) = asset.open_file_descriptor() {
Some(File::from(o_file_desc.fd))
} else {
Log::err(format!("open_asset, {:?} cannot get a new file_descriptor", asset_path.as_ref()));
None
}
} else {
Log::err(format!("open_asset, path {:?} cannot be a opened", asset_path.as_ref()));
None
}
} else {
Log::err(format!("open_asset, path {:?} cannot be a cstring", asset_path.as_ref()));
None
}
}
#[cfg(not(target_os = "android"))]
pub fn open_asset(_sk_info: &Option<Rc<RefCell<SkInfo>>>, asset_path: impl AsRef<Path>) -> Option<File> {
use std::env;
let path_assets = env::current_dir().unwrap().join(get_assets_dir());
let path_asset = path_assets.join(asset_path);
File::open(path_asset).ok()
}
#[cfg(target_os = "android")]
pub fn read_asset(sk_info: &Option<Rc<RefCell<SkInfo>>>, asset_path: impl AsRef<Path>) -> Option<Vec<u8>> {
use std::ffi::CString;
if sk_info.is_none() {
Log::err("open_asset, sk_info is None");
return None;
}
let sk_i = sk_info.as_ref().unwrap().borrow_mut();
let app = sk_i.get_android_app();
if let Ok(cstring) = CString::new(asset_path.as_ref().to_str().unwrap_or("Error!!!")) {
if let Some(mut asset) = app.asset_manager().open(cstring.as_c_str()) {
if let Ok(o_buffer) = asset.buffer() {
Some(o_buffer.to_vec())
} else {
Log::err(format!("open_asset, {:?} cannot get the buffer", asset_path.as_ref()));
None
}
} else {
Log::err(format!("open_asset, path {:?} cannot be a opened", asset_path.as_ref()));
None
}
} else {
Log::err(format!("open_asset, path {:?} cannot be a cstring", asset_path.as_ref()));
None
}
}
#[cfg(not(target_os = "android"))]
pub fn read_asset(_sk_info: &Option<Rc<RefCell<SkInfo>>>, asset_path: impl AsRef<Path>) -> Option<Vec<u8>> {
use std::{env, io::Read};
let path_assets = env::current_dir().unwrap().join(get_assets_dir());
let path_asset = path_assets.join(&asset_path);
let mut fd = match File::open(path_asset).ok() {
Some(file) => file,
None => {
Log::err(format!("open_asset, path {:?} cannot be opened", asset_path.as_ref()));
return None;
}
};
let mut o_buffer = vec![];
match fd.read_to_end(&mut o_buffer) {
Ok(_) => Some(o_buffer),
Err(err) => {
Log::err(format!("open_asset, path {:?} cannot be read: {}", asset_path.as_ref(), err));
None
}
}
}
pub fn get_files(
_sk_info: &Option<Rc<RefCell<SkInfo>>>,
dir: PathBuf,
file_extensions: &Vec<String>,
show_sub_dirs: bool,
) -> Vec<PathEntry> {
use std::fs::read_dir;
let mut exts = vec![];
for extension in file_extensions {
let extension = extension[1..].to_string();
exts.push(OsString::from(extension));
}
let mut vec = vec![];
if dir.exists()
&& dir.is_dir()
&& let Ok(read_dir) = read_dir(dir)
{
for file in read_dir.flatten() {
let path = file.path();
if file.path().is_file() {
if exts.is_empty() {
vec.push(PathEntry::File(file.file_name()))
} else if let Some(extension) = path.extension()
&& (exts.is_empty() || exts.contains(&extension.to_os_string()))
{
vec.push(PathEntry::File(file.file_name()))
}
} else if show_sub_dirs && file.path().is_dir() {
vec.push(PathEntry::Dir(file.file_name()))
}
}
}
vec
}
#[cfg(target_os = "android")]
pub fn show_soft_input_ime(sk_info: &Option<Rc<RefCell<SkInfo>>>, show: bool) -> bool {
if sk_info.is_none() {
Log::err("show_soft_input_ime, sk_info is None");
return false;
}
let sk_i = sk_info.as_ref().unwrap().borrow_mut();
let app = sk_i.get_android_app();
if show {
app.show_soft_input(false);
} else {
app.hide_soft_input(false);
}
true
}
#[cfg(not(target_os = "android"))]
pub fn show_soft_input_ime(_sk_info: &Option<Rc<RefCell<SkInfo>>>, _show: bool) -> bool {
false
}
#[cfg(target_os = "android")]
pub fn show_soft_input(show: bool) -> bool {
use jni::objects::JValue;
let ctx = ndk_context::android_context();
let vm = match unsafe { jni::JavaVM::from_raw(ctx.vm() as _) } {
Ok(value) => value,
Err(e) => {
Log::err(format!("virtual_kbd : no vm !! : {:?}", e));
return false;
}
};
let activity = unsafe { jni::objects::JObject::from_raw(ctx.context() as _) };
let mut env = match vm.attach_current_thread() {
Ok(value) => value,
Err(e) => {
Log::err(format!("virtual_kbd : no env !! : {:?}", e));
return false;
}
};
let class_ctxt = match env.find_class("android/content/Context") {
Ok(value) => value,
Err(e) => {
Log::err(format!("virtual_kbd : no class_ctxt !! : {:?}", e));
return false;
}
};
let ims = match env.get_static_field(class_ctxt, "INPUT_METHOD_SERVICE", "Ljava/lang/String;") {
Ok(value) => value,
Err(e) => {
Log::err(format!("virtual_kbd : no ims !! : {:?}", e));
return false;
}
};
let im_manager = match env
.call_method(&activity, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", &[ims.borrow()])
.unwrap()
.l()
{
Ok(value) => value,
Err(e) => {
Log::err(format!("virtual_kbd : no im_manager !! : {:?}", e));
return false;
}
};
let jni_window = match env.call_method(&activity, "getWindow", "()Landroid/view/Window;", &[]).unwrap().l() {
Ok(value) => value,
Err(e) => {
Log::err(format!("virtual_kbd : no jni_window !! : {:?}", e));
return false;
}
};
let view = match env.call_method(jni_window, "getDecorView", "()Landroid/view/View;", &[]).unwrap().l() {
Ok(value) => value,
Err(e) => {
Log::err(format!("virtual_kbd : no view !! : {:?}", e));
return false;
}
};
if show {
let result = env
.call_method(im_manager, "showSoftInput", "(Landroid/view/View;I)Z", &[JValue::Object(&view), 0i32.into()])
.unwrap()
.z()
.unwrap();
result
} else {
let window_token = env.call_method(view, "getWindowToken", "()Landroid/os/IBinder;", &[]).unwrap().l().unwrap();
let jvalue_window_token = jni::objects::JValueGen::Object(&window_token);
let result = env
.call_method(
im_manager,
"hideSoftInputFromWindow",
"(Landroid/os/IBinder;I)Z",
&[jvalue_window_token, 0i32.into()],
)
.unwrap()
.z()
.unwrap();
result
}
}
#[cfg(not(target_os = "android"))]
pub fn show_soft_input(_show: bool) -> bool {
false
}
#[cfg(target_os = "android")]
pub fn launch_browser_android(url: &str) -> bool {
use jni::objects::{JObject, JValue};
Log::diag(format!("launch_browser_android: Attempting to open URL: {}", url));
let ctx = ndk_context::android_context();
let vm = match unsafe { jni::JavaVM::from_raw(ctx.vm() as _) } {
Ok(value) => value,
Err(e) => {
Log::err(format!("launch_browser_android: no vm !! : {:?}", e));
return false;
}
};
let activity = unsafe { jni::objects::JObject::from_raw(ctx.context() as _) };
let mut env = match vm.attach_current_thread() {
Ok(value) => value,
Err(e) => {
Log::err(format!("launch_browser_android: no env !! : {:?}", e));
return false;
}
};
let intent_class = match env.find_class("android/content/Intent") {
Ok(value) => value,
Err(e) => {
Log::err(format!("launch_browser_android: no intent_class !! : {:?}", e));
return false;
}
};
let action_view = match env.get_static_field(&intent_class, "ACTION_VIEW", "Ljava/lang/String;") {
Ok(value) => value,
Err(e) => {
Log::err(format!("launch_browser_android: no action_view !! : {:?}", e));
return false;
}
};
let uri_class = match env.find_class("android/net/Uri") {
Ok(value) => value,
Err(e) => {
Log::err(format!("launch_browser_android: no uri_class !! : {:?}", e));
return false;
}
};
let url_string = match env.new_string(url) {
Ok(value) => value,
Err(e) => {
Log::err(format!("launch_browser_android: no url_string !! : {:?}", e));
return false;
}
};
let uri = match env
.call_static_method(
&uri_class,
"parse",
"(Ljava/lang/String;)Landroid/net/Uri;",
&[JValue::Object(&JObject::from(url_string))],
)
.unwrap()
.l()
{
Ok(value) => value,
Err(e) => {
Log::err(format!("launch_browser_android: no uri !! : {:?}", e));
return false;
}
};
let intent = match env.alloc_object(&intent_class) {
Ok(value) => value,
Err(e) => {
Log::err(format!("launch_browser_android: no intent !! : {:?}", e));
return false;
}
};
if let Err(e) = env.call_method(
&intent,
"<init>",
"(Ljava/lang/String;Landroid/net/Uri;)V",
&[action_view.borrow(), JValue::Object(&uri)],
) {
Log::err(format!("launch_browser_android: intent init failed !! : {:?}", e));
return false;
}
match env.call_method(&activity, "startActivity", "(Landroid/content/Intent;)V", &[JValue::Object(&intent)]) {
Ok(_) => {
if env.exception_check().unwrap_or(false) {
let _ = env.exception_clear();
Log::err("launch_browser_android: Activity exception occurred (cleared)");
return false;
}
true
}
Err(e) => {
Log::err(format!("launch_browser_android: startActivity failed: {} | URL: {}", e, url));
if env.exception_check().unwrap_or(false) {
let _ = env.exception_clear();
}
false
}
}
}
#[cfg(not(target_os = "android"))]
pub fn launch_browser_android(_url: &str) -> bool {
false
}
#[derive(Debug, Clone, PartialEq)]
pub enum SystemAction {
Browser { url: String },
Store { app_id: Option<String> },
Settings { setting: Option<String> },
FileManager { path: Option<String> },
BugReport,
}
#[cfg(target_os = "android")]
pub fn system_deep_link(action: SystemAction) -> bool {
use crate::system::Log;
use jni::objects::JValue;
let ctx = ndk_context::android_context();
let vm = match unsafe { jni::JavaVM::from_raw(ctx.vm() as _) } {
Ok(value) => value,
Err(e) => {
Log::err(format!("system_deep_link: Failed to get VM: {:?}", e));
return false;
}
};
let activity = unsafe { jni::objects::JObject::from_raw(ctx.context() as _) };
let mut env = match vm.attach_current_thread() {
Ok(env) => env,
Err(e) => {
Log::err(format!("system_deep_link: Failed to attach to JVM thread: {:?}", e));
return false;
}
};
let (intent_data, uri_value, display_data) = match &action {
SystemAction::Browser { url } => {
("systemux://browser", url.clone(), format!("Opening browser with URL: {}", url))
}
SystemAction::Store { app_id } => {
let uri = match app_id {
Some(id) => format!("/item/{}", id),
None => String::new(),
};
(
"systemux://store",
uri,
format!(
"Opening store{}",
if app_id.is_some() { format!(" for app: {}", app_id.as_ref().unwrap()) } else { String::new() }
),
)
}
SystemAction::Settings { setting } => {
let uri = setting.as_deref().unwrap_or("");
(
"systemux://settings",
uri.to_string(),
format!("Opening settings{}", if !uri.is_empty() { format!(": {}", uri) } else { String::new() }),
)
}
SystemAction::FileManager { path } => {
let uri = path.as_deref().unwrap_or("");
(
"systemux://file-manager",
uri.to_string(),
format!("Opening file manager{}", if !uri.is_empty() { format!(": {}", uri) } else { String::new() }),
)
}
SystemAction::BugReport => ("systemux://bug_report", String::new(), "Opening bug report".to_string()),
};
Log::info(format!("system_deep_link: {}", display_data));
Log::diag(format!(
"system_deep_link: Attempting to launch VR Shell with intent_data='{}', uri='{}'",
intent_data, uri_value
));
let package_manager =
match env.call_method(&activity, "getPackageManager", "()Landroid/content/pm/PackageManager;", &[]) {
Ok(pm) => match pm.l() {
Ok(pm_obj) => pm_obj,
Err(e) => {
Log::err(format!("system_deep_link: Failed to extract PackageManager object: {}", e));
return false;
}
},
Err(e) => {
Log::err(format!("system_deep_link: Failed to get PackageManager: {}", e));
return false;
}
};
let package_name = match env.new_string("com.oculus.vrshell") {
Ok(s) => s,
Err(e) => {
Log::err(format!("system_deep_link: Failed to create package name string: {}", e));
return false;
}
};
let intent = match env.call_method(
&package_manager,
"getLaunchIntentForPackage",
"(Ljava/lang/String;)Landroid/content/Intent;",
&[JValue::Object(&package_name.into())],
) {
Ok(intent_result) => match intent_result.l() {
Ok(intent_obj) if !intent_obj.is_null() => intent_obj,
Ok(_) => {
Log::err("system_deep_link: getLaunchIntentForPackage returned null");
return false;
}
Err(e) => {
Log::err(format!("system_deep_link: Failed to extract Intent object: {}", e));
return false;
}
},
Err(e) => {
Log::err(format!("system_deep_link: Failed to get launch intent: {}", e));
return false;
}
};
let intent_data_key = match env.new_string("intent_data") {
Ok(s) => s,
Err(e) => {
Log::err(format!("system_deep_link: Failed to create intent_data key: {}", e));
return false;
}
};
let intent_data_value = match env.new_string(intent_data) {
Ok(s) => s,
Err(e) => {
Log::err(format!("system_deep_link: Failed to create intent_data value: {}", e));
return false;
}
};
if let Err(e) = env.call_method(
&intent,
"putExtra",
"(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
&[JValue::Object(&intent_data_key.into()), JValue::Object(&intent_data_value.into())],
) {
Log::err(format!("system_deep_link: Failed to add intent_data extra: {}", e));
return false;
}
if !uri_value.is_empty() {
let uri_key = match env.new_string("uri") {
Ok(s) => s,
Err(e) => {
Log::err(format!("system_deep_link: Failed to create uri key: {}", e));
return false;
}
};
let uri_value_string = match env.new_string(&uri_value) {
Ok(s) => s,
Err(e) => {
Log::err(format!("system_deep_link: Failed to create uri value: {}", e));
return false;
}
};
if let Err(e) = env.call_method(
&intent,
"putExtra",
"(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
&[JValue::Object(&uri_key.into()), JValue::Object(&uri_value_string.into())],
) {
Log::err(format!("system_deep_link: Failed to add uri extra: {}", e));
return false;
}
}
match env.call_method(&activity, "startActivity", "(Landroid/content/Intent;)V", &[JValue::Object(&intent)]) {
Ok(_) => {
if env.exception_check().unwrap_or(false) {
let _ = env.exception_clear();
Log::err("system_deep_link: Activity exception occurred (cleared)");
return false;
}
Log::info(format!("system_deep_link: Successfully executed: {}", display_data));
true
}
Err(e) => {
Log::err(format!(
"system_deep_link: Failed to start activity: {} | Action: {:?} | Intent data: {} | URI: {}",
e, action, intent_data, uri_value
));
if env.exception_check().unwrap_or(false) {
let _ = env.exception_clear();
}
false
}
}
}
#[cfg(not(target_os = "android"))]
pub fn system_deep_link(_action: SystemAction) -> bool {
use crate::system::Log;
Log::warn("system_deep_link: Not supported on non-Android platforms");
false
}
pub fn get_env_blend_modes(with_log: bool) -> Vec<EnvironmentBlendMode> {
let mut count = 0u32;
let mut modes = [EnvironmentBlendMode::OPAQUE; 20];
if Backend::xr_type() != BackendXRType::OpenXR {
return vec![];
}
if let Some(get_modes) =
BackendOpenXR::get_function::<EnumerateEnvironmentBlendModes>("xrEnumerateEnvironmentBlendModes")
{
match unsafe {
get_modes(
Instance::from_raw(BackendOpenXR::instance()),
SystemId::from_raw(BackendOpenXR::system_id()),
ViewConfigurationType::PRIMARY_STEREO,
0,
&mut count,
modes.as_mut_ptr(),
)
} {
Result::SUCCESS => {
if with_log {
if count > 20 {
count = 20
}
match unsafe {
get_modes(
Instance::from_raw(BackendOpenXR::instance()),
SystemId::from_raw(BackendOpenXR::system_id()),
ViewConfigurationType::PRIMARY_STEREO,
count,
&mut count,
modes.as_mut_ptr(),
)
} {
Result::SUCCESS => {
if with_log {
Log::info(format!("✅ There are {count} env blend modes:"));
for (i, iter) in modes.iter().enumerate() {
if i >= count as usize {
break;
}
Log::info(format!(" {iter:?} "));
}
}
}
otherwise => {
if with_log {
Log::err(format!("❌ xrEnumerateEnvironmentBlendModes failed: {otherwise}"));
}
}
}
}
}
otherwise => {
if with_log {
Log::err(format!("❌ xrEnumerateEnvironmentBlendModes failed: {otherwise}"));
}
}
}
} else {
Log::err("❌ xrEnumerateEnvironmentBlendModes binding function error !");
}
modes[0..(count as usize)].into()
}