diff --git a/wayvr-ipc/src/client.rs b/wayvr-ipc/src/client.rs index bba9a98..b742ef4 100644 --- a/wayvr-ipc/src/client.rs +++ b/wayvr-ipc/src/client.rs @@ -486,6 +486,15 @@ impl WayVRClient { Ok(()) } + pub async fn fn_wlx_device_haptics( + client: WayVRClientMutex, + device: usize, + params: packet_client::WlxHapticsParams, + ) -> anyhow::Result<()> { + send_only!(client, &PacketClient::WlxDeviceHaptics(device, 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 83d7e91..3cb046b 100644 --- a/wayvr-ipc/src/packet_client.rs +++ b/wayvr-ipc/src/packet_client.rs @@ -85,4 +85,5 @@ pub enum PacketClient { WlxHaptics(WlxHapticsParams), WlxInputState(Serial), WlxModifyPanel(WlxModifyPanelParams), + WlxDeviceHaptics(usize, WlxHapticsParams), } diff --git a/wayvrctl/src/helper.rs b/wayvrctl/src/helper.rs index 19c9197..7ab0d17 100644 --- a/wayvrctl/src/helper.rs +++ b/wayvrctl/src/helper.rs @@ -214,15 +214,17 @@ pub async fn wvr_process_launch( ) } -pub async fn wlx_haptics( +pub async fn wlx_device_haptics( state: &mut WayVRClientState, + device: usize, intensity: f32, duration: f32, frequency: f32, ) { handle_empty_result( - WayVRClient::fn_wlx_haptics( + WayVRClient::fn_wlx_device_haptics( state.wayvr_client.clone(), + device, packet_client::WlxHapticsParams { intensity, duration, diff --git a/wayvrctl/src/main.rs b/wayvrctl/src/main.rs index 084fa87..1676bd4 100644 --- a/wayvrctl/src/main.rs +++ b/wayvrctl/src/main.rs @@ -10,7 +10,7 @@ use env_logger::Env; use wayvr_ipc::{client::WayVRClient, ipc, packet_client}; use crate::helper::{ - WayVRClientState, wlx_haptics, wlx_input_state, wlx_panel_modify, wvr_display_create, + WayVRClientState, wlx_device_haptics, wlx_input_state, wlx_panel_modify, wvr_display_create, wvr_display_get, wvr_display_list, wvr_display_remove, wvr_display_set_visible, wvr_display_window_list, wvr_process_get, wvr_process_launch, wvr_process_list, wvr_process_terminate, wvr_window_set_visible, @@ -162,11 +162,12 @@ async fn run_once(state: &mut WayVRClientState, args: Args) -> anyhow::Result<() wvr_process_launch(state, exec, name, env, handle, args, HashMap::new()).await; } Subcommands::Haptics { + device, intensity, duration, frequency, } => { - wlx_haptics(state, intensity, duration, frequency).await; + wlx_device_haptics(state, device, intensity, duration, frequency).await; } Subcommands::PanelModify { overlay, @@ -283,6 +284,8 @@ enum Subcommands { }, /// Trigger haptics on the user's controller Haptics { + /// 0 for left, 1 for right controller + device: usize, #[arg(short, long, default_value = "0.25")] intensity: f32, #[arg(short, long, default_value = "0.1")] diff --git a/wlx-overlay-s/src/backend/input.rs b/wlx-overlay-s/src/backend/input.rs index 52453a4..703130a 100644 --- a/wlx-overlay-s/src/backend/input.rs +++ b/wlx-overlay-s/src/backend/input.rs @@ -10,7 +10,7 @@ use smallvec::{SmallVec, smallvec}; use wlx_common::common::LeftRight; use wlx_common::windowing::{OverlayWindowState, Positioning}; -use crate::backend::task::OverlayTask; +use crate::backend::task::{InputTask, OverlayTask}; use crate::overlays::anchor::{ANCHOR_NAME, GRAB_HELP_NAME}; use crate::overlays::watch::WATCH_NAME; use crate::state::{AppSession, AppState}; @@ -65,6 +65,18 @@ impl InputState { } } + pub fn handle_task(&mut self, task: InputTask) { + match task { + InputTask::Haptics { device, haptics } => { + if let Some(pointer) = self.pointers.get_mut(device) { + pointer.pending_haptics = Some(haptics); + } else { + log::warn!("Can't trigger haptics on non-existing device: {device}"); + } + } + } + } + pub const fn pre_update(&mut self) { self.pointers[0].before = self.pointers[0].now; self.pointers[1].before = self.pointers[1].now; @@ -218,6 +230,7 @@ pub struct Pointer { pub now: PointerState, pub before: PointerState, pub last_click: Instant, + pub pending_haptics: Option, pub(super) interaction: InteractionState, } @@ -231,6 +244,7 @@ impl Pointer { now: PointerState::default(), before: PointerState::default(), last_click: Instant::now(), + pending_haptics: None, interaction: InteractionState::default(), } } @@ -340,6 +354,8 @@ where { // already grabbing, ignore everything else let mut pointer = &mut app.input_state.pointers[idx]; + let pending_haptics = pointer.pending_haptics.take(); + if let Some(grab_data) = pointer.interaction.grabbed { if let Some(grabbed) = overlays.mut_by_id(grab_data.grabbed_id) { handle_grabbed(idx, grabbed, app); @@ -347,13 +363,13 @@ where log::warn!("Grabbed overlay {:?} does not exist", grab_data.grabbed_id); pointer.interaction.grabbed = None; } - return (0.1, None); + return (0.1, pending_haptics); } let hovered_id = pointer.interaction.hovered_id.take(); let (Some(mut hit), haptics) = get_nearest_hit(idx, overlays, app) else { handle_no_hit(idx, hovered_id, overlays, app); - return (0.0, None); // no hit + return (0.0, pending_haptics); // no hit }; // focus change @@ -378,7 +394,7 @@ where let Some(hovered) = overlays.mut_by_id(hit.overlay) else { log::warn!("Hit overlay {:?} does not exist", hit.overlay); - return (0.0, None); // no hit + return (0.0, pending_haptics); // no hit }; pointer = &mut app.input_state.pointers[idx]; pointer.interaction.hovered_id = Some(hit.overlay); @@ -447,7 +463,7 @@ where } } - (hit.dist, haptics) + (hit.dist, haptics.or(pending_haptics)) } fn handle_no_hit( diff --git a/wlx-overlay-s/src/backend/openvr/mod.rs b/wlx-overlay-s/src/backend/openvr/mod.rs index 03e8860..f7dd8ba 100644 --- a/wlx-overlay-s/src/backend/openvr/mod.rs +++ b/wlx-overlay-s/src/backend/openvr/mod.rs @@ -26,7 +26,7 @@ use crate::{ manifest::{install_manifest, uninstall_manifest}, overlay::OpenVrOverlayData, }, - task::{OpenVrTask, OverlayTask, TaskType}, + task::{InputTask, OpenVrTask, OverlayTask, TaskType}, }, config::save_state, graphics::{GpuFutures, init_openvr_graphics}, @@ -220,6 +220,9 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr while let Some(task) = due_tasks.pop_front() { match task { + TaskType::Input(task) => { + app.input_state.handle_task(task); + } TaskType::Overlay(task) => { overlays.handle_task(&mut app, task)?; } diff --git a/wlx-overlay-s/src/backend/openxr/mod.rs b/wlx-overlay-s/src/backend/openxr/mod.rs index 2b2dadb..c14b993 100644 --- a/wlx-overlay-s/src/backend/openxr/mod.rs +++ b/wlx-overlay-s/src/backend/openxr/mod.rs @@ -489,6 +489,9 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr app.tasks.retrieve_due(&mut due_tasks); while let Some(task) = due_tasks.pop_front() { match task { + TaskType::Input(task) => { + app.input_state.handle_task(task); + } TaskType::Overlay(task) => { overlays.handle_task(&mut app, task)?; } diff --git a/wlx-overlay-s/src/backend/task.rs b/wlx-overlay-s/src/backend/task.rs index cd228b9..2ed191a 100644 --- a/wlx-overlay-s/src/backend/task.rs +++ b/wlx-overlay-s/src/backend/task.rs @@ -8,6 +8,7 @@ use std::{ use serde::Deserialize; use crate::{ + backend::input, state::AppState, windowing::{OverlaySelector, window::OverlayWindowConfig}, }; @@ -43,6 +44,13 @@ impl Ord for AppTask { } } +pub enum InputTask { + Haptics { + device: usize, + haptics: input::Haptics, + }, +} + #[cfg(feature = "openvr")] pub enum OpenVrTask { ColorGain(ColorChannel, f32), @@ -87,6 +95,7 @@ pub enum OverlayTask { } pub enum TaskType { + Input(InputTask), Overlay(OverlayTask), Playspace(PlayspaceTask), #[cfg(feature = "openvr")] diff --git a/wlx-overlay-s/src/ipc/events.rs b/wlx-overlay-s/src/ipc/events.rs index b303244..99dea31 100644 --- a/wlx-overlay-s/src/ipc/events.rs +++ b/wlx-overlay-s/src/ipc/events.rs @@ -10,7 +10,7 @@ use crate::{ use crate::{ backend::{ self, - task::{OverlayTask, TaskType}, + task::{InputTask, OverlayTask, TaskType}, }, ipc::signal::WayVRSignal, overlays::{self}, @@ -160,6 +160,10 @@ where wayland_server.pending_haptics = Some(haptics); } } + WayVRSignal::DeviceHaptics(device, haptics) => { + app.tasks + .enqueue(TaskType::Input(InputTask::Haptics { device, haptics })); + } WayVRSignal::DropOverlay(overlay_id) => { app.tasks .enqueue(TaskType::Overlay(OverlayTask::Drop(OverlaySelector::Id( diff --git a/wlx-overlay-s/src/ipc/ipc_server.rs b/wlx-overlay-s/src/ipc/ipc_server.rs index e5aee89..9ee919d 100644 --- a/wlx-overlay-s/src/ipc/ipc_server.rs +++ b/wlx-overlay-s/src/ipc/ipc_server.rs @@ -507,6 +507,21 @@ impl Connection { })); } + fn handle_wlx_device_haptics( + params: &mut TickParams, + device: usize, + haptics_params: packet_client::WlxHapticsParams, + ) { + params.signals.send(WayVRSignal::DeviceHaptics( + device, + crate::backend::input::Haptics { + duration: haptics_params.duration, + frequency: haptics_params.frequency, + intensity: haptics_params.intensity, + }, + )); + } + fn handle_wlx_panel( params: &mut TickParams, custom_params: packet_client::WlxModifyPanelParams, @@ -606,6 +621,9 @@ impl Connection { #[cfg(feature = "wayvr")] Self::handle_wlx_haptics(params, haptics_params); } + PacketClient::WlxDeviceHaptics(device, haptics_params) => { + Self::handle_wlx_device_haptics(params, device, haptics_params); + } PacketClient::WlxModifyPanel(custom_params) => { Self::handle_wlx_panel(params, custom_params); } diff --git a/wlx-overlay-s/src/ipc/signal.rs b/wlx-overlay-s/src/ipc/signal.rs index 1bb4289..9cdb69a 100644 --- a/wlx-overlay-s/src/ipc/signal.rs +++ b/wlx-overlay-s/src/ipc/signal.rs @@ -14,6 +14,7 @@ pub enum WayVRSignal { BroadcastStateChanged(wayvr_ipc::packet_server::WvrStateChanged), #[cfg(feature = "wayvr")] Haptics(crate::backend::input::Haptics), + DeviceHaptics(usize, crate::backend::input::Haptics), DropOverlay(crate::windowing::OverlayID), CustomTask(crate::backend::task::ModifyPanelTask), }