diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 3532078b6a..e22c40f2a4 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -21,6 +21,7 @@ use smithay::output::Output; use smithay::reexports::rustix::fs::{fcntl_setfl, OFlags}; use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1; use smithay::reexports::wayland_server::protocol::wl_output::WlOutput; +use smithay::reexports::wayland_server::protocol::wl_pointer::WlPointer; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; use smithay::reexports::wayland_server::Resource; use smithay::utils::{Logical, Point, Rectangle, Serial}; @@ -38,6 +39,7 @@ use smithay::wayland::keyboard_shortcuts_inhibit::{ }; use smithay::wayland::output::OutputHandler; use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraintsHandler}; +use smithay::wayland::pointer_warp::PointerWarpHandler; use smithay::wayland::security_context::{ SecurityContext, SecurityContextHandler, SecurityContextListenerSource, }; @@ -89,7 +91,9 @@ use crate::protocols::virtual_pointer::{ VirtualPointerInputBackend, VirtualPointerManagerState, VirtualPointerMotionAbsoluteEvent, VirtualPointerMotionEvent, }; -use crate::utils::{output_size, send_scale_transform}; +use crate::utils::{ + get_surface_size, get_surface_toplevel_coords, output_size, send_scale_transform, +}; use crate::{ delegate_ext_workspace, delegate_foreign_toplevel, delegate_gamma_control, delegate_mutter_x11_interop, delegate_output_management, delegate_screencopy, @@ -219,6 +223,62 @@ impl PointerConstraintsHandler for State { } delegate_pointer_constraints!(State); +impl PointerWarpHandler for State { + fn warp_pointer( + &mut self, + surface: WlSurface, + _pointer: WlPointer, + pos: Point, + serial: Serial, + ) { + let Some(seat_pointer) = &self.niri.seat.get_pointer() else { + return; + }; + + let Some(last_serial) = seat_pointer.last_enter() else { + return; + }; + + if serial != last_serial { + return; + } + + let surface_size = get_surface_size(&surface).to_f64(); + if pos.x < 0. || pos.y < 0. || pos.x > surface_size.w || pos.y > surface_size.h { + return; + } + + let Some((mapped, output)) = self.niri.layout.find_window_and_output(&surface) else { + return; + }; + + let Some(output) = output else { + return; + }; + + let Some(output_geo) = self.niri.global_space.output_geometry(output) else { + return; + }; + + let Some(monitor) = self.niri.layout.monitor_for_output(output) else { + return; + }; + + let Some(rect) = monitor.window_visual_rectangle(&mapped.window) else { + return; + }; + + let mut coords = pos; + coords += rect.loc; + coords += get_surface_toplevel_coords(&surface).to_f64(); + coords += output_geo.loc.to_f64(); + + self.move_cursor(coords); + } +} + +smithay::delegate_pointer_warp!(State); + impl InputMethodHandler for State { fn new_popup(&mut self, surface: PopupSurface) { let popup = PopupKind::InputMethod(surface); diff --git a/src/layout/floating.rs b/src/layout/floating.rs index 075787fdf4..7840e1c486 100644 --- a/src/layout/floating.rs +++ b/src/layout/floating.rs @@ -358,6 +358,22 @@ impl FloatingSpace { self.working_area.intersection(window_rect) } + /// Returns the geometry of the window matching id relative to and clamped to the working area. + /// + /// During animations, assumes the final tile position. + pub fn window_visual_rectangle(&self, id: &W::Id) -> Option> { + for (tile, pos) in self.tiles_with_offsets() { + if tile.window().id() == id { + let window_pos = pos + tile.window_loc(); + let window_size = tile.window_size(); + let window_rect = Rectangle::new(window_pos, window_size); + return self.working_area.intersection(window_rect); + } + } + + None + } + pub fn popup_target_rect(&self, id: &W::Id) -> Option> { for (tile, pos) in self.tiles_with_offsets() { if tile.window().id() == id { diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index d71ca55a91..d5b8dbaf35 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -1353,6 +1353,19 @@ impl Monitor { self.active_workspace_ref().active_window_visual_rectangle() } + /// Returns the geometry of the window matching id relative to and clamped to the view. + /// + /// During animations, assumes the final view position. + pub fn window_visual_rectangle(&self, id: &W::Id) -> Option> { + if self.overview_open { + return None; + } + + self.workspaces + .iter() + .find_map(|ws| ws.window_visual_rectangle(id)) + } + fn workspace_size(&self, zoom: f64) -> Size { let ws_size = self.view_size.upscale(zoom); let scale = self.scale.fractional_scale(); diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs index 9739abdfc1..471a1691fa 100644 --- a/src/layout/scrolling.rs +++ b/src/layout/scrolling.rs @@ -2559,6 +2559,29 @@ impl ScrollingSpace { view.intersection(window_rect) } + /// Returns the geometry of the window matching id relative to and clamped to the view. + /// + /// During animations, assumes the final view position. + pub fn window_visual_rectangle(&self, id: &W::Id) -> Option> { + let final_view_offset = self.view_offset.target(); + let view_off = Point::from((-final_view_offset, 0.)); + + for col in &self.columns { + for (tile, pos) in col.tiles() { + if tile.window().id() == id { + let window_pos = view_off + pos + tile.window_loc(); + let window_size = tile.window_size(); + let window_rect = Rectangle::new(window_pos, window_size); + + let view = Rectangle::from_size(self.view_size); + return view.intersection(window_rect); + } + } + } + + None + } + pub fn popup_target_rect(&self, id: &W::Id) -> Option> { for col in &self.columns { for (tile, pos) in col.tiles() { diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 7448779722..832797238a 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -1617,6 +1617,14 @@ impl Workspace { } } + pub fn window_visual_rectangle(&self, window: &W::Id) -> Option> { + if self.floating.has_window(window) { + self.floating.window_visual_rectangle(window) + } else { + self.scrolling.window_visual_rectangle(window) + } + } + pub fn popup_target_rect(&self, window: &W::Id) -> Option> { if self.floating.has_window(window) { self.floating.popup_target_rect(window) diff --git a/src/niri.rs b/src/niri.rs index 190ef09d55..21c50c2aed 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -88,6 +88,7 @@ use smithay::wayland::keyboard_shortcuts_inhibit::{ use smithay::wayland::output::OutputManagerState; use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraintsState}; use smithay::wayland::pointer_gestures::PointerGesturesState; +use smithay::wayland::pointer_warp::PointerWarpManager; use smithay::wayland::presentation::PresentationState; use smithay::wayland::relative_pointer::RelativePointerManagerState; use smithay::wayland::security_context::SecurityContextState; @@ -298,6 +299,7 @@ pub struct Niri { pub pointer_gestures_state: PointerGesturesState, pub relative_pointer_state: RelativePointerManagerState, pub pointer_constraints_state: PointerConstraintsState, + pub pointer_warp_manager: PointerWarpManager, pub idle_notifier_state: IdleNotifierState, pub idle_inhibit_manager_state: IdleInhibitManagerState, pub data_device_state: DataDeviceState, @@ -2298,6 +2300,7 @@ impl Niri { let pointer_gestures_state = PointerGesturesState::new::(&display_handle); let relative_pointer_state = RelativePointerManagerState::new::(&display_handle); let pointer_constraints_state = PointerConstraintsState::new::(&display_handle); + let pointer_warp_manager = PointerWarpManager::new::(&display_handle); let idle_notifier_state = IdleNotifierState::new(&display_handle, event_loop.clone()); let idle_inhibit_manager_state = IdleInhibitManagerState::new::(&display_handle); let data_device_state = DataDeviceState::new::(&display_handle); @@ -2542,6 +2545,7 @@ impl Niri { pointer_gestures_state, relative_pointer_state, pointer_constraints_state, + pointer_warp_manager, idle_notifier_state, idle_inhibit_manager_state, data_device_state, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index bbeac5bc6a..3328792531 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -25,7 +25,9 @@ use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; use smithay::reexports::wayland_server::{Client, DisplayHandle, Resource as _}; use smithay::utils::{Coordinate, Logical, Point, Rectangle, Size, Transform}; -use smithay::wayland::compositor::{send_surface_state, with_states, SurfaceData}; +use smithay::wayland::compositor::{ + get_parent, send_surface_state, with_states, SubsurfaceCachedState, SurfaceData, +}; use smithay::wayland::fractional_scale::with_fractional_scale; use smithay::wayland::shell::xdg::{ ToplevelCachedState, ToplevelConfigure, ToplevelState, ToplevelSurface, XdgToplevelSurfaceData, @@ -592,6 +594,37 @@ pub fn show_screenshot_notification(image_path: Option<&Path>) -> anyhow::Result Ok(()) } +/// Computes this surface's location relative to its toplevel wl_surface's geometry. +/// +/// This function will go up the parent stack and add up the relative locations. Useful for +/// transitive subsurfaces. +pub fn get_surface_toplevel_coords(surface: &WlSurface) -> Point { + let mut offset = (0, 0).into(); + let mut current = surface.clone(); + while let Some(parent) = get_parent(¤t) { + offset += with_states(¤t, |states| { + states + .cached_state + .get::() + .current() + .location + }); + current = parent; + } + + offset +} + +pub fn get_surface_size(surface: &WlSurface) -> Size { + with_states(&surface, |states| { + states + .data_map + .get::() + .and_then(|d| d.lock().unwrap().surface_size()) + .unwrap_or_default() + }) +} + #[inline(never)] pub fn cause_panic() { let a = Duration::from_secs(1);