stereokit_rust/tools/
os_api.rs

1use openxr_sys::pfn::{
2    EnumerateDisplayRefreshRatesFB, EnumerateEnvironmentBlendModes, GetDisplayRefreshRateFB,
3    RequestDisplayRefreshRateFB,
4};
5use openxr_sys::{EnvironmentBlendMode, Instance, Result, Session, SystemId, ViewConfigurationType};
6
7use crate::sk::SkInfo;
8use crate::system::{Backend, BackendOpenXR, BackendXRType, Log};
9use std::ffi::OsString;
10use std::fs::File;
11use std::path::Path;
12use std::path::PathBuf;
13use std::{cell::RefCell, rc::Rc};
14
15/// When browsing files because of Android we need this API.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum PathEntry {
18    File(OsString),
19    Dir(OsString),
20}
21
22/// For bin tools only
23pub fn get_shaders_source_dir() -> String {
24    std::env::var("SK_RUST_SHADERS_SOURCE_DIR").unwrap_or("shaders_src".into())
25}
26
27/// Where sks shaders are store under assets dir. For bin tools and none android exe. For Android use app.asset_manager()
28pub fn get_shaders_sks_dir() -> String {
29    std::env::var("SK_RUST_SHADERS_SKS_DIR").unwrap_or("shaders".into())
30}
31
32/// For bin tools and non android exe. For Android use app.asset_manager()
33pub fn get_assets_dir() -> String {
34    std::env::var("SK_RUST_ASSETS_DIR").unwrap_or("assets".into())
35}
36
37/// Read all the assets of a given assets sub directory.
38/// * `sk_info` - The SkInfo smart pointer
39/// * `sub_dir` - The sub directory of the assets directory.
40/// * `file_extensions` - The file extensions to filter by.
41///
42/// Returns a vector of PathEntry.
43#[cfg(target_os = "android")]
44pub fn get_assets(
45    sk_info: &Option<Rc<RefCell<SkInfo>>>,
46    sub_dir: PathBuf,
47    file_extensions: &Vec<String>,
48) -> Vec<PathEntry> {
49    use std::ffi::CString;
50
51    if sk_info.is_none() {
52        Log::err("get_assets, sk_info is None");
53        return vec![];
54    }
55
56    let sk_i = sk_info.as_ref().unwrap().borrow_mut();
57    let app = sk_i.get_android_app();
58    let mut exts = vec![];
59    for extension in file_extensions {
60        let extension = extension[1..].to_string();
61        exts.push(OsString::from(extension));
62    }
63    let mut vec = vec![];
64    if let Ok(cstring) = CString::new(sub_dir.to_str().unwrap_or("Error!!!")) {
65        if let Some(asset_dir) = app.asset_manager().open_dir(cstring.as_c_str()) {
66            for entry in asset_dir {
67                if let Ok(entry_string) = entry.into_string() {
68                    let path = PathBuf::from(entry_string.clone());
69
70                    if exts.is_empty() {
71                        if let Some(file_name) = path.file_name() {
72                            vec.push(PathEntry::File(file_name.into()))
73                        } else {
74                            Log::err(format!("get_assets, path {:?} don't have a file_name", path));
75                        }
76                    } else if let Some(extension) = path.extension() {
77                        if exts.contains(&extension.to_os_string()) {
78                            if let Some(file_name) = path.file_name() {
79                                vec.push(PathEntry::File(file_name.into()))
80                            }
81                        }
82                    }
83                }
84            }
85        }
86    }
87    vec
88}
89
90/// Read all the assets of a given assets sub directory.
91/// * `sk_info` - The SkInfo smart pointer
92/// * `sub_dir` - The sub directory of the assets directory.
93/// * `file_extensions` - The file extensions to filter by.
94///
95/// Returns a vector of PathEntry.
96/// ### Examples
97/// ```
98/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
99/// use stereokit_rust::{tools::os_api::{get_assets, PathEntry}, include_asset_tree};
100///
101/// let sk_info  = Some(sk.get_sk_info_clone());
102///
103/// const ASSET_DIR: &[&str] = include_asset_tree!("assets");
104///
105/// let mut file_found = false;
106/// let exts = vec![".png".into(), ".jpg".into(), ".jpeg".into()];
107/// for dir_name_str in ASSET_DIR {
108///     let dir_name_str = if dir_name_str.starts_with("assets/") {
109///         &dir_name_str[7..] // remove "assets/" from the path
110///     } else {
111///         &dir_name_str[6..]  // remove "assets" from the path
112///     };
113///     println!("{} :", dir_name_str);
114///     let mut asset_sub_dir = std::path::PathBuf::new();
115///     asset_sub_dir.push(dir_name_str);
116///     for file in get_assets(&sk_info, asset_sub_dir, &exts) {
117///         println!("--- {:?}", file);
118///         if let PathEntry::File(file) = file {
119///             if file.into_string().unwrap_or_default()
120///                    .ends_with("log_viewer.png") { file_found = true}
121///         }
122///     }
123/// }
124/// assert!(file_found);
125/// ```
126#[cfg(not(target_os = "android"))]
127pub fn get_assets(
128    _sk_info: &Option<Rc<RefCell<SkInfo>>>,
129    sub_dir: PathBuf,
130    file_extensions: &Vec<String>,
131) -> Vec<PathEntry> {
132    use std::{env, fs::read_dir};
133
134    let sub_dir = sub_dir.to_str().unwrap_or("");
135    let mut exts = vec![];
136    for extension in file_extensions {
137        let extension = extension[1..].to_string();
138        exts.push(OsString::from(extension));
139    }
140
141    let path_text = env::current_dir().unwrap().to_owned().join(get_assets_dir());
142    let path_asset = path_text.join(sub_dir);
143    let mut vec = vec![];
144
145    if path_asset.exists() {
146        if path_asset.is_dir() {
147            match read_dir(&path_asset) {
148                Ok(read_dir) => {
149                    for file in read_dir.flatten() {
150                        let path = file.path();
151
152                        if file.path().is_file() {
153                            if exts.is_empty() {
154                                vec.push(PathEntry::File(file.file_name()))
155                            } else if let Some(extension) = path.extension() {
156                                if exts.is_empty() || exts.contains(&extension.to_os_string()) {
157                                    vec.push(PathEntry::File(file.file_name()))
158                                }
159                            }
160                        }
161                    }
162                }
163                Err(err) => {
164                    Log::diag(format!("Unable to read {path_asset:?}: {err}"));
165                }
166            }
167        } else {
168            Log::diag(format!("{path_asset:?} is not a dir"));
169        }
170    } else {
171        Log::diag(format!("{path_asset:?} do not exists"));
172    }
173
174    vec
175}
176
177/// Get the path to internal data directory for Android
178#[cfg(target_os = "android")]
179pub fn get_internal_path(sk_info: &Option<Rc<RefCell<SkInfo>>>) -> Option<PathBuf> {
180    if sk_info.is_none() {
181        Log::err("get_internal_path, sk_info is None");
182        return None;
183    }
184
185    let sk_i = sk_info.as_ref().unwrap().borrow_mut();
186    let app = sk_i.get_android_app();
187    app.internal_data_path()
188}
189
190/// Get the path to external data directory for non android
191#[cfg(not(target_os = "android"))]
192pub fn get_internal_path(_sk_info: &Option<Rc<RefCell<SkInfo>>>) -> Option<PathBuf> {
193    None
194}
195
196/// Get the path to external data directory for Android
197#[cfg(target_os = "android")]
198pub fn get_external_path(sk_info: &Option<Rc<RefCell<SkInfo>>>) -> Option<PathBuf> {
199    if sk_info.is_none() {
200        Log::err("get_external_path, sk_info is None");
201        return None;
202    }
203
204    let sk_i = sk_info.as_ref().unwrap().borrow_mut();
205    let app = sk_i.get_android_app();
206    app.external_data_path()
207}
208
209/// Get the path to external data directory for non android (assets)
210#[cfg(not(target_os = "android"))]
211pub fn get_external_path(_sk_info: &Option<Rc<RefCell<SkInfo>>>) -> Option<PathBuf> {
212    use std::env;
213
214    let path_assets = env::current_dir().unwrap().join(get_assets_dir());
215    Some(path_assets)
216}
217
218/// Open an asset like a file
219#[cfg(target_os = "android")]
220pub fn open_asset(sk_info: &Option<Rc<RefCell<SkInfo>>>, asset_path: impl AsRef<Path>) -> Option<File> {
221    use std::ffi::CString;
222
223    if sk_info.is_none() {
224        Log::err("open_asset, sk_info is None");
225        return None;
226    }
227
228    let sk_i = sk_info.as_ref().unwrap().borrow_mut();
229    let app = sk_i.get_android_app();
230
231    if let Ok(cstring) = CString::new(asset_path.as_ref().to_str().unwrap_or("Error!!!")) {
232        if let Some(asset) = app.asset_manager().open(cstring.as_c_str()) {
233            if let Ok(o_file_desc) = asset.open_file_descriptor() {
234                Some(File::from(o_file_desc.fd))
235            } else {
236                Log::err(format!("open_asset, {:?} cannot get a new file_descriptor", asset_path.as_ref()));
237                None
238            }
239        } else {
240            Log::err(format!("open_asset, path {:?} cannot be a opened", asset_path.as_ref()));
241            None
242        }
243    } else {
244        Log::err(format!("open_asset, path {:?} cannot be a cstring", asset_path.as_ref()));
245        None
246    }
247}
248
249/// Open an asset like a file
250/// * `sk_info` - The SkInfo smart pointer
251/// * `asset_path` - The path to the asset.
252///
253/// Returns a File if the asset was opened successfully, None otherwise.
254///
255/// ### Examples
256/// ```
257/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
258/// use stereokit_rust::tools::os_api::open_asset;
259/// use std::io::Read;
260///
261/// let sk_info  = Some(sk.get_sk_info_clone());
262///
263/// let asset_path = "textures/readme.md";
264///
265/// let mut file = open_asset(&sk_info, asset_path).expect("File readme should be opened");
266///
267/// let mut buffer = String::new();
268/// file.read_to_string(&mut buffer).expect("File readme should be read");
269/// assert!(buffer.starts_with("# Images"));
270/// ```
271#[cfg(not(target_os = "android"))]
272pub fn open_asset(_sk_info: &Option<Rc<RefCell<SkInfo>>>, asset_path: impl AsRef<Path>) -> Option<File> {
273    use std::env;
274
275    let path_assets = env::current_dir().unwrap().join(get_assets_dir());
276    let path_asset = path_assets.join(asset_path);
277    File::open(path_asset).ok()
278}
279
280/// Open and read an asset like a file
281#[cfg(target_os = "android")]
282pub fn read_asset(sk_info: &Option<Rc<RefCell<SkInfo>>>, asset_path: impl AsRef<Path>) -> Option<Vec<u8>> {
283    use std::ffi::CString;
284
285    if sk_info.is_none() {
286        Log::err("open_asset, sk_info is None");
287        return None;
288    }
289
290    let sk_i = sk_info.as_ref().unwrap().borrow_mut();
291    let app = sk_i.get_android_app();
292
293    if let Ok(cstring) = CString::new(asset_path.as_ref().to_str().unwrap_or("Error!!!")) {
294        if let Some(mut asset) = app.asset_manager().open(cstring.as_c_str()) {
295            if let Ok(o_buffer) = asset.buffer() {
296                Some(o_buffer.to_vec())
297            } else {
298                Log::err(format!("open_asset, {:?} cannot get the buffer", asset_path.as_ref()));
299                None
300            }
301        } else {
302            Log::err(format!("open_asset, path {:?} cannot be a opened", asset_path.as_ref()));
303            None
304        }
305    } else {
306        Log::err(format!("open_asset, path {:?} cannot be a cstring", asset_path.as_ref()));
307        None
308    }
309}
310
311/// Open and read an asset like a file
312/// * `sk_info` - The SkInfo smart pointer
313/// * `asset_path` - The path to the asset.
314///
315/// Returns a File if the asset was opened successfully, None otherwise.
316///
317/// ### Examples
318/// ```
319/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
320/// use stereokit_rust::tools::os_api::read_asset;
321/// use std::io::Read;
322///
323/// let sk_info  = Some(sk.get_sk_info_clone());
324///
325/// let asset_path = "textures/readme.md";
326///
327/// let buffer = read_asset(&sk_info, asset_path).expect("File readme should be readable");
328/// assert!(buffer.starts_with(b"# Images"));
329/// ```
330#[cfg(not(target_os = "android"))]
331pub fn read_asset(_sk_info: &Option<Rc<RefCell<SkInfo>>>, asset_path: impl AsRef<Path>) -> Option<Vec<u8>> {
332    use std::{env, io::Read};
333
334    let path_assets = env::current_dir().unwrap().join(get_assets_dir());
335    let path_asset = path_assets.join(&asset_path);
336    let mut fd = match File::open(path_asset).ok() {
337        Some(file) => file,
338        None => {
339            Log::err(format!("open_asset, path {:?} cannot be opened", asset_path.as_ref()));
340            return None;
341        }
342    };
343    let mut o_buffer = vec![];
344    match fd.read_to_end(&mut o_buffer) {
345        Ok(_) => Some(o_buffer),
346        Err(err) => {
347            Log::err(format!("open_asset, path {:?} cannot be read: {}", asset_path.as_ref(), err));
348            None
349        }
350    }
351}
352
353/// Read the files and eventually the sub directory of a given directory
354/// * `_sk_info` - The SkInfo smart pointer
355/// * `dir` - The directory to read.
356/// * `file_extensions` - The file extensions to filter by.
357/// * `show_other_dirs` - If true, the sub directories will be shown.
358///
359/// Returns a vector of PathEntry.
360/// ### Examples
361/// ```
362/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
363/// use stereokit_rust::tools::os_api::{get_files, PathEntry};
364///
365/// let sk_info  = Some(sk.get_sk_info_clone());
366///
367/// let mut file_found = false;
368/// let mut dir_found = false;
369/// let exts = vec![".png".into(), ".jpg".into(), ".jpeg".into()];
370/// let mut asset_sub_dir = std::path::PathBuf::new();
371/// asset_sub_dir.push("assets/textures");
372/// for file in get_files(&sk_info, asset_sub_dir, &exts, true) {
373///     println!("--- {:?}", file);
374///     match file {
375///         PathEntry::File(file) => {
376///             if file.into_string().unwrap_or_default()
377///                    .ends_with("log_viewer.jpeg") { file_found = true }
378///         }
379///         PathEntry::Dir(dir) => {
380///             if dir.into_string().unwrap_or_default()
381///                   .ends_with("water") { dir_found = true }
382///         }
383///     }
384/// }
385/// assert!(file_found);
386/// assert!(dir_found);
387/// ```
388pub fn get_files(
389    _sk_info: &Option<Rc<RefCell<SkInfo>>>,
390    dir: PathBuf,
391    file_extensions: &Vec<String>,
392    show_sub_dirs: bool,
393) -> Vec<PathEntry> {
394    use std::fs::read_dir;
395    let mut exts = vec![];
396    for extension in file_extensions {
397        let extension = extension[1..].to_string();
398        exts.push(OsString::from(extension));
399    }
400    let mut vec = vec![];
401
402    if dir.exists() && dir.is_dir() {
403        if let Ok(read_dir) = read_dir(dir) {
404            for file in read_dir.flatten() {
405                let path = file.path();
406
407                if file.path().is_file() {
408                    if exts.is_empty() {
409                        vec.push(PathEntry::File(file.file_name()))
410                    } else if let Some(extension) = path.extension() {
411                        if exts.is_empty() || exts.contains(&extension.to_os_string()) {
412                            vec.push(PathEntry::File(file.file_name()))
413                        }
414                    }
415                } else if show_sub_dirs && file.path().is_dir() {
416                    vec.push(PathEntry::Dir(file.file_name()))
417                }
418            }
419        }
420    }
421    vec
422}
423
424/// Open winit IME keyboard. Does nothing on Quest
425#[cfg(target_os = "android")]
426pub fn show_soft_input_ime(sk_info: &Option<Rc<RefCell<SkInfo>>>, show: bool) -> bool {
427    if sk_info.is_none() {
428        Log::err("show_soft_input_ime, sk_info is None");
429        return false;
430    }
431
432    let sk_i = sk_info.as_ref().unwrap().borrow_mut();
433    let app = sk_i.get_android_app();
434    if show {
435        app.show_soft_input(false);
436    } else {
437        app.hide_soft_input(false);
438    }
439    true
440}
441/// Open nothing has we don't have a winit IME keyboard
442#[cfg(not(target_os = "android"))]
443pub fn show_soft_input_ime(_sk_info: &Option<Rc<RefCell<SkInfo>>>, _show: bool) -> bool {
444    false
445}
446
447/// Open Android IMS keyboard. This doesn't work for accentuated characters.
448#[cfg(target_os = "android")]
449pub fn show_soft_input(show: bool) -> bool {
450    use jni::objects::JValue;
451
452    let ctx = ndk_context::android_context();
453    let vm = match unsafe { jni::JavaVM::from_raw(ctx.vm() as _) } {
454        Ok(value) => value,
455        Err(e) => {
456            Log::err(format!("virtual_kbd : no vm !! : {:?}", e));
457            return false;
458        }
459    };
460    let activity = unsafe { jni::objects::JObject::from_raw(ctx.context() as _) };
461    let mut env = match vm.attach_current_thread() {
462        Ok(value) => value,
463        Err(e) => {
464            Log::err(format!("virtual_kbd : no env !! : {:?}", e));
465            return false;
466        }
467    };
468
469    let class_ctxt = match env.find_class("android/content/Context") {
470        Ok(value) => value,
471        Err(e) => {
472            Log::err(format!("virtual_kbd : no class_ctxt !! : {:?}", e));
473            return false;
474        }
475    };
476    let ims = match env.get_static_field(class_ctxt, "INPUT_METHOD_SERVICE", "Ljava/lang/String;") {
477        Ok(value) => value,
478        Err(e) => {
479            Log::err(format!("virtual_kbd : no ims !! : {:?}", e));
480            return false;
481        }
482    };
483
484    let im_manager = match env
485        .call_method(&activity, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", &[ims.borrow()])
486        .unwrap()
487        .l()
488    {
489        Ok(value) => value,
490        Err(e) => {
491            Log::err(format!("virtual_kbd : no im_manager !! : {:?}", e));
492            return false;
493        }
494    };
495
496    let jni_window = match env.call_method(&activity, "getWindow", "()Landroid/view/Window;", &[]).unwrap().l() {
497        Ok(value) => value,
498        Err(e) => {
499            Log::err(format!("virtual_kbd : no jni_window !! : {:?}", e));
500            return false;
501        }
502    };
503
504    let view = match env.call_method(jni_window, "getDecorView", "()Landroid/view/View;", &[]).unwrap().l() {
505        Ok(value) => value,
506        Err(e) => {
507            Log::err(format!("virtual_kbd : no view !! : {:?}", e));
508            return false;
509        }
510    };
511
512    if show {
513        let result = env
514            .call_method(im_manager, "showSoftInput", "(Landroid/view/View;I)Z", &[JValue::Object(&view), 0i32.into()])
515            .unwrap()
516            .z()
517            .unwrap();
518        result
519    } else {
520        let window_token = env.call_method(view, "getWindowToken", "()Landroid/os/IBinder;", &[]).unwrap().l().unwrap();
521        let jvalue_window_token = jni::objects::JValueGen::Object(&window_token);
522
523        let result = env
524            .call_method(
525                im_manager,
526                "hideSoftInputFromWindow",
527                "(Landroid/os/IBinder;I)Z",
528                &[jvalue_window_token, 0i32.into()],
529            )
530            .unwrap()
531            .z()
532            .unwrap();
533        result
534    }
535}
536
537/// Open nothing has we don't have a virtual keyboard
538#[cfg(not(target_os = "android"))]
539pub fn show_soft_input(_show: bool) -> bool {
540    false
541}
542
543pub const USUAL_FPS_SUSPECTS: [i32; 12] = [30, 60, 72, 80, 90, 100, 110, 120, 144, 165, 240, 360];
544
545/// Return and maybe Log all the display refresh rates available.
546/// * `with_log` - If true, log the refresh rates available
547///
548/// ### Examples
549/// ```
550/// use stereokit_rust::system::BackendOpenXR;
551/// BackendOpenXR::request_ext("XR_FB_display_refresh_rate");
552///
553/// stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
554///
555/// use stereokit_rust::tools::os_api::{get_all_display_refresh_rates,
556///                                     get_display_refresh_rate,
557///                                     set_display_refresh_rate};
558///
559/// let refresh_rate_editable = BackendOpenXR::ext_enabled("XR_FB_display_refresh_rate");
560/// if refresh_rate_editable {
561///     let rates = get_all_display_refresh_rates(true);
562///     assert!(!rates.is_empty());
563///     let rate = get_display_refresh_rate().unwrap_or(0.0);
564///     assert!(rate >= 20.0);
565///     assert!(set_display_refresh_rate(60.0, true));
566///     let rate = get_display_refresh_rate().unwrap_or(0.0);
567///     assert_eq!(rate, 60.0);
568/// } else {
569///     let rates = get_all_display_refresh_rates(true);
570///     // assert!(rates.len(), 5); // with 5 value 0.0
571///     let rate = get_display_refresh_rate();
572///     assert_eq!(rate , None);
573///     assert_eq!(set_display_refresh_rate(60.0, true), false);
574/// }
575/// ```
576pub fn get_all_display_refresh_rates(with_log: bool) -> Vec<f32> {
577    let mut array = [0.0; 40];
578    let mut count = 5u32;
579    if BackendOpenXR::ext_enabled("XR_FB_display_refresh_rate") {
580        if let Some(rate_display) =
581            BackendOpenXR::get_function::<EnumerateDisplayRefreshRatesFB>("xrEnumerateDisplayRefreshRatesFB")
582        {
583            match unsafe {
584                rate_display(Session::from_raw(BackendOpenXR::session()), 0, &mut count, array.as_mut_ptr())
585            } {
586                Result::SUCCESS => {
587                    if count > 40 {
588                        count = 40
589                    }
590                    match unsafe {
591                        rate_display(Session::from_raw(BackendOpenXR::session()), count, &mut count, array.as_mut_ptr())
592                    } {
593                        Result::SUCCESS => {
594                            if with_log {
595                                Log::info(format!("There are {count} display rate:"));
596                                for (i, iter) in array.iter().enumerate() {
597                                    if i >= count as usize {
598                                        break;
599                                    }
600                                    Log::info(format!("   {iter:?} "));
601                                }
602                            }
603                        }
604                        otherwise => {
605                            Log::err(format!("xrEnumerateDisplayRefreshRatesFB failed: {otherwise}"));
606                        }
607                    }
608                }
609                otherwise => {
610                    Log::err(format!("xrEnumerateDisplayRefreshRatesFB failed: {otherwise}"));
611                }
612            }
613        } else {
614            Log::err("xrEnumerateDisplayRefreshRatesFB binding function error !")
615        }
616    }
617    array[0..(count as usize)].into()
618}
619
620/// Get the display rates available from the given list. See [`USUAL_FPS_SUSPECTS`])
621/// * `fps_to_get` - The list of fps to test.
622/// * `with_log` - If true, will log the available rates.
623///
624/// see also [`get_all_display_refresh_rates`]
625pub fn get_display_refresh_rates(fps_to_get: &[i32], with_log: bool) -> Vec<f32> {
626    let default_refresh_rate = get_display_refresh_rate();
627    let mut available_rates = vec![];
628    for rate in fps_to_get {
629        if set_display_refresh_rate(*rate as f32, false) {
630            available_rates.push(*rate as f32);
631        }
632    }
633    if let Some(rate) = default_refresh_rate {
634        set_display_refresh_rate(rate, with_log);
635    }
636    if with_log {
637        Log::info(format!("There are {} display rate from the given selection:", available_rates.len()));
638        for iter in &available_rates {
639            Log::info(format!("   {iter:?} "));
640        }
641    }
642
643    available_rates
644}
645
646/// Get the current display rate if possible.
647///
648/// see also [`set_display_refresh_rate`]
649/// see example in [`get_all_display_refresh_rates`]
650pub fn get_display_refresh_rate() -> Option<f32> {
651    if BackendOpenXR::ext_enabled("XR_FB_display_refresh_rate") {
652        if let Some(get_default_rate) =
653            BackendOpenXR::get_function::<GetDisplayRefreshRateFB>("xrGetDisplayRefreshRateFB")
654        {
655            let mut default_rate = 0.0;
656            match unsafe { get_default_rate(Session::from_raw(BackendOpenXR::session()), &mut default_rate) } {
657                Result::SUCCESS => Some(default_rate),
658                otherwise => {
659                    Log::err(format!("xrGetDisplayRefreshRateFB failed: {otherwise}"));
660                    None
661                }
662            }
663        } else {
664            Log::err("xrRequestDisplayRefreshRateFB binding function error !");
665            None
666        }
667    } else {
668        None
669    }
670}
671
672/// Set the current display rate if possible.
673/// Possible values on Quest and WiVRn are 60 - 80 - 72 - 80 - 90 - 120
674/// returns true if the given value was accepted
675/// * `rate` - the rate to set
676/// * `with_log` - if true, will log the error if the rate was not accepted.
677///
678/// see example in [`get_all_display_refresh_rates`]
679pub fn set_display_refresh_rate(rate: f32, with_log: bool) -> bool {
680    if BackendOpenXR::ext_enabled("XR_FB_display_refresh_rate") {
681        //>>>>>>>>>>> Set the value
682        if let Some(set_new_rate) =
683            BackendOpenXR::get_function::<RequestDisplayRefreshRateFB>("xrRequestDisplayRefreshRateFB")
684        {
685            match unsafe { set_new_rate(Session::from_raw(BackendOpenXR::session()), rate) } {
686                Result::SUCCESS => true,
687                otherwise => {
688                    if with_log {
689                        Log::err(format!("xrRequestDisplayRefreshRateFB failed: {otherwise}"));
690                    }
691                    false
692                }
693            }
694        } else {
695            Log::err("xrRequestDisplayRefreshRateFB binding function error !");
696            false
697        }
698    } else {
699        false
700    }
701}
702
703/// Get the list of environnement blend_modes available on this device.
704/// * `with_log` - if true, will log the available blend modes.
705///
706/// see also [`crate::util::Device::valid_blend`]
707///
708/// ### Examples
709/// ```
710/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
711///
712/// use stereokit_rust::{tools::os_api::get_env_blend_modes, util::Device, sk::DisplayBlend};
713/// use openxr_sys::EnvironmentBlendMode;
714///
715/// let blend_modes = get_env_blend_modes(true);
716/// if blend_modes.len() > 0 {
717///     assert!(blend_modes.contains(&EnvironmentBlendMode::OPAQUE));
718///     if blend_modes.contains(&EnvironmentBlendMode::ADDITIVE)
719///     || blend_modes.contains(&EnvironmentBlendMode::ALPHA_BLEND)
720///     {
721///        println!("Passthrough available !!");
722///        // we can activate passthrough:
723///        Device::display_blend(DisplayBlend::AnyTransparent);
724///     }
725/// } else {
726///     // Simplest way to check if passthrough is available:
727///     assert_eq!(Device::valid_blend(DisplayBlend::AnyTransparent), false);
728/// }
729///
730/// test_steps!( // !!!! Get a proper main loop !!!!
731///     if iter == 0 {
732///         // activate passthrough if available
733///         Device::display_blend(DisplayBlend::AnyTransparent);
734///     } else if iter == 1 {
735///         // deactivate passthrough
736///         Device::display_blend(DisplayBlend::Opaque);
737///     }
738/// );
739/// ```
740pub fn get_env_blend_modes(with_log: bool) -> Vec<EnvironmentBlendMode> {
741    //>>>>>>>>>>> Get the env blend mode
742    let mut count = 0u32;
743    let mut modes = [EnvironmentBlendMode::OPAQUE; 20];
744    if Backend::xr_type() != BackendXRType::OpenXR {
745        return vec![];
746    }
747    if let Some(get_modes) =
748        BackendOpenXR::get_function::<EnumerateEnvironmentBlendModes>("xrEnumerateEnvironmentBlendModes")
749    {
750        match unsafe {
751            get_modes(
752                Instance::from_raw(BackendOpenXR::instance()),
753                SystemId::from_raw(BackendOpenXR::system_id()),
754                ViewConfigurationType::PRIMARY_STEREO,
755                0,
756                &mut count,
757                modes.as_mut_ptr(),
758            )
759        } {
760            Result::SUCCESS => {
761                if with_log {
762                    if count > 20 {
763                        count = 20
764                    }
765                    match unsafe {
766                        get_modes(
767                            Instance::from_raw(BackendOpenXR::instance()),
768                            SystemId::from_raw(BackendOpenXR::system_id()),
769                            ViewConfigurationType::PRIMARY_STEREO,
770                            count,
771                            &mut count,
772                            modes.as_mut_ptr(),
773                        )
774                    } {
775                        Result::SUCCESS => {
776                            if with_log {
777                                Log::info(format!("There are {count} env blend modes:"));
778                                for (i, iter) in modes.iter().enumerate() {
779                                    if i >= count as usize {
780                                        break;
781                                    }
782                                    Log::info(format!("   {iter:?} "));
783                                }
784                            }
785                        }
786                        otherwise => {
787                            if with_log {
788                                Log::err(format!("xrEnumerateEnvironmentBlendModes failed: {otherwise}"));
789                            }
790                        }
791                    }
792                }
793            }
794            otherwise => {
795                if with_log {
796                    Log::err(format!("xrEnumerateEnvironmentBlendModes failed: {otherwise}"));
797                }
798            }
799        }
800    } else {
801        Log::err("xrEnumerateEnvironmentBlendModes binding function error !");
802    }
803    modes[0..(count as usize)].into()
804}