3131#include <wlr/types/wlr_virtual_pointer_v1.h>
3232#include <wlr/types/wlr_xcursor_manager.h>
3333#include <wlr/util/log.h>
34+ #include <wlr/util/region.h>
3435#if CAGE_HAS_XWAYLAND
3536#include <wlr/xwayland.h>
3637#endif
@@ -608,7 +609,7 @@ handle_cursor_button(struct wl_listener *listener, void *data)
608609}
609610
610611static void
611- process_cursor_motion (struct cg_seat * seat , uint32_t time_msec , double dx , double dy , double dx_unaccel ,
612+ process_cursor_motion (struct cg_seat * seat , uint32_t time_msec , double * dx , double * dy , double dx_unaccel ,
612613 double dy_unaccel )
613614{
614615 double sx , sy ;
@@ -625,10 +626,30 @@ process_cursor_motion(struct cg_seat *seat, uint32_t time_msec, double dx, doubl
625626
626627 if (dx != 0 || dy != 0 ) {
627628 wlr_relative_pointer_manager_v1_send_relative_motion (seat -> server -> relative_pointer_manager , wlr_seat ,
628- (uint64_t ) time_msec * 1000 , dx , dy , dx_unaccel ,
629+ (uint64_t ) time_msec * 1000 , * dx , * dy , dx_unaccel ,
629630 dy_unaccel );
630631 }
631632
633+ /* Apply pointer constraints. It has to be done before calling wlr_cursor_move() */
634+ if (seat -> active_constraint ) {
635+ double sx_confined , sy_confined ;
636+ /* wlr_region_confine() checks if the current position is within a confinement region.
637+ * If it is, returns true and takes the next position after a move and ajusts it
638+ * to the confinement region.
639+ * It it's not, it returns false and does nothing.
640+ * seat->confine: confinement region.
641+ * sx/sy:current position.
642+ * sx+dx/sy+dy: next position after a move.
643+ * sx_confined/sy_confined: next position, but confined to the region.
644+ */
645+ if (!wlr_region_confine (& seat -> confine , sx , sy , sx + * dx , sy + * dy , & sx_confined , & sy_confined )) {
646+ return ;
647+ }
648+
649+ * dx = sx_confined - sx ;
650+ * dy = sy_confined - sy ;
651+ }
652+
632653 struct cg_drag_icon * drag_icon ;
633654 wl_list_for_each (drag_icon , & seat -> drag_icons , link ) {
634655 drag_icon_update_position (drag_icon );
@@ -637,6 +658,173 @@ process_cursor_motion(struct cg_seat *seat, uint32_t time_msec, double dx, doubl
637658 wlr_idle_notifier_v1_notify_activity (seat -> server -> idle , seat -> seat );
638659}
639660
661+ static void
662+ warp_to_constraint_cursor_hint (struct cg_seat * seat )
663+ {
664+ struct wlr_pointer_constraint_v1 * constraint = seat -> active_constraint ;
665+
666+ if (constraint -> current .committed & WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT ) {
667+ double sx = constraint -> current .cursor_hint .x ;
668+ double sy = constraint -> current .cursor_hint .y ;
669+
670+ struct cg_view * view = view_from_wlr_surface (constraint -> surface );
671+ if (!view ) {
672+ return ;
673+ }
674+
675+ wlr_cursor_warp (seat -> cursor , NULL , sx , sy );
676+
677+ // Warp the pointer as well, so that on the next pointer rebase we don't
678+ // send an unexpected synthetic motion event to clients.
679+ wlr_seat_pointer_warp (constraint -> seat , sx , sy );
680+ }
681+ }
682+
683+ static void
684+ check_constraint_region (struct cg_seat * seat )
685+ {
686+ struct wlr_pointer_constraint_v1 * constraint = seat -> active_constraint ;
687+ pixman_region32_t * region = & constraint -> region ;
688+ if (seat -> active_confine_requires_warp ) {
689+ seat -> active_confine_requires_warp = false;
690+
691+ double sx = seat -> cursor -> x ;
692+ double sy = seat -> cursor -> y ;
693+
694+ if (!pixman_region32_contains_point (region , floor (sx ), floor (sy ), NULL )) {
695+ int nboxes ;
696+ pixman_box32_t * boxes = pixman_region32_rectangles (region , & nboxes );
697+ if (nboxes > 0 ) {
698+ double sx = (boxes [0 ].x1 + boxes [0 ].x2 ) / 2. ;
699+ double sy = (boxes [0 ].y1 + boxes [0 ].y2 ) / 2. ;
700+
701+ wlr_cursor_warp_closest (seat -> cursor , NULL , sx , sy );
702+ }
703+ }
704+ }
705+
706+ // A locked pointer will result in an empty region, thus disallowing all movement
707+ if (constraint -> type == WLR_POINTER_CONSTRAINT_V1_CONFINED ) {
708+ pixman_region32_copy (& seat -> confine , region );
709+ } else {
710+ pixman_region32_clear (& seat -> confine );
711+ }
712+ }
713+
714+ static void
715+ handle_constraint_commit (struct wl_listener * listener , void * data )
716+ {
717+ struct cg_seat * seat = wl_container_of (listener , seat , constraint_commit );
718+ struct wlr_pointer_constraint_v1 * constraint = seat -> active_constraint ;
719+ assert (constraint -> surface == data );
720+
721+ check_constraint_region (seat );
722+ }
723+
724+ /* This is where active_constraint is assigned. Also remember that everything
725+ * regarding cursor is in cg_seat, because in Cage there's only a cursor per seat. */
726+ void
727+ cg_cursor_constrain (struct cg_seat * seat , struct wlr_pointer_constraint_v1 * constraint )
728+ {
729+
730+ if (seat -> active_constraint == constraint ) {
731+ return ;
732+ }
733+
734+ wl_list_remove (& seat -> constraint_commit .link );
735+ if (seat -> active_constraint ) {
736+ if (constraint == NULL ) {
737+ warp_to_constraint_cursor_hint (seat );
738+ }
739+ wlr_pointer_constraint_v1_send_deactivated (seat -> active_constraint );
740+ }
741+
742+ seat -> active_constraint = constraint ;
743+
744+ if (constraint == NULL ) {
745+ wl_list_init (& seat -> constraint_commit .link );
746+ return ;
747+ }
748+
749+ seat -> active_confine_requires_warp = true;
750+
751+ // FIXME: Big hack, stolen from wlr_pointer_constraints_v1.c:121.
752+ // This is necessary because the focus may be set before the surface
753+ // has finished committing, which means that warping won't work properly,
754+ // since this code will be run *after* the focus has been set.
755+ // That is why we duplicate the code here.
756+ if (pixman_region32_not_empty (& constraint -> current .region )) {
757+ pixman_region32_intersect (& constraint -> region , & constraint -> surface -> input_region ,
758+ & constraint -> current .region );
759+ } else {
760+ pixman_region32_copy (& constraint -> region , & constraint -> surface -> input_region );
761+ }
762+
763+ check_constraint_region (seat );
764+
765+ wlr_pointer_constraint_v1_send_activated (constraint );
766+
767+ seat -> constraint_commit .notify = handle_constraint_commit ;
768+ wl_signal_add (& constraint -> surface -> events .commit , & seat -> constraint_commit );
769+ }
770+
771+ static void
772+ handle_pointer_constraint_set_region (struct wl_listener * listener , void * data )
773+ {
774+ struct cg_pointer_constraint * cg_constraint = wl_container_of (listener , cg_constraint , set_region );
775+ struct cg_seat * seat = cg_constraint -> seat ;
776+
777+ seat -> active_confine_requires_warp = true;
778+ }
779+
780+ void
781+ handle_constraint_destroy (struct wl_listener * listener , void * data )
782+ {
783+ struct cg_pointer_constraint * cg_constraint = wl_container_of (listener , cg_constraint , destroy );
784+ struct wlr_pointer_constraint_v1 * constraint = data ;
785+ struct cg_seat * seat = cg_constraint -> seat ;
786+
787+ wl_list_remove (& cg_constraint -> set_region .link );
788+ wl_list_remove (& cg_constraint -> destroy .link );
789+
790+ if (seat -> active_constraint == constraint ) {
791+ warp_to_constraint_cursor_hint (seat );
792+
793+ if (seat -> constraint_commit .link .next != NULL ) {
794+ wl_list_remove (& seat -> constraint_commit .link );
795+ }
796+ wl_list_init (& seat -> constraint_commit .link );
797+ seat -> active_constraint = NULL ;
798+ }
799+
800+ free (cg_constraint );
801+ }
802+
803+ void
804+ handle_pointer_constraint (struct wl_listener * listener , void * data )
805+ {
806+ struct wlr_pointer_constraint_v1 * constraint = data ;
807+
808+ /* Recover cg_seat from a wlr_seat's data field. Every wlr_seat has it's data field
809+ set to the cg_seat containing it, in seat_create(). */
810+ struct cg_seat * seat = constraint -> seat -> data ;
811+
812+ struct cg_pointer_constraint * cg_constraint = calloc (1 , sizeof (struct cg_pointer_constraint ));
813+ cg_constraint -> constraint = constraint ;
814+ cg_constraint -> seat = seat ;
815+
816+ cg_constraint -> set_region .notify = handle_pointer_constraint_set_region ;
817+ wl_signal_add (& constraint -> events .set_region , & cg_constraint -> set_region );
818+
819+ cg_constraint -> destroy .notify = handle_constraint_destroy ;
820+ wl_signal_add (& constraint -> events .destroy , & cg_constraint -> destroy );
821+
822+ struct wlr_surface * surface = seat -> seat -> keyboard_state .focused_surface ;
823+ if (surface && surface == constraint -> surface ) {
824+ cg_cursor_constrain (seat , constraint );
825+ }
826+ }
827+
640828static void
641829handle_cursor_motion_absolute (struct wl_listener * listener , void * data )
642830{
@@ -650,7 +838,7 @@ handle_cursor_motion_absolute(struct wl_listener *listener, void *data)
650838 double dy = ly - seat -> cursor -> y ;
651839
652840 wlr_cursor_warp_absolute (seat -> cursor , & event -> pointer -> base , event -> x , event -> y );
653- process_cursor_motion (seat , event -> time_msec , dx , dy , dx , dy );
841+ process_cursor_motion (seat , event -> time_msec , & dx , & dy , dx , dy );
654842 wlr_idle_notifier_v1_notify_activity (seat -> server -> idle , seat -> seat );
655843}
656844
@@ -659,10 +847,12 @@ handle_cursor_motion_relative(struct wl_listener *listener, void *data)
659847{
660848 struct cg_seat * seat = wl_container_of (listener , seat , cursor_motion_relative );
661849 struct wlr_pointer_motion_event * event = data ;
850+ double dx = event -> delta_x ;
851+ double dy = event -> delta_y ;
662852
663- wlr_cursor_move (seat -> cursor , & event -> pointer -> base , event -> delta_x , event -> delta_y );
664- process_cursor_motion (seat , event -> time_msec , event -> delta_x , event -> delta_y , event -> unaccel_dx ,
853+ process_cursor_motion (seat , event -> time_msec , & dx , & dy , event -> unaccel_dx ,
665854 event -> unaccel_dy );
855+ wlr_cursor_move (seat -> cursor , & event -> pointer -> base , dx , dy );
666856 wlr_idle_notifier_v1_notify_activity (seat -> server -> idle , seat -> seat );
667857}
668858
@@ -812,6 +1002,7 @@ seat_create(struct cg_server *server, struct wlr_backend *backend)
8121002 free (seat );
8131003 return NULL ;
8141004 }
1005+ seat -> seat -> data = seat ;
8151006 seat -> server = server ;
8161007 seat -> destroy .notify = handle_destroy ;
8171008 wl_signal_add (& seat -> seat -> events .destroy , & seat -> destroy );
@@ -880,6 +1071,8 @@ seat_create(struct cg_server *server, struct wlr_backend *backend)
8801071 seat -> start_drag .notify = handle_start_drag ;
8811072 wl_signal_add (& seat -> seat -> events .start_drag , & seat -> start_drag );
8821073
1074+ wl_list_init (& seat -> constraint_commit .link );
1075+
8831076 return seat ;
8841077}
8851078
@@ -955,7 +1148,8 @@ seat_set_focus(struct cg_seat *seat, struct cg_view *view)
9551148 wlr_seat_keyboard_notify_enter (wlr_seat , view -> wlr_surface , NULL , 0 , NULL );
9561149 }
9571150
958- process_cursor_motion (seat , -1 , 0 , 0 , 0 , 0 );
1151+ double dx = 0 , dy = 0 ;
1152+ process_cursor_motion (seat , -1 , & dx , & dy , 0 , 0 );
9591153}
9601154
9611155void
0 commit comments