diff --git a/wayvr-ipc/src/client.rs b/wayvr-ipc/src/client.rs index b1c4c598..b6fcfd60 100644 --- a/wayvr-ipc/src/client.rs +++ b/wayvr-ipc/src/client.rs @@ -15,7 +15,7 @@ use tokio_util::sync::CancellationToken; use crate::{ gen_id, ipc::{self, Serial}, - packet_client::{self, PacketClient}, + packet_client::{self, HandsfreeParams, PacketClient}, packet_server::{self, PacketServer}, util::notifier::Notifier, }; @@ -422,6 +422,14 @@ impl WayVRClient { Ok(()) } + pub async fn fn_wlx_handsfree( + client: WayVRClientMutex, + params: HandsfreeParams + ) -> anyhow::Result<()> { + send_only!(client, &PacketClient::WlxHandsfree(params)); + Ok(()) + } + pub async fn fn_wlx_modify_panel( client: WayVRClientMutex, params: packet_client::WlxModifyPanelParams, diff --git a/wayvr-ipc/src/packet_client.rs b/wayvr-ipc/src/packet_client.rs index 6f8649bb..9226e779 100644 --- a/wayvr-ipc/src/packet_client.rs +++ b/wayvr-ipc/src/packet_client.rs @@ -20,6 +20,32 @@ pub enum PositionMode { Static, } +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum HandsfreeMode { + None, + Hmd, + HmdPinch, + EyeTracking, + EyeTrackingPinch, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum HandsfreeAction { + Click, + Grab, + RightModifier, + MiddleModifier, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum HandsfreeParams { + SetMode(HandsfreeMode), + Press(HandsfreeAction), + Release(HandsfreeAction), + Toggle(HandsfreeAction), + Scroll(f32), +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WvrProcessLaunchParams { pub name: String, @@ -69,4 +95,5 @@ pub enum PacketClient { WlxDeviceHaptics(usize, WlxHapticsParams), WlxShowHide, WlxSwitchSet(Option), + WlxHandsfree(HandsfreeParams), } diff --git a/wayvr/src/backend/input.rs b/wayvr/src/backend/input.rs index 8a71c674..ba8c37ca 100644 --- a/wayvr/src/backend/input.rs +++ b/wayvr/src/backend/input.rs @@ -8,6 +8,7 @@ use glam::{Affine3A, Vec2, Vec3A, Vec3Swizzles}; use idmap_derive::IntegerId; use smallvec::{SmallVec, smallvec}; use strum::AsRefStr; +use wayvr_ipc::packet_client::{HandsfreeAction, HandsfreeParams}; use wlx_common::common::LeftRight; use wlx_common::windowing::{OverlayWindowState, Positioning}; @@ -62,6 +63,7 @@ pub struct InputState { pub ipd: f32, pub pointers: [Pointer; 2], pub devices: Vec, + pub handsfree_state: PointerState, processes: Vec, } @@ -73,9 +75,44 @@ impl InputState { pointers: [Pointer::new(0), Pointer::new(1)], devices: Vec::new(), processes: Vec::new(), + handsfree_state: PointerState::default(), } } + pub fn apply_handsfree_action(&mut self, params: HandsfreeParams) { + fn set_true(v: &mut bool) { + *v = true; + } + fn set_false(v: &mut bool) { + *v = false; + } + fn toggle(v: &mut bool) { + *v = !*v; + } + + let (action, apply) = match params { + HandsfreeParams::SetMode(_) => { + return; + } + HandsfreeParams::Press(action) => (action, set_true as fn(&mut bool)), + HandsfreeParams::Release(action) => (action, set_false as fn(&mut bool)), + HandsfreeParams::Toggle(action) => (action, toggle as fn(&mut bool)), + HandsfreeParams::Scroll(val) => { + self.handsfree_state.scroll_y = val; + return; + } + }; + + match action { + HandsfreeAction::Click => apply(&mut self.handsfree_state.click), + HandsfreeAction::RightModifier => apply(&mut self.handsfree_state.click_modifier_right), + HandsfreeAction::MiddleModifier => { + apply(&mut self.handsfree_state.click_modifier_middle) + } + HandsfreeAction::Grab => apply(&mut self.handsfree_state.grab), + }; + } + pub fn handle_task(&mut self, task: InputTask) { match task { InputTask::Haptics { device, haptics } => { @@ -271,7 +308,7 @@ impl Pointer { } } -#[derive(Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, Default)] pub struct PointerState { pub scroll_x: f32, pub scroll_y: f32, @@ -350,7 +387,7 @@ fn populate_lines( // horizontal arm lines.push(PointerLine { - mode: PointerMode::Left, + mode: hit.mode, a: raw_hit.global_pos - (hmd.x_axis * HALF_SIZE), b: raw_hit.global_pos + (hmd.x_axis * HALF_SIZE), }); diff --git a/wayvr/src/backend/openxr/input.rs b/wayvr/src/backend/openxr/input.rs index b9d3880a..6e231bea 100644 --- a/wayvr/src/backend/openxr/input.rs +++ b/wayvr/src/backend/openxr/input.rs @@ -14,7 +14,9 @@ use wlx_common::{ }; use crate::{ - backend::input::{Haptics, InputState, Pointer, TrackedDevice, TrackedDeviceRole}, + backend::input::{ + Haptics, InputState, Pointer, PointerState, TrackedDevice, TrackedDeviceRole, + }, state::{AppSession, AppState}, }; @@ -246,6 +248,7 @@ impl OpenXrInputSource { &state.session, hmd, hmd_tracked, + &state.input_state.handsfree_state, )?; } @@ -349,6 +352,7 @@ impl OpenXrPointer { session: &AppSession, hmd: Affine3A, hmd_tracked: bool, + handsfree_state: &PointerState, ) -> anyhow::Result<()> { match session.config.handsfree_pointer { HandsfreePointer::None => return Ok(()), @@ -379,11 +383,21 @@ impl OpenXrPointer { session.config.handsfree_pointer, HandsfreePointer::HmdOnly | HandsfreePointer::EyeTrackingOnly ) { - // skip actions + // input from wayvrctl + pointer.now.click = handsfree_state.click; + pointer.now.grab = handsfree_state.grab; + pointer.now.click_modifier_right = handsfree_state.click_modifier_right; + pointer.now.click_modifier_middle = handsfree_state.click_modifier_middle; + pointer.now.scroll_y = handsfree_state.scroll_y; + + // skip action loading return Ok(()); } self.pointer_load_actions(pointer, xr)?; + pointer.now.click_modifier_right = handsfree_state.click_modifier_right; + pointer.now.click_modifier_middle = handsfree_state.click_modifier_middle; + pointer.now.scroll_y = handsfree_state.scroll_y; Ok(()) } diff --git a/wayvr/src/ipc/events.rs b/wayvr/src/ipc/events.rs index ac0c6591..bfad3970 100644 --- a/wayvr/src/ipc/events.rs +++ b/wayvr/src/ipc/events.rs @@ -1,4 +1,6 @@ +use wayvr_ipc::packet_client::{HandsfreeMode, HandsfreeParams}; use wayvr_ipc::packet_server; +use wlx_common::config::HandsfreePointer; use crate::backend::wayvr::{self, WvrServerState}; @@ -58,6 +60,19 @@ where app.tasks .enqueue(TaskType::Overlay(OverlayTask::ModifyPanel(custom_task))); } + WayVRSignal::Handsfree(params) => match params { + HandsfreeParams::SetMode(mode) => { + let mode = match mode { + HandsfreeMode::None => HandsfreePointer::None, + HandsfreeMode::Hmd => HandsfreePointer::HmdOnly, + HandsfreeMode::HmdPinch => HandsfreePointer::Hmd, + HandsfreeMode::EyeTracking => HandsfreePointer::EyeTrackingOnly, + HandsfreeMode::EyeTrackingPinch => HandsfreePointer::EyeTracking, + }; + app.session.config.handsfree_pointer = mode; + } + _ => app.input_state.apply_handsfree_action(params), + }, } } diff --git a/wayvr/src/ipc/ipc_server.rs b/wayvr/src/ipc/ipc_server.rs index 7dead59d..7f1cf4c9 100644 --- a/wayvr/src/ipc/ipc_server.rs +++ b/wayvr/src/ipc/ipc_server.rs @@ -9,6 +9,7 @@ use glam::Vec3A; use interprocess::local_socket::{self, ToNsName, traits::Listener}; use smallvec::SmallVec; use std::io::{Read, Write}; +use wayvr_ipc::packet_client::HandsfreeParams; use wayvr_ipc::{ ipc::{self}, packet_client::{self, PacketClient}, @@ -356,6 +357,10 @@ impl Connection { params.signals.send(WayVRSignal::SwitchSet(set)); } + fn handle_wlx_handsfree(params: &mut TickParams, payload: HandsfreeParams) { + params.signals.send(WayVRSignal::Handsfree(payload)); + } + fn handle_wlx_panel( params: &mut TickParams, custom_params: packet_client::WlxModifyPanelParams, @@ -433,6 +438,9 @@ impl Connection { PacketClient::WlxModifyPanel(custom_params) => { Self::handle_wlx_panel(params, custom_params); } + PacketClient::WlxHandsfree(payload) => { + Self::handle_wlx_handsfree(params, payload); + } } Ok(()) diff --git a/wayvr/src/ipc/signal.rs b/wayvr/src/ipc/signal.rs index 87111ee3..bff9703d 100644 --- a/wayvr/src/ipc/signal.rs +++ b/wayvr/src/ipc/signal.rs @@ -3,6 +3,7 @@ pub enum WayVRSignal { BroadcastStateChanged(wayvr_ipc::packet_server::WvrStateChanged), DeviceHaptics(usize, crate::backend::input::Haptics), SwitchSet(Option), + Handsfree(wayvr_ipc::packet_client::HandsfreeParams), ShowHide, CustomTask(crate::backend::task::ModifyPanelTask), } diff --git a/wayvrctl/src/helper.rs b/wayvrctl/src/helper.rs index 60fb91e3..05d70dac 100644 --- a/wayvrctl/src/helper.rs +++ b/wayvrctl/src/helper.rs @@ -5,7 +5,7 @@ use serde::Serialize; use wayvr_ipc::{ client::{WayVRClient, WayVRClientMutex}, ipc, - packet_client::{self, PositionMode}, + packet_client::{self, HandsfreeParams, PositionMode}, packet_server, }; @@ -173,6 +173,14 @@ pub async fn wlx_switch_set(state: &mut WayVRClientState, set: Option) { ) } +pub async fn wlx_handsfree(state: &mut WayVRClientState, params: HandsfreeParams) { + handle_empty_result( + WayVRClient::fn_wlx_handsfree(state.wayvr_client.clone(), params) + .await + .context("failed to set handsfree mode"), + ) +} + pub async fn wlx_panel_modify( state: &mut WayVRClientState, overlay: String, diff --git a/wayvrctl/src/main.rs b/wayvrctl/src/main.rs index 6df69149..7b08c0b2 100644 --- a/wayvrctl/src/main.rs +++ b/wayvrctl/src/main.rs @@ -14,12 +14,13 @@ use wayvr_ipc::{ }; use crate::helper::{ - WayVRClientState, wlx_device_haptics, wlx_input_state, wlx_panel_modify, wlx_show_hide, - wlx_switch_set, wvr_process_get, wvr_process_launch, wvr_process_list, wvr_process_terminate, - wvr_window_list, wvr_window_set_visible, + WayVRClientState, wlx_device_haptics, wlx_handsfree, wlx_input_state, wlx_panel_modify, + wlx_show_hide, wlx_switch_set, wvr_process_get, wvr_process_launch, wvr_process_list, + wvr_process_terminate, wvr_window_list, wvr_window_set_visible, }; mod helper; +mod types; #[tokio::main(flavor = "current_thread")] async fn main() -> ExitCode { @@ -195,6 +196,9 @@ async fn run_once(state: &mut WayVRClientState, args: Args) -> anyhow::Result<() let set = if set == 0 { None } else { Some((set - 1) as _) }; wlx_switch_set(state, set).await; } + Subcommands::Handsfree { command } => { + wlx_handsfree(state, command.into()).await; + } } Ok(()) } @@ -291,6 +295,11 @@ enum Subcommands { /// Set number to switch to, 0 to hide all sets set_or_0: usize, }, + Handsfree { + /// Command to execute + #[command(subcommand)] + command: SubcommandHandsfree, + }, } #[derive(Debug, Clone, Copy, clap::ValueEnum)] @@ -300,6 +309,47 @@ enum PosModeEnum { Static, } +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +pub enum HandsfreeMode { + /// No handsfree pointer control + None, + /// Pointer controlled by HMD + Hmd, + /// Pointer controlled by HMD. Left pinch click, right pinch grab. + HmdPinch, + /// Pointer controlled by eye gaze + EyeTracking, + /// Pointer controlled eye gaze. Left pinch click, right pinch grab. + EyeTrackingPinch, +} + +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +pub enum HandsfreeAction { + /// The click action + Click, + /// The grab action + Grab, + /// Right-click modifier (use with click) + RightModifier, + /// Middle-click modifier (use with click) + MiddleModifier, +} + +#[derive(clap::Parser, Debug)] +#[allow(clippy::enum_variant_names)] +pub enum SubcommandHandsfree { + /// Set the handsfree mode + SetMode { mode: HandsfreeMode }, + /// Press and hold an action + Press { action: HandsfreeAction }, + /// Release a held action + Release { action: HandsfreeAction }, + /// Toggle the state of an action + Toggle { action: HandsfreeAction }, + /// Emulate a joystick scroll + Scroll { amount: f32 }, +} + #[derive(clap::Parser, Debug)] #[allow(clippy::enum_variant_names)] enum SubcommandPanelModify { diff --git a/wayvrctl/src/types.rs b/wayvrctl/src/types.rs new file mode 100644 index 00000000..254ed653 --- /dev/null +++ b/wayvrctl/src/types.rs @@ -0,0 +1,36 @@ +use wayvr_ipc::packet_client as ipc; + +impl From for ipc::HandsfreeMode { + fn from(mode: crate::HandsfreeMode) -> Self { + match mode { + crate::HandsfreeMode::None => Self::None, + crate::HandsfreeMode::Hmd => Self::Hmd, + crate::HandsfreeMode::HmdPinch => Self::HmdPinch, + crate::HandsfreeMode::EyeTracking => Self::EyeTracking, + crate::HandsfreeMode::EyeTrackingPinch => Self::EyeTrackingPinch, + } + } +} + +impl From for ipc::HandsfreeAction { + fn from(action: crate::HandsfreeAction) -> Self { + match action { + crate::HandsfreeAction::Click => Self::Click, + crate::HandsfreeAction::Grab => Self::Grab, + crate::HandsfreeAction::RightModifier => Self::RightModifier, + crate::HandsfreeAction::MiddleModifier => Self::MiddleModifier, + } + } +} + +impl From for ipc::HandsfreeParams { + fn from(cmd: crate::SubcommandHandsfree) -> Self { + match cmd { + crate::SubcommandHandsfree::SetMode { mode } => Self::SetMode(mode.into()), + crate::SubcommandHandsfree::Press { action } => Self::Press(action.into()), + crate::SubcommandHandsfree::Release { action } => Self::Release(action.into()), + crate::SubcommandHandsfree::Toggle { action } => Self::Toggle(action.into()), + crate::SubcommandHandsfree::Scroll { amount } => Self::Scroll(amount), + } + } +}