1use std::{
6 collections::{hash_map, HashMap},
7 ops,
8 sync::{
9 atomic::{AtomicBool, Ordering},
10 Mutex,
11 },
12};
13
14use wayland_protocols::wp::pointer_constraints::zv1::server::{
15 zwp_confined_pointer_v1::{self, ZwpConfinedPointerV1},
16 zwp_locked_pointer_v1::{self, ZwpLockedPointerV1},
17 zwp_pointer_constraints_v1::{self, Lifetime, ZwpPointerConstraintsV1},
18};
19use wayland_server::{
20 backend::GlobalId, protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle,
21 GlobalDispatch, New, Resource, WEnum,
22};
23
24use super::compositor::{self, RegionAttributes};
25use crate::{
26 input::{pointer::PointerHandle, SeatHandler},
27 utils::{Logical, Point},
28 wayland::seat::PointerUserData,
29};
30
31const VERSION: u32 = 1;
32
33pub trait PointerConstraintsHandler: SeatHandler {
35 fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle<Self>);
39
40 fn cursor_position_hint(
46 &mut self,
47 surface: &WlSurface,
48 pointer: &PointerHandle<Self>,
49 location: Point<f64, Logical>,
50 );
51}
52
53#[derive(Debug)]
55pub struct ConfinedPointer {
56 handle: zwp_confined_pointer_v1::ZwpConfinedPointerV1,
57 region: Option<RegionAttributes>,
58 pending_region: Option<RegionAttributes>,
59 lifetime: WEnum<Lifetime>,
60 active: AtomicBool,
61}
62
63impl ConfinedPointer {
64 pub fn region(&self) -> Option<&RegionAttributes> {
66 self.region.as_ref()
67 }
68}
69
70#[derive(Debug)]
72pub struct LockedPointer {
73 handle: zwp_locked_pointer_v1::ZwpLockedPointerV1,
74 region: Option<RegionAttributes>,
75 pending_region: Option<RegionAttributes>,
76 lifetime: WEnum<Lifetime>,
77 cursor_position_hint: Option<Point<f64, Logical>>,
78 pending_cursor_position_hint: Option<Point<f64, Logical>>,
79 active: AtomicBool,
80}
81
82impl LockedPointer {
83 pub fn region(&self) -> Option<&RegionAttributes> {
85 self.region.as_ref()
86 }
87
88 pub fn cursor_position_hint(&self) -> Option<Point<f64, Logical>> {
90 self.cursor_position_hint
91 }
92}
93
94#[derive(Debug)]
96pub enum PointerConstraint {
97 Confined(ConfinedPointer),
99 Locked(LockedPointer),
101}
102
103#[derive(Debug)]
107pub struct PointerConstraintRef<'a, D: SeatHandler + 'static> {
108 entry: hash_map::OccupiedEntry<'a, PointerHandle<D>, PointerConstraint>,
109}
110
111impl<D: SeatHandler + 'static> ops::Deref for PointerConstraintRef<'_, D> {
112 type Target = PointerConstraint;
113
114 fn deref(&self) -> &Self::Target {
115 self.entry.get()
116 }
117}
118
119impl<D: SeatHandler + 'static> PointerConstraintRef<'_, D> {
120 pub fn activate(&self) {
125 match self.entry.get() {
126 PointerConstraint::Confined(confined) => {
127 confined.handle.confined();
128 confined.active.store(true, Ordering::SeqCst);
129 }
130 PointerConstraint::Locked(locked) => {
131 locked.handle.locked();
132 locked.active.store(true, Ordering::SeqCst);
133 }
134 }
135 }
136
137 pub fn deactivate(self) {
144 match self.entry.get() {
145 PointerConstraint::Confined(confined) => {
146 confined.handle.unconfined();
147 confined.active.store(false, Ordering::SeqCst);
148 }
149 PointerConstraint::Locked(locked) => {
150 locked.handle.unlocked();
151 locked.active.store(false, Ordering::SeqCst);
152 }
153 }
154
155 if self.lifetime() == WEnum::Value(Lifetime::Oneshot) {
156 self.entry.remove_entry();
157 }
158 }
159}
160
161impl PointerConstraint {
162 pub fn is_active(&self) -> bool {
164 match self {
165 PointerConstraint::Confined(confined) => &confined.active,
166 PointerConstraint::Locked(locked) => &locked.active,
167 }
168 .load(Ordering::SeqCst)
169 }
170
171 pub fn region(&self) -> Option<&RegionAttributes> {
173 match self {
174 PointerConstraint::Confined(confined) => confined.region(),
175 PointerConstraint::Locked(locked) => locked.region(),
176 }
177 }
178
179 fn lifetime(&self) -> WEnum<Lifetime> {
180 match self {
181 PointerConstraint::Confined(confined) => confined.lifetime,
182 PointerConstraint::Locked(locked) => locked.lifetime,
183 }
184 }
185
186 fn commit(&mut self) -> Option<Point<f64, Logical>> {
188 match self {
189 Self::Confined(confined) => {
190 confined.region.clone_from(&confined.pending_region);
191 None
192 }
193 Self::Locked(locked) => {
194 locked.region.clone_from(&locked.pending_region);
195 locked.pending_cursor_position_hint.take().inspect(|hint| {
196 locked.cursor_position_hint = Some(*hint);
197 })
198 }
199 }
200 }
201}
202
203#[derive(Debug)]
205pub struct PointerConstraintsState {
206 global: GlobalId,
207}
208
209impl PointerConstraintsState {
210 pub fn new<D>(display: &DisplayHandle) -> Self
212 where
213 D: GlobalDispatch<ZwpPointerConstraintsV1, ()>,
214 D: Dispatch<ZwpPointerConstraintsV1, ()>,
215 D: Dispatch<ZwpConfinedPointerV1, PointerConstraintUserData<D>>,
216 D: Dispatch<ZwpLockedPointerV1, PointerConstraintUserData<D>>,
217 D: SeatHandler,
218 D: 'static,
219 {
220 let global = display.create_global::<D, ZwpPointerConstraintsV1, _>(VERSION, ());
221
222 Self { global }
223 }
224
225 pub fn global(&self) -> GlobalId {
227 self.global.clone()
228 }
229}
230
231#[doc(hidden)]
232#[derive(Debug)]
233pub struct PointerConstraintUserData<D: SeatHandler> {
234 surface: WlSurface,
235 pointer: Option<PointerHandle<D>>,
236}
237
238struct PointerConstraintData<D: SeatHandler + 'static> {
239 constraints: HashMap<PointerHandle<D>, PointerConstraint>,
240}
241
242pub fn with_pointer_constraint<
245 D: SeatHandler + 'static,
246 T,
247 F: FnOnce(Option<PointerConstraintRef<'_, D>>) -> T,
248>(
249 surface: &WlSurface,
250 pointer: &PointerHandle<D>,
251 f: F,
252) -> T {
253 with_constraint_data::<D, _, _>(surface, |data| {
254 let constraint = data.and_then(|data| match data.constraints.entry(pointer.clone()) {
255 hash_map::Entry::Occupied(entry) => Some(PointerConstraintRef { entry }),
256 hash_map::Entry::Vacant(_) => None,
257 });
258 f(constraint)
259 })
260}
261
262fn commit_hook<D: SeatHandler + PointerConstraintsHandler + 'static>(
263 state: &mut D,
264 _dh: &DisplayHandle,
265 surface: &WlSurface,
266) {
267 let position_hints = with_constraint_data::<D, _, _>(surface, |data| {
274 let data = data.unwrap();
275 data.constraints
276 .iter_mut()
277 .filter_map(|(pointer, constraint)| constraint.commit().map(|hint| (pointer.clone(), hint)))
278 .collect::<Vec<_>>()
279 });
280
281 for (pointer, hint) in position_hints {
282 state.cursor_position_hint(surface, &pointer, hint);
283 }
284}
285
286fn with_constraint_data<
288 D: SeatHandler + 'static,
289 T,
290 F: FnOnce(Option<&mut PointerConstraintData<D>>) -> T,
291>(
292 surface: &WlSurface,
293 f: F,
294) -> T {
295 compositor::with_states(surface, |states| {
296 let data = states.data_map.get::<Mutex<PointerConstraintData<D>>>();
297 if let Some(data) = data {
298 f(Some(&mut data.lock().unwrap()))
299 } else {
300 f(None)
301 }
302 })
303}
304
305fn add_constraint<D: SeatHandler + PointerConstraintsHandler + 'static>(
307 pointer_constraints: &ZwpPointerConstraintsV1,
308 surface: &WlSurface,
309 pointer: &PointerHandle<D>,
310 constraint: PointerConstraint,
311) {
312 let mut added = false;
313 compositor::with_states(surface, |states| {
314 added = states.data_map.insert_if_missing_threadsafe(|| {
315 Mutex::new(PointerConstraintData::<D> {
316 constraints: HashMap::new(),
317 })
318 });
319 let data = states.data_map.get::<Mutex<PointerConstraintData<D>>>().unwrap();
320 let mut data = data.lock().unwrap();
321
322 if data.constraints.contains_key(pointer) {
323 pointer_constraints.post_error(
324 zwp_pointer_constraints_v1::Error::AlreadyConstrained,
325 "pointer constrait already exists for surface and seat",
326 );
327 } else {
328 data.constraints.insert(pointer.clone(), constraint);
329 }
330 });
331
332 if added {
333 compositor::add_post_commit_hook(surface, commit_hook::<D>);
334 }
335}
336
337fn remove_constraint<D: SeatHandler + 'static>(surface: &WlSurface, pointer: &PointerHandle<D>) {
338 with_constraint_data::<D, _, _>(surface, |data| {
339 if let Some(data) = data {
340 data.constraints.remove(pointer);
341 }
342 });
343}
344
345impl<D> Dispatch<ZwpPointerConstraintsV1, (), D> for PointerConstraintsState
346where
347 D: Dispatch<ZwpPointerConstraintsV1, ()>,
348 D: Dispatch<ZwpConfinedPointerV1, PointerConstraintUserData<D>>,
349 D: Dispatch<ZwpLockedPointerV1, PointerConstraintUserData<D>>,
350 D: SeatHandler,
351 D: PointerConstraintsHandler,
352 D: 'static,
353{
354 fn request(
355 state: &mut D,
356 _client: &wayland_server::Client,
357 pointer_constraints: &ZwpPointerConstraintsV1,
358 request: zwp_pointer_constraints_v1::Request,
359 _data: &(),
360 _dh: &DisplayHandle,
361 data_init: &mut wayland_server::DataInit<'_, D>,
362 ) {
363 match request {
364 zwp_pointer_constraints_v1::Request::LockPointer {
365 id,
366 surface,
367 pointer,
368 region,
369 lifetime,
370 } => {
371 let region = region.as_ref().map(compositor::get_region_attributes);
372 let pointer = pointer.data::<PointerUserData<D>>().unwrap().handle.clone();
373 let handle = data_init.init(
374 id,
375 PointerConstraintUserData {
376 surface: surface.clone(),
377 pointer: pointer.clone(),
378 },
379 );
380 if let Some(pointer) = pointer {
381 add_constraint(
382 pointer_constraints,
383 &surface,
384 &pointer,
385 PointerConstraint::Locked(LockedPointer {
386 handle,
387 region: region.clone(),
388 pending_region: region,
389 lifetime,
390 cursor_position_hint: None,
391 pending_cursor_position_hint: None,
392 active: AtomicBool::new(false),
393 }),
394 );
395 state.new_constraint(&surface, &pointer);
396 }
397 }
398 zwp_pointer_constraints_v1::Request::ConfinePointer {
399 id,
400 surface,
401 pointer,
402 region,
403 lifetime,
404 } => {
405 let region = region.as_ref().map(compositor::get_region_attributes);
406 let pointer = pointer.data::<PointerUserData<D>>().unwrap().handle.clone();
407 let handle = data_init.init(
408 id,
409 PointerConstraintUserData {
410 surface: surface.clone(),
411 pointer: pointer.clone(),
412 },
413 );
414 if let Some(pointer) = pointer {
415 add_constraint(
416 pointer_constraints,
417 &surface,
418 &pointer,
419 PointerConstraint::Confined(ConfinedPointer {
420 handle,
421 region: region.clone(),
422 pending_region: region,
423 lifetime,
424 active: AtomicBool::new(false),
425 }),
426 );
427 state.new_constraint(&surface, &pointer);
428 }
429 }
430 zwp_pointer_constraints_v1::Request::Destroy => {}
431 _ => unreachable!(),
432 }
433 }
434}
435
436impl<D> GlobalDispatch<ZwpPointerConstraintsV1, (), D> for PointerConstraintsState
437where
438 D: GlobalDispatch<ZwpPointerConstraintsV1, ()>
439 + Dispatch<ZwpPointerConstraintsV1, ()>
440 + SeatHandler
441 + 'static,
442{
443 fn bind(
444 _state: &mut D,
445 _dh: &DisplayHandle,
446 _client: &Client,
447 resource: New<ZwpPointerConstraintsV1>,
448 _global_data: &(),
449 data_init: &mut DataInit<'_, D>,
450 ) {
451 data_init.init(resource, ());
452 }
453}
454
455impl<D> Dispatch<ZwpConfinedPointerV1, PointerConstraintUserData<D>, D> for PointerConstraintsState
456where
457 D: Dispatch<ZwpConfinedPointerV1, PointerConstraintUserData<D>>,
458 D: SeatHandler,
459 D: 'static,
460{
461 fn request(
462 _state: &mut D,
463 _client: &wayland_server::Client,
464 _confined_pointer: &ZwpConfinedPointerV1,
465 request: zwp_confined_pointer_v1::Request,
466 data: &PointerConstraintUserData<D>,
467 _dh: &DisplayHandle,
468 _data_init: &mut wayland_server::DataInit<'_, D>,
469 ) {
470 let Some(pointer) = &data.pointer else {
471 return;
472 };
473
474 match request {
475 zwp_confined_pointer_v1::Request::SetRegion { region } => {
476 with_pointer_constraint(&data.surface, pointer, |constraint| {
477 if let Some(PointerConstraint::Confined(confined)) =
478 constraint.map(|x| x.entry.into_mut())
479 {
480 confined.pending_region = region.as_ref().map(compositor::get_region_attributes);
481 }
482 });
483 }
484 zwp_confined_pointer_v1::Request::Destroy => {}
485 _ => unreachable!(),
486 }
487 }
488
489 fn destroyed(
490 _state: &mut D,
491 _client: wayland_server::backend::ClientId,
492 _resource: &ZwpConfinedPointerV1,
493 data: &PointerConstraintUserData<D>,
494 ) {
495 let Some(pointer) = &data.pointer else {
496 return;
497 };
498
499 remove_constraint(&data.surface, pointer);
500 }
501}
502
503impl<D> Dispatch<ZwpLockedPointerV1, PointerConstraintUserData<D>, D> for PointerConstraintsState
504where
505 D: Dispatch<ZwpLockedPointerV1, PointerConstraintUserData<D>>,
506 D: SeatHandler,
507 D: 'static,
508{
509 fn request(
510 _state: &mut D,
511 _client: &wayland_server::Client,
512 _locked_pointer: &ZwpLockedPointerV1,
513 request: zwp_locked_pointer_v1::Request,
514 data: &PointerConstraintUserData<D>,
515 _dh: &DisplayHandle,
516 _data_init: &mut wayland_server::DataInit<'_, D>,
517 ) {
518 let Some(pointer) = &data.pointer else {
519 return;
520 };
521
522 match request {
523 zwp_locked_pointer_v1::Request::SetCursorPositionHint { surface_x, surface_y } => {
524 with_pointer_constraint(&data.surface, pointer, |constraint| {
525 if let Some(PointerConstraint::Locked(locked)) = constraint.map(|x| x.entry.into_mut()) {
526 locked.pending_cursor_position_hint = Some((surface_x, surface_y).into());
527 }
528 });
529 }
530 zwp_locked_pointer_v1::Request::SetRegion { region } => {
531 with_pointer_constraint(&data.surface, pointer, |constraint| {
532 if let Some(PointerConstraint::Locked(locked)) = constraint.map(|x| x.entry.into_mut()) {
533 locked.pending_region = region.as_ref().map(compositor::get_region_attributes);
534 }
535 });
536 }
537 zwp_locked_pointer_v1::Request::Destroy => {}
538 _ => unreachable!(),
539 }
540 }
541
542 fn destroyed(
543 _state: &mut D,
544 _client: wayland_server::backend::ClientId,
545 _resource: &ZwpLockedPointerV1,
546 data: &PointerConstraintUserData<D>,
547 ) {
548 let Some(pointer) = &data.pointer else {
549 return;
550 };
551
552 remove_constraint(&data.surface, pointer);
553 }
554}
555
556#[allow(missing_docs)]
557#[macro_export]
558macro_rules! delegate_pointer_constraints {
559 ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
560 $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
561 $crate::reexports::wayland_protocols::wp::pointer_constraints::zv1::server::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1: ()
562 ] => $crate::wayland::pointer_constraints::PointerConstraintsState);
563 $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
564 $crate::reexports::wayland_protocols::wp::pointer_constraints::zv1::server::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1: ()
565 ] => $crate::wayland::pointer_constraints::PointerConstraintsState);
566 $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
567 $crate::reexports::wayland_protocols::wp::pointer_constraints::zv1::server::zwp_confined_pointer_v1::ZwpConfinedPointerV1: $crate::wayland::pointer_constraints::PointerConstraintUserData<Self>
568 ] => $crate::wayland::pointer_constraints::PointerConstraintsState);
569 $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
570 $crate::reexports::wayland_protocols::wp::pointer_constraints::zv1::server::zwp_locked_pointer_v1::ZwpLockedPointerV1: $crate::wayland::pointer_constraints::PointerConstraintUserData<Self>
571 ] => $crate::wayland::pointer_constraints::PointerConstraintsState);
572 };
573}