diff --git a/src/backend/common.rs b/src/backend/common.rs index 7b03bf8..8a6bdf6 100644 --- a/src/backend/common.rs +++ b/src/backend/common.rs @@ -5,10 +5,11 @@ use std::{ time::Instant, }; +use once_cell::sync::Lazy; #[cfg(feature = "openxr")] use openxr as xr; -use glam::{Affine3A, Vec2, Vec3A, Vec3Swizzles}; +use glam::{Affine3A, Vec2, Vec3, Vec3A, Vec3Swizzles}; use idmap::IdMap; use serde::Deserialize; use thiserror::Error; @@ -16,6 +17,7 @@ use thiserror::Error; use crate::{ config::{AStrMapExt, AStrSetExt}, overlays::{ + anchor::create_anchor, keyboard::{create_keyboard, KEYBOARD_NAME}, screen::WlxClientAlias, watch::{create_watch, WATCH_NAME}, @@ -102,6 +104,9 @@ where app.screens.push(meta); } + let anchor = create_anchor(app)?; + overlays.insert(anchor.state.id, anchor); + let mut watch = create_watch::(app)?; watch.state.want_visible = true; overlays.insert(watch.state.id, watch); @@ -314,6 +319,13 @@ where .values() .any(|o| o.state.show_hide && o.state.want_visible); + if !any_shown { + static ANCHOR_LOCAL: Lazy = + Lazy::new(|| Affine3A::from_translation(Vec3::NEG_Z)); + let hmd = snap_upright(app.input_state.hmd, Vec3A::Y); + app.anchor = hmd * *ANCHOR_LOCAL; + } + self.overlays.values_mut().for_each(|o| { if o.state.show_hide { o.state.want_visible = !any_shown; @@ -332,7 +344,7 @@ where } } -#[derive(Clone, Deserialize)] +#[derive(Clone, Deserialize, Debug)] #[serde(untagged)] pub enum OverlaySelector { Id(usize), diff --git a/src/backend/input.rs b/src/backend/input.rs index 61728d1..9b76db3 100644 --- a/src/backend/input.rs +++ b/src/backend/input.rs @@ -1,13 +1,15 @@ use std::{collections::VecDeque, time::Instant}; -use glam::{Affine3A, Vec2, Vec3A}; +use glam::{Affine3A, Vec2, Vec3, Vec3A}; use smallvec::{smallvec, SmallVec}; -use crate::backend::common::snap_upright; +use crate::backend::common::{snap_upright, OverlaySelector, TaskType}; use crate::config::{save_state, AStrMapExt, GeneralConfig}; +use crate::overlays::anchor::ANCHOR_NAME; use crate::state::AppState; +use super::common::TaskContainer; use super::{ common::{raycast_cylinder, raycast_plane, OverlayContainer}, overlay::OverlayData, @@ -244,6 +246,7 @@ pub struct GrabData { pub offset: Vec3A, pub grabbed_id: usize, pub old_curvature: Option, + pub grab_all: bool, } #[repr(u8)] @@ -286,7 +289,13 @@ where let mut pointer = &mut app.input_state.pointers[idx]; if let Some(grab_data) = pointer.interaction.grabbed { if let Some(grabbed) = overlays.mut_by_id(grab_data.grabbed_id) { - pointer.handle_grabbed(grabbed, &hmd, &mut app.session.config); + pointer.handle_grabbed( + grabbed, + &hmd, + &app.anchor, + &mut app.tasks, + &mut app.session.config, + ); } else { log::warn!("Grabbed overlay {} does not exist", grab_data.grabbed_id); pointer.interaction.grabbed = None; @@ -348,7 +357,7 @@ where log::trace!("Hit: {} {:?}", hovered.state.name, hit); if pointer.now.grab && !pointer.before.grab && hovered.state.grabbable { - pointer.start_grab(hovered); + pointer.start_grab(hovered, &mut app.tasks); return ( hit.dist, Some(Haptics { @@ -455,7 +464,7 @@ impl Pointer { None } - fn start_grab(&mut self, overlay: &mut OverlayData) + fn start_grab(&mut self, overlay: &mut OverlayData, tasks: &mut TaskContainer) where O: Default, { @@ -468,7 +477,21 @@ impl Pointer { offset, grabbed_id: overlay.state.id, old_curvature: overlay.state.curvature, + grab_all: matches!(self.interaction.mode, PointerMode::Right), }); + tasks.enqueue(TaskType::Overlay( + OverlaySelector::Name(ANCHOR_NAME.clone()), + Box::new(|app, o| { + o.transform = app.anchor + * Affine3A::from_scale_rotation_translation( + Vec3::ONE * o.spawn_scale, + o.spawn_rotation, + o.spawn_point.into(), + ); + o.dirty = true; + o.want_visible = true; + }), + )); log::info!("Hand {}: grabbed {}", self.idx, overlay.state.name); } @@ -476,6 +499,8 @@ impl Pointer { &mut self, overlay: &mut OverlayData, hmd: &Affine3A, + anchor: &Affine3A, + tasks: &mut TaskContainer, config: &mut GeneralConfig, ) where O: Default, @@ -509,7 +534,7 @@ impl Pointer { } } else { overlay.state.saved_transform = - Some(snap_upright(*hmd, Vec3A::Y).inverse() * overlay.state.transform); + Some(snap_upright(*anchor, Vec3A::Y).inverse() * overlay.state.transform); if let Some(grab_data) = self.interaction.grabbed.as_ref() { let mut state_dirty = false; @@ -531,6 +556,12 @@ impl Pointer { } self.interaction.grabbed = None; + tasks.enqueue(TaskType::Overlay( + OverlaySelector::Name(ANCHOR_NAME.clone()), + Box::new(|_app, o| { + o.want_visible = false; + }), + )); log::info!("Hand {}: dropped {}", self.idx, overlay.state.name); } } diff --git a/src/backend/openvr/mod.rs b/src/backend/openvr/mod.rs index cc27ac6..8628956 100644 --- a/src/backend/openvr/mod.rs +++ b/src/backend/openvr/mod.rs @@ -213,6 +213,8 @@ pub fn openvr_run(running: Arc) -> Result<(), BackendError> { TaskType::Overlay(sel, f) => { if let Some(o) = overlays.mut_by_selector(&sel) { f(&mut state, &mut o.state); + } else { + log::warn!("Overlay not found for task: {:?}", sel); } } TaskType::CreateOverlay(sel, f) => { diff --git a/src/backend/openvr/overlay.rs b/src/backend/openvr/overlay.rs index 0264d88..897cb72 100644 --- a/src/backend/openvr/overlay.rs +++ b/src/backend/openvr/overlay.rs @@ -11,7 +11,7 @@ use vulkano::{Handle, VulkanObject}; use crate::{ backend::overlay::{OverlayData, RelativeTo}, graphics::WlxGraphics, - overlays::watch::WATCH_NAME, + overlays::{anchor::ANCHOR_NAME, watch::WATCH_NAME}, state::AppState, }; @@ -50,6 +50,10 @@ impl OverlayData { self.data.sort_order = 68; } + if *self.state.name == *ANCHOR_NAME.as_ref() { + self.data.sort_order = 67; + } + self.data.handle = Some(handle); self.data.color = Vec4::ONE; @@ -260,7 +264,7 @@ impl OverlayData { m_pQueue: graphics.queue.handle().as_raw() as *mut _, m_nQueueFamilyIndex: graphics.queue.queue_family_index(), }; - log::debug!( + log::trace!( "{}: UploadTex {:?}, {}x{}, {:?}", self.state.name, format, diff --git a/src/backend/openxr/mod.rs b/src/backend/openxr/mod.rs index a1e2396..c3bb05e 100644 --- a/src/backend/openxr/mod.rs +++ b/src/backend/openxr/mod.rs @@ -334,6 +334,8 @@ pub fn openxr_run(running: Arc) -> Result<(), BackendError> { TaskType::Overlay(sel, f) => { if let Some(o) = overlays.mut_by_selector(&sel) { f(&mut app_state, &mut o.state); + } else { + log::warn!("Overlay not found for task: {:?}", sel); } } TaskType::CreateOverlay(sel, f) => { diff --git a/src/backend/overlay.rs b/src/backend/overlay.rs index ea69cea..4dfe0cd 100644 --- a/src/backend/overlay.rs +++ b/src/backend/overlay.rs @@ -12,10 +12,7 @@ use vulkano::image::view::ImageView; use crate::state::AppState; -use super::{ - common::snap_upright, - input::{DummyInteractionHandler, Haptics, InteractionHandler, PointerHit}, -}; +use super::input::{DummyInteractionHandler, Haptics, InteractionHandler, PointerHit}; static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0); @@ -127,15 +124,10 @@ impl OverlayState { self.saved_transform = None; } - let hmd = snap_upright(app.input_state.hmd, Vec3A::Y); - self.transform = hmd * self.get_transform(); + self.transform = app.anchor * self.get_transform(); - if self.grabbable { - if hard_reset { - self.realign(&app.input_state.hmd); - } else { - //self.transform = snap_upright(self.transform, app.input_state.hmd.y_axis); - } + if self.grabbable && hard_reset { + self.realign(&app.input_state.hmd); } self.dirty = true; } diff --git a/src/config.rs b/src/config.rs index 1fe76e9..4126e1e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -254,13 +254,19 @@ impl GeneralConfig { } } -const FALLBACKS: [&str; 3] = [ +const FALLBACKS: [&str; 4] = [ include_str!("res/keyboard.yaml"), include_str!("res/watch.yaml"), include_str!("res/settings.yaml"), + include_str!("res/anchor.yaml"), ]; -const FILES: [&str; 3] = ["keyboard.yaml", "watch.yaml", "settings.yaml"]; +const FILES: [&str; 4] = [ + "keyboard.yaml", + "watch.yaml", + "settings.yaml", + "anchor.yaml", +]; #[derive(Clone, Copy)] #[repr(usize)] @@ -268,6 +274,7 @@ pub enum ConfigType { Keyboard, Watch, Settings, + Anchor, } pub fn load_known_yaml(config_type: ConfigType) -> T diff --git a/src/overlays/anchor.rs b/src/overlays/anchor.rs new file mode 100644 index 0000000..ab265f4 --- /dev/null +++ b/src/overlays/anchor.rs @@ -0,0 +1,31 @@ +use glam::Vec3A; +use once_cell::sync::Lazy; +use std::sync::Arc; + +use crate::backend::overlay::{OverlayData, OverlayState}; +use crate::config::{load_known_yaml, ConfigType}; +use crate::gui::modular::{modular_canvas, ModularUiConfig}; +use crate::state::AppState; + +pub static ANCHOR_NAME: Lazy> = Lazy::new(|| Arc::from("anchor")); + +pub fn create_anchor(state: &AppState) -> anyhow::Result> +where + O: Default, +{ + let config = load_known_yaml::(ConfigType::Anchor); + + Ok(OverlayData { + state: OverlayState { + name: ANCHOR_NAME.clone(), + want_visible: false, + interactable: false, + grabbable: false, + spawn_scale: config.width, + spawn_point: Vec3A::NEG_Z * 0.5, + ..Default::default() + }, + backend: Box::new(modular_canvas(&config.size, &config.elements, state)?), + ..Default::default() + }) +} diff --git a/src/overlays/keyboard.rs b/src/overlays/keyboard.rs index f63a4e9..37617cf 100644 --- a/src/overlays/keyboard.rs +++ b/src/overlays/keyboard.rs @@ -85,12 +85,12 @@ where log::error!("Keyboard: EXEC args empty for {}", key); continue; } + let mut iter = exec_args.iter().cloned(); maybe_state = Some(KeyButtonData::Exec { - program: exec_args - .first() - .unwrap() // safe because we checked is_empty - .clone(), - args: exec_args.iter().skip(1).cloned().collect(), + program: iter.next().unwrap(), + args: iter.by_ref().take_while(|arg| arg[..] != *"null").collect(), + release_program: iter.next(), + release_args: iter.collect(), }); } else { log::error!("Unknown key: {}", key); @@ -124,7 +124,7 @@ where recenter: true, interactable: true, spawn_scale: width, - spawn_point: vec3a(0., -0.5, -1.), + spawn_point: vec3a(0., -0.5, 0.), interaction_transform, ..Default::default() }, @@ -163,7 +163,7 @@ fn key_press( app.hid_provider.send_key(*vk as _, *press); } } - Some(KeyButtonData::Exec { program, args }) => { + Some(KeyButtonData::Exec { program, args, .. }) => { // Reap previous processes data.processes .retain_mut(|child| !matches!(child.try_wait(), Ok(Some(_)))); @@ -200,6 +200,21 @@ fn key_release( app.hid_provider.set_modifiers(data.modifiers); } } + Some(KeyButtonData::Exec { + release_program, + release_args, + .. + }) => { + // Reap previous processes + data.processes + .retain_mut(|child| !matches!(child.try_wait(), Ok(Some(_)))); + + if let Some(program) = release_program { + if let Ok(child) = Command::new(program).args(release_args).spawn() { + data.processes.push(child); + } + } + } _ => {} } } @@ -240,10 +255,23 @@ impl KeyboardData { } enum KeyButtonData { - Key { vk: VirtualKey, pressed: bool }, - Modifier { modifier: KeyModifier, sticky: bool }, - Macro { verbs: Vec<(VirtualKey, bool)> }, - Exec { program: String, args: Vec }, + Key { + vk: VirtualKey, + pressed: bool, + }, + Modifier { + modifier: KeyModifier, + sticky: bool, + }, + Macro { + verbs: Vec<(VirtualKey, bool)>, + }, + Exec { + program: String, + args: Vec, + release_program: Option, + release_args: Vec, + }, } static LAYOUT: Lazy = Lazy::new(Layout::load_from_disk); diff --git a/src/overlays/mirror.rs b/src/overlays/mirror.rs index ddd4419..d26fbf3 100644 --- a/src/overlays/mirror.rs +++ b/src/overlays/mirror.rs @@ -132,7 +132,7 @@ pub fn new_mirror( show_hide, want_visible: true, spawn_scale: 0.5 * session.config.desktop_view_scale, - spawn_point: vec3a(0., 0.5, -0.5), + spawn_point: vec3a(0., 0.5, 0.5), ..Default::default() }; let backend = Box::new(SplitOverlayBackend { diff --git a/src/overlays/mod.rs b/src/overlays/mod.rs index 38cfcd4..7ddf524 100644 --- a/src/overlays/mod.rs +++ b/src/overlays/mod.rs @@ -1,3 +1,4 @@ +pub mod anchor; pub mod custom; pub mod keyboard; #[cfg(feature = "wayland")] diff --git a/src/overlays/screen.rs b/src/overlays/screen.rs index 27934ce..ffd14bf 100644 --- a/src/overlays/screen.rs +++ b/src/overlays/screen.rs @@ -622,7 +622,7 @@ fn create_screen_state( recenter: true, interactable: true, spawn_scale: 1.5 * session.config.desktop_view_scale, - spawn_point: vec3a(0., 0.5, -1.), + spawn_point: vec3a(0., 0.5, 0.), spawn_rotation: Quat::from_axis_angle(Vec3::Z, angle), interaction_transform, ..Default::default() diff --git a/src/res/anchor.yaml b/src/res/anchor.yaml new file mode 100644 index 0000000..d48068e --- /dev/null +++ b/src/res/anchor.yaml @@ -0,0 +1,26 @@ +# looking to make changes? +# drop me in ~/.config/wlxoverlay/anchor.yaml +# + +width: 0.1 + +size: [200, 200] + +# +X: right, +Y: up, +Z: back +spawn_pos: [0, 0, -1] + +elements: + - type: Panel + rect: [98, 0, 4, 200] + bg_color: "#ffff00" + + - type: Panel + rect: [0, 98, 200, 4] + bg_color: "#ffff00" + + - type: Label + rect: [8, 90, 600, 70] + font_size: 18 + fg_color: "#ffff00" + source: Static + text: Center diff --git a/src/state.rs b/src/state.rs index ba6c907..4d882ba 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,7 @@ use std::{io::Cursor, path::PathBuf, sync::Arc}; use anyhow::bail; -use glam::Vec3; +use glam::{Affine3A, Vec3}; use idmap::IdMap; use rodio::{Decoder, OutputStream, OutputStreamHandle, Source}; use serde::{Deserialize, Serialize}; @@ -27,6 +27,7 @@ pub struct AppState { pub hid_provider: Box, pub audio: AudioOutput, pub screens: SmallVec<[ScreenMeta; 8]>, + pub anchor: Affine3A, } impl AppState { @@ -67,6 +68,7 @@ impl AppState { hid_provider: crate::hid::initialize(), audio: AudioOutput::new(), screens: smallvec![], + anchor: Affine3A::IDENTITY, }) } }