From 38d1267ec9530603dd744691bc8b5bcbd4f2bc65 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 21 Jul 2022 20:16:22 +0200 Subject: [PATCH] tree: implement pointer constraints --- README.md | 2 +- jay-config/src/_private/client.rs | 4 + jay-config/src/_private/ipc.rs | 3 + jay-config/src/input.rs | 5 + src/config/handler.rs | 9 + src/fixed.rs | 18 + src/globals.rs | 2 + src/ifs/wl_seat.rs | 46 +++ src/ifs/wl_seat/event_handling.rs | 37 ++- src/ifs/wl_seat/zwp_pointer_constraints_v1.rs | 308 ++++++++++++++++++ .../zwp_confined_pointer_v1.rs | 78 +++++ .../zwp_locked_pointer_v1.rs | 85 +++++ src/ifs/wl_surface.rs | 12 +- src/rect.rs | 16 + src/rect/region.rs | 12 + src/utils/smallmap.rs | 13 + wire/zwp_confined_pointer_v1.txt | 19 ++ wire/zwp_locked_pointer_v1.txt | 21 ++ wire/zwp_pointer_constraints_v1.txt | 21 ++ 19 files changed, 707 insertions(+), 4 deletions(-) create mode 100644 src/ifs/wl_seat/zwp_pointer_constraints_v1.rs create mode 100644 src/ifs/wl_seat/zwp_pointer_constraints_v1/zwp_confined_pointer_v1.rs create mode 100644 src/ifs/wl_seat/zwp_pointer_constraints_v1/zwp_locked_pointer_v1.rs create mode 100644 wire/zwp_confined_pointer_v1.txt create mode 100644 wire/zwp_locked_pointer_v1.txt create mode 100644 wire/zwp_pointer_constraints_v1.txt diff --git a/README.md b/README.md index 50e715f7..c8b14e16 100644 --- a/README.md +++ b/README.md @@ -38,13 +38,13 @@ The following features have been implemented and should work: - Monitor hotplug - Fractional scaling - Hardware cursors +- Pointer constraints ### Missing Features The following features are known to be missing or broken and will be implemented later: -- Games that require pointer grabs - Touch and tablet support - Damage tracking (any kind of damage causes a complete re-render currently) - Selecting the primary device in multi-GPU systems diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 8ba07aac..ee33bad5 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -297,6 +297,10 @@ impl Client { axis } + pub fn disable_pointer_constraint(&self, seat: Seat) { + self.send(&ClientMessage::DisablePointerConstraint { seat }); + } + pub fn set_fullscreen(&self, seat: Seat, fullscreen: bool) { self.send(&ClientMessage::SetFullscreen { seat, fullscreen }); } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 783b4aa8..84acb03f 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -304,6 +304,9 @@ pub enum ClientMessage<'a> { seat: Seat, use_hardware_cursor: bool, }, + DisablePointerConstraint { + seat: Seat, + }, } #[derive(Encode, Decode, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index cf2caf8b..94259bd0 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -278,6 +278,11 @@ impl Seat { pub fn set_fullscreen(self, fullscreen: bool) { get!().set_fullscreen(self, fullscreen) } + + /// Disables the currently active pointer constraint on this seat. + pub fn disable_pointer_constraint(self) { + get!().disable_pointer_constraint(self) + } } /// Returns all seats. diff --git a/src/config/handler.rs b/src/config/handler.rs index dc5ae142..5f8f73f2 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -605,6 +605,12 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_disable_pointer_constraint(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.disable_pointer_constraint(); + Ok(()) + } + fn handle_set_use_hardware_cursor( &self, seat: Seat, @@ -1195,6 +1201,9 @@ impl ConfigProxyHandler { } => self .handle_set_use_hardware_cursor(seat, use_hardware_cursor) .wrn("set_use_hardware_cursor")?, + ClientMessage::DisablePointerConstraint { seat } => self + .handle_disable_pointer_constraint(seat) + .wrn("disable_pointer_constraint")?, } Ok(()) } diff --git a/src/fixed.rs b/src/fixed.rs index d1511453..e621f68c 100644 --- a/src/fixed.rs +++ b/src/fixed.rs @@ -9,6 +9,8 @@ use std::{ pub struct Fixed(pub i32); impl Fixed { + pub const EPSILON: Self = Fixed(1); + pub fn is_integer(self) -> bool { self.0 & 255 == 0 } @@ -86,6 +88,22 @@ impl Add for Fixed { } } +impl Sub for Fixed { + type Output = Self; + + fn sub(self, rhs: i32) -> Self::Output { + Self(self.0 - (rhs << 8)) + } +} + +impl Add for Fixed { + type Output = Self; + + fn add(self, rhs: i32) -> Self::Output { + Self(self.0 + (rhs << 8)) + } +} + impl AddAssign for Fixed { fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0; diff --git a/src/globals.rs b/src/globals.rs index 09e19400..cc4431aa 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -14,6 +14,7 @@ use { wl_output::WlOutputGlobal, wl_registry::WlRegistry, wl_seat::{ + zwp_pointer_constraints_v1::ZwpPointerConstraintsV1Global, zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1Global, WlSeatGlobal, }, wl_shm::WlShmGlobal, @@ -147,6 +148,7 @@ impl Globals { add_singleton!(ExtSessionLockManagerV1Global); add_singleton!(WpViewporterGlobal); add_singleton!(WpFractionalScaleManagerV1Global); + add_singleton!(ZwpPointerConstraintsV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 7056ade1..9b07eab6 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -4,6 +4,7 @@ mod pointer_owner; pub mod wl_keyboard; pub mod wl_pointer; pub mod wl_touch; +pub mod zwp_pointer_constraints_v1; pub mod zwp_relative_pointer_manager_v1; pub mod zwp_relative_pointer_v1; @@ -32,6 +33,7 @@ use { wl_keyboard::{WlKeyboard, WlKeyboardError, REPEAT_INFO_SINCE}, wl_pointer::WlPointer, wl_touch::WlTouch, + zwp_pointer_constraints_v1::{SeatConstraint, SeatConstraintStatus}, zwp_relative_pointer_v1::ZwpRelativePointerV1, }, wl_surface::WlSurface, @@ -152,6 +154,7 @@ pub struct WlSeatGlobal { changes: NumCell, cursor_size: Cell, hardware_cursor: Cell, + constraint: CloneCell>>, } const CHANGE_CURSOR_MOVED: u32 = 1 << 0; @@ -205,6 +208,7 @@ impl WlSeatGlobal { changes: NumCell::new(CHANGE_CURSOR_MOVED | CHANGE_TREE), cursor_size: Cell::new(DEFAULT_CURSOR_SIZE), hardware_cursor: Cell::new(state.globals.seats.len() == 0), + constraint: Default::default(), }); state.add_cursor_size(DEFAULT_CURSOR_SIZE); let seat = slf.clone(); @@ -388,6 +392,47 @@ impl WlSeatGlobal { .set(Some(self.state.seat_queue.add_last(self.clone()))); } + pub fn disable_pointer_constraint(&self) { + if let Some(constraint) = self.constraint.get() { + constraint.deactivate(); + if constraint.status.get() == SeatConstraintStatus::Inactive { + constraint + .status + .set(SeatConstraintStatus::ActivatableOnFocus); + } + } + } + + fn maybe_constrain_pointer_node(&self) { + if let Some(pn) = self.pointer_node() { + if let Some(surface) = pn.node_into_surface() { + let (mut x, mut y) = self.pos.get(); + let (sx, sy) = surface.buffer_abs_pos.get().position(); + x -= Fixed::from_int(sx); + y -= Fixed::from_int(sy); + self.maybe_constrain(&surface, x, y); + } + } + } + + fn maybe_constrain(&self, surface: &WlSurface, x: Fixed, y: Fixed) { + if self.constraint.get().is_some() { + return; + } + let candidate = match surface.constraints.get(&self.id) { + Some(c) if c.status.get() == SeatConstraintStatus::Inactive => c, + _ => return, + }; + if !candidate.contains(x.round_down(), y.round_down()) { + return; + } + candidate.status.set(SeatConstraintStatus::Active); + if let Some(owner) = candidate.owner.get() { + owner.send_enabled(); + } + self.constraint.set(Some(candidate)); + } + pub fn set_fullscreen(&self, fullscreen: bool) { if let Some(tl) = self.keyboard_node.get().node_toplevel() { tl.tl_set_fullscreen(fullscreen); @@ -800,6 +845,7 @@ impl WlSeatGlobal { self.queue_link.set(None); self.tree_changed_handler.set(None); self.output.set(self.state.dummy_output.get().unwrap()); + self.constraint.take(); } pub fn id(&self) -> SeatId { diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 700d369a..9fca68df 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -19,6 +19,7 @@ use { AXIS_VALUE120_SINCE_VERSION, POINTER_FRAME_SINCE_VERSION, WHEEL_TILT, WHEEL_TILT_SINCE_VERSION, }, + zwp_pointer_constraints_v1::{ConstraintType, SeatConstraintStatus}, zwp_relative_pointer_v1::ZwpRelativePointerV1, Dnd, SeatId, WlSeat, WlSeatGlobal, CHANGE_CURSOR_MOVED, }, @@ -216,6 +217,11 @@ impl WlSeatGlobal { let pos = output.node.global.pos.get(); x += Fixed::from_int(pos.x1()); y += Fixed::from_int(pos.y1()); + if let Some(c) = self.constraint.get() { + if c.ty == ConstraintType::Lock || !c.contains(x.round_down(), y.round_down()) { + c.deactivate(); + } + } self.state.for_each_seat_tester(|t| { t.send_pointer_abs(self.id, time_usec, x, y); }); @@ -238,9 +244,26 @@ impl WlSeatGlobal { dx_unaccelerated, dy_unaccelerated, ); + let constraint = self.constraint.get(); + let locked = match &constraint { + Some(c) if c.ty == ConstraintType::Lock => true, + _ => false, + }; let (mut x, mut y) = self.pos.get(); - x += dx; - y += dy; + if !locked { + x += dx; + y += dy; + if let Some(c) = &constraint { + let surface_pos = c.surface.buffer_abs_pos.get(); + let (x_rel, y_rel) = (x - surface_pos.x1(), y - surface_pos.y1()); + let contained = surface_pos.contains(x.round_down(), y.round_down()) + && c.contains(x_rel.round_down(), y_rel.round_down()); + if !contained { + let (x_rel, y_rel) = c.warp(x_rel, y_rel); + (x, y) = (x_rel + surface_pos.x1(), y_rel + surface_pos.y1()); + } + } + } self.state.for_each_seat_tester(|t| { t.send_pointer_rel( self.id, @@ -604,6 +627,7 @@ impl WlSeatGlobal { let time = (self.pos_time_usec.get() / 1000) as u32; self.surface_pointer_event(0, n, |p| p.send_motion(time, x, y)); self.surface_pointer_frame(n); + self.maybe_constrain(n, x, y); } } @@ -641,6 +665,12 @@ impl WlSeatGlobal { n.client.last_enter_serial.set(serial); self.surface_pointer_event(0, n, |p| p.send_enter(serial, n.id, x, y)); self.surface_pointer_frame(n); + for (_, constraint) in &n.constraints { + if constraint.status.get() == SeatConstraintStatus::ActivatableOnFocus { + constraint.status.set(SeatConstraintStatus::Inactive); + } + } + self.maybe_constrain(n, x, y); } } @@ -648,6 +678,9 @@ impl WlSeatGlobal { impl WlSeatGlobal { pub fn leave_surface(&self, n: &WlSurface) { let serial = n.client.next_serial(); + for (_, constraint) in &n.constraints { + constraint.deactivate(); + } self.surface_pointer_event(0, n, |p| p.send_leave(serial, n.id)); self.surface_pointer_frame(n); } diff --git a/src/ifs/wl_seat/zwp_pointer_constraints_v1.rs b/src/ifs/wl_seat/zwp_pointer_constraints_v1.rs new file mode 100644 index 00000000..8d84a73c --- /dev/null +++ b/src/ifs/wl_seat/zwp_pointer_constraints_v1.rs @@ -0,0 +1,308 @@ +use { + crate::{ + client::{Client, ClientError}, + fixed::Fixed, + globals::{Global, GlobalName}, + ifs::{ + wl_seat::{ + zwp_pointer_constraints_v1::zwp_confined_pointer_v1::ZwpConfinedPointerV1, + WlSeatGlobal, + }, + wl_surface::WlSurface, + }, + leaks::Tracker, + object::Object, + rect::Region, + utils::{ + buffd::{MsgParser, MsgParserError}, + clonecell::CloneCell, + }, + wire::{ + zwp_pointer_constraints_v1::*, WlPointerId, WlRegionId, WlSurfaceId, + ZwpPointerConstraintsV1Id, + }, + }, + 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, +} + +#[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, +} + +impl SeatConstraint { + pub fn deactivate(&self) { + if self.status.get() == SeatConstraintStatus::Active { + self.seat.constraint.take(); + 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); + } + } + } + + 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(); + self.owner.take(); + self.surface.constraints.remove(&self.seat.id); + } + + fn set_region(&self, region: WlRegionId) -> Result<(), ZwpPointerConstraintsV1Error> { + let region = if region.is_some() { + Some(self.client.lookup(region)?.region()) + } else { + None + }; + 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: u32, + ) -> Result<(), ZwpPointerConstraintsV1Error> { + let cs = Rc::new(ZwpPointerConstraintsV1 { + id, + client: client.clone(), + tracker: Default::default(), + }); + track!(client, cs); + client.add_client_obj(&cs)?; + Ok(()) + } +} + +impl ZwpPointerConstraintsV1 { + fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpPointerConstraintsV1Error> { + let _req: Destroy = self.client.parse(self, msg)?; + self.client.remove_obj(self)?; + Ok(()) + } + + 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 = if region.is_some() { + Some(self.client.lookup(region)?.region()) + } else { + None + }; + 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, + })) + } + + fn lock_pointer(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpPointerConstraintsV1Error> { + let req: LockPointer = self.client.parse(self, msg)?; + 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, + }); + 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, msg: MsgParser<'_, '_>) -> Result<(), ZwpPointerConstraintsV1Error> { + let req: ConfinePointer = self.client.parse(self, msg)?; + 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, + }); + 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! { + ZwpPointerConstraintsV1; + + DESTROY => destroy, + LOCK_POINTER => lock_pointer, + CONFINE_POINTER => confine_pointer, +} + +impl Object for ZwpPointerConstraintsV1 { + fn num_requests(&self) -> u32 { + CONFINE_POINTER + 1 + } +} + +simple_add_obj!(ZwpPointerConstraintsV1); + +#[derive(Debug, Error)] +pub enum ZwpPointerConstraintsV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Parsing failed")] + MsgParserError(#[source] 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); +efrom!(ZwpPointerConstraintsV1Error, MsgParserError); diff --git a/src/ifs/wl_seat/zwp_pointer_constraints_v1/zwp_confined_pointer_v1.rs b/src/ifs/wl_seat/zwp_pointer_constraints_v1/zwp_confined_pointer_v1.rs new file mode 100644 index 00000000..7aba3296 --- /dev/null +++ b/src/ifs/wl_seat/zwp_pointer_constraints_v1/zwp_confined_pointer_v1.rs @@ -0,0 +1,78 @@ +use { + crate::{ + client::ClientError, + ifs::wl_seat::zwp_pointer_constraints_v1::{ + ConstraintOwner, SeatConstraint, ZwpPointerConstraintsV1Error, + }, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{zwp_confined_pointer_v1::*, ZwpConfinedPointerV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwpConfinedPointerV1 { + pub id: ZwpConfinedPointerV1Id, + pub tracker: Tracker, + pub constraint: Rc, +} + +impl ZwpConfinedPointerV1 { + fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpConfinedPointerV1Error> { + let _req: Destroy = self.constraint.client.parse(self, msg)?; + self.constraint.detach(); + self.constraint.client.remove_obj(self)?; + Ok(()) + } + + fn set_region(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpConfinedPointerV1Error> { + let req: SetRegion = self.constraint.client.parse(self, msg)?; + self.constraint.set_region(req.region)?; + Ok(()) + } +} + +impl ConstraintOwner for ZwpConfinedPointerV1 { + fn send_enabled(&self) { + self.constraint.client.event(Confined { self_id: self.id }); + } + + fn send_disabled(&self) { + self.constraint + .client + .event(Unconfined { self_id: self.id }); + } +} + +object_base! { + ZwpConfinedPointerV1; + + DESTROY => destroy, + SET_REGION => set_region, +} + +impl Object for ZwpConfinedPointerV1 { + fn num_requests(&self) -> u32 { + SET_REGION + 1 + } + + fn break_loops(&self) { + self.constraint.detach(); + } +} + +simple_add_obj!(ZwpConfinedPointerV1); + +#[derive(Debug, Error)] +pub enum ZwpConfinedPointerV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ZwpPointerConstraintsV1Error(#[from] ZwpPointerConstraintsV1Error), +} +efrom!(ZwpConfinedPointerV1Error, ClientError); +efrom!(ZwpConfinedPointerV1Error, MsgParserError); diff --git a/src/ifs/wl_seat/zwp_pointer_constraints_v1/zwp_locked_pointer_v1.rs b/src/ifs/wl_seat/zwp_pointer_constraints_v1/zwp_locked_pointer_v1.rs new file mode 100644 index 00000000..d5eb5806 --- /dev/null +++ b/src/ifs/wl_seat/zwp_pointer_constraints_v1/zwp_locked_pointer_v1.rs @@ -0,0 +1,85 @@ +use { + crate::{ + client::ClientError, + ifs::wl_seat::zwp_pointer_constraints_v1::{ + ConstraintOwner, SeatConstraint, ZwpPointerConstraintsV1Error, + }, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{zwp_locked_pointer_v1::*, ZwpLockedPointerV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwpLockedPointerV1 { + pub id: ZwpLockedPointerV1Id, + pub tracker: Tracker, + pub constraint: Rc, +} + +impl ZwpLockedPointerV1 { + fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpLockedPointerV1Error> { + let _req: Destroy = self.constraint.client.parse(self, msg)?; + self.constraint.detach(); + self.constraint.client.remove_obj(self)?; + Ok(()) + } + + fn set_cursor_position_hint( + &self, + msg: MsgParser<'_, '_>, + ) -> Result<(), ZwpLockedPointerV1Error> { + let _req: SetCursorPositionHint = self.constraint.client.parse(self, msg)?; + Ok(()) + } + + fn set_region(&self, msg: MsgParser<'_, '_>) -> Result<(), ZwpLockedPointerV1Error> { + let req: SetRegion = self.constraint.client.parse(self, msg)?; + self.constraint.set_region(req.region)?; + Ok(()) + } +} + +impl ConstraintOwner for ZwpLockedPointerV1 { + fn send_enabled(&self) { + self.constraint.client.event(Locked { self_id: self.id }); + } + + fn send_disabled(&self) { + self.constraint.client.event(Unlocked { self_id: self.id }); + } +} + +object_base! { + ZwpLockedPointerV1; + + DESTROY => destroy, + SET_CURSOR_POSITION_HINT => set_cursor_position_hint, + SET_REGION => set_region, +} + +impl Object for ZwpLockedPointerV1 { + fn num_requests(&self) -> u32 { + SET_REGION + 1 + } + + fn break_loops(&self) { + self.constraint.detach(); + } +} + +simple_add_obj!(ZwpLockedPointerV1); + +#[derive(Debug, Error)] +pub enum ZwpLockedPointerV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ZwpPointerConstraintsV1Error(#[from] ZwpPointerConstraintsV1Error), +} +efrom!(ZwpLockedPointerV1Error, ClientError); +efrom!(ZwpLockedPointerV1Error, MsgParserError); diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 76206549..8c55c595 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -20,7 +20,10 @@ use { TF_180, TF_270, TF_90, TF_FLIPPED, TF_FLIPPED_180, TF_FLIPPED_270, TF_FLIPPED_90, TF_NORMAL, }, - wl_seat::{wl_pointer::PendingScroll, Dnd, NodeSeatState, SeatId, WlSeatGlobal}, + wl_seat::{ + wl_pointer::PendingScroll, zwp_pointer_constraints_v1::SeatConstraint, Dnd, + NodeSeatState, SeatId, WlSeatGlobal, + }, wl_surface::{ cursor::CursorSurface, wl_subsurface::WlSubsurface, wp_fractional_scale_v1::WpFractionalScaleV1, wp_viewport::WpViewport, @@ -253,6 +256,7 @@ pub struct WlSurface { viewporter: CloneCell>>, output: CloneCell>, fractional_scale: CloneCell>>, + pub constraints: SmallMap, 1>, } impl Debug for WlSurface { @@ -386,6 +390,7 @@ impl WlSurface { viewporter: Default::default(), output: CloneCell::new(client.state.dummy_output.get().unwrap()), fractional_scale: Default::default(), + constraints: Default::default(), } } @@ -595,6 +600,7 @@ impl WlSurface { self.toplevel.set(None); self.client.remove_obj(self)?; self.idle_inhibitors.clear(); + self.constraints.take(); Ok(()) } @@ -957,6 +963,9 @@ impl WlSurface { } pub fn destroy_node(&self) { + for (_, constraint) in &self.constraints { + constraint.deactivate(); + } for (_, inhibitor) in self.idle_inhibitors.lock().drain() { inhibitor.deactivate(); } @@ -1020,6 +1029,7 @@ impl Object for WlSurface { self.presentation_feedback.borrow_mut().clear(); self.viewporter.take(); self.fractional_scale.take(); + self.constraints.clear(); } } diff --git a/src/rect.rs b/src/rect.rs index c582d619..a3031c79 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -118,6 +118,22 @@ impl Rect { self.raw.x1 <= x && self.raw.y1 <= y && self.raw.x2 > x && self.raw.y2 > y } + pub fn dist_squared(&self, x: i32, y: i32) -> i32 { + let mut dx = 0; + if self.raw.x1 > x { + dx = self.raw.x1 - x; + } else if self.raw.x2 < x { + dx = x - self.raw.x1; + } + let mut dy = 0; + if self.raw.y1 > y { + dy = self.raw.y1 - y; + } else if self.raw.y2 < y { + dy = y - self.raw.y1; + } + dx * dx + dy * dy + } + #[allow(dead_code)] pub fn contains_rect(&self, rect: &Self) -> bool { self.raw.x1 <= rect.raw.x1 diff --git a/src/rect/region.rs b/src/rect/region.rs index 16212691..98c46257 100644 --- a/src/rect/region.rs +++ b/src/rect/region.rs @@ -78,6 +78,18 @@ impl Region { pub fn extents(&self) -> Rect { self.extents } + + pub fn contains(&self, x: i32, y: i32) -> bool { + if !self.extents.contains(x, y) { + return false; + } + for r in self.deref() { + if r.contains(x, y) { + return true; + } + } + false + } } impl Deref for Region { diff --git a/src/utils/smallmap.rs b/src/utils/smallmap.rs index bc9e98b1..b93b58d3 100644 --- a/src/utils/smallmap.rs +++ b/src/utils/smallmap.rs @@ -42,6 +42,10 @@ impl SmallMap { } } + pub fn contains(&self, k: &K) -> bool { + unsafe { self.m.get().deref().contains(k) } + } + pub fn len(&self) -> usize { unsafe { self.m.get().deref().len() } } @@ -158,6 +162,15 @@ impl SmallMapMut { self.m.len() } + pub fn contains(&self, k: &K) -> bool { + for (ek, _) in &self.m { + if ek == k { + return true; + } + } + false + } + pub fn insert(&mut self, k: K, v: V) -> Option { for (ek, ev) in &mut self.m { if ek == &k { diff --git a/wire/zwp_confined_pointer_v1.txt b/wire/zwp_confined_pointer_v1.txt new file mode 100644 index 00000000..1ab2ad5f --- /dev/null +++ b/wire/zwp_confined_pointer_v1.txt @@ -0,0 +1,19 @@ +# requests + +msg destroy = 0 { + +} + +msg set_region = 1 { + region: id(wl_region), +} + +# events + +msg confined = 0 { + +} + +msg unconfined = 1 { + +} diff --git a/wire/zwp_locked_pointer_v1.txt b/wire/zwp_locked_pointer_v1.txt new file mode 100644 index 00000000..afdbcc41 --- /dev/null +++ b/wire/zwp_locked_pointer_v1.txt @@ -0,0 +1,21 @@ +# requests + +msg destroy = 0 { +} + +msg set_cursor_position_hint = 1 { + surface_x: fixed, + surface_y: fixed, +} + +msg set_region = 2 { + region: id(wl_region), +} + +# events + +msg locked = 0 { +} + +msg unlocked = 1 { +} diff --git a/wire/zwp_pointer_constraints_v1.txt b/wire/zwp_pointer_constraints_v1.txt new file mode 100644 index 00000000..a70f3752 --- /dev/null +++ b/wire/zwp_pointer_constraints_v1.txt @@ -0,0 +1,21 @@ +# requests + +msg destroy = 0 { + +} + +msg lock_pointer = 1 { + id: id(zwp_locked_pointer_v1), + surface: id(wl_surface), + pointer: id(wl_pointer), + region: id(wl_region), + lifetime: u32, +} + +msg confine_pointer = 2 { + id: id(zwp_confined_pointer_v1), + surface: id(wl_surface), + pointer: id(wl_pointer), + region: id(wl_region), + lifetime: u32, +}