move bindings const to wlx-common, cleanups

This commit is contained in:
galister 2026-07-02 16:23:54 +09:00
parent b906bc6656
commit a3b1fdcd0d
10 changed files with 972 additions and 748 deletions

View File

@ -49,15 +49,21 @@
"Y_AXIS": "Y axis" "Y_AXIS": "Y axis"
}, },
"TYPE": { "TYPE": {
"TRIGGER": "Trigger", "BUMPER": "Bumper",
"TRACKPAD": "Trackpad", "DPAD_UP": "D-pad Up",
"THUMBSTICK": "Thumbstick", "DPAD_DOWN": "D-pad Down",
"DPAD_LEFT": "D-pad Left",
"DPAD_RIGHT": "D-pad Right",
"JOYSTICK": "Joystick", "JOYSTICK": "Joystick",
"MENU": "Menu", "MENU": "Menu",
"SHOULDER": "Shoulder",
"SQUEEZE": "Grip",
"SYSTEM": "System", "SYSTEM": "System",
"THUMBREST": "Thumbrest", "THUMBREST": "Thumbrest",
"SHOULDER": "Shoulder", "THUMBSTICK": "Thumbstick",
"SQUEEZE": "Grip" "TRACKPAD": "Trackpad",
"TRIGGER": "Trigger",
"VIEW": "View"
}, },
"CLICK": { "CLICK": {
"ANY": "Any", "ANY": "Any",

View File

@ -1,7 +1,6 @@
pub mod cached_fetcher; pub mod cached_fetcher;
pub mod networking; pub mod networking;
pub mod openxr_bindings_schema; pub mod openxr_bindings;
pub mod openxr_controller_profiles;
pub mod pactl_wrapper; pub mod pactl_wrapper;
pub mod popup_manager; pub mod popup_manager;
pub mod steam_utils; pub mod steam_utils;

View File

@ -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<str>;
fn clear_str(action: &str, side: XrInputSide) -> Option<Rc<str>>;
}
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<str> {
let value = self.as_ref();
format!("click;{action};-;{value}").into()
}
fn clear_str(_action: &str, _side: XrInputSide) -> Option<Rc<str>> {
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<str> {
let value = self.as_ref();
let side = side.as_ref();
format!("subpath;{action};{side};{value}").into()
}
fn clear_str(action: &str, side: XrInputSide) -> Option<Rc<str>> {
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<str> {
let value = self.as_ref();
let side = side.as_ref();
format!("comp;{action};{side};{value}").into()
}
fn clear_str(_action: &str, _side: XrInputSide) -> Option<Rc<str>> {
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<Self> {
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,
})
}
}

View File

@ -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<str> {
let value = self.as_ref();
let side = side.as_ref();
format!("subpath;{action};{side};{value}").into()
}
fn clear_str(action: &str, side: Side) -> Option<Rc<str>> {
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<str> {
let value = self.as_ref();
let side = side.as_ref();
format!("comp;{action};{side};{value}").into()
}
fn clear_str(_action: &str, _side: Side) -> Option<Rc<str>> {
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<Self> {
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<str> {
let value = self.as_ref();
format!("click;{action};-;{value}").into()
}
fn clear_str(_action: &str, _side: Side) -> Option<Rc<str>> {
None
}
}
pub trait BindingsDropdown {
fn translation(&self) -> Translation;
fn action_str(&self, action: &str, side: Side) -> Rc<str>;
fn clear_str(action: &str, side: Side) -> Option<Rc<str>>;
}

View File

@ -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: &[],
},
];

View File

@ -20,15 +20,14 @@ use wgui::{
use wlx_common::{ use wlx_common::{
config_io, config_io,
openxr_actions::{OneOrMany, OpenXrInputAction, OpenXrInputProfile, load_xr_input_profiles}, openxr_actions::{OneOrMany, OpenXrInputAction, OpenXrInputProfile, load_xr_input_profiles},
openxr_bindings_schema::{XrControllerProfile, XrInputComponent, XrInputSide, XrInputSubpathKind},
}; };
use crate::{ use crate::{
frontend::{FrontendTask, FrontendTasks}, frontend::{FrontendTask, FrontendTasks},
tab::settings::horiz_cell, tab::settings::horiz_cell,
util::{ util::{
openxr_bindings_schema::{ openxr_bindings::{BindingsDropdown, ClickType, ParsedOpenXrInputPath},
BindingsDropdown, ClickType, Component, ControllerProfile, ParsedOpenXrInputPath, Side, SubpathKind,
},
popup_manager::{MountPopupOnceParams, MountPopupOnceParamsExtra, PopupHolder, PopupPadding}, popup_manager::{MountPopupOnceParams, MountPopupOnceParamsExtra, PopupHolder, PopupPadding},
wgui_simple, wgui_simple,
}, },
@ -40,14 +39,14 @@ enum Task {
Save, Save,
Cancel, Cancel,
OpenContextMenu(glam::Vec2, Vec<context_menu::Cell>), OpenContextMenu(glam::Vec2, Vec<context_menu::Cell>),
UpdateThreshold(Rc<str>, Side, f32, f32), UpdateThreshold(Rc<str>, XrInputSide, f32, f32),
} }
pub struct Params<'a> { pub struct Params<'a> {
pub globals: WguiGlobals, pub globals: WguiGlobals,
pub layout: &'a mut Layout, pub layout: &'a mut Layout,
pub parent_id: WidgetID, pub parent_id: WidgetID,
pub controller_profile: &'static ControllerProfile, pub controller_profile: &'static XrControllerProfile,
pub close_callback: Box<dyn FnOnce()>, pub close_callback: Box<dyn FnOnce()>,
} }
@ -59,7 +58,7 @@ pub struct View {
profiles: Vec<OpenXrInputProfile>, profiles: Vec<OpenXrInputProfile>,
cur_profile_idx: usize, cur_profile_idx: usize,
context_menu: context_menu::ContextMenu, context_menu: context_menu::ContextMenu,
controller_profile: &'static ControllerProfile, controller_profile: &'static XrControllerProfile,
close_callback: Option<Box<dyn FnOnce()>>, close_callback: Option<Box<dyn FnOnce()>>,
} }
@ -90,10 +89,12 @@ impl ViewTrait for View {
}); });
} }
Task::UpdateThreshold(action_name, side, lo, hi) => { 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 cur_profile = &mut self.profiles[self.cur_profile_idx];
let action_mut = get_action_mut(cur_profile, &*action_name); 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]); action_mut.threshold_right = Some([lo, hi]);
} else { } else {
action_mut.threshold_left = Some([lo, hi]); action_mut.threshold_left = Some([lo, hi]);
@ -108,11 +109,6 @@ impl ViewTrait for View {
let mut s = name.splitn(4, ';'); let mut s = name.splitn(4, ';');
(s.next(), s.next(), s.next(), s.next()) (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 cur_profile = &mut self.profiles[self.cur_profile_idx];
let action_mut = get_action_mut(cur_profile, action_name); let action_mut = get_action_mut(cur_profile, action_name);
let side_mut = if side == "right" { let side_mut = if side == "right" {
@ -131,7 +127,7 @@ impl ViewTrait for View {
"comp" => { "comp" => {
apply_comp(side_mut, &side, &value); apply_comp(side_mut, &side, &value);
} }
"click" => match value.as_str() { "click" => match value {
"triple" => { "triple" => {
action_mut.triple_click = Some(true); action_mut.triple_click = Some(true);
action_mut.double_click = None; action_mut.double_click = None;
@ -257,8 +253,8 @@ impl View {
fn ensure_pose_and_haptics(&mut self) { fn ensure_pose_and_haptics(&mut self) {
let cur_profile = &mut self.profiles[self.cur_profile_idx]; let cur_profile = &mut self.profiles[self.cur_profile_idx];
let profile_left = self.controller_profile.find_userpath(Side::Left); let profile_left = self.controller_profile.find_userpath(XrInputSide::Left);
let profile_right = self.controller_profile.find_userpath(Side::Right); let profile_right = self.controller_profile.find_userpath(XrInputSide::Right);
let action = cur_profile.pose.get_or_insert_default(); 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 action = cur_profile.haptic.get_or_insert_default();
let has_haptic = profile_left 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(); .unwrap_or_default();
if action.left.is_none() && has_haptic { if action.left.is_none() && has_haptic {
let path = "/user/hand/left/output/haptic"; let path = "/user/hand/left/output/haptic";
@ -283,7 +279,7 @@ impl View {
} }
let has_haptic = profile_right 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(); .unwrap_or_default();
if action.right.is_none() && has_haptic { if action.right.is_none() && has_haptic {
let path = "/user/hand/right/output/haptic"; let path = "/user/hand/right/output/haptic";
@ -316,7 +312,7 @@ pub fn mount_popup(
frontend_tasks: FrontendTasks, frontend_tasks: FrontendTasks,
globals: WguiGlobals, globals: WguiGlobals,
popup: PopupHolder<View>, popup: PopupHolder<View>,
controller_profile: &'static ControllerProfile, controller_profile: &'static XrControllerProfile,
) { ) {
frontend_tasks frontend_tasks
.clone() .clone()
@ -353,7 +349,7 @@ fn input_controls_for_action(
mp: &mut MacroParams, mp: &mut MacroParams,
parent: WidgetID, parent: WidgetID,
action: Rc<str>, action: Rc<str>,
profile: &ControllerProfile, profile: &XrControllerProfile,
current: &mut OpenXrInputAction, current: &mut OpenXrInputAction,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let id = mp.idx.to_string(); let id = mp.idx.to_string();
@ -391,7 +387,7 @@ fn input_controls_for_action(
mp, mp,
parent, parent,
current_left, current_left,
Side::Left, XrInputSide::Left,
action.clone(), action.clone(),
click_type, click_type,
profile, profile,
@ -407,7 +403,7 @@ fn input_controls_for_action(
mp, mp,
parent, parent,
current_right, current_right,
Side::Right, XrInputSide::Right,
action, action,
click_type, click_type,
profile, profile,
@ -419,10 +415,10 @@ fn input_controls_for_hand(
mp: &mut MacroParams, mp: &mut MacroParams,
parent: WidgetID, parent: WidgetID,
current: Option<&str>, current: Option<&str>,
side: Side, side: XrInputSide,
action: Rc<str>, action: Rc<str>,
click_type: ClickType, click_type: ClickType,
profile: &ControllerProfile, profile: &XrControllerProfile,
threshold: Option<[f32; 2]>, threshold: Option<[f32; 2]>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let Some(user_path) = profile.find_userpath(side) else { 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 parent = horiz_cell(mp.layout, parent)?;
let available_components: Rc<[Component]> = current let available_components: Rc<[XrInputComponent]> = current
.as_ref() .as_ref()
.and_then(|par| user_path.find_subpath(par.subpath)) .and_then(|par| user_path.find_subpath(par.subpath))
.map(|subp| subp.components) .map(|subp| subp.components)
.unwrap_or_default() .unwrap_or_default()
.into(); .into();
let available_subpaths: Rc<[SubpathKind]> = user_path let available_subpaths: Rc<[XrInputSubpathKind]> = user_path
.paths .paths
.iter() .iter()
.filter(|x| !x.kind.get_bool("Hidden").unwrap_or_default()) .filter(|x| !x.kind.get_bool("Hidden").unwrap_or_default())
@ -482,9 +478,9 @@ fn subpath_dropdown(
mp: &mut MacroParams, mp: &mut MacroParams,
parent: WidgetID, parent: WidgetID,
action: Rc<str>, action: Rc<str>,
side: Side, side: XrInputSide,
available: Rc<[SubpathKind]>, available: Rc<[XrInputSubpathKind]>,
current: Option<SubpathKind>, current: Option<XrInputSubpathKind>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new(); let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert(Rc::from("tooltip"), Rc::from("APP_SETTINGS.BINDINGS.SUBPATH")); params.insert(Rc::from("tooltip"), Rc::from("APP_SETTINGS.BINDINGS.SUBPATH"));
@ -495,7 +491,7 @@ fn subpath_dropdown(
mp.layout, mp.layout,
parent, parent,
Vec2::new(32.0, 32.0), 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 let current_text = current
@ -511,9 +507,9 @@ fn component_dropdown(
mp: &mut MacroParams, mp: &mut MacroParams,
parent: WidgetID, parent: WidgetID,
action: Rc<str>, action: Rc<str>,
side: Side, side: XrInputSide,
available: Rc<[Component]>, available: Rc<[XrInputComponent]>,
current: Option<Component>, current: Option<XrInputComponent>,
) -> anyhow::Result<bool> { ) -> anyhow::Result<bool> {
if available.is_empty() { if available.is_empty() {
return Ok(false); return Ok(false);
@ -541,7 +537,7 @@ fn clicks_dropdown(mp: &mut MacroParams, parent: WidgetID, action: Rc<str>, curr
let current_text = current.translation(); let current_text = current.translation();
let available = [ClickType::Any, ClickType::Double, ClickType::Triple].into(); 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(()) Ok(())
} }
@ -550,7 +546,7 @@ fn threshold_slider(
mp: &mut MacroParams, mp: &mut MacroParams,
parent: WidgetID, parent: WidgetID,
action: Rc<str>, action: Rc<str>,
side: Side, side: XrInputSide,
current: Option<[f32; 2]>, current: Option<[f32; 2]>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let id = mp.idx.to_string(); let id = mp.idx.to_string();
@ -590,7 +586,7 @@ fn create_dropdown<B: 'static + BindingsDropdown>(
parent: WidgetID, parent: WidgetID,
mut params: HashMap<Rc<str>, Rc<str>>, mut params: HashMap<Rc<str>, Rc<str>>,
action: Rc<str>, action: Rc<str>,
side: Side, side: XrInputSide,
current_text: Translation, current_text: Translation,
available: Rc<[B]>, available: Rc<[B]>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
@ -651,16 +647,19 @@ fn apply_subpath(
side_mut: &mut Option<OneOrMany<String>>, side_mut: &mut Option<OneOrMany<String>>,
side_str: &str, side_str: &str,
subpath_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; return;
}; };
let Some(subpath_obj) = profile.find_userpath(side).and_then(|p| p.find_subpath(subpath)) else { let Some(subpath_obj) = profile.find_userpath(side).and_then(|p| p.find_subpath(subpath)) else {
return; 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::One(x) => x.as_str(),
OneOrMany::Many(x) => x.first().unwrap().as_str(), OneOrMany::Many(x) => x.first().unwrap().as_str(),
}) { }) {
@ -677,8 +676,7 @@ fn apply_subpath(
*subpath_obj.components.first().unwrap() *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!( *side_mut = Some(OneOrMany::One(format!(
"/user/hand/{side_str}/input/{subpath_str}/{comp_str}" "/user/hand/{side_str}/input/{subpath_str}/{comp_str}"
))); )));
@ -698,7 +696,7 @@ fn apply_comp(side_mut: &mut Option<OneOrMany<String>>, side: &str, comp: &str)
return; 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}"))); *side_mut = Some(OneOrMany::One(format!("/user/hand/{side}/input/{subpath}/{comp}")));
} }

View File

@ -9,20 +9,17 @@ use wgui::{
parser::{Fetchable, ParseDocumentParams}, parser::{Fetchable, ParseDocumentParams},
task::Tasks, task::Tasks,
}; };
use wlx_common::{openxr_bindings_schema::XrControllerProfile, openxr_controller_profiles::OPENXR_INPUT_PROFILES};
use crate::{ use crate::{
frontend::{FrontendTask, FrontendTasks}, frontend::{FrontendTask, FrontendTasks},
util::{ util::popup_manager::{MountPopupOnceParams, PopupHolder},
openxr_bindings_schema::ControllerProfile,
openxr_controller_profiles::OPENXR_INPUT_PROFILES,
popup_manager::{MountPopupOnceParams, PopupHolder},
},
views::{self, ViewTrait, ViewUpdateParams, bindings}, views::{self, ViewTrait, ViewUpdateParams, bindings},
}; };
#[derive(Clone)] #[derive(Clone)]
enum Task { enum Task {
SelectProfile(&'static ControllerProfile), SelectProfile(&'static XrControllerProfile),
} }
pub struct Params<'a> { pub struct Params<'a> {

View File

@ -11,6 +11,8 @@ pub mod desktop_finder;
mod handle; mod handle;
pub mod locale; pub mod locale;
pub mod openxr_actions; pub mod openxr_actions;
pub mod openxr_bindings_schema;
pub mod openxr_controller_profiles;
pub mod overlays; pub mod overlays;
pub mod timestep; pub mod timestep;
pub mod windowing; pub mod windowing;

View File

@ -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,
}

View File

@ -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: &[],
},
];