From b8a0e3630da1c8e589773c3f6a91cc1646a56992 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:57:04 +0900 Subject: [PATCH] feat: configurable openxr bindings --- Cargo.lock | 13 + Cargo.toml | 1 + src/backend/openxr/input.rs | 884 ++++++++++-------------- src/backend/openxr/mod.rs | 6 +- src/backend/openxr/openxr_actions.json5 | 243 +++++++ src/backend/openxr/playspace.rs | 12 +- src/gui/modular/button.rs | 4 +- src/gui/modular/mod.rs | 2 +- src/main.rs | 2 + 9 files changed, 642 insertions(+), 525 deletions(-) create mode 100644 src/backend/openxr/openxr_actions.json5 diff --git a/Cargo.lock b/Cargo.lock index 90e3e9e2..fb41e0ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3098,6 +3098,17 @@ dependencies = [ "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]] name = "serde_repr" version = "0.1.19" @@ -4266,6 +4277,7 @@ dependencies = [ [[package]] name = "wlx-capture" version = "0.3.9" +source = "git+https://github.com/galister/wlx-capture?tag=v0.3.9#0552577c45ce135a2f42d11399bcca0034762e2a" dependencies = [ "ashpd", "drm-fourcc", @@ -4316,6 +4328,7 @@ dependencies = [ "rosc", "serde", "serde_json", + "serde_json5", "serde_yaml", "smallvec", "strum", diff --git a/Cargo.toml b/Cargo.toml index 2d92162e..aca7d9cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ wlx-capture = { git = "https://github.com/galister/wlx-capture", tag = "v0.3.9", winit = { version = "0.29.15", optional = true } xdg = "2.5.2" log-panics = { version = "2.1.0", features = ["with-backtrace"] } +serde_json5 = "0.1.0" [features] default = ["openvr", "openxr", "osc", "x11", "wayland"] diff --git a/src/backend/openxr/input.rs b/src/backend/openxr/input.rs index fb21df7f..349fd26b 100644 --- a/src/backend/openxr/input.rs +++ b/src/backend/openxr/input.rs @@ -2,9 +2,11 @@ use std::time::{Duration, Instant}; use glam::{bool, Affine3A, Quat, Vec3}; use openxr as xr; +use serde::{Deserialize, Serialize}; use crate::{ backend::input::{Haptics, Pointer}, + config_io, state::{AppSession, AppState}, }; @@ -13,26 +15,7 @@ use super::XrState; type XrSession = xr::Session; static DOUBLE_CLICK_TIME: Duration = Duration::from_millis(500); -pub(super) struct DoubleClickCounter { - pub(super) last_click: Option, -} - -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 OpenXrAction {} pub(super) struct OpenXrInputSource { action_set: xr::ActionSet, @@ -44,22 +27,124 @@ pub(super) struct OpenXrHand { space: xr::Space, } +pub struct CustomClickAction { + action_f32: xr::Action, + action_bool: xr::Action, + action_f32_double: xr::Action, + action_bool_double: xr::Action, + last_click: Option, + held: bool, +} + +impl CustomClickAction { + pub fn new(action_set: &xr::ActionSet, name: &str, side: &str) -> anyhow::Result { + let action_f32 = action_set.create_action::( + &format!("{}_{}_value", side, name), + &format!("{} hand {} value", side, name), + &[], + )?; + let action_f32_double = action_set.create_action::( + &format!("{}_{}_value_double", side, name), + &format!("{} hand {} value double", side, name), + &[], + )?; + + let action_bool = action_set.create_action::( + &format!("{}_{}", side, name), + &format!("{} hand {}", side, name), + &[], + )?; + + let action_bool_double = action_set.create_action::( + &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 { + 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 { action_pose: xr::Action, - action_click: xr::Action, - action_grab: xr::Action, + action_click: CustomClickAction, + 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, - action_alt_click: xr::Action, - action_show_hide: xr::Action, - action_space_drag: xr::Action, - action_click_modifier_right: xr::Action, - action_click_modifier_middle: xr::Action, - action_move_mouse: xr::Action, action_haptics: xr::Action, } impl OpenXrInputSource { - pub fn new(xr: &XrState) -> Result { + pub fn new(xr: &XrState) -> anyhow::Result { let mut action_set = xr.session .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()])?; for i in 0..2 { @@ -118,11 +203,11 @@ impl OpenXrHand { } pub(super) fn update( - &self, + &mut self, pointer: &mut Pointer, xr: &XrState, session: &AppSession, - ) -> Result<(), xr::sys::Result> { + ) -> anyhow::Result<()> { let location = self.space.locate(&xr.stage, xr.predicted_display_time)?; if location .location_flags @@ -133,31 +218,15 @@ impl OpenXrHand { 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 .source .action_click - .state(&xr.session, xr::Path::NULL)? - .current_state - > click_sensitivity; - - let grab_sensitivity = if pointer.before.grab { - session.config.xr_grab_sensitivity_release - } else { - session.config.xr_grab_sensitivity - }; + .state(pointer.before.click, xr, session)?; pointer.now.grab = self .source .action_grab - .state(&xr.session, xr::Path::NULL)? - .current_state - > grab_sensitivity; + .state(pointer.before.grab, xr, session)?; pointer.now.scroll = self .source @@ -165,48 +234,37 @@ impl OpenXrHand { .state(&xr.session, xr::Path::NULL)? .current_state; - let alt_click_sensitivity = if pointer.before.alt_click { - session.config.xr_alt_click_sensitivity_release - } else { - session.config.xr_alt_click_sensitivity - }; + pointer.now.alt_click = + self.source + .action_alt_click + .state(pointer.before.alt_click, xr, session)?; - pointer.now.alt_click = self - .source - .action_alt_click - .state(&xr.session, xr::Path::NULL)? - .current_state - > alt_click_sensitivity; + pointer.now.show_hide = + self.source + .action_show_hide + .state(pointer.before.show_hide, xr, session)?; - pointer.now.show_hide = self - .source - .action_show_hide - .state(&xr.session, xr::Path::NULL)? - .current_state; + pointer.now.click_modifier_right = self.source.action_modifier_right.state( + pointer.before.click_modifier_right, + xr, + session, + )?; - pointer.now.click_modifier_right = self - .source - .action_click_modifier_right - .state(&xr.session, xr::Path::NULL)? - .current_state; + pointer.now.click_modifier_middle = self.source.action_modifier_middle.state( + pointer.before.click_modifier_middle, + xr, + session, + )?; - pointer.now.click_modifier_middle = self - .source - .action_click_modifier_middle - .state(&xr.session, xr::Path::NULL)? - .current_state; + pointer.now.move_mouse = + self.source + .action_move_mouse + .state(pointer.before.move_mouse, xr, session)?; - pointer.now.move_mouse = self - .source - .action_move_mouse - .state(&xr.session, xr::Path::NULL)? - .current_state; - - pointer.now.space_drag = self - .source - .action_space_drag - .state(&xr.session, xr::Path::NULL)? - .current_state; + pointer.now.space_drag = + self.source + .action_space_drag + .state(pointer.before.space_drag, xr, session)?; Ok(()) } @@ -214,459 +272,267 @@ impl OpenXrHand { // supported action types: Haptic, Posef, Vector2f, f32, bool impl OpenXrHandSource { - pub(super) fn new(action_set: &mut xr::ActionSet, side: &str) -> Result { + pub(super) fn new(action_set: &mut xr::ActionSet, side: &str) -> anyhow::Result { let action_pose = action_set.create_action::( &format!("{}_hand", side), &format!("{} hand pose", side), &[], )?; - let action_click = action_set.create_action::( - &format!("{}_click", side), - &format!("{} hand click", side), - &[], - )?; - let action_grab = action_set.create_action::( - &format!("{}_grab", side), - &format!("{} hand grab", side), - &[], - )?; let action_scroll = action_set.create_action::( &format!("{}_scroll", side), &format!("{} hand scroll", side), &[], )?; - let action_alt_click = action_set.create_action::( - &format!("{}_alt_click", side), - &format!("{} hand alt click", side), - &[], - )?; - let action_show_hide = action_set.create_action::( - &format!("{}_show_hide", side), - &format!("{} hand show/hide", side), - &[], - )?; - let action_click_modifier_right = action_set.create_action::( - &format!("{}_click_modifier_right", side), - &format!("{} hand right click modifier", side), - &[], - )?; - let action_click_modifier_middle = action_set.create_action::( - &format!("{}_click_modifier_middle", side), - &format!("{} hand middle click modifier", side), - &[], - )?; - let action_move_mouse = action_set.create_action::( - &format!("{}_move_mouse", side), - &format!("{} hand mouse move", side), - &[], - )?; let action_haptics = action_set.create_action::( &format!("{}_haptics", side), &format!("{} hand haptics", side), &[], )?; - let action_space_drag = action_set.create_action::( - &format!("{}_space_drag", side), - &format!("{} hand space drag", side), - &[], - )?; Ok(Self { action_pose, - action_click, - action_grab, + action_click: CustomClickAction::new(action_set, "click", side)?, + action_grab: CustomClickAction::new(action_set, "grab", side)?, action_scroll, - action_alt_click, - action_show_hide, - action_click_modifier_right, - action_click_modifier_middle, - action_move_mouse, + action_alt_click: CustomClickAction::new(action_set, "alt_click", side)?, + action_show_hide: CustomClickAction::new(action_set, "show_hide", side)?, + action_space_drag: CustomClickAction::new(action_set, "space_drag", side)?, + action_modifier_right: CustomClickAction::new( + 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_space_drag, }) } } -fn suggest_bindings( - instance: &xr::Instance, - hands: &[&OpenXrHandSource; 2], -) -> Result<(), xr::sys::Result> { - let path = instance.string_to_path("/interaction_profiles/khr/simple_controller")?; +fn to_path(maybe_path_str: &Option, instance: &xr::Instance) -> Option { + maybe_path_str + .as_ref() + .and_then(|s| match instance.string_to_path(s) { + Ok(path) => Some(path), + Err(_) => { + log::warn!("Invalid binding path: {}", s); + None + } + }) +} - // not fully functional, but helpful for debugging - 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[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")?, - ), - ], - )?; +fn is_bool(maybe_type_str: &Option) -> bool { + maybe_type_str + .as_ref() + .unwrap() + .split('/') + .last() + .map(|last| matches!(last, "click" | "touch")) + .unwrap_or(false) +} - let path = instance.string_to_path("/interaction_profiles/oculus/touch_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[0].action_click, - instance.string_to_path("/user/hand/left/input/trigger/value")?, - ), - 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_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/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")?, - ), - ], - )?; +macro_rules! add_custom { + ($action:expr, $left:expr, $right:expr, $bindings:expr, $instance:expr) => { + if let Some(action) = $action.as_ref() { + if let Some(p) = to_path(&action.left, $instance) { + if is_bool(&action.left) { + if action.double_click.unwrap_or(false) { + $bindings.push(xr::Binding::new(&$left.action_bool_double, p)); + } else { + $bindings.push(xr::Binding::new(&$left.action_bool, p)); + } + } else { + if action.double_click.unwrap_or(false) { + $bindings.push(xr::Binding::new(&$left.action_f32_double, p)); + } else { + $bindings.push(xr::Binding::new(&$left.action_f32, p)); + } + } + } + if let Some(p) = to_path(&action.right, $instance) { + if is_bool(&action.right) { + if action.double_click.unwrap_or(false) { + $bindings.push(xr::Binding::new(&$right.action_bool_double, p)); + } else { + $bindings.push(xr::Binding::new(&$right.action_bool, p)); + } + } else { + if action.double_click.unwrap_or(false) { + $bindings.push(xr::Binding::new(&$right.action_f32_double, p)); + } else { + $bindings.push(xr::Binding::new(&$right.action_f32, p)); + } + } + } + } + }; +} - let path = instance.string_to_path("/interaction_profiles/valve/index_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[0].action_click, - instance.string_to_path("/user/hand/left/input/trigger/value")?, - ), - 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/force")?, - ), - xr::Binding::new( - &hands[1].action_grab, - instance.string_to_path("/user/hand/right/input/squeeze/force")?, - ), - 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_alt_click, - instance.string_to_path("/user/hand/left/input/trackpad/force")?, - ), - xr::Binding::new( - &hands[1].action_alt_click, - instance.string_to_path("/user/hand/right/input/trackpad/force")?, - ), - xr::Binding::new( - &hands[0].action_show_hide, - instance.string_to_path("/user/hand/left/input/b/click")?, - ), - xr::Binding::new( - &hands[1].action_space_drag, - instance.string_to_path("/user/hand/right/input/b/click")?, - ), - xr::Binding::new( - &hands[0].action_click_modifier_right, - instance.string_to_path("/user/hand/left/input/b/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/a/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_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")?, - ), - ], - )?; +fn suggest_bindings(instance: &xr::Instance, hands: &[&OpenXrHandSource; 2]) -> anyhow::Result<()> { + let profiles = load_action_profiles()?; - let path = instance.string_to_path("/interaction_profiles/htc/vive_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_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")?, - ), - ], - )?; + for profile in profiles { + let Ok(profile_path) = instance.string_to_path(&profile.profile) else { + log::debug!("Profile not supported: {}", profile.profile); + continue; + }; - 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 mut bindings: Vec = vec![]; - 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")?, - ), - ], - )?; + if let Some(action) = profile.pose { + if let Some(p) = to_path(&action.left, instance) { + bindings.push(xr::Binding::new(&hands[0].action_pose, p)); + } + if let Some(p) = to_path(&action.right, instance) { + bindings.push(xr::Binding::new(&hands[1].action_pose, p)); + } + } + + if let Some(action) = profile.haptics { + if let Some(p) = to_path(&action.left, instance) { + bindings.push(xr::Binding::new(&hands[0].action_haptics, p)); + } + if let Some(p) = to_path(&action.right, instance) { + bindings.push(xr::Binding::new(&hands[1].action_haptics, p)); + } + } + + if let Some(action) = profile.scroll { + if let Some(p) = to_path(&action.left, instance) { + bindings.push(xr::Binding::new(&hands[0].action_scroll, p)); + } + if let Some(p) = to_path(&action.right, instance) { + bindings.push(xr::Binding::new(&hands[1].action_scroll, p)); + } + } + + 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[1].action_alt_click, + bindings, + instance + ); + + 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[1].action_show_hide, + bindings, + instance + ); + + add_custom!( + profile.space_drag, + &hands[0].action_space_drag, + &hands[1].action_space_drag, + bindings, + instance + ); + + add_custom!( + profile.click_modifier_right, + &hands[0].action_modifier_right, + &hands[1].action_modifier_right, + bindings, + instance + ); + + add_custom!( + profile.click_modifier_middle, + &hands[0].action_modifier_middle, + &hands[1].action_modifier_middle, + bindings, + instance + ); + + add_custom!( + profile.move_mouse, + &hands[0].action_move_mouse, + &hands[1].action_move_mouse, + bindings, + instance + ); + + if instance + .suggest_interaction_profile_bindings(profile_path, &bindings) + .is_err() + { + log::error!("Bad bindings for {}", &profile.profile[22..]); + log::error!("Verify config: ~/.config/wlxoverlay/openxr_actions.json5"); + } + } Ok(()) } + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct OpenXrActionConfAction { + left: Option, + right: Option, + threshold: Option<[f32; 2]>, + double_click: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct OpenXrActionConfProfile { + profile: String, + pose: Option, + click: Option, + grab: Option, + alt_click: Option, + show_hide: Option, + space_drag: Option, + click_modifier_right: Option, + click_modifier_middle: Option, + move_mouse: Option, + scroll: Option, + haptics: Option, +} + +const DEFAULT_PROFILES: &str = include_str!("openxr_actions.json5"); + +fn load_action_profiles() -> anyhow::Result> { + let mut profiles: Vec = + 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::>(&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) +} diff --git a/src/backend/openxr/mod.rs b/src/backend/openxr/mod.rs index 2017949c..02ed27cc 100644 --- a/src/backend/openxr/mod.rs +++ b/src/backend/openxr/mod.rs @@ -16,7 +16,7 @@ use crate::{ common::{BackendError, OverlayContainer}, input::interact, notifications::NotificationManager, - openxr::{input::DoubleClickCounter, lines::LinePool, overlay::OpenXrOverlayData}, + openxr::{lines::LinePool, overlay::OpenXrOverlayData}, overlay::OverlayData, task::TaskType, }, @@ -119,12 +119,11 @@ pub fn openxr_run(running: Arc) -> Result<(), BackendError> { 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 event_storage = xr::EventDataBuffer::new(); - let mut show_hide_counter = DoubleClickCounter::new(); let mut due_tasks = VecDeque::with_capacity(4); 'main_loop: loop { @@ -202,7 +201,6 @@ pub fn openxr_run(running: Arc) -> Result<(), BackendError> { .pointers .iter() .any(|p| p.now.show_hide && !p.before.show_hide) - && show_hide_counter.click() { overlays.show_hide(&mut app_state); } diff --git a/src/backend/openxr/openxr_actions.json5 b/src/backend/openxr/openxr_actions.json5 new file mode 100644 index 00000000..86618c2b --- /dev/null +++ b/src/backend/openxr/openxr_actions.json5 @@ -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", + }, + }, + +] diff --git a/src/backend/openxr/playspace.rs b/src/backend/openxr/playspace.rs index 2931ab4b..fe1dbd0b 100644 --- a/src/backend/openxr/playspace.rs +++ b/src/backend/openxr/playspace.rs @@ -5,15 +5,13 @@ use libloading::{Library, Symbol}; use crate::{backend::common::OverlayContainer, state::AppState}; -use super::{helpers, input::DoubleClickCounter, overlay::OpenXrOverlayData}; +use super::{helpers, overlay::OpenXrOverlayData}; pub(super) struct PlayspaceMover { drag_hand: Option, offset: Vec3A, start_position: Vec3A, - double_click_counter: DoubleClickCounter, - libmonado: Library, mnd_root: *mut c_void, playspace_move: extern "C" fn(*mut c_void, f32, f32, f32) -> i32, @@ -43,8 +41,6 @@ impl PlayspaceMover { offset: Vec3A::ZERO, start_position: Vec3A::ZERO, - double_click_counter: DoubleClickCounter::new(), - libmonado, mnd_root: root, playspace_move: playspace_move_raw, @@ -75,10 +71,8 @@ impl PlayspaceMover { self.apply_offset(); } else { for (i, pointer) in state.input_state.pointers.iter().enumerate() { - if pointer.now.space_drag - && !pointer.before.space_drag - && self.double_click_counter.click() - { + if pointer.now.space_drag && !pointer.before.space_drag { + log::info!("Start space drag"); self.drag_hand = Some(i); self.start_position = pointer.pose.translation; break; diff --git a/src/gui/modular/button.rs b/src/gui/modular/button.rs index 9384e990..d7921c1e 100644 --- a/src/gui/modular/button.rs +++ b/src/gui/modular/button.rs @@ -540,7 +540,7 @@ fn run_watch(data: &WatchAction, app: &mut AppState) { OverlaySelector::Name(WATCH_NAME.into()), Box::new(move |app, o| { o.spawn_rotation *= rot; - app.session.config.watch_rot = o.spawn_rotation.into(); + app.session.config.watch_rot = o.spawn_rotation; o.dirty = true; }), )); @@ -556,7 +556,7 @@ fn run_watch(data: &WatchAction, app: &mut AppState) { OverlaySelector::Name(WATCH_NAME.into()), Box::new(move |app, o| { 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; }), )); diff --git a/src/gui/modular/mod.rs b/src/gui/modular/mod.rs index 4916dd45..239ea0a7 100644 --- a/src/gui/modular/mod.rs +++ b/src/gui/modular/mod.rs @@ -158,7 +158,7 @@ pub fn modular_canvas( } => { canvas.font_size = *font_size; 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); } ModularElement::Button { diff --git a/src/main.rs b/src/main.rs index 413b1f13..8a5c4ae4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,6 +49,8 @@ struct Args { } fn main() -> Result<(), Box> { + std::env::set_var("RUST_BACKTRACE", "full"); + let mut args = Args::parse(); logging_init(&mut args)?;