diff --git a/compositor/src/handlers/mod.rs b/compositor/src/handlers/mod.rs index fdabbe3..7bae2e0 100644 --- a/compositor/src/handlers/mod.rs +++ b/compositor/src/handlers/mod.rs @@ -8,6 +8,7 @@ use smithay::{ pointer::{CursorImageStatus, PointerHandle}, }, reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface}, + utils::Point, wayland::{ compositor::{TraversalAction, with_states, with_surface_tree_downward}, fractional_scale::{FractionalScaleHandler, with_fractional_scale}, @@ -113,7 +114,15 @@ impl PointerConstraintsHandler for Ferese { return; }; if focused_surface == *surface { - pointer.set_location(origin + location); + let current = pointer.current_location(); + let scale = self + .window_under_visual(current) + .and_then(|window| self.visual_scale_for_window(&window)) + .unwrap_or(1.0); + let current_surface_location = current - origin; + let offset = location - current_surface_location; + + pointer.set_location(current + Point::from((offset.x * scale, offset.y * scale))); } } } diff --git a/compositor/src/input.rs b/compositor/src/input.rs index b542e14..f2602c0 100644 --- a/compositor/src/input.rs +++ b/compositor/src/input.rs @@ -135,14 +135,21 @@ impl Ferese { } InputEvent::PointerMotion { event, .. } => { let pointer = self.seat.get_pointer().expect("seat has a pointer"); - let focus = self.surface_under(pointer.current_location()); + let position = pointer.current_location(); + let focus = self.surface_under(position); + let scale = self + .window_under_visual(position) + .and_then(|window| self.visual_scale_for_window(&window)) + .unwrap_or(1.0); + let delta = event.delta(); + let delta_unaccel = event.delta_unaccel(); pointer.relative_motion( self, focus, &RelativeMotionEvent { - delta: event.delta(), - delta_unaccel: event.delta_unaccel(), + delta: (delta.x / scale, delta.y / scale).into(), + delta_unaccel: (delta_unaccel.x / scale, delta_unaccel.y / scale).into(), utime: (event.time_msec() as u64).saturating_mul(1_000), }, ); @@ -212,9 +219,7 @@ impl Ferese { let pointer = self.seat.get_pointer().expect("seat has a pointer"); let keyboard = self.seat.get_keyboard().expect("seat has a keyboard"); - if let Some((window, _)) = self.space.element_under(pointer.current_location()) { - let window = window.clone(); - + if let Some(window) = self.window_under_visual(pointer.current_location()) { self.focused_window = self.window_ids.get(&window).copied(); if let Some(focused) = self.focused_window && let Err(error) = self.workspaces.focus_window(focused) diff --git a/compositor/src/state.rs b/compositor/src/state.rs index ccdbb12..3d69242 100644 --- a/compositor/src/state.rs +++ b/compositor/src/state.rs @@ -162,15 +162,46 @@ impl Ferese { &self, position: Point, ) -> Option<(WlSurface, Point)> { - self.space - .element_under(position) - .and_then(|(window, location)| { - window - .surface_under(position - location.to_f64(), WindowSurfaceType::ALL) - .map(|(surface, surface_location)| { - (surface, (surface_location + location).to_f64()) - }) - }) + self.space.elements().rev().find_map(|window| { + let id = self.window_ids.get(window)?; + let geometry = self.window_geometry.get(id)?; + let visual = geometry.visual.current; + let inside_visual = position.x >= visual.x + && position.y >= visual.y + && position.x < visual.x + visual.width + && position.y < visual.y + visual.height; + if !inside_visual { + return None; + } + + let (source_x, source_y) = geometry.inverse_visual_point(position.x, position.y)?; + let source_point = Point::from((source_x, source_y)) + window.geometry().loc.to_f64(); + + window + .surface_under(source_point, WindowSurfaceType::ALL) + .map(|(surface, surface_location)| { + let surface_point = source_point - surface_location.to_f64(); + (surface, position - surface_point) + }) + }) + } + + pub fn window_under_visual(&self, position: Point) -> Option { + self.space.elements().rev().find_map(|window| { + let id = self.window_ids.get(window)?; + let visual = self.window_geometry.get(id)?.visual.current; + + (position.x >= visual.x + && position.y >= visual.y + && position.x < visual.x + visual.width + && position.y < visual.y + visual.height) + .then(|| window.clone()) + }) + } + + pub fn visual_scale_for_window(&self, window: &Window) -> Option { + let id = self.window_ids.get(window)?; + self.window_geometry.get(id)?.visual_scale() } pub fn add_tiled_window(&mut self, window: Window) { @@ -364,6 +395,15 @@ impl Ferese { }; active_animation |= geometry.advance(delta, SpringConfig::default(), true); + if let Some(size) = geometry.client.expire_wait(self.start_time.elapsed()) { + tracing::warn!( + ?id, + configured_width = size.width, + configured_height = size.height, + committed = ?geometry.client.committed_size, + "client did not commit the final configured size within 500 ms" + ); + } let visual = geometry.visual.current; self.space.map_element( window, @@ -386,7 +426,13 @@ impl Ferese { return; }; - geometry.client.commit(size); + let matches_target = geometry.client.commit(size); + tracing::debug!( + ?id, + ?size, + matches_target, + "recorded client geometry commit" + ); } pub fn focus_direction(&mut self, direction: Direction) { diff --git a/compositor/src/winit.rs b/compositor/src/winit.rs index 22761cd..887baa7 100644 --- a/compositor/src/winit.rs +++ b/compositor/src/winit.rs @@ -6,21 +6,35 @@ use std::{ use smithay::{ backend::{ renderer::{ - damage::OutputDamageTracker, element::surface::WaylandSurfaceRenderElement, + damage::OutputDamageTracker, + element::{ + surface::WaylandSurfaceRenderElement, + utils::{ + ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, + RelocateRenderElement, RescaleRenderElement, + }, + }, gles::GlesRenderer, }, winit::{self, WinitEvent}, }, - desktop::utils::OutputPresentationFeedback, + desktop::{ + space::{ConstrainBehavior, ConstrainReference, constrain_space_element}, + utils::OutputPresentationFeedback, + }, output::{Mode, Output, PhysicalProperties, Scale, Subpixel}, reexports::calloop::EventLoop, reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind, - utils::{Clock, Monotonic, Rectangle, Transform}, + utils::{Clock, Logical, Monotonic, Rectangle, Transform}, wayland::presentation::Refresh, }; use crate::Ferese; +type AnimatedWindowRenderElement = CropRenderElement< + RelocateRenderElement>>, +>; + pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<(), Box> { let (mut backend, event_source) = winit::init()?; let initial_scale = normalized_scale(backend.scale_factor()); @@ -90,20 +104,12 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() let rendered = (|| -> Result<(), Box> { { let (renderer, mut framebuffer) = backend.bind()?; - smithay::desktop::space::render_output::< - _, - WaylandSurfaceRenderElement, - _, - _, - >( - &output, + let elements = animated_window_elements(state, renderer, &output); + damage_tracker.render_output( renderer, &mut framebuffer, - 1.0, 0, - [&state.space], - &[], - &mut damage_tracker, + &elements, [0.035, 0.04, 0.055, 1.0], )?; } @@ -143,6 +149,58 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() Ok(()) } +fn animated_window_elements( + state: &Ferese, + renderer: &mut GlesRenderer, + output: &Output, +) -> Vec { + let Some(output_geometry) = state.space.output_geometry(output) else { + return Vec::new(); + }; + let scale = output.current_scale().fractional_scale(); + + state + .space + .elements() + .rev() + .filter_map(|window| { + let id = state.window_ids.get(window)?; + let visual = state.window_geometry.get(id)?.visual.current; + let constrain = Rectangle::::new( + ( + (visual.x - f64::from(output_geometry.loc.x)).round() as i32, + (visual.y - f64::from(output_geometry.loc.y)).round() as i32, + ) + .into(), + ( + visual.width.round().max(1.0) as i32, + visual.height.round().max(1.0) as i32, + ) + .into(), + ); + + Some(constrain_space_element::< + GlesRenderer, + _, + AnimatedWindowRenderElement, + >( + renderer, + window, + constrain.loc, + 1.0, + scale, + constrain, + ConstrainBehavior { + reference: ConstrainReference::Geometry, + behavior: ConstrainScaleBehavior::Zoom, + align: ConstrainAlign::TOP | ConstrainAlign::LEFT, + }, + )) + }) + .flatten() + .collect() +} + fn normalized_scale(scale: f64) -> f64 { if scale.is_finite() && scale > 0.0 { scale diff --git a/crates/ferese-animation/src/lib.rs b/crates/ferese-animation/src/lib.rs index 8baf35d..21385c9 100644 --- a/crates/ferese-animation/src/lib.rs +++ b/crates/ferese-animation/src/lib.rs @@ -179,6 +179,15 @@ impl ClientGeometry { self.waiting_since .is_some_and(|started| now.saturating_sub(started) >= CLIENT_COMMIT_TIMEOUT) } + + pub fn expire_wait(&mut self, now: Duration) -> Option { + if !self.timed_out(now) { + return None; + } + + self.waiting_since = None; + self.last_configured_size + } } #[derive(Clone, Copy, Debug, PartialEq)] @@ -225,6 +234,23 @@ impl WindowGeometry { self.visual.advance(delta, config) } + + pub fn inverse_visual_point(&self, x: f64, y: f64) -> Option<(f64, f64)> { + let visual = self.visual.current; + let scale = self.visual_scale()?; + + Some(((x - visual.x) / scale, (y - visual.y) / scale)) + } + + pub fn visual_scale(&self) -> Option { + let source = self.client.committed_size?; + let visual = self.visual.current; + if source.width <= 0 || source.height <= 0 || visual.width <= 0.0 || visual.height <= 0.0 { + return None; + } + + Some((visual.width / f64::from(source.width)).max(visual.height / f64::from(source.height))) + } } fn advance_value( @@ -347,6 +373,24 @@ mod tests { assert!(!client.timed_out(Duration::from_millis(499))); assert!(client.timed_out(Duration::from_millis(500))); + assert_eq!( + client.expire_wait(Duration::from_millis(500)), + Some(ClientSize::from_rect(rect)) + ); + assert!(!client.timed_out(Duration::from_secs(1))); + } + + #[test] + fn inverse_visual_point_matches_uniform_cover_transform() { + let initial = Rect::new(100.0, 50.0, 400.0, 300.0); + let mut geometry = WindowGeometry::new(initial, Some(ClientSize::from_rect(initial))); + geometry.visual.current = Rect::new(200.0, 100.0, 600.0, 600.0); + + // Cover scales the 4:3 source by 2x for a square allocation, then clips it. + assert_eq!( + geometry.inverse_visual_point(600.0, 500.0), + Some((200.0, 200.0)) + ); } #[test]