From ea6a6dc6e7a04a5e9621c8399bcb5896087b5ecb Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:24:57 +0900 Subject: [PATCH] wvr_server: popup input to respect surface tree --- wayvr/src/backend/wayvr/mod.rs | 2 - wayvr/src/overlays/wayvr.rs | 159 +++++++++++++++++++++++---------- 2 files changed, 111 insertions(+), 50 deletions(-) diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index 3717deb0..7ede4628 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -668,9 +668,7 @@ impl WvrServerState { pub fn send_mouse_scroll_to_surface( &mut self, - surface: WlSurface, global_pos: Vec2, - surface_origin: Vec2, hover_window: window::WindowHandle, delta: WheelDelta, ) { diff --git a/wayvr/src/overlays/wayvr.rs b/wayvr/src/overlays/wayvr.rs index 98268e86..7c1cc28d 100644 --- a/wayvr/src/overlays/wayvr.rs +++ b/wayvr/src/overlays/wayvr.rs @@ -108,6 +108,12 @@ pub fn create_wl_window_overlay( }) } +#[derive(Clone)] +struct PopupRoot { + surface: WlSurface, + surface_origin: Vec2, +} + #[derive(Clone)] struct RenderedSurface { surface: WlSurface, @@ -143,6 +149,7 @@ pub struct WvrWindowBackend { interaction_transform: Option, window: WindowHandle, popups: Vec, + popup_roots: Vec, surfaces: Vec, just_resumed: bool, meta: Option, @@ -242,6 +249,7 @@ impl WvrWindowBackend { pipeline: None, window, popups: vec![], + popup_roots: vec![], surfaces: vec![], subsurface_pipeline, popup_outside_button: None, @@ -336,19 +344,8 @@ impl WvrWindowBackend { } fn popup_hit_at_client_pos(&self, pos: Vec2) -> Option<(WlSurface, Vec2)> { - self.popups.iter().rev().find_map(|popup| { - let local = pos - popup.pos; - - let hit = local.x >= 0.0 - && local.y >= 0.0 - && local.x < popup.size.x - && local.y < popup.size.y; - - if hit { - Some((popup.surface.clone(), popup.pos)) - } else { - None - } + self.popup_roots.iter().find_map(|popup| { + surface_tree_input_hit_test(&popup.surface, popup.surface_origin, pos, true) }) } @@ -476,25 +473,41 @@ impl OverlayBackend for WvrWindowBackend { let surfaces = collect_rendered_surface_tree(toplevel.wl_surface()); let should_render_panel = self.panel.should_render(app)?; - let popups = PopupManager::popups_for_surface(toplevel.wl_surface()) - .flat_map(|(popup, point)| { - let configured = with_states(popup.wl_surface(), |states| { - states - .data_map - .get::() - .unwrap() - .lock() - .unwrap() - .configured - }); + let mut popup_roots = Vec::new(); + let mut popups = Vec::new(); - if !configured { - return Vec::new(); - } - let popup_origin = point - popup.geometry().loc; - collect_rendered_surface_tree_at(popup.wl_surface(), popup_origin, true) - }) - .collect::>(); + for (popup, point) in PopupManager::popups_for_surface(toplevel.wl_surface()) { + let configured = with_states(popup.wl_surface(), |states| { + states + .data_map + .get::() + .unwrap() + .lock() + .unwrap() + .configured + }); + + if !configured { + continue; + } + + // Same logic you already use. `point` is the popup tree position; + // `geometry().loc` accounts for xdg_surface window geometry offset. + let popup_origin = point - popup.geometry().loc; + + popup_roots.push(PopupRoot { + surface: popup.wl_surface().clone(), + surface_origin: vec2(popup_origin.x as f32, popup_origin.y as f32), + }); + + popups.extend(collect_rendered_surface_tree_at( + popup.wl_surface(), + popup_origin, + true, + )); + } + + self.popup_roots = popup_roots; let mut tree_dirty = false; @@ -879,24 +892,10 @@ impl OverlayBackend for WvrWindowBackend { let _ = hit2; } - Some(WvrHitTarget::Popup { - surface, - global_pos, - surface_origin, - }) - | Some(WvrHitTarget::Surface { - surface, - global_pos, - surface_origin, - }) => { + Some(WvrHitTarget::Popup { global_pos, .. }) + | Some(WvrHitTarget::Surface { global_pos, .. }) => { let wvr_server = app.wvr_server.as_mut().unwrap(); - wvr_server.send_mouse_scroll_to_surface( - surface, - global_pos, - surface_origin, - self.window, - delta, - ); + wvr_server.send_mouse_scroll_to_surface(global_pos, self.window, delta); } Some(WvrHitTarget::Toplevel { pos }) => { let wvr_server = app.wvr_server.as_mut().unwrap(); @@ -1045,3 +1044,67 @@ fn surface_accepts_input(surface: &RenderedSurface, global_pos: Vec2) -> bool { } }) } + +fn surface_accepts_input_states( + states: &smithay::wayland::compositor::SurfaceData, + local: Vec2, +) -> bool { + if local.x < 0.0 || local.y < 0.0 { + return false; + } + + let mut guard = states.cached_state.get::(); + let attrs = guard.current(); + + let point = Point::::from((local.x.floor() as i32, local.y.floor() as i32)); + + // explicit input region wins, even if no render buffer + if let Some(region) = attrs.input_region.as_ref() { + return region.contains(point); + } + + // fallback for normal rendered surfaces + if let Some(surf) = SurfaceBufWithImage::get_from_surface(states) { + let extent = surf.image.extent_f32(); + let scale = surf.scale.max(1) as f32; + + return local.x < extent[0] / scale && local.y < extent[1] / scale; + } + + false +} +fn surface_tree_input_hit_test( + root: &WlSurface, + root_origin: Vec2, + global_pos: Vec2, + include_root: bool, +) -> Option<(WlSurface, Vec2)> { + let mut hit = None; + let root_id = root.id(); + + with_surface_tree_upward( + root, + Point::::from((root_origin.x as i32, root_origin.y as i32)), + |_, states, parent_pos| { + let pos = *parent_pos + surface_location(states); + TraversalAction::DoChildren(pos) + }, + |surface, states, parent_pos| { + if !include_root && surface.id() == root_id { + return; + } + + let pos = *parent_pos + surface_location(states); + let surface_origin = vec2(pos.x as f32, pos.y as f32); + let local = global_pos - surface_origin; + + if surface_accepts_input_states(states, local) { + // with_surface_tree_upward visits bottom-to-top, so later hits win + hit = Some((surface.clone(), surface_origin)); + } + }, + |_, _, _| true, + ); + + hit +}