wayvrctl handsfree controls

This commit is contained in:
galister 2026-07-05 15:49:37 +09:00
parent 1f79f231c0
commit 2d820f1d1b
10 changed files with 213 additions and 9 deletions

View File

@ -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,

View File

@ -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<usize>),
WlxHandsfree(HandsfreeParams),
}

View File

@ -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<TrackedDevice>,
pub handsfree_state: PointerState,
processes: Vec<Child>,
}
@ -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),
});

View File

@ -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(())
}

View File

@ -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),
},
}
}

View File

@ -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(())

View File

@ -3,6 +3,7 @@ pub enum WayVRSignal {
BroadcastStateChanged(wayvr_ipc::packet_server::WvrStateChanged),
DeviceHaptics(usize, crate::backend::input::Haptics),
SwitchSet(Option<usize>),
Handsfree(wayvr_ipc::packet_client::HandsfreeParams),
ShowHide,
CustomTask(crate::backend::task::ModifyPanelTask),
}

View File

@ -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<usize>) {
)
}
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,

View File

@ -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 {

36
wayvrctl/src/types.rs Normal file
View File

@ -0,0 +1,36 @@
use wayvr_ipc::packet_client as ipc;
impl From<crate::HandsfreeMode> 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<crate::HandsfreeAction> 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<crate::SubcommandHandsfree> 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),
}
}
}