feat: configurable openxr bindings

This commit is contained in:
galister
2024-06-04 14:57:04 +09:00
parent 773ff6885d
commit b8a0e3630d
9 changed files with 642 additions and 525 deletions

13
Cargo.lock generated
View File

@@ -3098,6 +3098,17 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_json5"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a6b754515e1a7bd79fc2edeaecee526fc80cb3a918607e5ca149225a3a9586"
dependencies = [
"pest",
"pest_derive",
"serde",
]
[[package]] [[package]]
name = "serde_repr" name = "serde_repr"
version = "0.1.19" version = "0.1.19"
@@ -4266,6 +4277,7 @@ dependencies = [
[[package]] [[package]]
name = "wlx-capture" name = "wlx-capture"
version = "0.3.9" version = "0.3.9"
source = "git+https://github.com/galister/wlx-capture?tag=v0.3.9#0552577c45ce135a2f42d11399bcca0034762e2a"
dependencies = [ dependencies = [
"ashpd", "ashpd",
"drm-fourcc", "drm-fourcc",
@@ -4316,6 +4328,7 @@ dependencies = [
"rosc", "rosc",
"serde", "serde",
"serde_json", "serde_json",
"serde_json5",
"serde_yaml", "serde_yaml",
"smallvec", "smallvec",
"strum", "strum",

View File

@@ -57,6 +57,7 @@ wlx-capture = { git = "https://github.com/galister/wlx-capture", tag = "v0.3.9",
winit = { version = "0.29.15", optional = true } winit = { version = "0.29.15", optional = true }
xdg = "2.5.2" xdg = "2.5.2"
log-panics = { version = "2.1.0", features = ["with-backtrace"] } log-panics = { version = "2.1.0", features = ["with-backtrace"] }
serde_json5 = "0.1.0"
[features] [features]
default = ["openvr", "openxr", "osc", "x11", "wayland"] default = ["openvr", "openxr", "osc", "x11", "wayland"]

View File

@@ -2,9 +2,11 @@ use std::time::{Duration, Instant};
use glam::{bool, Affine3A, Quat, Vec3}; use glam::{bool, Affine3A, Quat, Vec3};
use openxr as xr; use openxr as xr;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
backend::input::{Haptics, Pointer}, backend::input::{Haptics, Pointer},
config_io,
state::{AppSession, AppState}, state::{AppSession, AppState},
}; };
@@ -13,26 +15,7 @@ use super::XrState;
type XrSession = xr::Session<xr::Vulkan>; type XrSession = xr::Session<xr::Vulkan>;
static DOUBLE_CLICK_TIME: Duration = Duration::from_millis(500); static DOUBLE_CLICK_TIME: Duration = Duration::from_millis(500);
pub(super) struct DoubleClickCounter { pub(super) struct OpenXrAction {}
pub(super) last_click: Option<Instant>,
}
impl DoubleClickCounter {
pub(super) fn new() -> Self {
Self { last_click: None }
}
// submit a click. returns true if it should count as a double click
pub(super) fn click(&mut self) -> bool {
let now = Instant::now();
let double_click = match self.last_click {
Some(last_click) => now - last_click < DOUBLE_CLICK_TIME,
None => false,
};
self.last_click = if double_click { None } else { Some(now) };
double_click
}
}
pub(super) struct OpenXrInputSource { pub(super) struct OpenXrInputSource {
action_set: xr::ActionSet, action_set: xr::ActionSet,
@@ -44,22 +27,124 @@ pub(super) struct OpenXrHand {
space: xr::Space, space: xr::Space,
} }
pub struct CustomClickAction {
action_f32: xr::Action<f32>,
action_bool: xr::Action<bool>,
action_f32_double: xr::Action<f32>,
action_bool_double: xr::Action<bool>,
last_click: Option<Instant>,
held: bool,
}
impl CustomClickAction {
pub fn new(action_set: &xr::ActionSet, name: &str, side: &str) -> anyhow::Result<Self> {
let action_f32 = action_set.create_action::<f32>(
&format!("{}_{}_value", side, name),
&format!("{} hand {} value", side, name),
&[],
)?;
let action_f32_double = action_set.create_action::<f32>(
&format!("{}_{}_value_double", side, name),
&format!("{} hand {} value double", side, name),
&[],
)?;
let action_bool = action_set.create_action::<bool>(
&format!("{}_{}", side, name),
&format!("{} hand {}", side, name),
&[],
)?;
let action_bool_double = action_set.create_action::<bool>(
&format!("{}_{}_double", side, name),
&format!("{} hand {} double", side, name),
&[],
)?;
Ok(Self {
action_f32,
action_f32_double,
action_bool,
action_bool_double,
last_click: None,
held: false,
})
}
pub fn state(
&mut self,
before: bool,
state: &XrState,
session: &AppSession,
) -> anyhow::Result<bool> {
let res = self.action_bool.state(&state.session, xr::Path::NULL)?;
if res.is_active && res.current_state {
return Ok(true);
}
let res = self
.action_bool_double
.state(&state.session, xr::Path::NULL)?;
if res.is_active && self.check_double_click(res.current_state) {
return Ok(true);
}
let threshold = if before {
session.config.xr_click_sensitivity_release
} else {
session.config.xr_click_sensitivity
};
let res = self.action_f32.state(&state.session, xr::Path::NULL)?;
if res.is_active && res.current_state > threshold {
return Ok(true);
}
let res = self.action_f32.state(&state.session, xr::Path::NULL)?;
if res.is_active && self.check_double_click(res.current_state > threshold) {
return Ok(true);
}
Ok(false)
}
// submit a click. returns true if it should count as a double click
fn check_double_click(&mut self, state: bool) -> bool {
if !state {
self.held = false;
return false;
}
if self.held {
return false;
}
let now = Instant::now();
let double_click = match self.last_click {
Some(last_click) => now - last_click < DOUBLE_CLICK_TIME,
None => false,
};
self.last_click = if double_click { None } else { Some(now) };
self.held = true;
double_click
}
}
pub(super) struct OpenXrHandSource { pub(super) struct OpenXrHandSource {
action_pose: xr::Action<xr::Posef>, action_pose: xr::Action<xr::Posef>,
action_click: xr::Action<f32>, action_click: CustomClickAction,
action_grab: xr::Action<f32>, action_grab: CustomClickAction,
action_alt_click: CustomClickAction,
action_show_hide: CustomClickAction,
action_space_drag: CustomClickAction,
action_modifier_right: CustomClickAction,
action_modifier_middle: CustomClickAction,
action_move_mouse: CustomClickAction,
action_scroll: xr::Action<f32>, action_scroll: xr::Action<f32>,
action_alt_click: xr::Action<f32>,
action_show_hide: xr::Action<bool>,
action_space_drag: xr::Action<bool>,
action_click_modifier_right: xr::Action<bool>,
action_click_modifier_middle: xr::Action<bool>,
action_move_mouse: xr::Action<bool>,
action_haptics: xr::Action<xr::Haptic>, action_haptics: xr::Action<xr::Haptic>,
} }
impl OpenXrInputSource { impl OpenXrInputSource {
pub fn new(xr: &XrState) -> Result<Self, xr::sys::Result> { pub fn new(xr: &XrState) -> anyhow::Result<Self> {
let mut action_set = let mut action_set =
xr.session xr.session
.instance() .instance()
@@ -96,7 +181,7 @@ impl OpenXrInputSource {
); );
} }
pub fn update(&self, xr: &XrState, state: &mut AppState) -> Result<(), xr::sys::Result> { pub fn update(&mut self, xr: &XrState, state: &mut AppState) -> anyhow::Result<()> {
xr.session.sync_actions(&[(&self.action_set).into()])?; xr.session.sync_actions(&[(&self.action_set).into()])?;
for i in 0..2 { for i in 0..2 {
@@ -118,11 +203,11 @@ impl OpenXrHand {
} }
pub(super) fn update( pub(super) fn update(
&self, &mut self,
pointer: &mut Pointer, pointer: &mut Pointer,
xr: &XrState, xr: &XrState,
session: &AppSession, session: &AppSession,
) -> Result<(), xr::sys::Result> { ) -> anyhow::Result<()> {
let location = self.space.locate(&xr.stage, xr.predicted_display_time)?; let location = self.space.locate(&xr.stage, xr.predicted_display_time)?;
if location if location
.location_flags .location_flags
@@ -133,31 +218,15 @@ impl OpenXrHand {
pointer.pose = Affine3A::from_rotation_translation(quat, pos); pointer.pose = Affine3A::from_rotation_translation(quat, pos);
} }
let click_sensitivity = if pointer.before.click {
session.config.xr_click_sensitivity_release
} else {
session.config.xr_click_sensitivity
};
pointer.now.click = self pointer.now.click = self
.source .source
.action_click .action_click
.state(&xr.session, xr::Path::NULL)? .state(pointer.before.click, xr, session)?;
.current_state
> click_sensitivity;
let grab_sensitivity = if pointer.before.grab {
session.config.xr_grab_sensitivity_release
} else {
session.config.xr_grab_sensitivity
};
pointer.now.grab = self pointer.now.grab = self
.source .source
.action_grab .action_grab
.state(&xr.session, xr::Path::NULL)? .state(pointer.before.grab, xr, session)?;
.current_state
> grab_sensitivity;
pointer.now.scroll = self pointer.now.scroll = self
.source .source
@@ -165,48 +234,37 @@ impl OpenXrHand {
.state(&xr.session, xr::Path::NULL)? .state(&xr.session, xr::Path::NULL)?
.current_state; .current_state;
let alt_click_sensitivity = if pointer.before.alt_click { pointer.now.alt_click =
session.config.xr_alt_click_sensitivity_release self.source
} else {
session.config.xr_alt_click_sensitivity
};
pointer.now.alt_click = self
.source
.action_alt_click .action_alt_click
.state(&xr.session, xr::Path::NULL)? .state(pointer.before.alt_click, xr, session)?;
.current_state
> alt_click_sensitivity;
pointer.now.show_hide = self pointer.now.show_hide =
.source self.source
.action_show_hide .action_show_hide
.state(&xr.session, xr::Path::NULL)? .state(pointer.before.show_hide, xr, session)?;
.current_state;
pointer.now.click_modifier_right = self pointer.now.click_modifier_right = self.source.action_modifier_right.state(
.source pointer.before.click_modifier_right,
.action_click_modifier_right xr,
.state(&xr.session, xr::Path::NULL)? session,
.current_state; )?;
pointer.now.click_modifier_middle = self pointer.now.click_modifier_middle = self.source.action_modifier_middle.state(
.source pointer.before.click_modifier_middle,
.action_click_modifier_middle xr,
.state(&xr.session, xr::Path::NULL)? session,
.current_state; )?;
pointer.now.move_mouse = self pointer.now.move_mouse =
.source self.source
.action_move_mouse .action_move_mouse
.state(&xr.session, xr::Path::NULL)? .state(pointer.before.move_mouse, xr, session)?;
.current_state;
pointer.now.space_drag = self pointer.now.space_drag =
.source self.source
.action_space_drag .action_space_drag
.state(&xr.session, xr::Path::NULL)? .state(pointer.before.space_drag, xr, session)?;
.current_state;
Ok(()) Ok(())
} }
@@ -214,459 +272,267 @@ impl OpenXrHand {
// supported action types: Haptic, Posef, Vector2f, f32, bool // supported action types: Haptic, Posef, Vector2f, f32, bool
impl OpenXrHandSource { impl OpenXrHandSource {
pub(super) fn new(action_set: &mut xr::ActionSet, side: &str) -> Result<Self, xr::sys::Result> { pub(super) fn new(action_set: &mut xr::ActionSet, side: &str) -> anyhow::Result<Self> {
let action_pose = action_set.create_action::<xr::Posef>( let action_pose = action_set.create_action::<xr::Posef>(
&format!("{}_hand", side), &format!("{}_hand", side),
&format!("{} hand pose", side), &format!("{} hand pose", side),
&[], &[],
)?; )?;
let action_click = action_set.create_action::<f32>(
&format!("{}_click", side),
&format!("{} hand click", side),
&[],
)?;
let action_grab = action_set.create_action::<f32>(
&format!("{}_grab", side),
&format!("{} hand grab", side),
&[],
)?;
let action_scroll = action_set.create_action::<f32>( let action_scroll = action_set.create_action::<f32>(
&format!("{}_scroll", side), &format!("{}_scroll", side),
&format!("{} hand scroll", side), &format!("{} hand scroll", side),
&[], &[],
)?; )?;
let action_alt_click = action_set.create_action::<f32>(
&format!("{}_alt_click", side),
&format!("{} hand alt click", side),
&[],
)?;
let action_show_hide = action_set.create_action::<bool>(
&format!("{}_show_hide", side),
&format!("{} hand show/hide", side),
&[],
)?;
let action_click_modifier_right = action_set.create_action::<bool>(
&format!("{}_click_modifier_right", side),
&format!("{} hand right click modifier", side),
&[],
)?;
let action_click_modifier_middle = action_set.create_action::<bool>(
&format!("{}_click_modifier_middle", side),
&format!("{} hand middle click modifier", side),
&[],
)?;
let action_move_mouse = action_set.create_action::<bool>(
&format!("{}_move_mouse", side),
&format!("{} hand mouse move", side),
&[],
)?;
let action_haptics = action_set.create_action::<xr::Haptic>( let action_haptics = action_set.create_action::<xr::Haptic>(
&format!("{}_haptics", side), &format!("{}_haptics", side),
&format!("{} hand haptics", side), &format!("{} hand haptics", side),
&[], &[],
)?; )?;
let action_space_drag = action_set.create_action::<bool>(
&format!("{}_space_drag", side),
&format!("{} hand space drag", side),
&[],
)?;
Ok(Self { Ok(Self {
action_pose, action_pose,
action_click, action_click: CustomClickAction::new(action_set, "click", side)?,
action_grab, action_grab: CustomClickAction::new(action_set, "grab", side)?,
action_scroll, action_scroll,
action_alt_click, action_alt_click: CustomClickAction::new(action_set, "alt_click", side)?,
action_show_hide, action_show_hide: CustomClickAction::new(action_set, "show_hide", side)?,
action_click_modifier_right, action_space_drag: CustomClickAction::new(action_set, "space_drag", side)?,
action_click_modifier_middle, action_modifier_right: CustomClickAction::new(
action_move_mouse, action_set,
"click_modifier_right",
side,
)?,
action_modifier_middle: CustomClickAction::new(
action_set,
"click_modifier_middle",
side,
)?,
action_move_mouse: CustomClickAction::new(action_set, "move_mouse", side)?,
action_haptics, action_haptics,
action_space_drag,
}) })
} }
} }
fn suggest_bindings( fn to_path(maybe_path_str: &Option<String>, instance: &xr::Instance) -> Option<xr::Path> {
instance: &xr::Instance, maybe_path_str
hands: &[&OpenXrHandSource; 2], .as_ref()
) -> Result<(), xr::sys::Result> { .and_then(|s| match instance.string_to_path(s) {
let path = instance.string_to_path("/interaction_profiles/khr/simple_controller")?; Ok(path) => Some(path),
Err(_) => {
log::warn!("Invalid binding path: {}", s);
None
}
})
}
// not fully functional, but helpful for debugging fn is_bool(maybe_type_str: &Option<String>) -> bool {
instance.suggest_interaction_profile_bindings( maybe_type_str
path, .as_ref()
&[ .unwrap()
xr::Binding::new( .split('/')
&hands[0].action_pose, .last()
instance.string_to_path("/user/hand/left/input/aim/pose")?, .map(|last| matches!(last, "click" | "touch"))
), .unwrap_or(false)
xr::Binding::new( }
&hands[1].action_pose,
instance.string_to_path("/user/hand/right/input/aim/pose")?,
),
xr::Binding::new(
&hands[0].action_click,
instance.string_to_path("/user/hand/left/input/select/click")?,
),
xr::Binding::new(
&hands[1].action_click,
instance.string_to_path("/user/hand/right/input/select/click")?,
),
xr::Binding::new(
&hands[0].action_show_hide,
instance.string_to_path("/user/hand/left/input/menu/click")?,
),
xr::Binding::new(
&hands[0].action_haptics,
instance.string_to_path("/user/hand/left/output/haptic")?,
),
xr::Binding::new(
&hands[1].action_haptics,
instance.string_to_path("/user/hand/right/output/haptic")?,
),
],
)?;
let path = instance.string_to_path("/interaction_profiles/oculus/touch_controller")?; macro_rules! add_custom {
instance.suggest_interaction_profile_bindings( ($action:expr, $left:expr, $right:expr, $bindings:expr, $instance:expr) => {
path, if let Some(action) = $action.as_ref() {
&[ if let Some(p) = to_path(&action.left, $instance) {
xr::Binding::new( if is_bool(&action.left) {
&hands[0].action_pose, if action.double_click.unwrap_or(false) {
instance.string_to_path("/user/hand/left/input/aim/pose")?, $bindings.push(xr::Binding::new(&$left.action_bool_double, p));
), } else {
xr::Binding::new( $bindings.push(xr::Binding::new(&$left.action_bool, p));
&hands[1].action_pose, }
instance.string_to_path("/user/hand/right/input/aim/pose")?, } else {
), if action.double_click.unwrap_or(false) {
xr::Binding::new( $bindings.push(xr::Binding::new(&$left.action_f32_double, p));
&hands[0].action_click, } else {
instance.string_to_path("/user/hand/left/input/trigger/value")?, $bindings.push(xr::Binding::new(&$left.action_f32, p));
), }
xr::Binding::new( }
&hands[1].action_click, }
instance.string_to_path("/user/hand/right/input/trigger/value")?, if let Some(p) = to_path(&action.right, $instance) {
), if is_bool(&action.right) {
xr::Binding::new( if action.double_click.unwrap_or(false) {
&hands[0].action_grab, $bindings.push(xr::Binding::new(&$right.action_bool_double, p));
instance.string_to_path("/user/hand/left/input/squeeze/value")?, } else {
), $bindings.push(xr::Binding::new(&$right.action_bool, p));
xr::Binding::new( }
&hands[1].action_grab, } else {
instance.string_to_path("/user/hand/right/input/squeeze/value")?, if action.double_click.unwrap_or(false) {
), $bindings.push(xr::Binding::new(&$right.action_f32_double, p));
xr::Binding::new( } else {
&hands[0].action_scroll, $bindings.push(xr::Binding::new(&$right.action_f32, p));
instance.string_to_path("/user/hand/left/input/thumbstick/y")?, }
), }
xr::Binding::new( }
&hands[1].action_scroll, }
instance.string_to_path("/user/hand/right/input/thumbstick/y")?, };
), }
xr::Binding::new(
&hands[0].action_show_hide,
instance.string_to_path("/user/hand/left/input/y/click")?,
),
xr::Binding::new(
&hands[0].action_click_modifier_right,
instance.string_to_path("/user/hand/left/input/y/touch")?,
),
xr::Binding::new(
&hands[1].action_click_modifier_right,
instance.string_to_path("/user/hand/right/input/b/touch")?,
),
xr::Binding::new(
&hands[0].action_click_modifier_middle,
instance.string_to_path("/user/hand/left/input/x/touch")?,
),
xr::Binding::new(
&hands[1].action_click_modifier_middle,
instance.string_to_path("/user/hand/right/input/a/touch")?,
),
xr::Binding::new(
&hands[0].action_move_mouse,
instance.string_to_path("/user/hand/left/input/trigger/touch")?,
),
xr::Binding::new(
&hands[1].action_move_mouse,
instance.string_to_path("/user/hand/right/input/trigger/touch")?,
),
xr::Binding::new(
&hands[0].action_space_drag,
instance.string_to_path("/user/hand/left/input/menu/click")?,
),
xr::Binding::new(
&hands[0].action_haptics,
instance.string_to_path("/user/hand/left/output/haptic")?,
),
xr::Binding::new(
&hands[1].action_haptics,
instance.string_to_path("/user/hand/right/output/haptic")?,
),
],
)?;
let path = instance.string_to_path("/interaction_profiles/valve/index_controller")?; fn suggest_bindings(instance: &xr::Instance, hands: &[&OpenXrHandSource; 2]) -> anyhow::Result<()> {
instance.suggest_interaction_profile_bindings( let profiles = load_action_profiles()?;
path,
&[ for profile in profiles {
xr::Binding::new( let Ok(profile_path) = instance.string_to_path(&profile.profile) else {
&hands[0].action_pose, log::debug!("Profile not supported: {}", profile.profile);
instance.string_to_path("/user/hand/left/input/aim/pose")?, continue;
), };
xr::Binding::new(
&hands[1].action_pose, let mut bindings: Vec<xr::Binding> = vec![];
instance.string_to_path("/user/hand/right/input/aim/pose")?,
), if let Some(action) = profile.pose {
xr::Binding::new( if let Some(p) = to_path(&action.left, instance) {
&hands[0].action_click, bindings.push(xr::Binding::new(&hands[0].action_pose, p));
instance.string_to_path("/user/hand/left/input/trigger/value")?, }
), if let Some(p) = to_path(&action.right, instance) {
xr::Binding::new( bindings.push(xr::Binding::new(&hands[1].action_pose, p));
&hands[1].action_click, }
instance.string_to_path("/user/hand/right/input/trigger/value")?, }
),
xr::Binding::new( if let Some(action) = profile.haptics {
&hands[0].action_grab, if let Some(p) = to_path(&action.left, instance) {
instance.string_to_path("/user/hand/left/input/squeeze/force")?, bindings.push(xr::Binding::new(&hands[0].action_haptics, p));
), }
xr::Binding::new( if let Some(p) = to_path(&action.right, instance) {
&hands[1].action_grab, bindings.push(xr::Binding::new(&hands[1].action_haptics, p));
instance.string_to_path("/user/hand/right/input/squeeze/force")?, }
), }
xr::Binding::new(
&hands[0].action_scroll, if let Some(action) = profile.scroll {
instance.string_to_path("/user/hand/left/input/thumbstick/y")?, if let Some(p) = to_path(&action.left, instance) {
), bindings.push(xr::Binding::new(&hands[0].action_scroll, p));
xr::Binding::new( }
&hands[1].action_scroll, if let Some(p) = to_path(&action.right, instance) {
instance.string_to_path("/user/hand/right/input/thumbstick/y")?, bindings.push(xr::Binding::new(&hands[1].action_scroll, p));
), }
xr::Binding::new( }
add_custom!(
profile.click,
hands[0].action_click,
hands[1].action_click,
bindings,
instance
);
add_custom!(
profile.alt_click,
&hands[0].action_alt_click, &hands[0].action_alt_click,
instance.string_to_path("/user/hand/left/input/trackpad/force")?,
),
xr::Binding::new(
&hands[1].action_alt_click, &hands[1].action_alt_click,
instance.string_to_path("/user/hand/right/input/trackpad/force")?, bindings,
), instance
xr::Binding::new( );
add_custom!(
profile.grab,
&hands[0].action_grab,
&hands[1].action_grab,
bindings,
instance
);
add_custom!(
profile.show_hide,
&hands[0].action_show_hide, &hands[0].action_show_hide,
instance.string_to_path("/user/hand/left/input/b/click")?, &hands[1].action_show_hide,
), bindings,
xr::Binding::new( instance
);
add_custom!(
profile.space_drag,
&hands[0].action_space_drag,
&hands[1].action_space_drag, &hands[1].action_space_drag,
instance.string_to_path("/user/hand/right/input/b/click")?, bindings,
), instance
xr::Binding::new( );
&hands[0].action_click_modifier_right,
instance.string_to_path("/user/hand/left/input/b/touch")?, add_custom!(
), profile.click_modifier_right,
xr::Binding::new( &hands[0].action_modifier_right,
&hands[1].action_click_modifier_right, &hands[1].action_modifier_right,
instance.string_to_path("/user/hand/right/input/b/touch")?, bindings,
), instance
xr::Binding::new( );
&hands[0].action_click_modifier_middle,
instance.string_to_path("/user/hand/left/input/a/touch")?, add_custom!(
), profile.click_modifier_middle,
xr::Binding::new( &hands[0].action_modifier_middle,
&hands[1].action_click_modifier_middle, &hands[1].action_modifier_middle,
instance.string_to_path("/user/hand/right/input/a/touch")?, bindings,
), instance
xr::Binding::new( );
add_custom!(
profile.move_mouse,
&hands[0].action_move_mouse, &hands[0].action_move_mouse,
instance.string_to_path("/user/hand/left/input/trigger/touch")?,
),
xr::Binding::new(
&hands[1].action_move_mouse, &hands[1].action_move_mouse,
instance.string_to_path("/user/hand/right/input/trigger/touch")?, bindings,
), instance
xr::Binding::new( );
&hands[0].action_haptics,
instance.string_to_path("/user/hand/left/output/haptic")?,
),
xr::Binding::new(
&hands[1].action_haptics,
instance.string_to_path("/user/hand/right/output/haptic")?,
),
],
)?;
let path = instance.string_to_path("/interaction_profiles/htc/vive_controller")?; if instance
instance.suggest_interaction_profile_bindings( .suggest_interaction_profile_bindings(profile_path, &bindings)
path, .is_err()
&[ {
xr::Binding::new( log::error!("Bad bindings for {}", &profile.profile[22..]);
&hands[0].action_pose, log::error!("Verify config: ~/.config/wlxoverlay/openxr_actions.json5");
instance.string_to_path("/user/hand/left/input/aim/pose")?, }
), }
xr::Binding::new(
&hands[1].action_pose,
instance.string_to_path("/user/hand/right/input/aim/pose")?,
),
xr::Binding::new(
&hands[1].action_click,
instance.string_to_path("/user/hand/right/input/trigger/value")?,
),
xr::Binding::new(
&hands[0].action_grab,
instance.string_to_path("/user/hand/left/input/squeeze/click")?,
),
xr::Binding::new(
&hands[1].action_grab,
instance.string_to_path("/user/hand/right/input/squeeze/click")?,
),
xr::Binding::new(
&hands[0].action_scroll,
instance.string_to_path("/user/hand/left/input/trackpad/y")?,
),
xr::Binding::new(
&hands[1].action_scroll,
instance.string_to_path("/user/hand/right/input/trackpad/y")?,
),
xr::Binding::new(
&hands[0].action_show_hide,
instance.string_to_path("/user/hand/left/input/menu/click")?,
),
xr::Binding::new(
&hands[0].action_haptics,
instance.string_to_path("/user/hand/left/output/haptic")?,
),
xr::Binding::new(
&hands[1].action_haptics,
instance.string_to_path("/user/hand/right/output/haptic")?,
),
],
)?;
let path = instance.string_to_path("/interaction_profiles/microsoft/motion_controller")?;
instance.suggest_interaction_profile_bindings(
path,
&[
xr::Binding::new(
&hands[0].action_pose,
instance.string_to_path("/user/hand/left/input/aim/pose")?,
),
xr::Binding::new(
&hands[1].action_pose,
instance.string_to_path("/user/hand/right/input/aim/pose")?,
),
xr::Binding::new(
&hands[1].action_click,
instance.string_to_path("/user/hand/right/input/trigger/value")?,
),
xr::Binding::new(
&hands[0].action_grab,
instance.string_to_path("/user/hand/left/input/squeeze/click")?,
),
xr::Binding::new(
&hands[1].action_grab,
instance.string_to_path("/user/hand/right/input/squeeze/click")?,
),
xr::Binding::new(
&hands[0].action_click_modifier_right,
instance.string_to_path("/user/hand/left/input/trackpad/dpad_up")?,
),
xr::Binding::new(
&hands[1].action_click_modifier_right,
instance.string_to_path("/user/hand/right/input/trackpad/dpad_up")?,
),
xr::Binding::new(
&hands[0].action_click_modifier_middle,
instance.string_to_path("/user/hand/left/input/trackpad/dpad_down")?,
),
xr::Binding::new(
&hands[1].action_click_modifier_middle,
instance.string_to_path("/user/hand/right/input/trackpad/dpad_down")?,
),
xr::Binding::new(
&hands[0].action_scroll,
instance.string_to_path("/user/hand/left/input/thumbstick/y")?,
),
xr::Binding::new(
&hands[1].action_scroll,
instance.string_to_path("/user/hand/right/input/thumbstick/y")?,
),
xr::Binding::new(
&hands[0].action_show_hide,
instance.string_to_path("/user/hand/left/input/menu/click")?,
),
xr::Binding::new(
&hands[0].action_haptics,
instance.string_to_path("/user/hand/left/output/haptic")?,
),
xr::Binding::new(
&hands[1].action_haptics,
instance.string_to_path("/user/hand/right/output/haptic")?,
),
],
)?;
let path = instance.string_to_path("/interaction_profiles/hp/mixed_reality_controller")?;
instance.suggest_interaction_profile_bindings(
path,
&[
xr::Binding::new(
&hands[0].action_pose,
instance.string_to_path("/user/hand/left/input/aim/pose")?,
),
xr::Binding::new(
&hands[1].action_pose,
instance.string_to_path("/user/hand/right/input/aim/pose")?,
),
xr::Binding::new(
&hands[1].action_click,
instance.string_to_path("/user/hand/right/input/trigger/value")?,
),
xr::Binding::new(
&hands[0].action_grab,
instance.string_to_path("/user/hand/left/input/squeeze/value")?,
),
xr::Binding::new(
&hands[1].action_grab,
instance.string_to_path("/user/hand/right/input/squeeze/value")?,
),
xr::Binding::new(
&hands[0].action_click_modifier_right,
instance.string_to_path("/user/hand/left/input/y/click")?,
),
xr::Binding::new(
&hands[1].action_click_modifier_right,
instance.string_to_path("/user/hand/right/input/b/click")?,
),
xr::Binding::new(
&hands[0].action_click_modifier_middle,
instance.string_to_path("/user/hand/left/input/x/click")?,
),
xr::Binding::new(
&hands[1].action_click_modifier_middle,
instance.string_to_path("/user/hand/right/input/a/click")?,
),
xr::Binding::new(
&hands[0].action_scroll,
instance.string_to_path("/user/hand/left/input/thumbstick/y")?,
),
xr::Binding::new(
&hands[1].action_scroll,
instance.string_to_path("/user/hand/right/input/thumbstick/y")?,
),
xr::Binding::new(
&hands[0].action_show_hide,
instance.string_to_path("/user/hand/left/input/menu/click")?,
),
xr::Binding::new(
&hands[0].action_haptics,
instance.string_to_path("/user/hand/left/output/haptic")?,
),
xr::Binding::new(
&hands[1].action_haptics,
instance.string_to_path("/user/hand/right/output/haptic")?,
),
],
)?;
Ok(()) Ok(())
} }
#[derive(Debug, Clone, Serialize, Deserialize)]
struct OpenXrActionConfAction {
left: Option<String>,
right: Option<String>,
threshold: Option<[f32; 2]>,
double_click: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct OpenXrActionConfProfile {
profile: String,
pose: Option<OpenXrActionConfAction>,
click: Option<OpenXrActionConfAction>,
grab: Option<OpenXrActionConfAction>,
alt_click: Option<OpenXrActionConfAction>,
show_hide: Option<OpenXrActionConfAction>,
space_drag: Option<OpenXrActionConfAction>,
click_modifier_right: Option<OpenXrActionConfAction>,
click_modifier_middle: Option<OpenXrActionConfAction>,
move_mouse: Option<OpenXrActionConfAction>,
scroll: Option<OpenXrActionConfAction>,
haptics: Option<OpenXrActionConfAction>,
}
const DEFAULT_PROFILES: &str = include_str!("openxr_actions.json5");
fn load_action_profiles() -> anyhow::Result<Vec<OpenXrActionConfProfile>> {
let mut profiles: Vec<OpenXrActionConfProfile> =
serde_json5::from_str(DEFAULT_PROFILES).unwrap(); // want panic
let Some(conf) = config_io::load("openxr_actions.json5") else {
return Ok(profiles);
};
match serde_json5::from_str::<Vec<OpenXrActionConfProfile>>(&conf) {
Ok(override_profiles) => {
override_profiles.into_iter().for_each(|new| {
if let Some(i) = profiles.iter().position(|old| old.profile == new.profile) {
profiles[i] = new;
}
});
}
Err(e) => {
log::error!("Failed to load openxr_actions.json5: {}", e);
}
}
Ok(profiles)
}

View File

@@ -16,7 +16,7 @@ use crate::{
common::{BackendError, OverlayContainer}, common::{BackendError, OverlayContainer},
input::interact, input::interact,
notifications::NotificationManager, notifications::NotificationManager,
openxr::{input::DoubleClickCounter, lines::LinePool, overlay::OpenXrOverlayData}, openxr::{lines::LinePool, overlay::OpenXrOverlayData},
overlay::OverlayData, overlay::OverlayData,
task::TaskType, task::TaskType,
}, },
@@ -119,12 +119,11 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
let watch_id = overlays.get_by_name(WATCH_NAME).unwrap().state.id; // want panic let watch_id = overlays.get_by_name(WATCH_NAME).unwrap().state.id; // want panic
let input_source = input::OpenXrInputSource::new(&xr_state)?; let mut input_source = input::OpenXrInputSource::new(&xr_state)?;
let mut session_running = false; let mut session_running = false;
let mut event_storage = xr::EventDataBuffer::new(); let mut event_storage = xr::EventDataBuffer::new();
let mut show_hide_counter = DoubleClickCounter::new();
let mut due_tasks = VecDeque::with_capacity(4); let mut due_tasks = VecDeque::with_capacity(4);
'main_loop: loop { 'main_loop: loop {
@@ -202,7 +201,6 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
.pointers .pointers
.iter() .iter()
.any(|p| p.now.show_hide && !p.before.show_hide) .any(|p| p.now.show_hide && !p.before.show_hide)
&& show_hide_counter.click()
{ {
overlays.show_hide(&mut app_state); overlays.show_hide(&mut app_state);
} }

View File

@@ -0,0 +1,243 @@
// Available bindings:
//
// -- click --
// primary click to interact with the watch or overlays. required
//
// -- grab --
// used to manipulate position, size, orientation of overlays in 3D space
//
// -- show_hide --
// used to quickly hide and show your last selection of screens + keyboard
//
// -- space_drag --
// move your stage (playspace drag)
//
// -- space_rotate --
// rotate your stage (playspace rotate, WIP)
//
// -- click_modifier_right --
// while this is held, your pointer will turn ORANGE and your mouse clicks will be RIGHT clicks
//
// -- click_modifier_middle --
// while this is held, your pointer will turn PURPLE and your mouse clicks will be MIDDLE clicks
//
// -- move_mouse --
// when using `focus_follows_mouse_mode`, you need to hold this for the mouse to move
//
// -- pose, haptic --
// do not mess with these, unless you know what you're doing
[
// Fallback controller, intended for testing
{
profile: "/interaction_profiles/khr/simple_controller",
pose: {
left: "/user/hand/left/input/aim/pose",
right: "/user/hand/right/input/aim/pose"
},
haptic: {
left: "/user/hand/left/output/haptic",
right: "/user/hand/right/output/haptic"
},
click: {
// left trigger is click
left: "/user/hand/left/input/select/click",
},
grab: {
// right trigger is grab
right: "/user/hand/right/input/select/click"
},
show_hide: {
left: "/user/hand/left/input/menu/click"
}
},
// Oculus Touch Controller. Compatible with Quest 2, Quest 3, Quest Pro
{
profile: "/interaction_profiles/oculus/touch_controller",
pose: {
left: "/user/hand/left/input/aim/pose",
right: "/user/hand/right/input/aim/pose"
},
haptic: {
left: "/user/hand/left/output/haptic",
right: "/user/hand/right/output/haptic"
},
click: {
left: "/user/hand/left/input/trigger/value",
right: "/user/hand/right/input/trigger/value"
},
grab: {
left: "/user/hand/left/input/squeeze/value",
right: "/user/hand/right/input/squeeze/value"
},
scroll: {
left: "/user/hand/left/input/thumbstick/y",
right: "/user/hand/right/input/thumbstick/y"
},
show_hide: {
double_click: true,
left: "/user/hand/left/input/y/click",
},
space_drag: {
left: "/user/hand/left/input/menu/click",
},
click_modifier_right: {
left: "/user/hand/left/input/y/touch",
right: "/user/hand/right/input/b/touch"
},
click_modifier_middle: {
left: "/user/hand/left/input/x/touch",
right: "/user/hand/right/input/a/touch"
},
mouse_move: {
// used with focus_follows_mouse_mode
left: "/user/hand/left/input/trigger/touch",
right: "/user/hand/right/input/trigger/touch"
}
},
// Index controller
{
profile: "/interaction_profiles/valve/index_controller",
pose: {
left: "/user/hand/left/input/aim/pose",
right: "/user/hand/right/input/aim/pose"
},
haptic: {
left: "/user/hand/left/output/haptic",
right: "/user/hand/right/output/haptic"
},
click: {
left: "/user/hand/left/input/trigger/value",
right: "/user/hand/right/input/trigger/value"
},
alt_click: {
// left trackpad is space_drag
right: "/user/hand/right/input/trackpad/force",
},
grab: {
left: "/user/hand/left/input/squeeze/force",
right: "/user/hand/right/input/squeeze/force"
},
scroll: {
left: "/user/hand/left/input/thumbstick/y",
right: "/user/hand/right/input/thumbstick/y"
},
show_hide: {
double_click: true,
left: "/user/hand/left/input/b/click",
},
space_drag: {
left: "/user/hand/left/input/trackpad/force",
// right trackpad is alt_click
},
click_modifier_right: {
left: "/user/hand/left/input/b/touch",
right: "/user/hand/right/input/b/touch"
},
click_modifier_middle: {
left: "/user/hand/left/input/a/touch",
right: "/user/hand/right/input/a/touch"
},
mouse_move: {
// used with focus_follows_mouse_mode
left: "/user/hand/left/input/trigger/touch",
right: "/user/hand/right/input/trigger/touch"
}
},
// Vive controller
{
profile: "/interaction_profiles/htc/vive_controller",
pose: {
left: "/user/hand/left/input/aim/pose",
right: "/user/hand/right/input/aim/pose"
},
click: {
left: "/user/hand/left/input/trigger/value",
right: "/user/hand/right/input/trigger/value"
},
grab: {
left: "/user/hand/left/input/squeeze/click",
right: "/user/hand/right/input/squeeze/click"
},
scroll: {
left: "/user/hand/left/input/trackpad/y",
right: "/user/hand/right/input/trackpad/y"
},
show_hide: {
left: "/user/hand/left/input/menu/click",
},
space_drag: {
right: "/user/hand/right/input/menu/click",
},
haptic: {
left: "/user/hand/left/output/haptic",
right: "/user/hand/right/output/haptic"
}
},
// Windows Mixed Reality controller
{
profile: "/interaction_profiles/microsoft/motion_controller",
pose: {
left: "/user/hand/left/input/aim/pose",
right: "/user/hand/right/input/aim/pose"
},
haptic: {
left: "/user/hand/left/output/haptic",
right: "/user/hand/right/output/haptic"
},
click: {
left: "/user/hand/left/input/trigger/value",
right: "/user/hand/right/input/trigger/value"
},
grab: {
left: "/user/hand/left/input/squeeze/click",
right: "/user/hand/right/input/squeeze/click"
},
scroll: {
left: "/user/hand/left/input/thumbstick/y",
right: "/user/hand/right/input/thumbstick/y"
},
show_hide: {
left: "/user/hand/left/input/menu/click",
},
space_drag: {
right: "/user/hand/right/input/menu/click",
},
},
// HP Reverb G2 controller
{
profile: "/interaction_profiles/hp/mixed_reality_controller",
pose: {
left: "/user/hand/left/input/aim/pose",
right: "/user/hand/right/input/aim/pose"
},
haptic: {
left: "/user/hand/left/output/haptic",
right: "/user/hand/right/output/haptic"
},
click: {
left: "/user/hand/left/input/trigger/value",
right: "/user/hand/right/input/trigger/value"
},
grab: {
left: "/user/hand/left/input/squeeze/value",
right: "/user/hand/right/input/squeeze/value"
},
scroll: {
left: "/user/hand/left/input/thumbstick/y",
right: "/user/hand/right/input/thumbstick/y"
},
show_hide: {
left: "/user/hand/left/input/menu/click",
},
space_drag: {
right: "/user/hand/right/input/menu/click",
},
},
]

View File

@@ -5,15 +5,13 @@ use libloading::{Library, Symbol};
use crate::{backend::common::OverlayContainer, state::AppState}; use crate::{backend::common::OverlayContainer, state::AppState};
use super::{helpers, input::DoubleClickCounter, overlay::OpenXrOverlayData}; use super::{helpers, overlay::OpenXrOverlayData};
pub(super) struct PlayspaceMover { pub(super) struct PlayspaceMover {
drag_hand: Option<usize>, drag_hand: Option<usize>,
offset: Vec3A, offset: Vec3A,
start_position: Vec3A, start_position: Vec3A,
double_click_counter: DoubleClickCounter,
libmonado: Library, libmonado: Library,
mnd_root: *mut c_void, mnd_root: *mut c_void,
playspace_move: extern "C" fn(*mut c_void, f32, f32, f32) -> i32, playspace_move: extern "C" fn(*mut c_void, f32, f32, f32) -> i32,
@@ -43,8 +41,6 @@ impl PlayspaceMover {
offset: Vec3A::ZERO, offset: Vec3A::ZERO,
start_position: Vec3A::ZERO, start_position: Vec3A::ZERO,
double_click_counter: DoubleClickCounter::new(),
libmonado, libmonado,
mnd_root: root, mnd_root: root,
playspace_move: playspace_move_raw, playspace_move: playspace_move_raw,
@@ -75,10 +71,8 @@ impl PlayspaceMover {
self.apply_offset(); self.apply_offset();
} else { } else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() { for (i, pointer) in state.input_state.pointers.iter().enumerate() {
if pointer.now.space_drag if pointer.now.space_drag && !pointer.before.space_drag {
&& !pointer.before.space_drag log::info!("Start space drag");
&& self.double_click_counter.click()
{
self.drag_hand = Some(i); self.drag_hand = Some(i);
self.start_position = pointer.pose.translation; self.start_position = pointer.pose.translation;
break; break;

View File

@@ -540,7 +540,7 @@ fn run_watch(data: &WatchAction, app: &mut AppState) {
OverlaySelector::Name(WATCH_NAME.into()), OverlaySelector::Name(WATCH_NAME.into()),
Box::new(move |app, o| { Box::new(move |app, o| {
o.spawn_rotation *= rot; o.spawn_rotation *= rot;
app.session.config.watch_rot = o.spawn_rotation.into(); app.session.config.watch_rot = o.spawn_rotation;
o.dirty = true; o.dirty = true;
}), }),
)); ));
@@ -556,7 +556,7 @@ fn run_watch(data: &WatchAction, app: &mut AppState) {
OverlaySelector::Name(WATCH_NAME.into()), OverlaySelector::Name(WATCH_NAME.into()),
Box::new(move |app, o| { Box::new(move |app, o| {
o.spawn_point[axis] += delta; o.spawn_point[axis] += delta;
app.session.config.watch_pos = o.spawn_point.into(); app.session.config.watch_pos = o.spawn_point;
o.dirty = true; o.dirty = true;
}), }),
)); ));

View File

@@ -158,7 +158,7 @@ pub fn modular_canvas(
} => { } => {
canvas.font_size = *font_size; canvas.font_size = *font_size;
canvas.fg_color = color_parse(fg_color).unwrap_or(*FALLBACK_COLOR); canvas.fg_color = color_parse(fg_color).unwrap_or(*FALLBACK_COLOR);
let label = canvas.label(*x, *y, *w, *h, empty_str.clone()); let label = canvas.label_centered(*x, *y, *w, *h, empty_str.clone());
modular_label_init(label, data); modular_label_init(label, data);
} }
ModularElement::Button { ModularElement::Button {

View File

@@ -49,6 +49,8 @@ struct Args {
} }
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
std::env::set_var("RUST_BACKTRACE", "full");
let mut args = Args::parse(); let mut args = Args::parse();
logging_init(&mut args)?; logging_init(&mut args)?;