mirror of https://github.com/wayvr-org/wayvr.git
controller profiles in rust instead of json
This commit is contained in:
parent
5e316fe0d3
commit
374f85ee81
Binary file not shown.
|
|
@ -53,6 +53,7 @@
|
||||||
"TRACKPAD": "Trackpad",
|
"TRACKPAD": "Trackpad",
|
||||||
"THUMBSTICK": "Thumbstick",
|
"THUMBSTICK": "Thumbstick",
|
||||||
"JOYSTICK": "Joystick",
|
"JOYSTICK": "Joystick",
|
||||||
|
"MENU": "Menu",
|
||||||
"SYSTEM": "System",
|
"SYSTEM": "System",
|
||||||
"THUMBREST": "Thumbrest",
|
"THUMBREST": "Thumbrest",
|
||||||
"SHOULDER": "Shoulder",
|
"SHOULDER": "Shoulder",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod cached_fetcher;
|
pub mod cached_fetcher;
|
||||||
pub mod networking;
|
pub mod networking;
|
||||||
pub mod openxr_bindings_schema;
|
pub mod openxr_bindings_schema;
|
||||||
|
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;
|
||||||
|
|
|
||||||
|
|
@ -1,107 +1,41 @@
|
||||||
use std::{collections::BTreeMap, io::Read, rc::Rc};
|
use std::rc::Rc;
|
||||||
|
|
||||||
use anyhow::{Context, bail};
|
use anyhow::{bail, Context};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::{AsRefStr, EnumProperty, EnumString};
|
use strum::{AsRefStr, EnumProperty, EnumString};
|
||||||
use wgui::i18n::Translation;
|
use wgui::i18n::Translation;
|
||||||
|
|
||||||
static BINDINGS_LZ4: &[u8] = include_bytes!("../../assets/bindings.json.lz4");
|
pub struct ControllerProfile {
|
||||||
|
pub display_name: &'static str,
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
pub profile_id: &'static str,
|
||||||
pub struct BindingsFile {
|
pub user_paths: &'static [ControllerUserPath],
|
||||||
#[serde(rename = "$schema")]
|
|
||||||
pub schema: Option<String>,
|
|
||||||
|
|
||||||
pub profiles: BTreeMap<String, Rc<Profile>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BindingsFile {
|
impl ControllerProfile {
|
||||||
pub fn load_embedded() -> Self {
|
pub fn find_userpath(&self, side: Side) -> Option<&ControllerUserPath> {
|
||||||
let mut decoder = lz4_flex::frame::FrameDecoder::new(BINDINGS_LZ4);
|
self.user_paths.iter().find(|x| x.hand == side)
|
||||||
let mut json = Vec::new();
|
|
||||||
decoder.read_to_end(&mut json).unwrap(); // safe
|
|
||||||
|
|
||||||
serde_json::from_slice(&json).unwrap() // safe
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
pub struct ControllerUserPath {
|
||||||
pub struct Profile {
|
pub hand: Side,
|
||||||
pub title: Rc<str>,
|
pub paths: &'static [Subpath],
|
||||||
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub kind: ProfileType,
|
|
||||||
|
|
||||||
pub steamvr_controllertype: Option<String>,
|
|
||||||
pub monado_device: Option<String>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub extended_by: Vec<String>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub subaction_paths: Vec<String>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub subpaths: BTreeMap<String, Subpath>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
impl ControllerUserPath {
|
||||||
#[serde(rename_all = "snake_case")]
|
pub fn find_subpath(&self, subpath: SubpathKind) -> Option<&Subpath> {
|
||||||
pub enum ProfileType {
|
self.paths.iter().find(|x| x.kind == subpath)
|
||||||
TrackedController,
|
}
|
||||||
|
|
||||||
#[serde(other)]
|
|
||||||
Other,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Subpath {
|
pub struct Subpath {
|
||||||
#[serde(rename = "type")]
|
pub kind: SubpathKind,
|
||||||
pub kind: SubpathType,
|
pub components: &'static [Component],
|
||||||
|
|
||||||
pub localized_name: String,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub components: Vec<Component>,
|
|
||||||
|
|
||||||
pub side: Option<Side>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Subpath {
|
|
||||||
pub fn get_effective_components(&self) -> Rc<[Component]> {
|
|
||||||
let mut v = vec![];
|
|
||||||
for c in self.components.iter() {
|
|
||||||
match c {
|
|
||||||
// position is not an openxr component, it's just a monado thing
|
|
||||||
Component::Position => {
|
|
||||||
v.push(Component::X);
|
|
||||||
v.push(Component::Y);
|
|
||||||
}
|
|
||||||
Component::Other => {}
|
|
||||||
other => v.push(*other),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum SubpathType {
|
|
||||||
Button,
|
|
||||||
Trigger,
|
|
||||||
Joystick,
|
|
||||||
Pose,
|
|
||||||
Trackpad,
|
|
||||||
Vibration,
|
|
||||||
|
|
||||||
#[serde(other)]
|
|
||||||
Other,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumString, AsRefStr, EnumProperty)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumString, AsRefStr, EnumProperty)]
|
||||||
#[strum(ascii_case_insensitive)]
|
#[strum(ascii_case_insensitive)]
|
||||||
pub enum IdentifierType {
|
pub enum SubpathKind {
|
||||||
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.TRIGGER"))]
|
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.TRIGGER"))]
|
||||||
Trigger,
|
Trigger,
|
||||||
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.TRACKPAD"))]
|
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.TRACKPAD"))]
|
||||||
|
|
@ -112,6 +46,12 @@ pub enum IdentifierType {
|
||||||
Joystick,
|
Joystick,
|
||||||
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.SYSTEM"))]
|
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.SYSTEM"))]
|
||||||
System,
|
System,
|
||||||
|
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.MENU"))]
|
||||||
|
Menu,
|
||||||
|
|
||||||
|
Primary,
|
||||||
|
Secondary,
|
||||||
|
|
||||||
A,
|
A,
|
||||||
B,
|
B,
|
||||||
X,
|
X,
|
||||||
|
|
@ -126,9 +66,16 @@ pub enum IdentifierType {
|
||||||
Shoulder,
|
Shoulder,
|
||||||
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.SQUEEZE"))]
|
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.TYPE.SQUEEZE"))]
|
||||||
Squeeze,
|
Squeeze,
|
||||||
|
|
||||||
|
#[strum(props(Hidden = true))]
|
||||||
|
Grip,
|
||||||
|
#[strum(props(Hidden = true))]
|
||||||
|
Aim,
|
||||||
|
#[strum(props(Hidden = true))]
|
||||||
|
Haptic,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BindingsDropdown for IdentifierType {
|
impl BindingsDropdown for SubpathKind {
|
||||||
fn translation(&self) -> Translation {
|
fn translation(&self) -> Translation {
|
||||||
self
|
self
|
||||||
.get_str("Translation")
|
.get_str("Translation")
|
||||||
|
|
@ -143,7 +90,7 @@ impl BindingsDropdown for IdentifierType {
|
||||||
}
|
}
|
||||||
fn clear_str(action: &str, side: Side) -> Option<Rc<str>> {
|
fn clear_str(action: &str, side: Side) -> Option<Rc<str>> {
|
||||||
let side = side.as_ref();
|
let side = side.as_ref();
|
||||||
Some(format!("subpath;{action};{side};-").into())
|
Some(format!("clear;{action};{side};-").into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,20 +107,16 @@ pub enum Component {
|
||||||
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.VALUE"))]
|
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.VALUE"))]
|
||||||
Value,
|
Value,
|
||||||
|
|
||||||
/// Not an actual component but monado uses this instead of X/Y
|
|
||||||
Position,
|
|
||||||
Pose,
|
|
||||||
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.PROXIMITY"))]
|
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.PROXIMITY"))]
|
||||||
Proximity,
|
Proximity,
|
||||||
Haptic,
|
|
||||||
|
|
||||||
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.X_AXIS"))]
|
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.X_AXIS"))]
|
||||||
X,
|
X,
|
||||||
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.Y_AXIS"))]
|
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.Y_AXIS"))]
|
||||||
Y,
|
Y,
|
||||||
|
|
||||||
#[serde(other)]
|
// below are hidden
|
||||||
Other,
|
Pose,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component {
|
impl Component {
|
||||||
|
|
@ -202,6 +145,7 @@ impl BindingsDropdown for Component {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumString, AsRefStr)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumString, AsRefStr)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[strum(ascii_case_insensitive)]
|
||||||
pub enum Side {
|
pub enum Side {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
|
|
@ -210,16 +154,10 @@ pub enum Side {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct ParsedOpenXrInputPath {
|
pub struct ParsedOpenXrInputPath {
|
||||||
pub side: Side,
|
pub side: Side,
|
||||||
pub identifier: IdentifierType,
|
pub subpath: SubpathKind,
|
||||||
pub component: Component,
|
pub component: Component,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParsedOpenXrInputPath {
|
|
||||||
pub fn to_subpath(&self) -> String {
|
|
||||||
format!("/input/{}", self.identifier.as_ref().to_lowercase())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for ParsedOpenXrInputPath {
|
impl<'a> TryFrom<&'a str> for ParsedOpenXrInputPath {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
|
@ -245,11 +183,11 @@ impl<'a> TryFrom<&'a str> for ParsedOpenXrInputPath {
|
||||||
|
|
||||||
let component = Component::try_from(component).context("bad component")?;
|
let component = Component::try_from(component).context("bad component")?;
|
||||||
|
|
||||||
let identifier = IdentifierType::try_from(identifier).context("bad subpath")?;
|
let identifier = SubpathKind::try_from(identifier).context("bad subpath")?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
side,
|
side,
|
||||||
identifier,
|
subpath: identifier,
|
||||||
component,
|
component,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,465 @@
|
||||||
|
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: &[],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{borrow::Cow, collections::HashMap, rc::Rc};
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
|
use strum::EnumProperty;
|
||||||
use wgui::{
|
use wgui::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
components::{
|
components::{
|
||||||
|
|
@ -26,7 +27,7 @@ use crate::{
|
||||||
tab::settings::horiz_cell,
|
tab::settings::horiz_cell,
|
||||||
util::{
|
util::{
|
||||||
openxr_bindings_schema::{
|
openxr_bindings_schema::{
|
||||||
BindingsDropdown, ClickType, Component, IdentifierType, ParsedOpenXrInputPath, Profile, Side, SubpathType,
|
BindingsDropdown, ClickType, Component, ControllerProfile, ParsedOpenXrInputPath, Side, SubpathKind,
|
||||||
},
|
},
|
||||||
popup_manager::{MountPopupOnceParams, MountPopupOnceParamsExtra, PopupHolder, PopupPadding},
|
popup_manager::{MountPopupOnceParams, MountPopupOnceParamsExtra, PopupHolder, PopupPadding},
|
||||||
wgui_simple,
|
wgui_simple,
|
||||||
|
|
@ -46,8 +47,7 @@ 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 profile_id: Rc<str>,
|
pub controller_profile: &'static ControllerProfile,
|
||||||
pub profile: Rc<Profile>,
|
|
||||||
pub close_callback: Box<dyn FnOnce()>,
|
pub close_callback: Box<dyn FnOnce()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +59,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,
|
||||||
schema: Rc<Profile>,
|
controller_profile: &'static ControllerProfile,
|
||||||
close_callback: Option<Box<dyn FnOnce()>>,
|
close_callback: Option<Box<dyn FnOnce()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,10 +122,10 @@ impl ViewTrait for View {
|
||||||
*side_mut = None;
|
*side_mut = None;
|
||||||
}
|
}
|
||||||
"subpath" => {
|
"subpath" => {
|
||||||
reconstruct_path(&self.schema, side_mut, &side, Some(value.as_str()), None);
|
apply_subpath(side_mut, &side, &value, self.controller_profile);
|
||||||
}
|
}
|
||||||
"comp" => {
|
"comp" => {
|
||||||
reconstruct_path(&self.schema, side_mut, &side, None, Some(value.as_str()));
|
apply_comp(side_mut, &side, &value);
|
||||||
}
|
}
|
||||||
"click" => match value.as_str() {
|
"click" => match value.as_str() {
|
||||||
"triple" => {
|
"triple" => {
|
||||||
|
|
@ -163,11 +163,11 @@ impl View {
|
||||||
|
|
||||||
let cur_profile_idx = profiles
|
let cur_profile_idx = profiles
|
||||||
.iter()
|
.iter()
|
||||||
.position(|i| i.profile.as_str() == &*params.profile_id)
|
.position(|i| i.profile.as_str() == &*params.controller_profile.profile_id)
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
let idx = profiles.len();
|
let idx = profiles.len();
|
||||||
profiles.push(OpenXrInputProfile {
|
profiles.push(OpenXrInputProfile {
|
||||||
profile: params.profile_id.to_string(),
|
profile: params.controller_profile.profile_id.to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
idx
|
idx
|
||||||
|
|
@ -195,7 +195,7 @@ impl View {
|
||||||
profiles,
|
profiles,
|
||||||
cur_profile_idx,
|
cur_profile_idx,
|
||||||
context_menu: context_menu::ContextMenu::default(),
|
context_menu: context_menu::ContextMenu::default(),
|
||||||
schema: params.profile,
|
controller_profile: params.controller_profile,
|
||||||
close_callback: Some(params.close_callback),
|
close_callback: Some(params.close_callback),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -238,7 +238,7 @@ impl View {
|
||||||
|
|
||||||
for action in action_names {
|
for action in action_names {
|
||||||
let current = get_action_mut(&mut self.profiles[self.cur_profile_idx], action);
|
let current = get_action_mut(&mut self.profiles[self.cur_profile_idx], action);
|
||||||
input_controls_for_action(&mut mp, self.list_parent, action.into(), &self.schema, current)?;
|
input_controls_for_action(&mut mp, self.list_parent, action.into(), &self.controller_profile, current)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -247,65 +247,33 @@ 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];
|
||||||
|
|
||||||
if cur_profile.pose.is_none() {
|
let profile_left = self.controller_profile.find_userpath(Side::Left);
|
||||||
let schema = &self.schema;
|
let profile_right = self.controller_profile.find_userpath(Side::Right);
|
||||||
let mut aim_pose_name: Option<&str> = None;
|
|
||||||
let mut first_pose_name: Option<&str> = None;
|
|
||||||
|
|
||||||
for (key, subpath) in &schema.subpaths {
|
let action = cur_profile.pose.get_or_insert_default();
|
||||||
if subpath.kind != SubpathType::Pose {
|
|
||||||
continue;
|
if action.left.is_none() && profile_left.is_some() {
|
||||||
}
|
let path = "/user/hand/left/input/aim/pose";
|
||||||
let name = key.strip_prefix("/input/");
|
action.left = Some(OneOrMany::One(path.into()));
|
||||||
let Some(name) = name else { continue };
|
|
||||||
if first_pose_name.is_none() {
|
|
||||||
first_pose_name = Some(name);
|
|
||||||
}
|
|
||||||
if name == "aim" {
|
|
||||||
aim_pose_name = Some(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// no aim pose → use first one
|
if action.right.is_none() && profile_right.is_some() {
|
||||||
if let Some(name) = aim_pose_name.or(first_pose_name) {
|
let path = "/user/hand/right/input/aim/pose";
|
||||||
let pose_action = cur_profile.pose.get_or_insert_default();
|
action.right = Some(OneOrMany::One(path.into()));
|
||||||
if pose_action.left.is_none() {
|
|
||||||
let left_path = format!("/user/hand/left/input/{name}/pose");
|
|
||||||
pose_action.left = Some(OneOrMany::One(left_path));
|
|
||||||
}
|
|
||||||
if pose_action.right.is_none() {
|
|
||||||
let right_path = format!("/user/hand/right/input/{name}/pose");
|
|
||||||
pose_action.right = Some(OneOrMany::One(right_path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cur_profile.haptic.is_none() {
|
let action = cur_profile.haptic.get_or_insert_default();
|
||||||
let schema = &self.schema;
|
|
||||||
let mut first_haptic_name: Option<&str> = None;
|
|
||||||
|
|
||||||
for (key, subpath) in &schema.subpaths {
|
let has_haptic = profile_left.map(|x| x.find_subpath(SubpathKind::Haptic).is_some()).unwrap_or_default();
|
||||||
if subpath.kind != SubpathType::Vibration {
|
if action.left.is_none() && has_haptic {
|
||||||
continue;
|
let path = "/user/hand/left/output/haptic";
|
||||||
}
|
action.left = Some(OneOrMany::One(path.into()));
|
||||||
let name = key.strip_prefix("/output/");
|
|
||||||
let Some(name) = name else { continue };
|
|
||||||
if first_haptic_name.is_none() {
|
|
||||||
first_haptic_name = Some(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = first_haptic_name {
|
let has_haptic = profile_right.map(|x| x.find_subpath(SubpathKind::Haptic).is_some()).unwrap_or_default();
|
||||||
let haptic_action = cur_profile.haptic.get_or_insert_with(OpenXrInputAction::default);
|
if action.right.is_none() && has_haptic {
|
||||||
if haptic_action.left.is_none() {
|
let path = "/user/hand/right/output/haptic";
|
||||||
let left_path = format!("/user/hand/left/output/{name}");
|
action.right = Some(OneOrMany::One(path.into()));
|
||||||
haptic_action.left = Some(OneOrMany::One(left_path));
|
|
||||||
}
|
|
||||||
if haptic_action.right.is_none() {
|
|
||||||
let right_path = format!("/user/hand/right/output/{name}");
|
|
||||||
haptic_action.right = Some(OneOrMany::One(right_path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -334,21 +302,19 @@ pub fn mount_popup(
|
||||||
frontend_tasks: FrontendTasks,
|
frontend_tasks: FrontendTasks,
|
||||||
globals: WguiGlobals,
|
globals: WguiGlobals,
|
||||||
popup: PopupHolder<View>,
|
popup: PopupHolder<View>,
|
||||||
profile_id: Rc<str>,
|
controller_profile: &'static ControllerProfile,
|
||||||
profile: Rc<Profile>,
|
|
||||||
) {
|
) {
|
||||||
frontend_tasks
|
frontend_tasks
|
||||||
.clone()
|
.clone()
|
||||||
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
|
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
|
||||||
Translation::from_raw_text_rc(profile.title.clone()),
|
Translation::from_raw_text(controller_profile.display_name),
|
||||||
Box::new(move |data| {
|
Box::new(move |data| {
|
||||||
let close_callback = popup.get_close_callback(data.layout);
|
let close_callback = popup.get_close_callback(data.layout);
|
||||||
let view = View::new(Params {
|
let view = View::new(Params {
|
||||||
globals: globals.clone(),
|
globals: globals.clone(),
|
||||||
layout: data.layout,
|
layout: data.layout,
|
||||||
parent_id: data.id_content,
|
parent_id: data.id_content,
|
||||||
profile_id,
|
controller_profile,
|
||||||
profile,
|
|
||||||
close_callback,
|
close_callback,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
@ -373,7 +339,7 @@ fn input_controls_for_action(
|
||||||
mp: &mut MacroParams,
|
mp: &mut MacroParams,
|
||||||
parent: WidgetID,
|
parent: WidgetID,
|
||||||
action: Rc<str>,
|
action: Rc<str>,
|
||||||
profile: &Profile,
|
profile: &ControllerProfile,
|
||||||
current: &mut OpenXrInputAction,
|
current: &mut OpenXrInputAction,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let id = mp.idx.to_string();
|
let id = mp.idx.to_string();
|
||||||
|
|
@ -442,38 +408,24 @@ fn input_controls_for_hand(
|
||||||
side: Side,
|
side: Side,
|
||||||
action: Rc<str>,
|
action: Rc<str>,
|
||||||
click_type: ClickType,
|
click_type: ClickType,
|
||||||
profile: &Profile,
|
profile: &ControllerProfile,
|
||||||
threshold: Option<[f32; 2]>,
|
threshold: Option<[f32; 2]>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let subaction_path = match side {
|
let Some(user_path) = profile.find_userpath(side) else {
|
||||||
Side::Left => "/user/hand/left",
|
return Ok(()); // this hand is not available
|
||||||
Side::Right => "/user/hand/right",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if !profile.subaction_paths.iter().any(|p| p == subaction_path) {
|
|
||||||
return Ok(()); // skip
|
|
||||||
}
|
|
||||||
|
|
||||||
let current = current.and_then(|cur| ParsedOpenXrInputPath::try_from(cur).log_warn(cur).ok());
|
let current = current.and_then(|cur| ParsedOpenXrInputPath::try_from(cur).log_warn(cur).ok());
|
||||||
|
|
||||||
let parent = horiz_cell(mp.layout, parent)?;
|
let parent = horiz_cell(mp.layout, parent)?;
|
||||||
|
|
||||||
let available_components = current
|
let available_components : Rc<[Component]> = current
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|par| profile.subpaths.get(&par.to_subpath()))
|
.and_then(|par| user_path.find_subpath(par.subpath))
|
||||||
.map(|subp| subp.get_effective_components())
|
.map(|subp| subp.components)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default().into();
|
||||||
|
|
||||||
let available_subpaths = profile
|
let available_subpaths : Rc<[SubpathKind]> = user_path.paths.iter().filter(|x| !x.kind.get_bool("Hidden").unwrap_or_default()).map(|x| x.kind).collect();
|
||||||
.subpaths
|
|
||||||
.iter()
|
|
||||||
.filter(|(_, path)| path.side.is_none_or(|s| s == side))
|
|
||||||
.filter_map(|(key, _)| {
|
|
||||||
key
|
|
||||||
.strip_prefix("/input/")
|
|
||||||
.and_then(|ident| IdentifierType::try_from(ident).ok())
|
|
||||||
})
|
|
||||||
.collect::<Rc<[IdentifierType]>>();
|
|
||||||
|
|
||||||
subpath_dropdown(
|
subpath_dropdown(
|
||||||
mp,
|
mp,
|
||||||
|
|
@ -481,7 +433,7 @@ fn input_controls_for_hand(
|
||||||
action.clone(),
|
action.clone(),
|
||||||
side,
|
side,
|
||||||
available_subpaths,
|
available_subpaths,
|
||||||
current.as_ref().map(|x| x.identifier),
|
current.as_ref().map(|x| x.subpath),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if !component_dropdown(
|
if !component_dropdown(
|
||||||
|
|
@ -511,8 +463,8 @@ fn subpath_dropdown(
|
||||||
parent: WidgetID,
|
parent: WidgetID,
|
||||||
action: Rc<str>,
|
action: Rc<str>,
|
||||||
side: Side,
|
side: Side,
|
||||||
available: Rc<[IdentifierType]>,
|
available: Rc<[SubpathKind]>,
|
||||||
current: Option<IdentifierType>,
|
current: Option<SubpathKind>,
|
||||||
) -> 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"));
|
||||||
|
|
@ -674,30 +626,43 @@ fn create_dropdown<B: 'static + BindingsDropdown>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reconstruct_path(
|
fn apply_subpath(side_mut: &mut Option<OneOrMany<String>>, side_str: &str, subpath_str: &str, profile: &ControllerProfile,) {
|
||||||
schema: &Profile,
|
let (Ok(side), Ok(subpath)) = (Side::try_from(side_str), SubpathKind::try_from(subpath_str)) else {
|
||||||
side_mut: &mut Option<OneOrMany<String>>,
|
return;
|
||||||
side: &str,
|
};
|
||||||
subpath: Option<&str>,
|
let Some(subpath_obj) = profile.find_userpath(side).and_then(|p| p.find_subpath(subpath)) else {
|
||||||
comp: Option<&str>,
|
|
||||||
) {
|
|
||||||
if side_mut.is_none() {
|
|
||||||
let Some(subpath) = subpath else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(comp) = comp {
|
let comp : Component = if let Some(first) = side_mut.as_ref().map(|x| match x {
|
||||||
*side_mut = Some(OneOrMany::One(format!("/user/hand/{side}/input/{subpath}/{comp}")));
|
OneOrMany::One(x) => x.as_str(),
|
||||||
} else {
|
OneOrMany::Many(x) => x.first().unwrap().as_str(),
|
||||||
let key = format!("/input/{subpath}");
|
}) {
|
||||||
let Some(schema_subpath) = schema.subpaths.get(&key) else {
|
let Ok(parsed) = ParsedOpenXrInputPath::try_from(first) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let comps = schema_subpath.get_effective_components();
|
|
||||||
let comp = comps.first().unwrap().as_ref(); // safe
|
let mut parsed_compo = parsed.component;
|
||||||
*side_mut = Some(OneOrMany::One(format!("/user/hand/{side}/input/{subpath}/{comp}")));
|
if !subpath_obj.components.contains(&parsed_compo) {
|
||||||
|
parsed_compo = *subpath_obj.components.first().unwrap();
|
||||||
}
|
}
|
||||||
|
parsed_compo
|
||||||
} else {
|
} else {
|
||||||
|
*subpath_obj.components.first().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let comp_str = comp.as_ref().to_lowercase();
|
||||||
|
|
||||||
|
*side_mut = Some(OneOrMany::One(format!(
|
||||||
|
"/user/hand/{side_str}/input/{subpath_str}/{comp_str}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_comp(side_mut: &mut Option<OneOrMany<String>>, side: &str, comp: &str) {
|
||||||
|
if side_mut.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let first = match side_mut.as_ref().unwrap() {
|
let first = match side_mut.as_ref().unwrap() {
|
||||||
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(),
|
||||||
|
|
@ -707,22 +672,9 @@ fn reconstruct_path(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_subpath = subpath.map_or_else(|| Cow::Owned(parsed.identifier.as_ref().to_lowercase()), Cow::Borrowed);
|
let subpath = parsed.subpath.as_ref().to_lowercase();
|
||||||
|
|
||||||
let mut parsed_compo = parsed.component;
|
|
||||||
let key = format!("/input/{new_subpath}");
|
|
||||||
let Some(schema_subpath) = schema.subpaths.get(&key) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let effective_compo = schema_subpath.get_effective_components();
|
|
||||||
if !effective_compo.contains(&parsed_compo) {
|
|
||||||
parsed_compo = *effective_compo.first().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_comp = comp.map_or_else(|| Cow::Owned(parsed_compo.as_ref().to_lowercase()), Cow::Borrowed);
|
|
||||||
|
|
||||||
*side_mut = Some(OneOrMany::One(format!(
|
*side_mut = Some(OneOrMany::One(format!(
|
||||||
"/user/hand/{side}/input/{new_subpath}/{new_comp}"
|
"/user/hand/{side}/input/{subpath}/{comp}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{collections::HashMap, rc::Rc};
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use wgui::{
|
use wgui::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
components::button::ComponentButton,
|
components::button::ComponentButton,
|
||||||
|
|
@ -14,15 +13,16 @@ use wgui::{
|
||||||
use crate::{
|
use crate::{
|
||||||
frontend::{FrontendTask, FrontendTasks},
|
frontend::{FrontendTask, FrontendTasks},
|
||||||
util::{
|
util::{
|
||||||
openxr_bindings_schema,
|
openxr_bindings_schema::ControllerProfile,
|
||||||
|
openxr_controller_profiles::OPENXR_INPUT_PROFILES,
|
||||||
popup_manager::{MountPopupOnceParams, PopupHolder},
|
popup_manager::{MountPopupOnceParams, PopupHolder},
|
||||||
},
|
},
|
||||||
views::{self, ViewTrait, ViewUpdateParams, bindings},
|
views::{self, bindings, ViewTrait, ViewUpdateParams},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum Task {
|
enum Task {
|
||||||
SelectProfile(Rc<str>),
|
SelectProfile(&'static ControllerProfile),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Params<'a> {
|
pub struct Params<'a> {
|
||||||
|
|
@ -37,7 +37,6 @@ pub struct View {
|
||||||
frontend_tasks: FrontendTasks,
|
frontend_tasks: FrontendTasks,
|
||||||
globals: WguiGlobals,
|
globals: WguiGlobals,
|
||||||
bindings_popup: PopupHolder<bindings::View>,
|
bindings_popup: PopupHolder<bindings::View>,
|
||||||
bindings_file: openxr_bindings_schema::BindingsFile,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewTrait for View {
|
impl ViewTrait for View {
|
||||||
|
|
@ -46,19 +45,12 @@ impl ViewTrait for View {
|
||||||
|
|
||||||
for task in self.tasks.drain() {
|
for task in self.tasks.drain() {
|
||||||
match task {
|
match task {
|
||||||
Task::SelectProfile(profile_id) => {
|
Task::SelectProfile(profile) => {
|
||||||
let profile = self
|
|
||||||
.bindings_file
|
|
||||||
.profiles
|
|
||||||
.get(&*profile_id)
|
|
||||||
.context("Selected non-existing profile. UI bug?")?;
|
|
||||||
|
|
||||||
views::bindings::mount_popup(
|
views::bindings::mount_popup(
|
||||||
self.frontend_tasks.clone(),
|
self.frontend_tasks.clone(),
|
||||||
self.globals.clone(),
|
self.globals.clone(),
|
||||||
self.bindings_popup.clone(),
|
self.bindings_popup.clone(),
|
||||||
profile_id.clone(),
|
profile,
|
||||||
profile.clone(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,15 +73,12 @@ impl View {
|
||||||
|
|
||||||
let tasks = Tasks::new();
|
let tasks = Tasks::new();
|
||||||
|
|
||||||
let bindings_file = openxr_bindings_schema::BindingsFile::load_embedded();
|
for (idx, profile) in OPENXR_INPUT_PROFILES.iter().enumerate() {
|
||||||
|
|
||||||
for (idx, (profile_id, profile)) in bindings_file.profiles.iter().enumerate() {
|
|
||||||
let id = format!("profile_btn_{idx}");
|
let id = format!("profile_btn_{idx}");
|
||||||
let profile_name: Rc<str> = profile.title.clone();
|
|
||||||
|
|
||||||
let mut cell_params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
let mut cell_params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
||||||
cell_params.insert(Rc::from("id"), Rc::from(id.clone()));
|
cell_params.insert(Rc::from("id"), Rc::from(id.clone()));
|
||||||
cell_params.insert(Rc::from("text"), profile_name);
|
cell_params.insert(Rc::from("text"), Rc::from(profile.display_name));
|
||||||
|
|
||||||
parser_state.instantiate_template(
|
parser_state.instantiate_template(
|
||||||
doc_params,
|
doc_params,
|
||||||
|
|
@ -102,9 +91,8 @@ impl View {
|
||||||
let btn = parser_state.fetch_component_as::<ComponentButton>(&id)?;
|
let btn = parser_state.fetch_component_as::<ComponentButton>(&id)?;
|
||||||
let tasks_clone = tasks.clone();
|
let tasks_clone = tasks.clone();
|
||||||
btn.on_click(Rc::new({
|
btn.on_click(Rc::new({
|
||||||
let profile_id: Rc<str> = profile_id.clone().into();
|
|
||||||
move |_common, _e| {
|
move |_common, _e| {
|
||||||
tasks_clone.push(Task::SelectProfile(profile_id.clone()));
|
tasks_clone.push(Task::SelectProfile(profile));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
@ -115,7 +103,6 @@ impl View {
|
||||||
frontend_tasks: params.frontend_tasks.clone(),
|
frontend_tasks: params.frontend_tasks.clone(),
|
||||||
globals: params.globals.clone(),
|
globals: params.globals.clone(),
|
||||||
bindings_popup: Default::default(),
|
bindings_popup: Default::default(),
|
||||||
bindings_file,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
readonly ALLOWED_PROFILES="$(
|
|
||||||
cat <<'EOF'
|
|
||||||
/interaction_profiles/hp/mixed_reality_controller
|
|
||||||
/interaction_profiles/htc/vive_controller
|
|
||||||
/interaction_profiles/htc/vive_cosmos_controller
|
|
||||||
/interaction_profiles/htc/vive_focus3_controller
|
|
||||||
/interaction_profiles/ml/ml2_controller
|
|
||||||
/interaction_profiles/microsoft/motion_controller
|
|
||||||
/interaction_profiles/mndx/flipvr
|
|
||||||
/interaction_profiles/mndx/pssense_controller_mndx
|
|
||||||
/interaction_profiles/oculus/touch_controller
|
|
||||||
/interaction_profiles/oppo/mr_controller_oppo
|
|
||||||
/interaction_profiles/samsung/odyssey_controller
|
|
||||||
/interaction_profiles/valve/index_controller
|
|
||||||
EOF
|
|
||||||
)"
|
|
||||||
|
|
||||||
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
output_dir="${script_dir}/assets"
|
|
||||||
output_json="${output_dir}/bindings.json"
|
|
||||||
output_lz4="${output_json}.lz4"
|
|
||||||
|
|
||||||
repo_url="https://gitlab.freedesktop.org/monado/monado.git"
|
|
||||||
repo_branch="main"
|
|
||||||
bindings_path="src/xrt/auxiliary/bindings/bindings.json"
|
|
||||||
|
|
||||||
tmpdir="$(mktemp -d)"
|
|
||||||
tmp_output=""
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
rm -rf "$tmpdir"
|
|
||||||
|
|
||||||
if [[ -n "$tmp_output" && -f "$tmp_output" ]]; then
|
|
||||||
rm -f "$tmp_output"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
command -v git >/dev/null || { echo "git is required" >&2; exit 1; }
|
|
||||||
command -v jq >/dev/null || { echo "jq is required" >&2; exit 1; }
|
|
||||||
command -v lz4 >/dev/null || { echo "lz4 is required" >&2; exit 1; }
|
|
||||||
|
|
||||||
git clone \
|
|
||||||
--depth 1 \
|
|
||||||
--branch "$repo_branch" \
|
|
||||||
--filter=blob:none \
|
|
||||||
--sparse \
|
|
||||||
"$repo_url" \
|
|
||||||
"$tmpdir/monado"
|
|
||||||
|
|
||||||
git -C "$tmpdir/monado" sparse-checkout set --no-cone "$bindings_path"
|
|
||||||
|
|
||||||
input_json="$tmpdir/monado/$bindings_path"
|
|
||||||
|
|
||||||
mkdir -p "$output_dir"
|
|
||||||
tmp_output="$(mktemp "${output_json}.tmp.XXXXXX")"
|
|
||||||
|
|
||||||
jq_filter='
|
|
||||||
($allowed_lines
|
|
||||||
| split("\n")
|
|
||||||
| map(sub("\r$"; ""))
|
|
||||||
| map(select(length > 0))
|
|
||||||
| reduce .[] as $key ({}; .[$key] = true)
|
|
||||||
) as $allowed
|
|
||||||
| .profiles |= with_entries(select($allowed[.key] == true))
|
|
||||||
'
|
|
||||||
|
|
||||||
jq -c \
|
|
||||||
--arg allowed_lines "$ALLOWED_PROFILES" \
|
|
||||||
"$jq_filter" \
|
|
||||||
"$input_json" > "$tmp_output"
|
|
||||||
|
|
||||||
mv "$tmp_output" "$output_json"
|
|
||||||
tmp_output=""
|
|
||||||
|
|
||||||
lz4 -f --rm "$output_json" "$output_lz4"
|
|
||||||
|
|
||||||
echo "Wrote compressed filtered bindings to: $output_lz4"
|
|
||||||
Loading…
Reference in New Issue