diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 06c3fa28..ca0b1d27 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -49,15 +49,21 @@ "Y_AXIS": "Y axis" }, "TYPE": { - "TRIGGER": "Trigger", - "TRACKPAD": "Trackpad", - "THUMBSTICK": "Thumbstick", + "BUMPER": "Bumper", + "DPAD_UP": "D-pad Up", + "DPAD_DOWN": "D-pad Down", + "DPAD_LEFT": "D-pad Left", + "DPAD_RIGHT": "D-pad Right", "JOYSTICK": "Joystick", "MENU": "Menu", + "SHOULDER": "Shoulder", + "SQUEEZE": "Grip", "SYSTEM": "System", "THUMBREST": "Thumbrest", - "SHOULDER": "Shoulder", - "SQUEEZE": "Grip" + "THUMBSTICK": "Thumbstick", + "TRACKPAD": "Trackpad", + "TRIGGER": "Trigger", + "VIEW": "View" }, "CLICK": { "ANY": "Any", diff --git a/dash-frontend/src/util/mod.rs b/dash-frontend/src/util/mod.rs index ac473862..2dd7db67 100644 --- a/dash-frontend/src/util/mod.rs +++ b/dash-frontend/src/util/mod.rs @@ -1,7 +1,6 @@ pub mod cached_fetcher; pub mod networking; -pub mod openxr_bindings_schema; -pub mod openxr_controller_profiles; +pub mod openxr_bindings; pub mod pactl_wrapper; pub mod popup_manager; pub mod steam_utils; diff --git a/dash-frontend/src/util/openxr_bindings.rs b/dash-frontend/src/util/openxr_bindings.rs new file mode 100644 index 00000000..3cdbea1f --- /dev/null +++ b/dash-frontend/src/util/openxr_bindings.rs @@ -0,0 +1,126 @@ +use std::rc::Rc; + +use anyhow::{Context, bail}; +use serde::{Deserialize, Serialize}; +use strum::{AsRefStr, EnumProperty, EnumString}; +use wgui::i18n::Translation; +use wlx_common::openxr_bindings_schema::{XrInputComponent, XrInputSide, XrInputSubpathKind}; + +#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumString, AsRefStr, EnumProperty)] +#[strum(ascii_case_insensitive, serialize_all = "snake_case")] +pub enum ClickType { + #[default] + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.CLICK.ANY"))] + Any, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.CLICK.DOUBLE"))] + Double, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.CLICK.TRIPLE"))] + Triple, +} + +pub trait BindingsDropdown { + fn translation(&self) -> Translation; + fn action_str(&self, action: &str, side: XrInputSide) -> Rc; + fn clear_str(action: &str, side: XrInputSide) -> Option>; +} + +impl BindingsDropdown for ClickType { + fn translation(&self) -> Translation { + self + .get_str("Translation") + .map(Translation::from_translation_key) + .or_else(|| self.get_str("Text").map(Translation::from_raw_text)) + .unwrap_or_else(|| Translation::from_raw_text(self.as_ref())) + } + fn action_str(&self, action: &str, _side: XrInputSide) -> Rc { + let value = self.as_ref(); + format!("click;{action};-;{value}").into() + } + fn clear_str(_action: &str, _side: XrInputSide) -> Option> { + None + } +} + +impl BindingsDropdown for XrInputSubpathKind { + fn translation(&self) -> Translation { + self + .get_str("Translation") + .map(Translation::from_translation_key) + .unwrap_or_else(|| { + let mut chars = self.as_ref().chars(); + let capitalized = match chars.next() { + None => String::new(), + Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(), + }; + Translation::from_raw_text(&capitalized) + }) + } + fn action_str(&self, action: &str, side: XrInputSide) -> Rc { + let value = self.as_ref(); + let side = side.as_ref(); + format!("subpath;{action};{side};{value}").into() + } + fn clear_str(action: &str, side: XrInputSide) -> Option> { + let side = side.as_ref(); + Some(format!("clear;{action};{side};-").into()) + } +} + +impl BindingsDropdown for XrInputComponent { + fn translation(&self) -> Translation { + self + .get_str("Translation") + .map(Translation::from_translation_key) + .or_else(|| self.get_str("Text").map(Translation::from_raw_text)) + .unwrap_or_else(|| Translation::from_raw_text(self.as_ref())) + } + fn action_str(&self, action: &str, side: XrInputSide) -> Rc { + let value = self.as_ref(); + let side = side.as_ref(); + format!("comp;{action};{side};{value}").into() + } + fn clear_str(_action: &str, _side: XrInputSide) -> Option> { + None + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ParsedOpenXrInputPath { + pub side: XrInputSide, + pub subpath: XrInputSubpathKind, + pub component: XrInputComponent, +} + +impl<'a> TryFrom<&'a str> for ParsedOpenXrInputPath { + type Error = anyhow::Error; + + fn try_from(path: &str) -> anyhow::Result { + let (side, rest) = if let Some(rest) = path.strip_prefix("/user/hand/left/") { + (XrInputSide::Left, rest) + } else if let Some(rest) = path.strip_prefix("/user/hand/right/") { + (XrInputSide::Right, rest) + } else { + bail!("missing hand prefix"); + }; + + let (input, rest) = rest.split_once('/').context("path too short")?; + if input != "input" { + bail!("missing input prefix"); + } + + let (identifier, component) = rest.rsplit_once('/').context("missing identifier or component")?; + + if identifier.is_empty() || component.is_empty() { + bail!("identifier or component empty"); + } + + let component = XrInputComponent::try_from(component).context("bad component")?; + let subpath = XrInputSubpathKind::try_from(identifier).context("bad subpath")?; + + Ok(Self { + side, + subpath, + component, + }) + } +} diff --git a/dash-frontend/src/util/openxr_bindings_schema.rs b/dash-frontend/src/util/openxr_bindings_schema.rs deleted file mode 100644 index c06c9617..00000000 --- a/dash-frontend/src/util/openxr_bindings_schema.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::rc::Rc; - -use anyhow::{Context, bail}; -use serde::{Deserialize, Serialize}; -use strum::{AsRefStr, EnumProperty, EnumString}; -use wgui::i18n::Translation; - -pub struct ControllerProfile { - pub display_name: &'static str, - pub profile_id: &'static str, - pub user_paths: &'static [ControllerUserPath], -} - -impl ControllerProfile { - pub fn find_userpath(&self, side: Side) -> Option<&ControllerUserPath> { - self.user_paths.iter().find(|x| x.hand == side) - } -} - -pub struct ControllerUserPath { - pub hand: Side, - pub paths: &'static [Subpath], -} - -impl ControllerUserPath { - pub fn find_subpath(&self, subpath: SubpathKind) -> Option<&Subpath> { - self.paths.iter().find(|x| x.kind == subpath) - } -} - -pub struct Subpath { - pub kind: SubpathKind, - pub components: &'static [Component], -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumString, AsRefStr, EnumProperty)] -#[strum(ascii_case_insensitive)] -pub enum SubpathKind { - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.TRIGGER"))] - Trigger, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.TRACKPAD"))] - Trackpad, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.THUMBSTICK"))] - Thumbstick, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.JOYSTICK"))] - Joystick, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.SYSTEM"))] - System, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.MENU"))] - Menu, - - Primary, - Secondary, - - A, - B, - X, - Y, - Start, - Home, - End, - Select, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.THUMBREST"))] - Thumbrest, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.SHOULDER"))] - Shoulder, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.SQUEEZE"))] - Squeeze, - - #[strum(props(Hidden = true))] - Grip, - #[strum(props(Hidden = true))] - Aim, - #[strum(props(Hidden = true))] - Haptic, -} - -impl BindingsDropdown for SubpathKind { - fn translation(&self) -> Translation { - self - .get_str("Translation") - .map(Translation::from_translation_key) - .or_else(|| self.get_str("Text").map(Translation::from_raw_text)) - .unwrap_or_else(|| Translation::from_raw_text(self.as_ref())) - } - fn action_str(&self, action: &str, side: Side) -> Rc { - let value = self.as_ref(); - let side = side.as_ref(); - format!("subpath;{action};{side};{value}").into() - } - fn clear_str(action: &str, side: Side) -> Option> { - let side = side.as_ref(); - Some(format!("clear;{action};{side};-").into()) - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumString, AsRefStr, EnumProperty)] -#[serde(rename_all = "snake_case")] -#[strum(ascii_case_insensitive)] -pub enum Component { - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.CLICK"))] - Click, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.FORCE"))] - Force, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.TOUCH"))] - Touch, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.VALUE"))] - Value, - - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.PROXIMITY"))] - Proximity, - - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.X_AXIS"))] - X, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.Y_AXIS"))] - Y, - - // below are hidden - Pose, -} - -impl Component { - pub fn is_analog(&self) -> bool { - matches!(self, Component::Force | Component::Value | Component::X | Component::Y) - } -} - -impl BindingsDropdown for Component { - fn translation(&self) -> Translation { - self - .get_str("Translation") - .map(Translation::from_translation_key) - .or_else(|| self.get_str("Text").map(Translation::from_raw_text)) - .unwrap_or_else(|| Translation::from_raw_text(self.as_ref())) - } - fn action_str(&self, action: &str, side: Side) -> Rc { - let value = self.as_ref(); - let side = side.as_ref(); - format!("comp;{action};{side};{value}").into() - } - fn clear_str(_action: &str, _side: Side) -> Option> { - None - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumString, AsRefStr)] -#[serde(rename_all = "snake_case")] -#[strum(ascii_case_insensitive)] -pub enum Side { - Left, - Right, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct ParsedOpenXrInputPath { - pub side: Side, - pub subpath: SubpathKind, - pub component: Component, -} - -impl<'a> TryFrom<&'a str> for ParsedOpenXrInputPath { - type Error = anyhow::Error; - - fn try_from(path: &str) -> anyhow::Result { - let (side, rest) = if let Some(rest) = path.strip_prefix("/user/hand/left/") { - (Side::Left, rest) - } else if let Some(rest) = path.strip_prefix("/user/hand/right/") { - (Side::Right, rest) - } else { - bail!("missing hand prefix"); - }; - - let (input, rest) = rest.split_once('/').context("path too short")?; - if input != "input" { - bail!("missing input prefix"); - } - - let (identifier, component) = rest.rsplit_once('/').context("missing identifier or component")?; - - if identifier.is_empty() || component.is_empty() { - bail!("identifier or component empty"); - } - - let component = Component::try_from(component).context("bad component")?; - - let identifier = SubpathKind::try_from(identifier).context("bad subpath")?; - - Ok(Self { - side, - subpath: identifier, - component, - }) - } -} - -#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumString, AsRefStr, EnumProperty)] -#[strum(ascii_case_insensitive)] -pub enum ClickType { - #[default] - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.CLICK.ANY"))] - Any, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.CLICK.DOUBLE"))] - Double, - #[strum(props(Translation = "APP_SETTINGS.BINDINGS.CLICK.TRIPLE"))] - Triple, -} - -impl BindingsDropdown for ClickType { - fn translation(&self) -> Translation { - self - .get_str("Translation") - .map(Translation::from_translation_key) - .or_else(|| self.get_str("Text").map(Translation::from_raw_text)) - .unwrap_or_else(|| Translation::from_raw_text(self.as_ref())) - } - fn action_str(&self, action: &str, _side: Side) -> Rc { - let value = self.as_ref(); - format!("click;{action};-;{value}").into() - } - fn clear_str(_action: &str, _side: Side) -> Option> { - None - } -} - -pub trait BindingsDropdown { - fn translation(&self) -> Translation; - fn action_str(&self, action: &str, side: Side) -> Rc; - fn clear_str(action: &str, side: Side) -> Option>; -} diff --git a/dash-frontend/src/util/openxr_controller_profiles.rs b/dash-frontend/src/util/openxr_controller_profiles.rs deleted file mode 100644 index d4bbe2fe..00000000 --- a/dash-frontend/src/util/openxr_controller_profiles.rs +++ /dev/null @@ -1,465 +0,0 @@ -use crate::util::openxr_bindings_schema::{ - Component, ControllerProfile, ControllerUserPath, Side, Subpath, SubpathKind, -}; - -pub const OPENXR_INPUT_PROFILES: &[&ControllerProfile] = &[ - &VALVE_INDEX_CONTROLLER_PROFILE, - &OCULUS_TOUCH_CONTROLLER_PROFILE, - &HTC_VIVE_CONTROLLER_PROFILE, - &HP_MIXED_REALITY_CONTROLLER_PROFILE, - &MICROSOFT_MOTION_CONTROLLER_PROFILE, - &SAMSUNG_ODYSSEY_CONTROLLER_PROFILE, - &KHR_GENERIC_CONTROLLER_PROFILE, -]; - -pub const VALVE_INDEX_CONTROLLER_PROFILE: ControllerProfile = ControllerProfile { - display_name: "Valve Index Controller", - profile_id: "/interaction_profiles/valve/index_controller", - user_paths: &[ - ControllerUserPath { - hand: Side::Left, - paths: VALVE_INDEX_USER_PATHS, - }, - ControllerUserPath { - hand: Side::Right, - paths: VALVE_INDEX_USER_PATHS, - }, - ], -}; - -const VALVE_INDEX_USER_PATHS: &[Subpath] = &[ - Subpath { - kind: SubpathKind::System, - components: &[Component::Click, Component::Touch], - }, - Subpath { - kind: SubpathKind::A, - components: &[Component::Click, Component::Touch], - }, - Subpath { - kind: SubpathKind::B, - components: &[Component::Click, Component::Touch], - }, - Subpath { - kind: SubpathKind::Squeeze, - components: &[Component::Value, Component::Force], - }, - Subpath { - kind: SubpathKind::Trigger, - components: &[Component::Click, Component::Value, Component::Touch], - }, - Subpath { - kind: SubpathKind::Thumbstick, - components: &[Component::Click, Component::Touch, Component::X, Component::Y], - }, - Subpath { - kind: SubpathKind::Trackpad, - components: &[Component::Force, Component::Touch, Component::X, Component::Y], - }, - Subpath { - kind: SubpathKind::Grip, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Aim, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Haptic, - components: &[], - }, -]; - -pub const OCULUS_TOUCH_CONTROLLER_PROFILE: ControllerProfile = ControllerProfile { - display_name: "Touch Controller", - profile_id: "/interaction_profiles/oculus/touch_controller", - user_paths: &[ - ControllerUserPath { - hand: Side::Left, - paths: OCULUS_TOUCH_LEFT_USER_PATHS, - }, - ControllerUserPath { - hand: Side::Right, - paths: OCULUS_TOUCH_RIGHT_USER_PATHS, - }, - ], -}; - -const OCULUS_TOUCH_LEFT_USER_PATHS: &[Subpath] = &[ - Subpath { - kind: SubpathKind::X, - components: &[Component::Click, Component::Touch], - }, - Subpath { - kind: SubpathKind::Y, - components: &[Component::Click, Component::Touch], - }, - Subpath { - kind: SubpathKind::Menu, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Squeeze, - components: &[Component::Value], - }, - Subpath { - kind: SubpathKind::Trigger, - components: &[Component::Value, Component::Touch], - }, - Subpath { - kind: SubpathKind::Thumbstick, - components: &[Component::Click, Component::Touch, Component::X, Component::Y], - }, - Subpath { - kind: SubpathKind::Thumbrest, - components: &[Component::Touch], - }, - Subpath { - kind: SubpathKind::Grip, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Aim, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Haptic, - components: &[], - }, -]; - -const OCULUS_TOUCH_RIGHT_USER_PATHS: &[Subpath] = &[ - Subpath { - kind: SubpathKind::A, - components: &[Component::Click, Component::Touch], - }, - Subpath { - kind: SubpathKind::B, - components: &[Component::Click, Component::Touch], - }, - Subpath { - kind: SubpathKind::System, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Squeeze, - components: &[Component::Value], - }, - Subpath { - kind: SubpathKind::Trigger, - components: &[Component::Value, Component::Touch], - }, - Subpath { - kind: SubpathKind::Thumbstick, - components: &[Component::Click, Component::Touch, Component::X, Component::Y], - }, - Subpath { - kind: SubpathKind::Thumbrest, - components: &[Component::Touch], - }, - Subpath { - kind: SubpathKind::Grip, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Aim, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Haptic, - components: &[], - }, -]; - -pub const HP_MIXED_REALITY_CONTROLLER_PROFILE: ControllerProfile = ControllerProfile { - display_name: "HP Reverb G2 Controller", - profile_id: "/interaction_profiles/hp/mixed_reality_controller", - user_paths: &[ - ControllerUserPath { - hand: Side::Left, - paths: HP_MIXED_REALITY_LEFT_USER_PATHS, - }, - ControllerUserPath { - hand: Side::Right, - paths: HP_MIXED_REALITY_RIGHT_USER_PATHS, - }, - ], -}; - -const HP_MIXED_REALITY_LEFT_USER_PATHS: &[Subpath] = &[ - Subpath { - kind: SubpathKind::X, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Y, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Menu, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Squeeze, - components: &[Component::Value], - }, - Subpath { - kind: SubpathKind::Trigger, - components: &[Component::Value], - }, - Subpath { - kind: SubpathKind::Thumbstick, - components: &[Component::Click, Component::X, Component::Y], - }, - Subpath { - kind: SubpathKind::Grip, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Aim, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Haptic, - components: &[], - }, -]; - -const HP_MIXED_REALITY_RIGHT_USER_PATHS: &[Subpath] = &[ - Subpath { - kind: SubpathKind::A, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::B, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Menu, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Squeeze, - components: &[Component::Value], - }, - Subpath { - kind: SubpathKind::Trigger, - components: &[Component::Value], - }, - Subpath { - kind: SubpathKind::Thumbstick, - components: &[Component::Click, Component::X, Component::Y], - }, - Subpath { - kind: SubpathKind::Grip, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Aim, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Haptic, - components: &[], - }, -]; - -pub const SAMSUNG_ODYSSEY_CONTROLLER_PROFILE: ControllerProfile = ControllerProfile { - display_name: "Samsung Odyssey Controller", - profile_id: "/interaction_profiles/samsung/odyssey_controller", - user_paths: &[ - ControllerUserPath { - hand: Side::Left, - paths: SAMSUNG_ODYSSEY_USER_PATHS, - }, - ControllerUserPath { - hand: Side::Right, - paths: SAMSUNG_ODYSSEY_USER_PATHS, - }, - ], -}; - -const SAMSUNG_ODYSSEY_USER_PATHS: &[Subpath] = &[ - Subpath { - kind: SubpathKind::Menu, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Squeeze, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Trigger, - components: &[Component::Value], - }, - Subpath { - kind: SubpathKind::Thumbstick, - components: &[Component::Click, Component::X, Component::Y], - }, - Subpath { - kind: SubpathKind::Trackpad, - components: &[Component::Click, Component::Touch, Component::X, Component::Y], - }, - Subpath { - kind: SubpathKind::Grip, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Aim, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Haptic, - components: &[], - }, -]; - -pub const HTC_VIVE_CONTROLLER_PROFILE: ControllerProfile = ControllerProfile { - display_name: "HTC Vive Controller", - profile_id: "/interaction_profiles/htc/vive_controller", - user_paths: &[ - ControllerUserPath { - hand: Side::Left, - paths: HTC_VIVE_USER_PATHS, - }, - ControllerUserPath { - hand: Side::Right, - paths: HTC_VIVE_USER_PATHS, - }, - ], -}; - -const HTC_VIVE_USER_PATHS: &[Subpath] = &[ - Subpath { - kind: SubpathKind::System, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Squeeze, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Menu, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Trigger, - components: &[Component::Click, Component::Value], - }, - Subpath { - kind: SubpathKind::Trackpad, - components: &[Component::Click, Component::Touch, Component::X, Component::Y], - }, - Subpath { - kind: SubpathKind::Grip, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Aim, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Haptic, - components: &[], - }, -]; - -pub const MICROSOFT_MOTION_CONTROLLER_PROFILE: ControllerProfile = ControllerProfile { - display_name: "Microsoft WMR Controller", - profile_id: "/interaction_profiles/microsoft/motion_controller", - user_paths: &[ - ControllerUserPath { - hand: Side::Left, - paths: MICROSOFT_MOTION_CONTROLLER_USER_PATHS, - }, - ControllerUserPath { - hand: Side::Right, - paths: MICROSOFT_MOTION_CONTROLLER_USER_PATHS, - }, - ], -}; - -const MICROSOFT_MOTION_CONTROLLER_USER_PATHS: &[Subpath] = &[ - Subpath { - kind: SubpathKind::Menu, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Squeeze, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Trigger, - components: &[Component::Value], - }, - Subpath { - kind: SubpathKind::Thumbstick, - components: &[Component::Click, Component::X, Component::Y], - }, - Subpath { - kind: SubpathKind::Trackpad, - components: &[Component::Click, Component::Touch, Component::X, Component::Y], - }, - Subpath { - kind: SubpathKind::Grip, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Aim, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Haptic, - components: &[], - }, -]; - -pub const KHR_GENERIC_CONTROLLER_PROFILE: ControllerProfile = ControllerProfile { - display_name: "Khronos Generic Controller", - profile_id: "/interaction_profiles/khr/generic_controller", - user_paths: &[ - ControllerUserPath { - hand: Side::Left, - paths: KHR_GENERIC_CONTROLLER_USER_PATHS, - }, - ControllerUserPath { - hand: Side::Right, - paths: KHR_GENERIC_CONTROLLER_USER_PATHS, - }, - ], -}; - -const KHR_GENERIC_CONTROLLER_USER_PATHS: &[Subpath] = &[ - Subpath { - kind: SubpathKind::Primary, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Secondary, - components: &[Component::Click], - }, - Subpath { - kind: SubpathKind::Thumbstick, - components: &[Component::Click, Component::X, Component::Y], - }, - Subpath { - kind: SubpathKind::Squeeze, - components: &[Component::Value], - }, - Subpath { - kind: SubpathKind::Trigger, - components: &[Component::Value], - }, - Subpath { - kind: SubpathKind::Grip, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Aim, - components: &[Component::Pose], - }, - Subpath { - kind: SubpathKind::Haptic, - components: &[], - }, -]; diff --git a/dash-frontend/src/views/bindings.rs b/dash-frontend/src/views/bindings.rs index 1552e099..9062d068 100644 --- a/dash-frontend/src/views/bindings.rs +++ b/dash-frontend/src/views/bindings.rs @@ -20,15 +20,14 @@ use wgui::{ use wlx_common::{ config_io, openxr_actions::{OneOrMany, OpenXrInputAction, OpenXrInputProfile, load_xr_input_profiles}, + openxr_bindings_schema::{XrControllerProfile, XrInputComponent, XrInputSide, XrInputSubpathKind}, }; use crate::{ frontend::{FrontendTask, FrontendTasks}, tab::settings::horiz_cell, util::{ - openxr_bindings_schema::{ - BindingsDropdown, ClickType, Component, ControllerProfile, ParsedOpenXrInputPath, Side, SubpathKind, - }, + openxr_bindings::{BindingsDropdown, ClickType, ParsedOpenXrInputPath}, popup_manager::{MountPopupOnceParams, MountPopupOnceParamsExtra, PopupHolder, PopupPadding}, wgui_simple, }, @@ -40,14 +39,14 @@ enum Task { Save, Cancel, OpenContextMenu(glam::Vec2, Vec), - UpdateThreshold(Rc, Side, f32, f32), + UpdateThreshold(Rc, XrInputSide, f32, f32), } pub struct Params<'a> { pub globals: WguiGlobals, pub layout: &'a mut Layout, pub parent_id: WidgetID, - pub controller_profile: &'static ControllerProfile, + pub controller_profile: &'static XrControllerProfile, pub close_callback: Box, } @@ -59,7 +58,7 @@ pub struct View { profiles: Vec, cur_profile_idx: usize, context_menu: context_menu::ContextMenu, - controller_profile: &'static ControllerProfile, + controller_profile: &'static XrControllerProfile, close_callback: Option>, } @@ -90,10 +89,12 @@ impl ViewTrait for View { }); } Task::UpdateThreshold(action_name, side, lo, hi) => { + log::warn!("UpdateThreshold {action_name} {lo:.2} {hi:.2}"); + let cur_profile = &mut self.profiles[self.cur_profile_idx]; let action_mut = get_action_mut(cur_profile, &*action_name); - if matches!(side, Side::Right) { + if matches!(side, XrInputSide::Right) { action_mut.threshold_right = Some([lo, hi]); } else { action_mut.threshold_left = Some([lo, hi]); @@ -108,11 +109,6 @@ impl ViewTrait for View { let mut s = name.splitn(4, ';'); (s.next(), s.next(), s.next(), s.next()) } { - let side = side.to_lowercase(); - let value = value.to_lowercase(); - - log::warn!("{action_name}"); - let cur_profile = &mut self.profiles[self.cur_profile_idx]; let action_mut = get_action_mut(cur_profile, action_name); let side_mut = if side == "right" { @@ -131,7 +127,7 @@ impl ViewTrait for View { "comp" => { apply_comp(side_mut, &side, &value); } - "click" => match value.as_str() { + "click" => match value { "triple" => { action_mut.triple_click = Some(true); action_mut.double_click = None; @@ -257,8 +253,8 @@ impl View { fn ensure_pose_and_haptics(&mut self) { let cur_profile = &mut self.profiles[self.cur_profile_idx]; - let profile_left = self.controller_profile.find_userpath(Side::Left); - let profile_right = self.controller_profile.find_userpath(Side::Right); + let profile_left = self.controller_profile.find_userpath(XrInputSide::Left); + let profile_right = self.controller_profile.find_userpath(XrInputSide::Right); let action = cur_profile.pose.get_or_insert_default(); @@ -275,7 +271,7 @@ impl View { let action = cur_profile.haptic.get_or_insert_default(); let has_haptic = profile_left - .map(|x| x.find_subpath(SubpathKind::Haptic).is_some()) + .map(|x| x.find_subpath(XrInputSubpathKind::Haptic).is_some()) .unwrap_or_default(); if action.left.is_none() && has_haptic { let path = "/user/hand/left/output/haptic"; @@ -283,7 +279,7 @@ impl View { } let has_haptic = profile_right - .map(|x| x.find_subpath(SubpathKind::Haptic).is_some()) + .map(|x| x.find_subpath(XrInputSubpathKind::Haptic).is_some()) .unwrap_or_default(); if action.right.is_none() && has_haptic { let path = "/user/hand/right/output/haptic"; @@ -316,7 +312,7 @@ pub fn mount_popup( frontend_tasks: FrontendTasks, globals: WguiGlobals, popup: PopupHolder, - controller_profile: &'static ControllerProfile, + controller_profile: &'static XrControllerProfile, ) { frontend_tasks .clone() @@ -353,7 +349,7 @@ fn input_controls_for_action( mp: &mut MacroParams, parent: WidgetID, action: Rc, - profile: &ControllerProfile, + profile: &XrControllerProfile, current: &mut OpenXrInputAction, ) -> anyhow::Result<()> { let id = mp.idx.to_string(); @@ -391,7 +387,7 @@ fn input_controls_for_action( mp, parent, current_left, - Side::Left, + XrInputSide::Left, action.clone(), click_type, profile, @@ -407,7 +403,7 @@ fn input_controls_for_action( mp, parent, current_right, - Side::Right, + XrInputSide::Right, action, click_type, profile, @@ -419,10 +415,10 @@ fn input_controls_for_hand( mp: &mut MacroParams, parent: WidgetID, current: Option<&str>, - side: Side, + side: XrInputSide, action: Rc, click_type: ClickType, - profile: &ControllerProfile, + profile: &XrControllerProfile, threshold: Option<[f32; 2]>, ) -> anyhow::Result<()> { let Some(user_path) = profile.find_userpath(side) else { @@ -433,14 +429,14 @@ fn input_controls_for_hand( let parent = horiz_cell(mp.layout, parent)?; - let available_components: Rc<[Component]> = current + let available_components: Rc<[XrInputComponent]> = current .as_ref() .and_then(|par| user_path.find_subpath(par.subpath)) .map(|subp| subp.components) .unwrap_or_default() .into(); - let available_subpaths: Rc<[SubpathKind]> = user_path + let available_subpaths: Rc<[XrInputSubpathKind]> = user_path .paths .iter() .filter(|x| !x.kind.get_bool("Hidden").unwrap_or_default()) @@ -482,9 +478,9 @@ fn subpath_dropdown( mp: &mut MacroParams, parent: WidgetID, action: Rc, - side: Side, - available: Rc<[SubpathKind]>, - current: Option, + side: XrInputSide, + available: Rc<[XrInputSubpathKind]>, + current: Option, ) -> anyhow::Result<()> { let mut params: HashMap, Rc> = HashMap::new(); params.insert(Rc::from("tooltip"), Rc::from("APP_SETTINGS.BINDINGS.SUBPATH")); @@ -495,7 +491,7 @@ fn subpath_dropdown( mp.layout, parent, Vec2::new(32.0, 32.0), - AssetPath::BuiltIn(&format!("dashboard/hand_{}.svg", side.as_ref().to_lowercase())), + AssetPath::BuiltIn(&format!("dashboard/hand_{}.svg", side.as_ref())), )?; let current_text = current @@ -511,9 +507,9 @@ fn component_dropdown( mp: &mut MacroParams, parent: WidgetID, action: Rc, - side: Side, - available: Rc<[Component]>, - current: Option, + side: XrInputSide, + available: Rc<[XrInputComponent]>, + current: Option, ) -> anyhow::Result { if available.is_empty() { return Ok(false); @@ -541,7 +537,7 @@ fn clicks_dropdown(mp: &mut MacroParams, parent: WidgetID, action: Rc, curr let current_text = current.translation(); let available = [ClickType::Any, ClickType::Double, ClickType::Triple].into(); - create_dropdown(mp, parent, params, action, Side::Left, current_text, available)?; + create_dropdown(mp, parent, params, action, XrInputSide::Left, current_text, available)?; Ok(()) } @@ -550,7 +546,7 @@ fn threshold_slider( mp: &mut MacroParams, parent: WidgetID, action: Rc, - side: Side, + side: XrInputSide, current: Option<[f32; 2]>, ) -> anyhow::Result<()> { let id = mp.idx.to_string(); @@ -590,7 +586,7 @@ fn create_dropdown( parent: WidgetID, mut params: HashMap, Rc>, action: Rc, - side: Side, + side: XrInputSide, current_text: Translation, available: Rc<[B]>, ) -> anyhow::Result<()> { @@ -651,16 +647,19 @@ fn apply_subpath( side_mut: &mut Option>, side_str: &str, subpath_str: &str, - profile: &ControllerProfile, + profile: &XrControllerProfile, ) { - let (Ok(side), Ok(subpath)) = (Side::try_from(side_str), SubpathKind::try_from(subpath_str)) else { + let (Ok(side), Ok(subpath)) = ( + XrInputSide::try_from(side_str), + XrInputSubpathKind::try_from(subpath_str), + ) else { return; }; let Some(subpath_obj) = profile.find_userpath(side).and_then(|p| p.find_subpath(subpath)) else { return; }; - let comp: Component = if let Some(first) = side_mut.as_ref().map(|x| match x { + let comp: XrInputComponent = if let Some(first) = side_mut.as_ref().map(|x| match x { OneOrMany::One(x) => x.as_str(), OneOrMany::Many(x) => x.first().unwrap().as_str(), }) { @@ -677,8 +676,7 @@ fn apply_subpath( *subpath_obj.components.first().unwrap() }; - let comp_str = comp.as_ref().to_lowercase(); - + let comp_str = comp.as_ref(); *side_mut = Some(OneOrMany::One(format!( "/user/hand/{side_str}/input/{subpath_str}/{comp_str}" ))); @@ -698,7 +696,7 @@ fn apply_comp(side_mut: &mut Option>, side: &str, comp: &str) return; }; - let subpath = parsed.subpath.as_ref().to_lowercase(); + let subpath = parsed.subpath.as_ref(); *side_mut = Some(OneOrMany::One(format!("/user/hand/{side}/input/{subpath}/{comp}"))); } diff --git a/dash-frontend/src/views/input_profiles.rs b/dash-frontend/src/views/input_profiles.rs index d4b77af5..57698ac7 100644 --- a/dash-frontend/src/views/input_profiles.rs +++ b/dash-frontend/src/views/input_profiles.rs @@ -9,20 +9,17 @@ use wgui::{ parser::{Fetchable, ParseDocumentParams}, task::Tasks, }; +use wlx_common::{openxr_bindings_schema::XrControllerProfile, openxr_controller_profiles::OPENXR_INPUT_PROFILES}; use crate::{ frontend::{FrontendTask, FrontendTasks}, - util::{ - openxr_bindings_schema::ControllerProfile, - openxr_controller_profiles::OPENXR_INPUT_PROFILES, - popup_manager::{MountPopupOnceParams, PopupHolder}, - }, + util::popup_manager::{MountPopupOnceParams, PopupHolder}, views::{self, ViewTrait, ViewUpdateParams, bindings}, }; #[derive(Clone)] enum Task { - SelectProfile(&'static ControllerProfile), + SelectProfile(&'static XrControllerProfile), } pub struct Params<'a> { diff --git a/wlx-common/src/lib.rs b/wlx-common/src/lib.rs index a406b48f..fa81cfa1 100644 --- a/wlx-common/src/lib.rs +++ b/wlx-common/src/lib.rs @@ -11,6 +11,8 @@ pub mod desktop_finder; mod handle; pub mod locale; pub mod openxr_actions; +pub mod openxr_bindings_schema; +pub mod openxr_controller_profiles; pub mod overlays; pub mod timestep; pub mod windowing; diff --git a/wlx-common/src/openxr_bindings_schema.rs b/wlx-common/src/openxr_bindings_schema.rs new file mode 100644 index 00000000..645ddab9 --- /dev/null +++ b/wlx-common/src/openxr_bindings_schema.rs @@ -0,0 +1,126 @@ +use serde::{Deserialize, Serialize}; +use strum::{AsRefStr, EnumProperty, EnumString}; + +pub struct XrControllerProfile { + pub display_name: &'static str, + pub profile_id: &'static str, + pub extension: Option<&'static str>, + pub user_paths: &'static [XrControllerUserPath], +} + +impl XrControllerProfile { + pub fn find_userpath(&self, side: XrInputSide) -> Option<&XrControllerUserPath> { + self.user_paths.iter().find(|x| x.hand == side) + } +} + +pub struct XrControllerUserPath { + pub hand: XrInputSide, + pub paths: &'static [XrInputSubpath], +} + +impl XrControllerUserPath { + pub fn find_subpath(&self, subpath: XrInputSubpathKind) -> Option<&XrInputSubpath> { + self.paths.iter().find(|x| x.kind == subpath) + } +} + +pub struct XrInputSubpath { + pub kind: XrInputSubpathKind, + pub components: &'static [XrInputComponent], +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumString, AsRefStr, EnumProperty)] +#[strum(ascii_case_insensitive, serialize_all = "snake_case")] +pub enum XrInputSubpathKind { + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.TRIGGER"))] + Trigger, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.TRACKPAD"))] + Trackpad, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.THUMBSTICK"))] + Thumbstick, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.JOYSTICK"))] + Joystick, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.SYSTEM"))] + System, + Menu, + View, + + Primary, + Secondary, + + A, + B, + X, + Y, + Start, + Home, + End, + Select, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.THUMBREST"))] + Thumbrest, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.SHOULDER"))] + Shoulder, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.SQUEEZE"))] + Squeeze, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.BUMPER"))] + Bumper, + + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.DPAD_UP"))] + DpadUp, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.DPAD_DOWN"))] + DpadDown, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.DPAD_LEFT"))] + DpadLeft, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.DPAD_RIGHT"))] + DpadRight, + + #[strum(props(Hidden = true))] + Grip, + #[strum(props(Hidden = true))] + Aim, + #[strum(props(Hidden = true))] + Haptic, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumString, AsRefStr, EnumProperty)] +#[serde(rename_all = "snake_case")] +#[strum(ascii_case_insensitive, serialize_all = "snake_case")] +pub enum XrInputComponent { + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.CLICK"))] + Click, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.FORCE"))] + Force, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.TOUCH"))] + Touch, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.VALUE"))] + Value, + + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.PROXIMITY"))] + Proximity, + + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.X_AXIS"))] + X, + #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.Y_AXIS"))] + Y, + + // below are hidden + Pose, +} + +impl XrInputComponent { + pub fn is_analog(&self) -> bool { + matches!( + self, + XrInputComponent::Force | XrInputComponent::Value | XrInputComponent::X | XrInputComponent::Y + ) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumString, AsRefStr)] +#[serde(rename_all = "snake_case")] +#[strum(ascii_case_insensitive, serialize_all = "snake_case")] +pub enum XrInputSide { + Left, + Right, +} diff --git a/wlx-common/src/openxr_controller_profiles.rs b/wlx-common/src/openxr_controller_profiles.rs new file mode 100644 index 00000000..9c77da11 --- /dev/null +++ b/wlx-common/src/openxr_controller_profiles.rs @@ -0,0 +1,664 @@ +use crate::openxr_bindings_schema::{ + XrControllerProfile, XrControllerUserPath, XrInputComponent, XrInputSide, XrInputSubpath, XrInputSubpathKind, +}; + +pub const OPENXR_INPUT_PROFILES: &[&XrControllerProfile] = &[ + &OCULUS_TOUCH_CONTROLLER_PROFILE, + &VALVE_INDEX_CONTROLLER_PROFILE, + &VALVE_FRAME_CONTROLLER_VALVE_PROFILE, + &HTC_VIVE_CONTROLLER_PROFILE, + &HP_MIXED_REALITY_CONTROLLER_PROFILE, + &MICROSOFT_MOTION_CONTROLLER_PROFILE, + &SAMSUNG_ODYSSEY_CONTROLLER_PROFILE, + &KHR_GENERIC_CONTROLLER_PROFILE, +]; + +pub const VALVE_INDEX_CONTROLLER_PROFILE: XrControllerProfile = XrControllerProfile { + display_name: "Valve Index Controller", + extension: None, + profile_id: "/interaction_profiles/valve/index_controller", + user_paths: &[ + XrControllerUserPath { + hand: XrInputSide::Left, + paths: VALVE_INDEX_USER_PATHS, + }, + XrControllerUserPath { + hand: XrInputSide::Right, + paths: VALVE_INDEX_USER_PATHS, + }, + ], +}; + +const VALVE_INDEX_USER_PATHS: &[XrInputSubpath] = &[ + XrInputSubpath { + kind: XrInputSubpathKind::System, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::A, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::B, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Squeeze, + components: &[XrInputComponent::Value, XrInputComponent::Force], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trigger, + components: &[ + XrInputComponent::Click, + XrInputComponent::Value, + XrInputComponent::Touch, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Thumbstick, + components: &[ + XrInputComponent::Click, + XrInputComponent::Touch, + XrInputComponent::X, + XrInputComponent::Y, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trackpad, + components: &[ + XrInputComponent::Force, + XrInputComponent::Touch, + XrInputComponent::X, + XrInputComponent::Y, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Grip, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Aim, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Haptic, + components: &[], + }, +]; + +pub static VALVE_FRAME_CONTROLLER_VALVE_PROFILE: XrControllerProfile = XrControllerProfile { + display_name: "Steam Frame Controller", + extension: Some("XR_VALVE_frame_controller_interaction"), + profile_id: "/interaction_profiles/valve/frame_controller_valve", + user_paths: &[ + XrControllerUserPath { + hand: XrInputSide::Left, + paths: VALVE_FRAME_CONTROLLER_VALVE_LEFT_USER_PATHS, + }, + XrControllerUserPath { + hand: XrInputSide::Right, + paths: VALVE_FRAME_CONTROLLER_VALVE_RIGHT_USER_PATHS, + }, + ], +}; + +static VALVE_FRAME_CONTROLLER_VALVE_RIGHT_USER_PATHS: &[XrInputSubpath] = &[ + XrInputSubpath { + kind: XrInputSubpathKind::A, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::B, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::X, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Y, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Menu, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::System, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Bumper, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Squeeze, + components: &[ + XrInputComponent::Click, + XrInputComponent::Touch, + XrInputComponent::Value, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trigger, + components: &[ + XrInputComponent::Click, + XrInputComponent::Touch, + XrInputComponent::Value, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Thumbstick, + components: &[ + XrInputComponent::Click, + XrInputComponent::Touch, + XrInputComponent::X, + XrInputComponent::Y, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Grip, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Aim, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Haptic, + components: &[], + }, +]; + +static VALVE_FRAME_CONTROLLER_VALVE_LEFT_USER_PATHS: &[XrInputSubpath] = &[ + XrInputSubpath { + kind: XrInputSubpathKind::DpadUp, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::DpadLeft, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::DpadDown, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::DpadRight, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::View, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::System, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Bumper, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Squeeze, + components: &[ + XrInputComponent::Click, + XrInputComponent::Touch, + XrInputComponent::Value, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trigger, + components: &[ + XrInputComponent::Click, + XrInputComponent::Touch, + XrInputComponent::Value, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Thumbstick, + components: &[ + XrInputComponent::Click, + XrInputComponent::Touch, + XrInputComponent::X, + XrInputComponent::Y, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Grip, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Aim, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Haptic, + components: &[], + }, +]; + +pub const OCULUS_TOUCH_CONTROLLER_PROFILE: XrControllerProfile = XrControllerProfile { + display_name: "Touch Controller", + extension: None, + profile_id: "/interaction_profiles/oculus/touch_controller", + user_paths: &[ + XrControllerUserPath { + hand: XrInputSide::Left, + paths: OCULUS_TOUCH_LEFT_USER_PATHS, + }, + XrControllerUserPath { + hand: XrInputSide::Right, + paths: OCULUS_TOUCH_RIGHT_USER_PATHS, + }, + ], +}; + +const OCULUS_TOUCH_LEFT_USER_PATHS: &[XrInputSubpath] = &[ + XrInputSubpath { + kind: XrInputSubpathKind::X, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Y, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Menu, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Squeeze, + components: &[XrInputComponent::Value], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trigger, + components: &[XrInputComponent::Value, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Thumbstick, + components: &[ + XrInputComponent::Click, + XrInputComponent::Touch, + XrInputComponent::X, + XrInputComponent::Y, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Thumbrest, + components: &[XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Grip, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Aim, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Haptic, + components: &[], + }, +]; + +const OCULUS_TOUCH_RIGHT_USER_PATHS: &[XrInputSubpath] = &[ + XrInputSubpath { + kind: XrInputSubpathKind::A, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::B, + components: &[XrInputComponent::Click, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::System, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Squeeze, + components: &[XrInputComponent::Value], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trigger, + components: &[XrInputComponent::Value, XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Thumbstick, + components: &[ + XrInputComponent::Click, + XrInputComponent::Touch, + XrInputComponent::X, + XrInputComponent::Y, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Thumbrest, + components: &[XrInputComponent::Touch], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Grip, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Aim, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Haptic, + components: &[], + }, +]; + +pub const HP_MIXED_REALITY_CONTROLLER_PROFILE: XrControllerProfile = XrControllerProfile { + display_name: "HP Reverb G2 Controller", + extension: None, + profile_id: "/interaction_profiles/hp/mixed_reality_controller", + user_paths: &[ + XrControllerUserPath { + hand: XrInputSide::Left, + paths: HP_MIXED_REALITY_LEFT_USER_PATHS, + }, + XrControllerUserPath { + hand: XrInputSide::Right, + paths: HP_MIXED_REALITY_RIGHT_USER_PATHS, + }, + ], +}; + +const HP_MIXED_REALITY_LEFT_USER_PATHS: &[XrInputSubpath] = &[ + XrInputSubpath { + kind: XrInputSubpathKind::X, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Y, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Menu, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Squeeze, + components: &[XrInputComponent::Value], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trigger, + components: &[XrInputComponent::Value], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Thumbstick, + components: &[XrInputComponent::Click, XrInputComponent::X, XrInputComponent::Y], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Grip, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Aim, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Haptic, + components: &[], + }, +]; + +const HP_MIXED_REALITY_RIGHT_USER_PATHS: &[XrInputSubpath] = &[ + XrInputSubpath { + kind: XrInputSubpathKind::A, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::B, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Menu, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Squeeze, + components: &[XrInputComponent::Value], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trigger, + components: &[XrInputComponent::Value], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Thumbstick, + components: &[XrInputComponent::Click, XrInputComponent::X, XrInputComponent::Y], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Grip, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Aim, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Haptic, + components: &[], + }, +]; + +pub const SAMSUNG_ODYSSEY_CONTROLLER_PROFILE: XrControllerProfile = XrControllerProfile { + display_name: "Samsung Odyssey Controller", + extension: None, + profile_id: "/interaction_profiles/samsung/odyssey_controller", + user_paths: &[ + XrControllerUserPath { + hand: XrInputSide::Left, + paths: SAMSUNG_ODYSSEY_USER_PATHS, + }, + XrControllerUserPath { + hand: XrInputSide::Right, + paths: SAMSUNG_ODYSSEY_USER_PATHS, + }, + ], +}; + +const SAMSUNG_ODYSSEY_USER_PATHS: &[XrInputSubpath] = &[ + XrInputSubpath { + kind: XrInputSubpathKind::Menu, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Squeeze, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trigger, + components: &[XrInputComponent::Value], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Thumbstick, + components: &[XrInputComponent::Click, XrInputComponent::X, XrInputComponent::Y], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trackpad, + components: &[ + XrInputComponent::Click, + XrInputComponent::Touch, + XrInputComponent::X, + XrInputComponent::Y, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Grip, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Aim, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Haptic, + components: &[], + }, +]; + +pub const HTC_VIVE_CONTROLLER_PROFILE: XrControllerProfile = XrControllerProfile { + display_name: "HTC Vive Controller", + extension: None, + profile_id: "/interaction_profiles/htc/vive_controller", + user_paths: &[ + XrControllerUserPath { + hand: XrInputSide::Left, + paths: HTC_VIVE_USER_PATHS, + }, + XrControllerUserPath { + hand: XrInputSide::Right, + paths: HTC_VIVE_USER_PATHS, + }, + ], +}; + +const HTC_VIVE_USER_PATHS: &[XrInputSubpath] = &[ + XrInputSubpath { + kind: XrInputSubpathKind::System, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Squeeze, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Menu, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trigger, + components: &[XrInputComponent::Click, XrInputComponent::Value], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trackpad, + components: &[ + XrInputComponent::Click, + XrInputComponent::Touch, + XrInputComponent::X, + XrInputComponent::Y, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Grip, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Aim, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Haptic, + components: &[], + }, +]; + +pub const MICROSOFT_MOTION_CONTROLLER_PROFILE: XrControllerProfile = XrControllerProfile { + display_name: "Microsoft WMR Controller", + extension: None, + profile_id: "/interaction_profiles/microsoft/motion_controller", + user_paths: &[ + XrControllerUserPath { + hand: XrInputSide::Left, + paths: MICROSOFT_MOTION_CONTROLLER_USER_PATHS, + }, + XrControllerUserPath { + hand: XrInputSide::Right, + paths: MICROSOFT_MOTION_CONTROLLER_USER_PATHS, + }, + ], +}; + +const MICROSOFT_MOTION_CONTROLLER_USER_PATHS: &[XrInputSubpath] = &[ + XrInputSubpath { + kind: XrInputSubpathKind::Menu, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Squeeze, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trigger, + components: &[XrInputComponent::Value], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Thumbstick, + components: &[XrInputComponent::Click, XrInputComponent::X, XrInputComponent::Y], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trackpad, + components: &[ + XrInputComponent::Click, + XrInputComponent::Touch, + XrInputComponent::X, + XrInputComponent::Y, + ], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Grip, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Aim, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Haptic, + components: &[], + }, +]; + +pub const KHR_GENERIC_CONTROLLER_PROFILE: XrControllerProfile = XrControllerProfile { + display_name: "Khronos Generic Controller", + extension: None, + profile_id: "/interaction_profiles/khr/generic_controller", + user_paths: &[ + XrControllerUserPath { + hand: XrInputSide::Left, + paths: KHR_GENERIC_CONTROLLER_USER_PATHS, + }, + XrControllerUserPath { + hand: XrInputSide::Right, + paths: KHR_GENERIC_CONTROLLER_USER_PATHS, + }, + ], +}; + +const KHR_GENERIC_CONTROLLER_USER_PATHS: &[XrInputSubpath] = &[ + XrInputSubpath { + kind: XrInputSubpathKind::Primary, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Secondary, + components: &[XrInputComponent::Click], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Thumbstick, + components: &[XrInputComponent::Click, XrInputComponent::X, XrInputComponent::Y], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Squeeze, + components: &[XrInputComponent::Value], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Trigger, + components: &[XrInputComponent::Value], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Grip, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Aim, + components: &[XrInputComponent::Pose], + }, + XrInputSubpath { + kind: XrInputSubpathKind::Haptic, + components: &[], + }, +];