diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index 425f6630..502916cc 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -125,6 +125,7 @@ pub struct WvrServerState { mouse_freeze: Instant, window_to_overlay: HashMap, overlay_to_window: SecondaryMap, + process_overlays: HashMap>, } #[derive(Clone, Copy, PartialEq)] @@ -274,6 +275,7 @@ impl WvrServerState { mouse_freeze: Instant::now(), window_to_overlay: HashMap::new(), overlay_to_window: SecondaryMap::new(), + process_overlays: HashMap::new(), }) } @@ -379,6 +381,12 @@ impl WvrServerState { .unwrap_or_else(|| format!("P{}", client.pid)) .into(); + let spawn_pos = wvr_server + .last_process_overlay(process_handle) + .map_or(SpawnPos::Spread, |oid| { + SpawnPos::Parent(OverlaySelector::Id(oid)) + }); + let window_handle = wvr_server.wm.create_window( toplevel.clone(), process_handle, @@ -436,7 +444,7 @@ impl WvrServerState { app.tasks.enqueue(TaskType::Overlay(OverlayTask::Spawn( OverlaySelector::Nothing, - SpawnPos::Spread, + spawn_pos, Box::new(move |app: &mut AppState| { create_wl_window_overlay( title, @@ -469,11 +477,31 @@ impl WvrServerState { continue; }; + let process_handle = wvr_server + .wm + .windows + .get(&window_handle) + .map(|window| window.process); + if let Some(oid) = wvr_server.window_to_overlay.remove(&window_handle) { app.tasks.enqueue(TaskType::Overlay(OverlayTask::Drop( OverlaySelector::Id(oid), ))); wvr_server.overlay_to_window.remove(oid); + + if let Some(process_handle) = process_handle.as_ref() { + let mut empty = false; + if let Some(overlays) = + wvr_server.process_overlays.get_mut(process_handle) + { + overlays.retain(|other| *other != oid); + empty = overlays.is_empty(); + } + + if empty { + wvr_server.process_overlays.remove(process_handle); + } + } } wvr_server.wm.remove_window(window_handle); @@ -566,6 +594,19 @@ impl WvrServerState { pub fn overlay_added(&mut self, oid: OverlayID, window: window::WindowHandle) { self.overlay_to_window.insert(oid, window); self.window_to_overlay.insert(window, oid); + + if let Some(process_handle) = self.wm.windows.get(&window).map(|window| window.process) { + let overlays = self.process_overlays.entry(process_handle).or_default(); + overlays.retain(|other| *other != oid); + overlays.push(oid); + } + } + + fn last_process_overlay(&self, process_handle: process::ProcessHandle) -> Option { + self.process_overlays + .get(&process_handle) + .and_then(|overlays| overlays.last()) + .copied() } pub fn process_removed(&mut self, tasks: &mut TaskContainer, process: process::ProcessHandle) { @@ -591,6 +632,8 @@ impl WvrServerState { for hnd in &to_remove { self.wm.windows.remove(hnd); } + + self.process_overlays.remove(&process); } pub fn get_overlay_id(&self, window: window::WindowHandle) -> Option { diff --git a/wayvr/src/windowing/manager.rs b/wayvr/src/windowing/manager.rs index 069928db..75eb4ac7 100644 --- a/wayvr/src/windowing/manager.rs +++ b/wayvr/src/windowing/manager.rs @@ -16,7 +16,7 @@ use wlx_common::{ use crate::{ FRAME_COUNTER, - backend::task::{OverlayTask, ToggleMode}, + backend::task::{CreateOverlayTask, OverlayTask, SpawnPos, ToggleMode}, config::save_state, overlays::{ anchor::{create_anchor, create_grab_help}, @@ -34,7 +34,7 @@ use crate::{ backend::{OverlayEventData, OverlayMeta}, set::OverlayWindowSet, snap_upright, - window::{OverlayCategory, OverlayWindowData}, + window::{OverlayCategory, OverlayWindowData, save_transform, spawn_transform_from_parent}, }, }; @@ -345,24 +345,8 @@ where log::warn!("Overlay not found for task: {sel:?}"); } } - OverlayTask::Spawn(sel, pos, f) => { - let None = self.mut_by_selector(&sel) else { - log::debug!("Could not create {sel:?}: exists"); - return Ok(()); - }; - - let Some(overlay_config) = f(app) else { - log::debug!("Could not create {sel:?}: empty config"); - return Ok(()); - }; - - self.add( - OverlayWindowData { - birthframe: FRAME_COUNTER.load(Ordering::Relaxed), - ..OverlayWindowData::from_config(overlay_config) - }, - app, - ); + OverlayTask::Spawn(sel, spawn_pos, f) => { + self.spawn_overlay(app, sel, spawn_pos, f)?; } OverlayTask::Drop(sel) => { if let Some(o) = self.mut_by_selector(&sel) @@ -402,6 +386,35 @@ where } Ok(()) } + + fn spawn_overlay( + &mut self, + app: &mut AppState, + sel: OverlaySelector, + spawn_pos: SpawnPos, + f: Box, + ) -> anyhow::Result<()> { + let None = self.mut_by_selector(&sel) else { + log::debug!("Could not spawn {sel:?}: exists"); + return Ok(()); + }; + + let Some(overlay_config) = f(app) else { + log::debug!("Could not spawn {sel:?}: empty config"); + return Ok(()); + }; + + self.add_with_spawn_pos( + OverlayWindowData { + birthframe: FRAME_COUNTER.load(Ordering::Relaxed), + ..OverlayWindowData::from_config(overlay_config) + }, + app, + spawn_pos, + ); + + Ok(()) + } } const SAVED_ATTRIBS: [BackendAttrib; 3] = [ @@ -734,7 +747,16 @@ impl OverlayWindowManager { .map(|(k, _)| k) } - pub fn add(&mut self, mut overlay: OverlayWindowData, app: &mut AppState) -> OverlayID { + pub fn add(&mut self, overlay: OverlayWindowData, app: &mut AppState) -> OverlayID { + self.add_with_spawn_pos(overlay, app, SpawnPos::Fixed) + } + + fn add_with_spawn_pos( + &mut self, + mut overlay: OverlayWindowData, + app: &mut AppState, + spawn_pos: SpawnPos, + ) -> OverlayID { while self.lookup(&overlay.config.name).is_some() { log::error!( "An overlay with name {} already exists. Deduplicating, but things may break!", @@ -778,6 +800,7 @@ impl OverlayWindowManager { if !shown && show_on_spawn { log::debug!("activating {name} due to show_on_spawn"); self.overlays[oid].config.activate(app); + self.apply_spawn_pos(app, oid, spawn_pos); } if !internal && let Err(e) = self.overlays_changed(app) { log::error!("Error while adding overlay: {e:?}"); @@ -788,6 +811,71 @@ impl OverlayWindowManager { oid } + fn apply_spawn_pos(&mut self, app: &mut AppState, oid: OverlayID, spawn_pos: SpawnPos) { + match spawn_pos { + SpawnPos::Fixed => {} + SpawnPos::Spread => { + let Some(parent_id) = self.spread_parent_for(oid) else { + return; + }; + let parent = OverlaySelector::Id(parent_id); + let _ = self.offset_spawn_from_parent(app, oid, &parent); + } + SpawnPos::Parent(parent) => { + let _ = self.offset_spawn_from_parent(app, oid, &parent); + } + } + } + + // TODO: this just uses the last spawned overlay as parent, can probably do better? + fn spread_parent_for(&self, oid: OverlayID) -> Option { + self.overlays + .iter() + .filter(|(id, overlay)| { + *id != oid + && overlay.config.active_state.is_some() + && !matches!( + overlay.config.category, + OverlayCategory::Internal + | OverlayCategory::Keyboard + | OverlayCategory::Dashboard + ) + }) + .max_by_key(|(_, overlay)| overlay.birthframe) + .map(|(id, _)| id) + } + + fn offset_spawn_from_parent( + &mut self, + app: &mut AppState, + oid: OverlayID, + parent: &OverlaySelector, + ) -> Option<()> { + let parent_id = self.id_by_selector(parent)?; + + if parent_id == oid { + return None; + } + + let parent_transform = self + .overlays + .get(parent_id)? + .config + .active_state + .as_ref()? + .transform; + + let spawn_transform = spawn_transform_from_parent(&parent_transform, &app.input_state.hmd); + let overlay = self.overlays.get_mut(oid)?; + let state = overlay.config.active_state.as_mut()?; + + state.transform = spawn_transform; + save_transform(state, app); + overlay.config.dirty = true; + + Some(()) + } + pub fn switch_or_toggle_set(&mut self, app: &mut AppState, set: usize) { let new_set = if self.current_set.iter().any(|cur| *cur == set) { None diff --git a/wayvr/src/windowing/window.rs b/wayvr/src/windowing/window.rs index 0f578060..06abb366 100644 --- a/wayvr/src/windowing/window.rs +++ b/wayvr/src/windowing/window.rs @@ -310,10 +310,23 @@ pub fn save_transform(state: &mut OverlayWindowState, app: &mut AppState) { state.saved_transform = Some(parent_transform.inverse() * state.transform); } -pub fn spawn_transform_from_parent(parent: &Affine3A, _hmd: &Affine3A) -> Affine3A { - let local_offset = Vec3A::new(0.08, -0.08, 0.06); - let world_offset = parent.x_axis * local_offset.x - parent.y_axis * local_offset.y - + parent.z_axis * local_offset.z; +pub fn spawn_transform_from_parent(parent: &Affine3A, hmd: &Affine3A) -> Affine3A { + const LEFT: f32 = 0.08; + const DOWN: f32 = 0.08; + const CLOSER_TO_HMD: f32 = 0.06; - Affine3A::from_translation((parent.translation + world_offset).into()) + let x_axis = parent.x_axis.normalize(); + let y_axis = parent.y_axis.normalize(); + let z_axis = parent.z_axis.normalize(); + let to_hmd = hmd.translation - parent.translation; + let toward_hmd = if z_axis.dot(to_hmd) >= 0.0 { + z_axis + } else { + -z_axis + }; + + let world_offset = -x_axis * LEFT - y_axis * DOWN + toward_hmd * CLOSER_TO_HMD; + let mut transform = *parent; + transform.translation += world_offset; + transform }