use openxr_sys::{
Bool32, FALSE, Instance, MAX_SYSTEM_NAME_SIZE, Posef, Result as XrResult, Session, Space, StructureType,
SystemGraphicsProperties, SystemId, SystemProperties, SystemTrackingProperties,
SystemVirtualKeyboardPropertiesMETA, VirtualKeyboardCreateInfoMETA, VirtualKeyboardLocationTypeMETA,
VirtualKeyboardMETA, VirtualKeyboardModelVisibilitySetInfoMETA, VirtualKeyboardSpaceCreateInfoMETA,
pfn::{
CreateVirtualKeyboardMETA, CreateVirtualKeyboardSpaceMETA, DestroyVirtualKeyboardMETA, GetSystemProperties,
GetVirtualKeyboardDirtyTexturesMETA, GetVirtualKeyboardModelAnimationStatesMETA, GetVirtualKeyboardScaleMETA,
GetVirtualKeyboardTextureDataMETA, SendVirtualKeyboardInputMETA, SetVirtualKeyboardModelVisibilityMETA,
SuggestVirtualKeyboardLocationMETA,
},
};
use crate::{
model::Model,
prelude::*,
system::{Backend, BackendOpenXR, BackendXRType, Log},
tools::xr_fb_render_model::XrFbRenderModel,
};
use std::{ffi::c_void, ptr::null_mut};
pub const XR_META_VIRTUAL_KEYBOARD_EXTENSION_NAME: &str = "XR_META_virtual_keyboard";
pub const KEYBOARD_SHOW: &str = "KeyboardShow";
#[derive(Debug)]
pub struct XrMetaVirtualKeyboard {
xr_get_system_properties: Option<GetSystemProperties>,
xr_create_virtual_kbd: Option<CreateVirtualKeyboardMETA>,
xr_destroy_virtual_kbd: Option<DestroyVirtualKeyboardMETA>,
xr_create_virtual_kbd_space: Option<CreateVirtualKeyboardSpaceMETA>,
xr_suggest_virtual_kbd_location: Option<SuggestVirtualKeyboardLocationMETA>,
#[allow(dead_code)]
xr_get_virtual_kbd_scale: Option<GetVirtualKeyboardScaleMETA>,
xr_set_virtual_kbd_model_visibility: Option<SetVirtualKeyboardModelVisibilityMETA>,
#[allow(dead_code)]
xr_get_virtual_kbd_model_animation_states: Option<GetVirtualKeyboardModelAnimationStatesMETA>,
#[allow(dead_code)]
xr_get_virtual_kbd_dirty_textures: Option<GetVirtualKeyboardDirtyTexturesMETA>,
#[allow(dead_code)]
xr_get_virtual_kbd_texture_data: Option<GetVirtualKeyboardTextureDataMETA>,
#[allow(dead_code)]
xr_send_virtual_kbd_input: Option<SendVirtualKeyboardInputMETA>,
instance: Instance,
session: Session,
}
impl XrMetaVirtualKeyboard {
pub fn new() -> Option<Self> {
if !is_meta_virtual_keyboard_extension_available() {
Log::warn("⚠️ XR_META_virtual_keyboard extension not available");
return None;
}
let instance = Instance::from_raw(BackendOpenXR::instance());
let session = Session::from_raw(BackendOpenXR::session());
let xr_get_system_properties = BackendOpenXR::get_function::<GetSystemProperties>("xrGetSystemProperties");
let xr_create_virtual_kbd =
BackendOpenXR::get_function::<CreateVirtualKeyboardMETA>("xrCreateVirtualKeyboardMETA");
let xr_destroy_virtual_kbd =
BackendOpenXR::get_function::<DestroyVirtualKeyboardMETA>("xrDestroyVirtualKeyboardMETA");
let xr_create_virtual_kbd_space =
BackendOpenXR::get_function::<CreateVirtualKeyboardSpaceMETA>("xrCreateVirtualKeyboardSpaceMETA");
let xr_suggest_virtual_kbd_location =
BackendOpenXR::get_function::<SuggestVirtualKeyboardLocationMETA>("xrSuggestVirtualKeyboardLocationMETA");
let xr_get_virtual_kbd_scale =
BackendOpenXR::get_function::<GetVirtualKeyboardScaleMETA>("xrGetVirtualKeyboardScaleMETA");
let xr_set_virtual_kbd_model_visibility = BackendOpenXR::get_function::<SetVirtualKeyboardModelVisibilityMETA>(
"xrSetVirtualKeyboardModelVisibilityMETA",
);
let xr_get_virtual_kbd_model_animation_states = BackendOpenXR::get_function::<
GetVirtualKeyboardModelAnimationStatesMETA,
>("xrGetVirtualKeyboardModelAnimationStatesMETA");
let xr_get_virtual_kbd_dirty_textures =
BackendOpenXR::get_function::<GetVirtualKeyboardDirtyTexturesMETA>("xrGetVirtualKeyboardDirtyTexturesMETA");
let xr_get_virtual_kbd_texture_data =
BackendOpenXR::get_function::<GetVirtualKeyboardTextureDataMETA>("xrGetVirtualKeyboardTextureDataMETA");
let xr_send_virtual_kbd_input =
BackendOpenXR::get_function::<SendVirtualKeyboardInputMETA>("xrSendVirtualKeyboardInputMETA");
if xr_get_system_properties.is_none()
|| xr_create_virtual_kbd.is_none()
|| xr_destroy_virtual_kbd.is_none()
|| xr_create_virtual_kbd_space.is_none()
{
Log::warn("❌ Failed to load essential XR_META_virtual_keyboard functions");
return None;
}
Some(Self {
xr_get_system_properties,
xr_create_virtual_kbd,
xr_destroy_virtual_kbd,
xr_create_virtual_kbd_space,
xr_suggest_virtual_kbd_location,
xr_get_virtual_kbd_scale,
xr_set_virtual_kbd_model_visibility,
xr_get_virtual_kbd_model_animation_states,
xr_get_virtual_kbd_dirty_textures,
xr_get_virtual_kbd_texture_data,
xr_send_virtual_kbd_input,
instance,
session,
})
}
pub fn check_system_support(&self, with_log: bool) -> Result<SystemProperties, XrResult> {
let get_props_fn = self.xr_get_system_properties.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
let system_id = SystemId::from_raw(BackendOpenXR::system_id());
let mut virtual_kbd_props = SystemVirtualKeyboardPropertiesMETA {
ty: StructureType::SYSTEM_VIRTUAL_KEYBOARD_PROPERTIES_META,
next: null_mut(),
supports_virtual_keyboard: Bool32::from_raw(0),
};
let mut system_properties = SystemProperties {
ty: StructureType::SYSTEM_PROPERTIES,
next: &mut virtual_kbd_props as *mut _ as *mut c_void,
system_id,
vendor_id: 0,
system_name: [0; MAX_SYSTEM_NAME_SIZE],
graphics_properties: SystemGraphicsProperties {
max_swapchain_image_height: 0,
max_swapchain_image_width: 0,
max_layer_count: 0,
},
tracking_properties: SystemTrackingProperties {
orientation_tracking: Bool32::from_raw(0),
position_tracking: Bool32::from_raw(0),
},
};
let result = unsafe { get_props_fn(self.instance, system_id, &mut system_properties) };
if result != XrResult::SUCCESS {
return Err(result);
}
if with_log {
Log::diag("=== XR_META_virtual_keyboard System Properties ===");
Log::diag(format!("System ID: {:?}", system_properties.system_id));
Log::diag(format!("Vendor ID: {}", system_properties.vendor_id));
let system_name = system_properties
.system_name
.iter()
.take_while(|&&c| c != 0)
.map(|&c| c as u8 as char)
.collect::<String>();
Log::diag(format!("System name: {}", system_name));
Log::diag("Graphics properties:");
Log::diag(format!(
" Max swapchain image height: {}",
system_properties.graphics_properties.max_swapchain_image_height
));
Log::diag(format!(
" Max swapchain image width: {}",
system_properties.graphics_properties.max_swapchain_image_width
));
Log::diag(format!(" Max layer count: {}", system_properties.graphics_properties.max_layer_count));
Log::diag("Tracking properties:");
Log::diag(format!(
" Orientation tracking: {}",
system_properties.tracking_properties.orientation_tracking != FALSE
));
Log::diag(format!(
" Position tracking: {}",
system_properties.tracking_properties.position_tracking != FALSE
));
Log::diag("Virtual keyboard properties:");
Log::diag(format!(" Supports virtual keyboard: {}", virtual_kbd_props.supports_virtual_keyboard != FALSE));
Log::diag("================================================");
}
Ok(system_properties)
}
pub fn create_virtual_keyboard(&self) -> Result<VirtualKeyboardMETA, XrResult> {
let create_fn = self.xr_create_virtual_kbd.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
let mut virtual_kbd = VirtualKeyboardMETA::NULL;
let create_info =
VirtualKeyboardCreateInfoMETA { ty: StructureType::VIRTUAL_KEYBOARD_CREATE_INFO_META, next: null_mut() };
let result = unsafe { create_fn(self.session, &create_info, &mut virtual_kbd) };
if result != XrResult::SUCCESS {
return Err(result);
}
Ok(virtual_kbd)
}
pub fn destroy_virtual_keyboard(&self, keyboard: VirtualKeyboardMETA) -> Result<(), XrResult> {
let destroy_fn = self.xr_destroy_virtual_kbd.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
let result = unsafe { destroy_fn(keyboard) };
if result != XrResult::SUCCESS {
return Err(result);
}
Ok(())
}
pub fn create_virtual_keyboard_space(
&self,
keyboard: VirtualKeyboardMETA,
location_type: VirtualKeyboardLocationTypeMETA,
space: Space,
pose_in_space: Posef,
) -> Result<Space, XrResult> {
let create_space_fn = self.xr_create_virtual_kbd_space.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
let mut kbd_space = Space::from_raw(0);
let space_create_info = VirtualKeyboardSpaceCreateInfoMETA {
ty: StructureType::VIRTUAL_KEYBOARD_SPACE_CREATE_INFO_META,
next: null_mut(),
location_type,
space,
pose_in_space,
};
let result = unsafe { create_space_fn(self.session, keyboard, &space_create_info, &mut kbd_space) };
if result != XrResult::SUCCESS {
return Err(result);
}
Ok(kbd_space)
}
pub fn set_model_visibility(&self, keyboard: VirtualKeyboardMETA, visible: bool) -> Result<(), XrResult> {
let set_visibility_fn = self.xr_set_virtual_kbd_model_visibility.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
let visibility_info = VirtualKeyboardModelVisibilitySetInfoMETA {
ty: StructureType::VIRTUAL_KEYBOARD_MODEL_VISIBILITY_SET_INFO_META,
next: null_mut(),
visible: Bool32::from_raw(if visible { 1 } else { 0 }),
};
let result = unsafe { set_visibility_fn(keyboard, &visibility_info) };
if result != XrResult::SUCCESS {
return Err(result);
}
Log::info(format!("Virtual keyboard visibility set to: {}", visible));
Ok(())
}
#[allow(unused_variables)]
pub fn suggest_location(
&self,
keyboard: VirtualKeyboardMETA,
location_info: &VirtualKeyboardSpaceCreateInfoMETA,
) -> Result<(), XrResult> {
let _suggest_fn = self.xr_suggest_virtual_kbd_location.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
Log::info("Virtual keyboard location suggested");
Ok(())
}
}
pub fn is_meta_virtual_keyboard_extension_available() -> bool {
Backend::xr_type() == BackendXRType::OpenXR && BackendOpenXR::ext_enabled(XR_META_VIRTUAL_KEYBOARD_EXTENSION_NAME)
}
#[derive(IStepper)]
pub struct XrMetaVirtualKeyboardStepper {
id: StepperId,
sk_info: Option<Rc<RefCell<SkInfo>>>,
enabled: bool,
shutdown_completed: bool,
virtual_kbd: VirtualKeyboardMETA,
kbd_space: Space,
meta_kdb: Option<XrMetaVirtualKeyboard>,
keyboard_model: Option<Model>,
}
unsafe impl Send for XrMetaVirtualKeyboardStepper {}
impl Default for XrMetaVirtualKeyboardStepper {
fn default() -> Self {
Self {
id: "MetaVirtualKeyboard".to_string(),
sk_info: None,
enabled: false,
shutdown_completed: false,
virtual_kbd: VirtualKeyboardMETA::NULL,
kbd_space: Space::from_raw(0),
meta_kdb: XrMetaVirtualKeyboard::new(),
keyboard_model: None,
}
}
}
impl XrMetaVirtualKeyboardStepper {
pub fn new(enable_on_init: bool) -> Self {
Self { enabled: enable_on_init, ..Default::default() }
}
fn start(&mut self) -> bool {
if !is_meta_virtual_keyboard_extension_available() || self.meta_kdb.is_none() {
Log::warn("⚠️ XR_META_virtual_keyboard extension not available");
return false;
}
self.init_kbd()
}
fn check_event(&mut self, _id: &StepperId, key: &str, value: &str) {
if key.eq(KEYBOARD_SHOW) {
self.enabled = value == "true";
if let Some(ref meta_kdb) = self.meta_kdb {
if self.virtual_kbd == VirtualKeyboardMETA::NULL {
Log::warn("⚠️ Virtual keyboard not initialized yet, visibility will be set during initialization");
return;
}
if self.enabled {
Log::info("✅ Showing virtual keyboard...");
meta_kdb
.set_model_visibility(self.virtual_kbd, true)
.unwrap_or_else(|e| Log::warn(format!("❌ Failed to show keyboard: {:?}", e)));
} else {
Log::info("✅ Hiding virtual keyboard...");
meta_kdb
.set_model_visibility(self.virtual_kbd, false)
.unwrap_or_else(|e| Log::warn(format!("❌ Failed to hide keyboard: {:?}", e)));
}
}
}
}
fn init_kbd(&mut self) -> bool {
let Some(ref meta_kdb) = self.meta_kdb else {
Log::err("❌ Virtual keyboard extension not available");
return false;
};
let _sys_prop = match meta_kdb.check_system_support(false) {
Ok(val) => val,
Err(e) => {
Log::err(format!("❌ Failed to check system support: {:?}", e));
return false;
}
};
match meta_kdb.create_virtual_keyboard() {
Ok(kbd) => {
self.virtual_kbd = kbd;
Log::info(" Virtual keyboard created successfully");
}
Err(e) => {
Log::err(format!("❌ Failed to create virtual keyboard: {:?}", e));
return false;
}
}
match meta_kdb.create_virtual_keyboard_space(
self.virtual_kbd,
VirtualKeyboardLocationTypeMETA::CUSTOM,
Space::from_raw(BackendOpenXR::space()),
Posef::IDENTITY,
) {
Ok(space) => {
self.kbd_space = space;
Log::info(" Virtual keyboard space created successfully");
}
Err(e) => {
Log::err(format!("❌ Failed to create virtual keyboard space: {:?}", e));
return false;
}
}
if let Some(render_model_ext) = XrFbRenderModel::new(false) {
match render_model_ext.enumerate_render_model_paths() {
Ok(paths) => {
Log::info(format!(" Found {} render model paths:", paths.len()));
for path in &paths {
Log::info(format!(" - {}", path));
}
let model_path = "/model_meta/keyboard/virtual";
match render_model_ext.load_render_model(model_path) {
Ok(model_data) => {
Log::info(format!(" Loaded {} bytes of keyboard model data", model_data.len()));
match Model::from_memory("virtual_keyboard.gltf", &model_data, None) {
Ok(model) => {
self.keyboard_model = Some(model);
Log::info(" Keyboard 3D model created successfully");
}
Err(e) => {
Log::warn(format!("❌ Failed to create Model from keyboard data: {:?}", e));
}
}
}
Err(e) => {
Log::warn(format!("❌ Failed to load keyboard model from {}: {:?}", model_path, e));
}
}
}
Err(e) => {
Log::warn(format!("❌ Failed to enumerate render models: {:?}", e));
}
}
} else {
Log::warn("❌ XR_FB_render_model extension not available");
}
match meta_kdb.set_model_visibility(self.virtual_kbd, self.enabled) {
Ok(()) => {}
Err(e) => {
Log::err(format!("❌ Failed to set keyboard visibility: {:?}", e));
}
}
Log::info("✅ Virtual keyboard initialization completed successfully");
true
}
fn draw(&mut self, token: &MainThreadToken) {
if let Some(ref model) = self.keyboard_model {
use crate::maths::{Matrix, Quat, Vec3};
let pose = Matrix::t_r(Vec3::new(0.0, 1.0, -1.5), Quat::Y_180);
model.draw(token, pose, None, None);
}
}
fn close(&mut self, _triggering: bool) -> bool {
if !self.shutdown_completed
&& let Some(ref kdb) = self.meta_kdb
&& self.virtual_kbd != VirtualKeyboardMETA::NULL
{
let _ = kdb.destroy_virtual_keyboard(self.virtual_kbd);
self.shutdown_completed = true
}
self.shutdown_completed
}
}