use { crate::{ client::{Client, ClientError}, fixed::Fixed, globals::{Global, GlobalName}, ifs::{ wl_seat::{ WlSeatGlobal, zwp_pointer_constraints_v1::zwp_confined_pointer_v1::ZwpConfinedPointerV1, }, wl_surface::WlSurface, }, leaks::Tracker, object::{Object, Version}, rect::{Rect, Region}, utils::clonecell::CloneCell, wire::{ WlPointerId, WlRegionId, WlSurfaceId, ZwpPointerConstraintsV1Id, zwp_pointer_constraints_v1::*, }, }, std::{cell::Cell, rc::Rc}, thiserror::Error, zwp_locked_pointer_v1::ZwpLockedPointerV1, }; pub mod zwp_confined_pointer_v1; pub mod zwp_locked_pointer_v1; pub struct ZwpPointerConstraintsV1Global { pub name: GlobalName, } pub struct ZwpPointerConstraintsV1 { pub id: ZwpPointerConstraintsV1Id, pub client: Rc, pub tracker: Tracker, pub version: Version, } #[derive(Copy, Clone, Eq, PartialEq)] pub enum ConstraintType { Lock, Confine, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum SeatConstraintStatus { Active, ActivatableOnFocus, Inactive, TerminallyDisabled, } pub struct SeatConstraint { pub owner: CloneCell>>, pub client: Rc, pub seat: Rc, pub surface: Rc, pub region: CloneCell>>, pub one_shot: bool, pub status: Cell, pub ty: ConstraintType, pub position_hint: Cell>, } impl SeatConstraint { pub fn deactivate(&self, apply_position_hint: bool, defer: bool) { if self.status.get() == SeatConstraintStatus::Active { self.seat.constraint.take(); self.handle_position_hint(apply_position_hint, defer); if let Some(owner) = self.owner.get() { owner.send_disabled(); } if self.one_shot { self.status.set(SeatConstraintStatus::TerminallyDisabled); } else { self.status.set(SeatConstraintStatus::Inactive); } } } fn handle_position_hint(&self, apply: bool, defer: bool) { let Some((x, y)) = self.position_hint.take() else { return; }; if !apply { return; } let buffer = self.surface.buffer_abs_pos.get(); let (x_int, y_int) = buffer.translate_inv(x.round_down(), y.round_down()); if !buffer.contains(x_int, y_int) { return; } self.seat.motion_event_abs( self.client.state.now_usec(), x.apply_fract(x_int), y.apply_fract(y_int), defer, ); } pub fn contains(&self, x: i32, y: i32) -> bool { let region = self.region.get(); if let Some(region) = region { return region.contains(x, y); } true } pub fn warp(&self, mut x: Fixed, mut y: Fixed) -> (Fixed, Fixed) { let (x_int, y_int) = (x.round_down(), y.round_down()); let mut best_rect; if let Some(region) = self.region.get() { if region.is_empty() { return (x, y); } best_rect = region[0]; let mut best_dist = region[0].dist_squared(x_int, y_int); for rect in ®ion[1..] { let dist = rect.dist_squared(x_int, y_int); if dist < best_dist { best_dist = dist; best_rect = *rect; } } } else { best_rect = self.surface.buffer_abs_pos.get().at_point(0, 0); } if x_int < best_rect.x1() { x = Fixed::from_int(best_rect.x1()); } else if x_int >= best_rect.x2() { x = Fixed::from_int(best_rect.x2()) - Fixed::EPSILON; } if y_int < best_rect.y1() { y = Fixed::from_int(best_rect.y1()); } else if y_int >= best_rect.y2() { y = Fixed::from_int(best_rect.y2()) - Fixed::EPSILON; } (x, y) } fn detach(&self) { self.deactivate(true, false); self.owner.take(); self.surface.constraints.remove(&self.seat.id); } fn set_region(&self, region: WlRegionId) -> Result<(), ZwpPointerConstraintsV1Error> { let region = get_region(&self.client, region)?; self.region.set(region); Ok(()) } } pub trait ConstraintOwner { fn send_enabled(&self); fn send_disabled(&self); } const LT_ONESHOT: u32 = 1; const LT_PERSISTENT: u32 = 2; impl ZwpPointerConstraintsV1Global { pub fn new(name: GlobalName) -> Self { Self { name } } fn bind_( self: Rc, id: ZwpPointerConstraintsV1Id, client: &Rc, version: Version, ) -> Result<(), ZwpPointerConstraintsV1Error> { let cs = Rc::new(ZwpPointerConstraintsV1 { id, client: client.clone(), tracker: Default::default(), version, }); track!(client, cs); client.add_client_obj(&cs)?; Ok(()) } } fn get_region( client: &Client, region: WlRegionId, ) -> Result>, ZwpPointerConstraintsV1Error> { let region = if region.is_some() { let mut region = client.lookup(region)?.region(); if let Some(scale) = client.wire_scale.get() { let rects: Vec<_> = region .rects() .iter() .map(|r| { Rect::new_sized( r.x1() / scale, r.y1() / scale, r.width() / scale, r.height() / scale, ) .unwrap() }) .collect(); region = Region::from_rects(&rects); } Some(region) } else { None }; Ok(region) } impl ZwpPointerConstraintsV1 { fn create_constraint( &self, ty: ConstraintType, pointer: WlPointerId, surface: WlSurfaceId, region: WlRegionId, lifetime: u32, ) -> Result, ZwpPointerConstraintsV1Error> { let pointer = self.client.lookup(pointer)?; let seat = &pointer.seat.global; let surface = self.client.lookup(surface)?; if surface.constraints.contains(&seat.id) { return Err(ZwpPointerConstraintsV1Error::AlreadyConstrained); } let region = get_region(&self.client, region)?; let one_shot = match lifetime { LT_ONESHOT => true, LT_PERSISTENT => false, _ => return Err(ZwpPointerConstraintsV1Error::UnknownLifetime(lifetime)), }; Ok(Rc::new(SeatConstraint { owner: Default::default(), client: self.client.clone(), seat: seat.clone(), surface, region: CloneCell::new(region), one_shot, status: Cell::new(SeatConstraintStatus::Inactive), ty, position_hint: Default::default(), })) } } impl ZwpPointerConstraintsV1RequestHandler for ZwpPointerConstraintsV1 { type Error = ZwpPointerConstraintsV1Error; fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { self.client.remove_obj(self)?; Ok(()) } fn lock_pointer(&self, req: LockPointer, _slf: &Rc) -> Result<(), Self::Error> { let constraint = self.create_constraint( ConstraintType::Lock, req.pointer, req.surface, req.region, req.lifetime, )?; let lp = Rc::new(ZwpLockedPointerV1 { id: req.id, tracker: Default::default(), constraint, version: self.version, }); track!(self.client, lp); self.client.add_client_obj(&lp)?; lp.constraint.owner.set(Some(lp.clone())); lp.constraint .surface .constraints .insert(lp.constraint.seat.id, lp.constraint.clone()); lp.constraint.seat.maybe_constrain_pointer_node(); Ok(()) } fn confine_pointer(&self, req: ConfinePointer, _slf: &Rc) -> Result<(), Self::Error> { let constraint = self.create_constraint( ConstraintType::Confine, req.pointer, req.surface, req.region, req.lifetime, )?; let lp = Rc::new(ZwpConfinedPointerV1 { id: req.id, tracker: Default::default(), constraint, version: self.version, }); track!(self.client, lp); self.client.add_client_obj(&lp)?; lp.constraint.owner.set(Some(lp.clone())); lp.constraint .surface .constraints .insert(lp.constraint.seat.id, lp.constraint.clone()); lp.constraint.seat.maybe_constrain_pointer_node(); Ok(()) } } global_base!( ZwpPointerConstraintsV1Global, ZwpPointerConstraintsV1, ZwpPointerConstraintsV1Error ); impl Global for ZwpPointerConstraintsV1Global { fn singleton(&self) -> bool { true } fn version(&self) -> u32 { 1 } } simple_add_global!(ZwpPointerConstraintsV1Global); object_base! { self = ZwpPointerConstraintsV1; version = self.version; } impl Object for ZwpPointerConstraintsV1 {} simple_add_obj!(ZwpPointerConstraintsV1); #[derive(Debug, Error)] pub enum ZwpPointerConstraintsV1Error { #[error(transparent)] ClientError(Box), #[error("The surface already has a constraint attached for the seat")] AlreadyConstrained, #[error("The constraint lifetime {0} is unknown")] UnknownLifetime(u32), } efrom!(ZwpPointerConstraintsV1Error, ClientError);