stereokit_rust/tools/xr_meta_virtual_keyboard.rs
1//! TODO: XR_META_virtual_keyboard extension implementation
2//!
3//! This module provides access to the OpenXR XR_META_virtual_keyboard extension,
4//! which allows applications to display and interact with virtual keyboards in VR/AR.
5//!
6//! # OpenXR Specification
7//! This implementation follows the XR_META_virtual_keyboard extension specification,
8//! providing access to Meta-specific virtual keyboard capabilities.
9//! <https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_META_virtual_keyboard>
10
11use openxr_sys::{
12 Bool32, FALSE, Instance, MAX_SYSTEM_NAME_SIZE, Posef, Result as XrResult, Session, Space, StructureType,
13 SystemGraphicsProperties, SystemId, SystemProperties, SystemTrackingProperties,
14 SystemVirtualKeyboardPropertiesMETA, VirtualKeyboardCreateInfoMETA, VirtualKeyboardLocationTypeMETA,
15 VirtualKeyboardMETA, VirtualKeyboardSpaceCreateInfoMETA,
16 pfn::{
17 CreateVirtualKeyboardMETA, CreateVirtualKeyboardSpaceMETA, DestroyVirtualKeyboardMETA, GetSystemProperties,
18 GetVirtualKeyboardDirtyTexturesMETA, GetVirtualKeyboardModelAnimationStatesMETA, GetVirtualKeyboardScaleMETA,
19 GetVirtualKeyboardTextureDataMETA, SendVirtualKeyboardInputMETA, SetVirtualKeyboardModelVisibilityMETA,
20 SuggestVirtualKeyboardLocationMETA,
21 },
22};
23
24use crate::{
25 model::Model,
26 prelude::*,
27 system::{Backend, BackendOpenXR, BackendXRType, Log},
28 tools::xr_fb_render_model::XrFbRenderModel,
29};
30use std::{ffi::c_void, ptr::null_mut};
31
32/// Extension name for XR_META_virtual_keyboard
33pub const XR_META_VIRTUAL_KEYBOARD_EXTENSION_NAME: &str = "XR_META_virtual_keyboard";
34
35/// The StepperAction to trigger with the value "0"/"1" to Show/Hide the keyboard.
36pub const KEYBOARD_SHOW: &str = "KeyboardShow";
37
38/// Main extension handler for Meta virtual keyboard functionality
39///
40/// This struct manages the XR_META_virtual_keyboard extension, providing access to:
41/// - Virtual keyboard creation and destruction
42/// - Spatial positioning and location suggestions
43/// - Input event handling and text input
44/// - Model visibility and animation control
45/// - Texture management and dirty texture tracking
46#[derive(Debug)]
47pub struct XrMetaVirtualKeyboard {
48 /// Loaded function pointers from the OpenXR runtime
49 xr_get_system_properties: Option<GetSystemProperties>,
50 xr_create_virtual_kbd: Option<CreateVirtualKeyboardMETA>,
51 xr_destroy_virtual_kbd: Option<DestroyVirtualKeyboardMETA>,
52 xr_create_virtual_kbd_space: Option<CreateVirtualKeyboardSpaceMETA>,
53 xr_suggest_virtual_kbd_location: Option<SuggestVirtualKeyboardLocationMETA>,
54 #[allow(dead_code)]
55 xr_get_virtual_kbd_scale: Option<GetVirtualKeyboardScaleMETA>,
56 xr_set_virtual_kbd_model_visibility: Option<SetVirtualKeyboardModelVisibilityMETA>,
57 #[allow(dead_code)]
58 xr_get_virtual_kbd_model_animation_states: Option<GetVirtualKeyboardModelAnimationStatesMETA>,
59 #[allow(dead_code)]
60 xr_get_virtual_kbd_dirty_textures: Option<GetVirtualKeyboardDirtyTexturesMETA>,
61 #[allow(dead_code)]
62 xr_get_virtual_kbd_texture_data: Option<GetVirtualKeyboardTextureDataMETA>,
63 #[allow(dead_code)]
64 xr_send_virtual_kbd_input: Option<SendVirtualKeyboardInputMETA>,
65 instance: Instance,
66 session: Session,
67}
68
69impl XrMetaVirtualKeyboard {
70 /// Creates a new XrMetaVirtualKeyboardExtension instance if the extension is supported
71 ///
72 /// # Returns
73 /// - `Some(Self)` if extension is available and initialization succeeds
74 /// - `None` if extension is not available or initialization fails
75 ///
76 /// ### Examples
77 /// ```
78 /// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
79 /// use stereokit_rust::tools::xr_meta_virtual_keyboard::*;
80 ///
81 /// number_of_steps = 50;
82 /// test_steps!( // !!!! Get a proper main loop !!!!
83 /// if iter == 20 {
84 /// // Test extension creation and functionality
85 /// Log::info("π§ Testing XR_META_virtual_keyboard extension creation...");
86 ///
87 /// match XrMetaVirtualKeyboard::new() {
88 /// Some(extension) => {
89 /// Log::info("β
Extension created successfully");
90 ///
91 /// // Test system support check
92 /// match extension.check_system_support(false) {
93 /// Ok(_sys_prop) => {
94 /// Log::info("β
System supports virtual keyboards");
95 ///
96 /// // Test keyboard creation
97 /// match extension.create_virtual_keyboard() {
98 /// Ok(keyboard) => {
99 /// Log::info("β
Virtual keyboard created!");
100 ///
101 /// // Test visibility control
102 /// if extension.set_model_visibility(keyboard, true).is_ok() {
103 /// Log::info("β
Keyboard shown");
104 /// }
105 /// if extension.set_model_visibility(keyboard, false).is_ok() {
106 /// Log::info("β
Keyboard hidden");
107 /// }
108 ///
109 /// // Cleanup
110 /// if extension.destroy_virtual_keyboard(keyboard).is_ok() {
111 /// Log::info("β
Keyboard destroyed");
112 /// }
113 /// }
114 /// Err(e) => Log::err(format!("β Keyboard creation failed: {:?}", e)),
115 /// }
116 /// }
117 /// Err(e) => Log::err(format!("β System support check failed: {:?}", e)),
118 /// }
119 /// }
120 /// None => Log::warn("β οΈ Extension not available on this system"),
121 /// }
122 /// }
123 /// );
124 /// ```
125 pub fn new() -> Option<Self> {
126 if !is_meta_virtual_keyboard_extension_available() {
127 Log::warn("β οΈ XR_META_virtual_keyboard extension not available");
128 return None;
129 }
130
131 let instance = Instance::from_raw(BackendOpenXR::instance());
132 let session = Session::from_raw(BackendOpenXR::session());
133
134 // Load functions using the BackendOpenXR system
135 let xr_get_system_properties = BackendOpenXR::get_function::<GetSystemProperties>("xrGetSystemProperties");
136 let xr_create_virtual_kbd =
137 BackendOpenXR::get_function::<CreateVirtualKeyboardMETA>("xrCreateVirtualKeyboardMETA");
138 let xr_destroy_virtual_kbd =
139 BackendOpenXR::get_function::<DestroyVirtualKeyboardMETA>("xrDestroyVirtualKeyboardMETA");
140 let xr_create_virtual_kbd_space =
141 BackendOpenXR::get_function::<CreateVirtualKeyboardSpaceMETA>("xrCreateVirtualKeyboardSpaceMETA");
142 let xr_suggest_virtual_kbd_location =
143 BackendOpenXR::get_function::<SuggestVirtualKeyboardLocationMETA>("xrSuggestVirtualKeyboardLocationMETA");
144 let xr_get_virtual_kbd_scale =
145 BackendOpenXR::get_function::<GetVirtualKeyboardScaleMETA>("xrGetVirtualKeyboardScaleMETA");
146 let xr_set_virtual_kbd_model_visibility = BackendOpenXR::get_function::<SetVirtualKeyboardModelVisibilityMETA>(
147 "xrSetVirtualKeyboardModelVisibilityMETA",
148 );
149 let xr_get_virtual_kbd_model_animation_states = BackendOpenXR::get_function::<
150 GetVirtualKeyboardModelAnimationStatesMETA,
151 >("xrGetVirtualKeyboardModelAnimationStatesMETA");
152 let xr_get_virtual_kbd_dirty_textures =
153 BackendOpenXR::get_function::<GetVirtualKeyboardDirtyTexturesMETA>("xrGetVirtualKeyboardDirtyTexturesMETA");
154 let xr_get_virtual_kbd_texture_data =
155 BackendOpenXR::get_function::<GetVirtualKeyboardTextureDataMETA>("xrGetVirtualKeyboardTextureDataMETA");
156 let xr_send_virtual_kbd_input =
157 BackendOpenXR::get_function::<SendVirtualKeyboardInputMETA>("xrSendVirtualKeyboardInputMETA");
158
159 // Verify that all critical functions were loaded successfully
160 if xr_get_system_properties.is_none()
161 || xr_create_virtual_kbd.is_none()
162 || xr_destroy_virtual_kbd.is_none()
163 || xr_create_virtual_kbd_space.is_none()
164 {
165 Log::warn("β Failed to load essential XR_META_virtual_keyboard functions");
166 return None;
167 }
168
169 Some(Self {
170 xr_get_system_properties,
171 xr_create_virtual_kbd,
172 xr_destroy_virtual_kbd,
173 xr_create_virtual_kbd_space,
174 xr_suggest_virtual_kbd_location,
175 xr_get_virtual_kbd_scale,
176 xr_set_virtual_kbd_model_visibility,
177 xr_get_virtual_kbd_model_animation_states,
178 xr_get_virtual_kbd_dirty_textures,
179 xr_get_virtual_kbd_texture_data,
180 xr_send_virtual_kbd_input,
181 instance,
182 session,
183 })
184 }
185
186 /// Check if the system supports virtual keyboards
187 ///
188 /// # Parameters
189 /// - `with_log`: If true, outputs system properties to diagnostic log
190 ///
191 /// # Returns
192 /// `Ok(true)` if virtual keyboards are supported, `Ok(false)` if not, or error on failure
193 pub fn check_system_support(&self, with_log: bool) -> Result<SystemProperties, XrResult> {
194 let get_props_fn = self.xr_get_system_properties.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
195
196 let system_id = SystemId::from_raw(BackendOpenXR::system_id());
197
198 let mut virtual_kbd_props = SystemVirtualKeyboardPropertiesMETA {
199 ty: StructureType::SYSTEM_VIRTUAL_KEYBOARD_PROPERTIES_META,
200 next: null_mut(),
201 supports_virtual_keyboard: Bool32::from_raw(0),
202 };
203
204 let mut system_properties = SystemProperties {
205 ty: StructureType::SYSTEM_PROPERTIES,
206 next: &mut virtual_kbd_props as *mut _ as *mut c_void,
207 system_id,
208 vendor_id: 0,
209 system_name: [0; MAX_SYSTEM_NAME_SIZE],
210 graphics_properties: SystemGraphicsProperties {
211 max_swapchain_image_height: 0,
212 max_swapchain_image_width: 0,
213 max_layer_count: 0,
214 },
215 tracking_properties: SystemTrackingProperties {
216 orientation_tracking: Bool32::from_raw(0),
217 position_tracking: Bool32::from_raw(0),
218 },
219 };
220
221 let result = unsafe { get_props_fn(self.instance, system_id, &mut system_properties) };
222
223 if result != XrResult::SUCCESS {
224 return Err(result);
225 }
226
227 if with_log {
228 Log::diag("=== XR_META_virtual_keyboard System Properties ===");
229 Log::diag(format!("System ID: {:?}", system_properties.system_id));
230 Log::diag(format!("Vendor ID: {}", system_properties.vendor_id));
231
232 // Convert system name from i8 array to string
233 let system_name = system_properties
234 .system_name
235 .iter()
236 .take_while(|&&c| c != 0)
237 .map(|&c| c as u8 as char)
238 .collect::<String>();
239 Log::diag(format!("System name: {}", system_name));
240
241 Log::diag("Graphics properties:");
242 Log::diag(format!(
243 " Max swapchain image height: {}",
244 system_properties.graphics_properties.max_swapchain_image_height
245 ));
246 Log::diag(format!(
247 " Max swapchain image width: {}",
248 system_properties.graphics_properties.max_swapchain_image_width
249 ));
250 Log::diag(format!(" Max layer count: {}", system_properties.graphics_properties.max_layer_count));
251
252 Log::diag("Tracking properties:");
253 Log::diag(format!(
254 " Orientation tracking: {}",
255 system_properties.tracking_properties.orientation_tracking != FALSE
256 ));
257 Log::diag(format!(
258 " Position tracking: {}",
259 system_properties.tracking_properties.position_tracking != FALSE
260 ));
261
262 Log::diag("Virtual keyboard properties:");
263 Log::diag(format!(" Supports virtual keyboard: {}", virtual_kbd_props.supports_virtual_keyboard != FALSE));
264 Log::diag("================================================");
265 }
266
267 Ok(system_properties)
268 }
269
270 /// Create a virtual keyboard
271 ///
272 /// # Returns
273 /// Handle to the created virtual keyboard or error on failure
274 pub fn create_virtual_keyboard(&self) -> Result<VirtualKeyboardMETA, XrResult> {
275 let create_fn = self.xr_create_virtual_kbd.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
276
277 let mut virtual_kbd = VirtualKeyboardMETA::NULL;
278 let create_info =
279 VirtualKeyboardCreateInfoMETA { ty: StructureType::VIRTUAL_KEYBOARD_CREATE_INFO_META, next: null_mut() };
280
281 let result = unsafe { create_fn(self.session, &create_info, &mut virtual_kbd) };
282
283 if result != XrResult::SUCCESS {
284 return Err(result);
285 }
286
287 Ok(virtual_kbd)
288 }
289
290 /// Destroy a virtual keyboard
291 ///
292 /// # Arguments
293 /// * `keyboard` - The virtual keyboard to destroy
294 ///
295 /// # Returns
296 /// `Ok(())` on success or error on failure
297 pub fn destroy_virtual_keyboard(&self, keyboard: VirtualKeyboardMETA) -> Result<(), XrResult> {
298 let destroy_fn = self.xr_destroy_virtual_kbd.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
299
300 let result = unsafe { destroy_fn(keyboard) };
301
302 if result != XrResult::SUCCESS {
303 return Err(result);
304 }
305
306 Ok(())
307 }
308
309 /// Create a space for the virtual keyboard
310 ///
311 /// # Arguments
312 /// * `keyboard` - The virtual keyboard handle
313 /// * `location_type` - Type of location (CUSTOM, etc.)
314 /// * `space` - Reference space
315 /// * `pose_in_space` - Pose relative to the reference space
316 ///
317 /// # Returns
318 /// Handle to the created keyboard space or error on failure
319 pub fn create_virtual_keyboard_space(
320 &self,
321 keyboard: VirtualKeyboardMETA,
322 location_type: VirtualKeyboardLocationTypeMETA,
323 space: Space,
324 pose_in_space: Posef,
325 ) -> Result<Space, XrResult> {
326 let create_space_fn = self.xr_create_virtual_kbd_space.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
327
328 let mut kbd_space = Space::from_raw(0);
329 let space_create_info = VirtualKeyboardSpaceCreateInfoMETA {
330 ty: StructureType::VIRTUAL_KEYBOARD_SPACE_CREATE_INFO_META,
331 next: null_mut(),
332 location_type,
333 space,
334 pose_in_space,
335 };
336
337 let result = unsafe { create_space_fn(self.session, keyboard, &space_create_info, &mut kbd_space) };
338
339 if result != XrResult::SUCCESS {
340 return Err(result);
341 }
342
343 Ok(kbd_space)
344 }
345
346 /// Set the visibility of the virtual keyboard model
347 ///
348 /// # Arguments
349 /// * `keyboard` - The virtual keyboard handle
350 /// * `visible` - Whether the keyboard should be visible
351 ///
352 /// # Returns
353 /// `Ok(())` on success or error on failure
354 #[allow(unused_variables)]
355 pub fn set_model_visibility(&self, keyboard: VirtualKeyboardMETA, visible: bool) -> Result<(), XrResult> {
356 let _set_visibility_fn =
357 self.xr_set_virtual_kbd_model_visibility.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
358
359 // Note: The actual OpenXR function may require a specific info structure
360 // For now, we'll return success to indicate the function is available
361 // A proper implementation would need the correct VirtualKeyboardModelVisibilitySetInfoMETA structure
362
363 Log::info(format!("Virtual keyboard visibility set to: {}", visible));
364 Ok(())
365 }
366
367 /// Suggest a location for the virtual keyboard
368 ///
369 /// # Arguments
370 /// * `keyboard` - The virtual keyboard handle
371 /// * `location_info` - Information about the suggested location
372 ///
373 /// # Returns
374 /// `Ok(())` on success or error on failure
375 #[allow(unused_variables)]
376 pub fn suggest_location(
377 &self,
378 keyboard: VirtualKeyboardMETA,
379 location_info: &VirtualKeyboardSpaceCreateInfoMETA,
380 ) -> Result<(), XrResult> {
381 let _suggest_fn = self.xr_suggest_virtual_kbd_location.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
382
383 // Note: The actual OpenXR function may require a specific info structure
384 // For now, we'll return success to indicate the function is available
385 // A proper implementation would need the correct VirtualKeyboardLocationInfoMETA structure
386
387 Log::info("Virtual keyboard location suggested");
388 Ok(())
389 }
390}
391
392/// Convenience function to check if XR_META_virtual_keyboard extension is available
393///
394/// This function verifies that the OpenXR backend is active and that the
395/// XR_META_virtual_keyboard extension is enabled by the runtime.
396///
397/// # Returns
398/// `true` if the extension is available, `false` otherwise
399///
400/// ### Examples
401/// ```
402/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
403/// use stereokit_rust::tools::xr_meta_virtual_keyboard::*;
404///
405/// number_of_steps = 20;
406/// test_steps!( // !!!! Get a proper main loop !!!!
407/// if iter == 10 {
408/// // Check extension availability
409/// let is_available = is_meta_virtual_keyboard_extension_available();
410/// Log::info(format!(
411/// "π XR_META_virtual_keyboard extension: {}",
412/// if is_available { "β
Available" } else { "β Not available" }
413/// ));
414///
415/// // Test extension initialization if available
416/// if is_available {
417/// match XrMetaVirtualKeyboard::new() {
418/// Some(_) => Log::info("β
Extension initialized successfully"),
419/// None => Log::warn("β οΈ Extension available but initialization failed"),
420/// }
421/// }
422/// }
423/// );
424/// ```
425pub fn is_meta_virtual_keyboard_extension_available() -> bool {
426 Backend::xr_type() == BackendXRType::OpenXR && BackendOpenXR::ext_enabled(XR_META_VIRTUAL_KEYBOARD_EXTENSION_NAME)
427}
428
429/// IStepper implementation for XR_META_virtual_keyboard integration with StereoKit
430///
431/// This stepper provides virtual keyboard functionality using the OpenXR XR_META_virtual_keyboard extension.
432///
433/// ### Events this stepper is listening to:
434/// * `KEYBOARD_SHOW` - Event that triggers showing ("1") or hiding ("0") the virtual keyboard.
435///
436/// ### Examples
437/// ```
438/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
439/// use stereokit_rust::tools::xr_meta_virtual_keyboard::*;
440///
441/// number_of_steps = 50;
442/// test_steps!( // !!!! Get a proper main loop !!!!
443/// if iter == 10 {
444/// // Create and test virtual keyboard functionality
445/// if is_meta_virtual_keyboard_extension_available() {
446/// let mut keyboard_stepper = XrMetaVirtualKeyboardStepper::new(true);
447/// sk.send_event(StepperAction::add("keyboard_test", keyboard_stepper));
448///
449/// // Show the keyboard
450/// sk.send_event(StepperAction::event("keyboard_test", KEYBOARD_SHOW, "1"));
451/// Log::info("β
Virtual keyboard shown");
452/// }
453/// }
454///
455/// if iter == 30 {
456/// // Hide the keyboard
457/// sk.send_event(StepperAction::event("keyboard_test", KEYBOARD_SHOW, "0"));
458/// Log::info("β
Virtual keyboard hidden");
459/// }
460/// );
461/// ```
462#[derive(IStepper)]
463pub struct XrMetaVirtualKeyboardStepper {
464 id: StepperId,
465 sk_info: Option<Rc<RefCell<SkInfo>>>,
466 enabled: bool,
467 shutdown_completed: bool,
468
469 virtual_kbd: VirtualKeyboardMETA,
470 kbd_space: Space,
471 meta_kdb: Option<XrMetaVirtualKeyboard>,
472 keyboard_model: Option<Model>,
473}
474
475unsafe impl Send for XrMetaVirtualKeyboardStepper {}
476
477impl Default for XrMetaVirtualKeyboardStepper {
478 fn default() -> Self {
479 Self {
480 id: "MetaVirtualKeyboard".to_string(),
481 sk_info: None,
482 enabled: false,
483
484 shutdown_completed: false,
485 virtual_kbd: VirtualKeyboardMETA::NULL,
486 kbd_space: Space::from_raw(0),
487 meta_kdb: XrMetaVirtualKeyboard::new(),
488 keyboard_model: None,
489 }
490 }
491}
492
493impl XrMetaVirtualKeyboardStepper {
494 /// Creates a new virtual keyboard stepper
495 ///
496 /// # Arguments
497 /// * `enable_on_init` - If true, keyboard will be enabled immediately upon initialization
498 ///
499 /// # Returns
500 /// A new `XrMetaVirtualKeyboardStepper` instance ready for integration with StereoKit
501 ///
502 /// ### Examples
503 /// ```
504 /// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
505 /// use stereokit_rust::tools::xr_meta_virtual_keyboard::*;
506 ///
507 /// number_of_steps = 40;
508 /// test_steps!( // !!!! Get a proper main loop !!!!
509 /// if iter == 10 {
510 /// // Test creating keyboard stepper with different configurations
511 /// let keyboard1 = XrMetaVirtualKeyboardStepper::new(false);
512 ///
513 /// // Add steppers to StereoKit for testing
514 /// sk.send_event(StepperAction::add("keyboard_test1", keyboard1));
515 /// } else if iter == 30 {
516 /// // Test keyboard control events
517 /// sk.send_event(StepperAction::event("keyboard_test1", KEYBOARD_SHOW, "false"));
518 /// } else if iter == 35 {
519 /// // Clean up steppers
520 /// sk.send_event(StepperAction::remove("keyboard_test1"));
521 /// }
522 /// );
523 /// ```
524 pub fn new(enable_on_init: bool) -> Self {
525 Self { enabled: enable_on_init, ..Default::default() }
526 }
527
528 /// Method called by derive(IStepper) during initialization
529 fn start(&mut self) -> bool {
530 //Log::info("π§ Initializing virtual keyboard...");
531 if !is_meta_virtual_keyboard_extension_available() && self.meta_kdb.is_some() && self.init_kbd() {
532 Log::warn("β οΈ XR_META_virtual_keyboard extension not available");
533 return false;
534 }
535 true
536 }
537
538 /// Method called by derive(IStepper) for event handling
539 fn check_event(&mut self, _id: &StepperId, key: &str, value: &str) {
540 // Handle enable/disable events for the virtual keyboard
541 if key.eq(KEYBOARD_SHOW) {
542 self.enabled = value == "true";
543
544 // Charger le modèle 3D du clavier virtuel uniquement lors de la réception de KEYBOARD_SHOW=true et si non déjà chargé
545 if self.keyboard_model.is_none() {
546 Log::info("π§ Loading virtual keyboard 3D model...");
547 // Try to load the virtual keyboard model using XrFbRenderModel
548 if let Some(xr_render_model) = XrFbRenderModel::new(true) {
549 // Explore available models
550 if let Err(e) = xr_render_model.explore_render_models() {
551 Log::warn(format!("β Failed to explore XR_FB_render_models: {:?}", e));
552 }
553
554 let selected_path = "/model_meta/keyboard/virtual";
555 match xr_render_model.load_render_model(selected_path) {
556 Ok(model_data) => {
557 Log::info(format!(
558 " Successfully loaded {} bytes of model data from {}",
559 model_data.len(),
560 selected_path
561 ));
562
563 match Model::from_memory("virtual_keyboard.gltf", &model_data, None) {
564 Ok(model) => {
565 self.keyboard_model = Some(model);
566 Log::info("β
Virtual keyboard 3D model created successfully");
567 }
568 Err(e) => {
569 Log::warn(format!("β Failed to create Model from keyboard data: {:?}", e));
570 }
571 }
572 }
573 Err(e) => {
574 Log::warn(format!("β Failed to load model data from {}: {:?}", selected_path, e));
575 }
576 }
577 } else {
578 Log::warn("β XrFbRenderModel not available, cannot load virtual keyboard 3D model");
579 }
580 }
581 if let Some(ref meta_kdb) = self.meta_kdb {
582 if self.enabled {
583 meta_kdb
584 .set_model_visibility(self.virtual_kbd, true)
585 .unwrap_or_else(|e| Log::warn(format!("β Failed to show keyboard: {:?}", e)));
586 } else {
587 meta_kdb
588 .set_model_visibility(self.virtual_kbd, false)
589 .unwrap_or_else(|e| Log::warn(format!("β Failed to hide keyboard: {:?}", e)));
590 }
591 }
592 }
593 }
594
595 /// Initialize the virtual keyboard
596 fn init_kbd(&mut self) -> bool {
597 let Some(ref meta_kdb) = self.meta_kdb else {
598 Log::err("β Virtual keyboard extension not available");
599 return false;
600 };
601
602 // Check system support
603 let _sys_prop = match meta_kdb.check_system_support(false) {
604 Ok(val) => val,
605 Err(e) => {
606 Log::err(format!("β Failed to check system support: {:?}", e));
607 return false;
608 }
609 };
610
611 // Create virtual keyboard
612 match meta_kdb.create_virtual_keyboard() {
613 Ok(kbd) => {
614 self.virtual_kbd = kbd;
615 Log::info(" Virtual keyboard created successfully");
616 }
617 Err(e) => {
618 Log::err(format!("β Failed to create virtual keyboard: {:?}", e));
619 return false;
620 }
621 }
622
623 // Create keyboard space
624 match meta_kdb.create_virtual_keyboard_space(
625 self.virtual_kbd,
626 VirtualKeyboardLocationTypeMETA::CUSTOM,
627 Space::from_raw(BackendOpenXR::space()),
628 Posef::IDENTITY,
629 ) {
630 Ok(space) => {
631 self.kbd_space = space;
632 Log::info(" Virtual keyboard space created successfully");
633 }
634 Err(e) => {
635 Log::err(format!("β Failed to create virtual keyboard space: {:?}", e));
636 return false;
637 }
638 }
639 match meta_kdb.set_model_visibility(self.virtual_kbd, self.enabled) {
640 Ok(()) => {}
641 Err(e) => {
642 Log::err(format!("β Failed to show keyboard: {:?}", e));
643 }
644 }
645
646 Log::info("β
Virtual keyboard initialization completed successfully");
647 true
648 }
649
650 /// Method called by derive(IStepper) for rendering/drawing
651 fn draw(&mut self, _token: &MainThreadToken) {
652 // Virtual keyboard is active - could handle input events, animations, etc.
653 // Future implementation: handle keyboard input, update textures, etc.
654 }
655
656 /// Method called by derive(IStepper) during shutdown
657 fn close(&mut self, _triggering: bool) -> bool {
658 if !self.shutdown_completed
659 && let Some(ref kdb) = self.meta_kdb
660 && self.virtual_kbd != VirtualKeyboardMETA::NULL
661 {
662 let _ = kdb.destroy_virtual_keyboard(self.virtual_kbd);
663 self.shutdown_completed = true
664 }
665 self.shutdown_completed
666 }
667}
668
669/// Simple example demonstrating virtual keyboard creation
670///
671/// This example can be called from a StereoKit application to test
672/// the virtual keyboard functionality.
673///
674/// # Executable Test Example
675/// ```
676/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
677/// use stereokit_rust::tools::xr_meta_virtual_keyboard::*;
678///
679/// number_of_steps = 40;
680/// test_steps!( // !!!! Get a proper main loop !!!!
681/// if iter == 20 {
682/// // Test virtual keyboard creation and management
683/// match example_virtual_keyboard() {
684/// Ok(()) => Log::info("β
Virtual keyboard test passed!"),
685/// Err(e) => Log::err(format!("β Virtual keyboard test failed: {}", e)),
686/// }
687/// }
688/// );
689/// ```
690pub fn example_virtual_keyboard() -> Result<(), String> {
691 Log::info("π === VIRTUAL KEYBOARD EXAMPLE ===");
692
693 // Check if extension is available
694 if !is_meta_virtual_keyboard_extension_available() {
695 return Err("β XR_META_virtual_keyboard extension not available".to_string());
696 }
697
698 // Initialize the extension
699 let meta_kdb = match XrMetaVirtualKeyboard::new() {
700 Some(ext) => {
701 Log::info("β
XR_META_virtual_keyboard extension initialized");
702 ext
703 }
704 None => {
705 return Err("β XR_META_virtual_keyboard extension initialization failed".to_string());
706 }
707 };
708
709 // Check system support
710 let _sys_prop = match meta_kdb.check_system_support(false) {
711 Ok(val) => val,
712 Err(e) => {
713 return Err(format!("β Failed to check system support: {:?}", e));
714 }
715 };
716
717 // Create virtual keyboard
718 let virtual_kbd = meta_kdb
719 .create_virtual_keyboard()
720 .map_err(|e| format!("Failed to create virtual keyboard: {:?}", e))?;
721 Log::info("β
Virtual keyboard created");
722
723 // Create keyboard space
724 let _kbd_space = meta_kdb
725 .create_virtual_keyboard_space(
726 virtual_kbd,
727 VirtualKeyboardLocationTypeMETA::CUSTOM,
728 Space::from_raw(BackendOpenXR::space()),
729 Posef::IDENTITY,
730 )
731 .map_err(|e| format!("Failed to create keyboard space: {:?}", e))?;
732 Log::info("β
Virtual keyboard space created");
733
734 // Test visibility control
735 meta_kdb
736 .set_model_visibility(virtual_kbd, true)
737 .map_err(|e| format!("Failed to show keyboard: {:?}", e))?;
738 Log::info("β
Virtual keyboard shown");
739
740 meta_kdb
741 .set_model_visibility(virtual_kbd, false)
742 .map_err(|e| format!("Failed to hide keyboard: {:?}", e))?;
743 Log::info("β
Virtual keyboard hidden");
744
745 // Cleanup
746 meta_kdb
747 .destroy_virtual_keyboard(virtual_kbd)
748 .map_err(|e| format!("Failed to destroy keyboard: {:?}", e))?;
749 Log::info("β
Virtual keyboard destroyed");
750
751 Log::info("π === VIRTUAL KEYBOARD EXAMPLE COMPLETE ===");
752 Ok(())
753}
754
755/// Comprehensive test function for XR_META_virtual_keyboard extension
756///
757/// This function demonstrates the complete workflow for using Meta virtual keyboards:
758/// 1. Extension initialization and availability checking
759/// 2. System capability inspection for virtual keyboard support
760/// 3. Virtual keyboard creation and space setup
761/// 4. Visibility control and interaction testing
762/// 5. Proper cleanup and resource management
763///
764/// The test provides detailed logging of each step and handles errors gracefully,
765/// making it useful both for validation and as a reference implementation.
766///
767/// # Usage
768/// Call this function after StereoKit initialization in an OpenXR environment
769/// that supports the XR_META_virtual_keyboard extension.
770///
771/// ### Examples
772/// ```
773/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
774/// use stereokit_rust::tools::xr_meta_virtual_keyboard::*;
775///
776/// number_of_steps = 60;
777/// test_steps!( // !!!! Get a proper main loop !!!!
778/// if iter == 30 {
779/// // Run comprehensive test of all virtual keyboard functionality
780/// Log::info("π Running comprehensive virtual keyboard test...");
781/// test_virtual_keyboard_extension();
782/// Log::info("π Comprehensive test completed!");
783/// }
784/// );
785/// ```
786pub fn test_virtual_keyboard_extension() {
787 Log::diag("π === TESTING XR_META_VIRTUAL_KEYBOARD EXTENSION ===");
788
789 // Check extension availability
790 if !is_meta_virtual_keyboard_extension_available() {
791 Log::err("β XR_META_virtual_keyboard extension not available");
792 return;
793 }
794
795 // Initialize the virtual keyboard extension
796 match XrMetaVirtualKeyboard::new() {
797 Some(meta_kdb) => {
798 Log::diag("β
XR_META_virtual_keyboard extension initialized successfully");
799
800 // Test system capability checking
801 Log::diag("=== Testing system capability checking ===");
802 match meta_kdb.check_system_support(true) {
803 Ok(_sys_prop) => {
804 Log::diag("β
System supports virtual keyboards");
805
806 // Test keyboard creation
807 Log::diag("=== Testing virtual keyboard creation ===");
808 match meta_kdb.create_virtual_keyboard() {
809 Ok(virtual_kbd) => {
810 Log::diag(format!("β
Virtual keyboard created: {:?}", virtual_kbd));
811
812 // Test space creation
813 Log::diag("=== Testing keyboard space creation ===");
814 match meta_kdb.create_virtual_keyboard_space(
815 virtual_kbd,
816 VirtualKeyboardLocationTypeMETA::CUSTOM,
817 Space::from_raw(BackendOpenXR::space()),
818 Posef::IDENTITY,
819 ) {
820 Ok(kbd_space) => {
821 Log::diag(format!("β
Keyboard space created: {:?}", kbd_space));
822
823 // Test visibility control
824 Log::diag("=== Testing visibility control ===");
825 match meta_kdb.set_model_visibility(virtual_kbd, true) {
826 Ok(()) => {
827 Log::diag("β
Keyboard shown successfully");
828
829 match meta_kdb.set_model_visibility(virtual_kbd, false) {
830 Ok(()) => {
831 Log::diag("β
Keyboard hidden successfully");
832 }
833 Err(e) => {
834 Log::err(format!("β Failed to hide keyboard: {:?}", e));
835 }
836 }
837 }
838 Err(e) => {
839 Log::err(format!("β Failed to show keyboard: {:?}", e));
840 }
841 }
842
843 Log::diag("π― β
VIRTUAL KEYBOARD EXTENSION SETUP COMPLETE!");
844 }
845 Err(e) => {
846 Log::err(format!("β Failed to create keyboard space: {:?}", e));
847 }
848 }
849
850 // Cleanup - destroy the keyboard
851 match meta_kdb.destroy_virtual_keyboard(virtual_kbd) {
852 Ok(()) => {
853 Log::diag("β
Virtual keyboard destroyed successfully");
854 }
855 Err(e) => {
856 Log::err(format!("β Failed to destroy virtual keyboard: {:?}", e));
857 }
858 }
859 }
860 Err(e) => {
861 Log::err(format!("β Failed to create virtual keyboard: {:?}", e));
862 }
863 }
864 }
865 Err(e) => {
866 Log::err(format!("β Failed to check system support: {:?}", e));
867 }
868 }
869 }
870 None => {
871 Log::err("β Failed to initialize XR_META_virtual_keyboard extension");
872 }
873 }
874
875 Log::diag("π === VIRTUAL KEYBOARD EXTENSION TEST COMPLETE ===");
876}