diff --git a/Cargo.lock b/Cargo.lock index 23b0d3d..e12b474 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1040,7 +1040,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows", + "windows 0.54.0", ] [[package]] @@ -1052,6 +1052,25 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -2302,6 +2321,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.49.0" @@ -2850,6 +2878,26 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -3304,6 +3352,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sysinfo" +version = "0.30.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732ffa00f53e6b2af46208fba5718d9662a421049204e156328b66791ffa15ae" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows 0.52.0", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3914,6 +3977,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.4", +] + [[package]] name = "windows" version = "0.54.0" @@ -4250,6 +4323,7 @@ dependencies = [ "json", "json5", "libc", + "libloading 0.8.3", "log", "log-panics", "once_cell", @@ -4263,6 +4337,7 @@ dependencies = [ "serde_yaml", "smallvec", "strum", + "sysinfo", "thiserror", "vulkano", "vulkano-shaders", diff --git a/Cargo.toml b/Cargo.toml index 4e4c112..1eff015 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ input-linux = "0.6.0" json = { version = "0.12.4", optional = true } json5 = "0.4.1" libc = "0.2.153" +libloading = "0.8.3" log = "0.4.21" once_cell = "1.19.0" openxr = { version = "0.17.1", features = ["linked"], optional = true } @@ -48,6 +49,7 @@ serde_json = "1.0.113" serde_yaml = "0.9.34" smallvec = "1.11.0" strum = { version = "0.26.2", features = ["derive"] } +sysinfo = { version = "0.30.0", optional = true } thiserror = "1.0.56" vulkano = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" } vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" } @@ -59,7 +61,7 @@ log-panics = { version = "2.1.0", features = ["with-backtrace"] } [features] default = ["openvr", "openxr", "osc", "x11", "wayland"] openvr = ["dep:ovr_overlay", "dep:json"] -openxr = ["dep:openxr"] +openxr = ["dep:openxr", "dep:sysinfo"] osc = ["dep:rosc"] x11 = ["wlx-capture/xshm"] wayland = ["wlx-capture/pipewire", "wlx-capture/wlr"] diff --git a/src/backend/openxr/helpers.rs b/src/backend/openxr/helpers.rs index aadce8d..92bc9b6 100644 --- a/src/backend/openxr/helpers.rs +++ b/src/backend/openxr/helpers.rs @@ -1,6 +1,9 @@ +use std::path::PathBuf; + use anyhow::{bail, ensure}; use glam::{Affine3A, Quat, Vec3, Vec3A}; use openxr as xr; +use sysinfo::Process; use xr::OverlaySessionCreateFlagsEXTX; pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> { @@ -169,3 +172,44 @@ pub(super) fn transform_to_posef(transform: &Affine3A) -> xr::Posef { let rotation = transform_to_norm_quat(transform); translation_rotation_to_posef(translation, rotation) } + +pub(super) fn find_libmonado() -> anyhow::Result { + //check env var first + if let Ok(path) = std::env::var("LIBMONADO_PATH") { + let path = PathBuf::from(path); + if path.exists() { + return Ok(unsafe { libloading::Library::new(path)? }); + } else { + bail!("LIBMONADO_PATH points to a non-existing file."); + } + } + + const PROC_NAMES: [&str; 1] = ["monado-service"]; + let mut system = sysinfo::System::new(); + system.refresh_processes(); + for p in system.processes().values() { + for proc_name in PROC_NAMES.iter() { + if p.name().contains(proc_name) { + if let Some(lib) = proc_load_libmonado(p) { + return Ok(lib); + } + } + } + } + bail!("Could not find libmonado."); +} + +fn proc_load_libmonado(proc: &Process) -> Option { + let path = proc + .exe()? + .parent()? + .parent()? + .join("lib") + .join("libmonado.so"); + + if path.exists() { + Some(unsafe { libloading::Library::new(path).ok()? }) + } else { + None + } +} diff --git a/src/backend/openxr/input.rs b/src/backend/openxr/input.rs index bcc2ef3..fb21df7 100644 --- a/src/backend/openxr/input.rs +++ b/src/backend/openxr/input.rs @@ -51,6 +51,7 @@ pub(super) struct OpenXrHandSource { 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, @@ -201,6 +202,12 @@ impl OpenXrHand { .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; + Ok(()) } } @@ -259,6 +266,11 @@ impl OpenXrHandSource { &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, @@ -271,6 +283,7 @@ impl OpenXrHandSource { action_click_modifier_middle, action_move_mouse, action_haptics, + action_space_drag, }) } } @@ -380,6 +393,10 @@ fn suggest_bindings( &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")?, @@ -439,6 +456,10 @@ fn suggest_bindings( &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")?, diff --git a/src/backend/openxr/mod.rs b/src/backend/openxr/mod.rs index c409564..2017949 100644 --- a/src/backend/openxr/mod.rs +++ b/src/backend/openxr/mod.rs @@ -32,6 +32,7 @@ mod helpers; mod input; mod lines; mod overlay; +mod playspace; mod swapchain; const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO; @@ -72,6 +73,9 @@ pub fn openxr_run(running: Arc) -> Result<(), BackendError> { notifications.run_udp(); let mut delete_queue = vec![]; + let mut space_mover = playspace::PlayspaceMover::try_new() + .map_err(|e| log::warn!("Failed to initialize Monado playspace mover: {}", e)) + .ok(); #[cfg(feature = "osc")] let mut osc_sender = @@ -204,6 +208,9 @@ pub fn openxr_run(running: Arc) -> Result<(), BackendError> { } watch_fade(&mut app_state, overlays.mut_by_id(watch_id).unwrap()); // want panic + if let Some(ref mut space_mover) = space_mover { + space_mover.update(&mut overlays, &app_state); + } for o in overlays.iter_mut() { o.after_input(&mut app_state)?; diff --git a/src/backend/openxr/playspace.rs b/src/backend/openxr/playspace.rs new file mode 100644 index 0000000..2931ab4 --- /dev/null +++ b/src/backend/openxr/playspace.rs @@ -0,0 +1,98 @@ +use std::ffi::c_void; + +use glam::Vec3A; +use libloading::{Library, Symbol}; + +use crate::{backend::common::OverlayContainer, state::AppState}; + +use super::{helpers, input::DoubleClickCounter, 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, +} + +impl PlayspaceMover { + pub fn try_new() -> anyhow::Result { + unsafe { + let libmonado = helpers::find_libmonado()?; + + let root_create: Symbol i32> = + libmonado.get(b"mnd_root_create\0")?; + let playspace_move: Symbol i32> = + libmonado.get(b"mnd_root_playspace_move\0")?; + let playspace_move_raw = *playspace_move; + + let mut root: *mut c_void = std::ptr::null_mut(); + + let ret = root_create(&mut root); + + if ret != 0 { + anyhow::bail!("Failed to create root, code: {}", ret); + } + + Ok(Self { + drag_hand: None, + offset: Vec3A::ZERO, + start_position: Vec3A::ZERO, + + double_click_counter: DoubleClickCounter::new(), + + libmonado, + mnd_root: root, + playspace_move: playspace_move_raw, + }) + } + } + + pub fn update(&mut self, overlays: &mut OverlayContainer, state: &AppState) { + if let Some(hand) = self.drag_hand { + let pointer = &state.input_state.pointers[hand]; + if !pointer.now.space_drag { + self.drag_hand = None; + log::info!("End space drag"); + return; + } + + let hand_pos = state.input_state.pointers[hand].pose.translation; + let relative_pos = hand_pos - self.start_position; + + overlays.iter_mut().for_each(|overlay| { + if overlay.state.grabbable { + overlay.state.dirty = true; + overlay.state.transform.translation += relative_pos * -1.0; + } + }); + + self.offset += relative_pos; + 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() + { + self.drag_hand = Some(i); + self.start_position = pointer.pose.translation; + break; + } + } + } + } + + pub fn reset(&mut self) { + self.offset = Vec3A::ZERO; + self.start_position = Vec3A::ZERO; + } + + fn apply_offset(&mut self) { + (self.playspace_move)(self.mnd_root, self.offset.x, self.offset.y, self.offset.z); + } +}