diff --git a/anvil/src/state.rs b/anvil/src/state.rs index 946cee7ea93e..28558bd109f5 100644 --- a/anvil/src/state.rs +++ b/anvil/src/state.rs @@ -365,6 +365,29 @@ impl PointerConstraintsHandler for AnvilState }); } } + + fn cursor_position_hint( + &mut self, + surface: &WlSurface, + pointer: &PointerHandle, + location: Point, + ) { + if with_pointer_constraint(surface, pointer, |constraint| { + constraint.map_or(false, |c| c.is_active()) + }) { + let origin = self + .space + .elements() + .find_map(|window| { + (window.wl_surface().as_deref() == Some(surface)).then(|| window.geometry()) + }) + .unwrap_or_default() + .loc + .to_f64(); + + pointer.set_location(origin + location); + } + } } delegate_pointer_constraints!(@ AnvilState); diff --git a/src/input/pointer/mod.rs b/src/input/pointer/mod.rs index 3d29c2460c8b..08d28d12bec9 100644 --- a/src/input/pointer/mod.rs +++ b/src/input/pointer/mod.rs @@ -435,6 +435,26 @@ impl PointerHandle { self.inner.lock().unwrap().location } + /// Update the current location of this pointer in the global space, + /// without sending any event and without updating the focus. + /// + /// If you want to update the location, and update the focus, + /// and send events, use [Self::motion] instead of this. + /// + /// This is useful when the pointer is only moved by relative events, + /// such as when a pointer lock is held by the focused surface. + /// The client can give us a cursor position hint, which corresponds to + /// the actual location the client may be rendering a pointer at. + /// This position hint should be used as the initial location + /// when the pointer lock is deactivated. + /// + /// The next time [Self::motion] is called, the focus will be + /// updated accordingly as if this function was never called. + /// Clients will never be notified of a location hint. + pub fn set_location(&self, location: Point) { + self.inner.lock().unwrap().location = location; + } + /// Access the [`Serial`] of the last `pointer_enter` event, if that focus is still active. /// /// In other words this will return `None` again, once a `pointer_leave` event occurred. diff --git a/src/wayland/pointer_constraints.rs b/src/wayland/pointer_constraints.rs index 51268068f945..04ce56f0b6bb 100644 --- a/src/wayland/pointer_constraints.rs +++ b/src/wayland/pointer_constraints.rs @@ -36,6 +36,18 @@ pub trait PointerConstraintsHandler: SeatHandler { /// /// Use [`with_pointer_constraint`] to access the constraint. fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle); + + /// The client holding a LockedPointer has commited a cursor position hint. + /// + /// This is emitted upon a surface commit if the cursor position hint has been updated. + /// + /// Use [`with_pointer_constraint`] to access the constraint and check if it is active. + fn cursor_position_hint( + &mut self, + surface: &WlSurface, + pointer: &PointerHandle, + location: Point, + ); } /// Constraint confining pointer to a region of the surface @@ -171,14 +183,19 @@ impl PointerConstraint { } } - fn commit(&mut self) { + /// Commits the pending state of the constraint, and returns the cursor position hint if it has changed. + fn commit(&mut self) -> Option> { match self { Self::Confined(confined) => { confined.region.clone_from(&confined.pending_region); + None } Self::Locked(locked) => { locked.region.clone_from(&locked.pending_region); - locked.cursor_position_hint = locked.pending_cursor_position_hint; + locked.pending_cursor_position_hint.take().map(|hint| { + locked.cursor_position_hint = Some(hint); + hint + }) } } } @@ -243,13 +260,28 @@ pub fn with_pointer_constraint< }) } -fn commit_hook(_: &mut D, _dh: &DisplayHandle, surface: &WlSurface) { - with_constraint_data::(surface, |data| { +fn commit_hook( + state: &mut D, + _dh: &DisplayHandle, + surface: &WlSurface, +) { + // `with_constraint_data` locks the pointer constraints, + // so we collect the hints first into a Vec, then release the mutex + // and only once the mutex is released, we call the handler method. + // + // This is to avoid deadlocks when the handler method might try to access the constraints again. + // It's not a hypothetical, it bit me while implementing the position hint functionality. + let position_hints = with_constraint_data::(surface, |data| { let data = data.unwrap(); - for constraint in data.constraints.values_mut() { - constraint.commit(); - } - }) + data.constraints + .iter_mut() + .filter_map(|(pointer, constraint)| constraint.commit().map(|hint| (pointer.clone(), hint))) + .collect::>() + }); + + for (pointer, hint) in position_hints { + state.cursor_position_hint(surface, &pointer, hint); + } } /// Get `PointerConstraintData` associated with a surface, if any. @@ -272,7 +304,7 @@ fn with_constraint_data< } /// Add constraint for surface, or raise protocol error if one exists -fn add_constraint( +fn add_constraint( pointer_constraints: &ZwpPointerConstraintsV1, surface: &WlSurface, pointer: &PointerHandle,