Skip to content

Commit 3f72f1e

Browse files
committed
Add pointer constraint functionality.
1 parent 8a00921 commit 3f72f1e

File tree

5 files changed

+233
-6
lines changed

5 files changed

+233
-6
lines changed

cage.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <wlr/types/wlr_idle_notify_v1.h>
3030
#include <wlr/types/wlr_output_layout.h>
3131
#include <wlr/types/wlr_output_management_v1.h>
32+
#include <wlr/types/wlr_pointer_constraints_v1.h>
3233
#include <wlr/types/wlr_presentation_time.h>
3334
#include <wlr/types/wlr_relative_pointer_v1.h>
3435
#include <wlr/types/wlr_scene.h>
@@ -501,6 +502,15 @@ main(int argc, char *argv[])
501502
goto end;
502503
}
503504

505+
server.pointer_constraints = wlr_pointer_constraints_v1_create(server.wl_display);
506+
if (!server.pointer_constraints) {
507+
wlr_log(WLR_ERROR, "Unable to create the pointer constraints");
508+
ret = 1;
509+
goto end;
510+
}
511+
server.pointer_constraint.notify = handle_pointer_constraint;
512+
wl_signal_add(&server.pointer_constraints->events.new_constraint, &server.pointer_constraint);
513+
504514
#if CAGE_HAS_XWAYLAND
505515
struct wlr_xcursor_manager *xcursor_manager = NULL;
506516
struct wlr_xwayland *xwayland = wlr_xwayland_create(server.wl_display, compositor, true);

meson.build

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ wlroots = dependency('wlroots', version: '>= 0.17.0', fallback: ['wlroots
3939
wayland_protos = dependency('wayland-protocols', version: '>=1.14')
4040
wayland_server = dependency('wayland-server')
4141
xkbcommon = dependency('xkbcommon')
42+
pixman = dependency('pixman-1')
4243
math = cc.find_library('m')
4344

4445
wl_protocol_dir = wayland_protos.get_variable('pkgdatadir')
@@ -51,6 +52,7 @@ wayland_scanner_server = generator(
5152

5253
server_protocols = [
5354
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
55+
[wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'],
5456
]
5557

5658
server_protos_headers = []
@@ -153,6 +155,7 @@ executable(
153155
wlroots,
154156
xkbcommon,
155157
math,
158+
pixman,
156159
],
157160
install: true,
158161
)

seat.c

Lines changed: 200 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
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

610611
static 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+
640828
static void
641829
handle_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

9611155
void

seat.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <wlr/types/wlr_cursor.h>
66
#include <wlr/types/wlr_data_device.h>
77
#include <wlr/types/wlr_input_device.h>
8+
#include <wlr/types/wlr_pointer_constraints_v1.h>
89
#include <wlr/types/wlr_seat.h>
910
#include <wlr/types/wlr_xcursor_manager.h>
1011

@@ -48,6 +49,20 @@ struct cg_seat {
4849
struct wl_listener request_set_cursor;
4950
struct wl_listener request_set_selection;
5051
struct wl_listener request_set_primary_selection;
52+
53+
struct wl_listener constraint_commit;
54+
struct wlr_pointer_constraint_v1 *active_constraint;
55+
pixman_region32_t confine; // invalid if active_constraint == NULL
56+
bool active_confine_requires_warp;
57+
};
58+
59+
struct cg_pointer_constraint {
60+
struct cg_seat *seat;
61+
62+
struct wlr_pointer_constraint_v1 *constraint;
63+
64+
struct wl_listener set_region;
65+
struct wl_listener destroy;
5166
};
5267

5368
struct cg_keyboard_group {
@@ -93,4 +108,6 @@ struct cg_view *seat_get_focus(struct cg_seat *seat);
93108
void seat_set_focus(struct cg_seat *seat, struct cg_view *view);
94109
void seat_center_cursor(struct cg_seat *seat);
95110

111+
void handle_pointer_constraint(struct wl_listener *listener, void *data);
112+
96113
#endif

server.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ struct cg_server {
5757

5858
struct wlr_relative_pointer_manager_v1 *relative_pointer_manager;
5959

60+
struct wlr_pointer_constraints_v1 *pointer_constraints;
61+
struct wl_listener pointer_constraint;
62+
6063
bool xdg_decoration;
6164
bool allow_vt_switch;
6265
bool return_app_code;

0 commit comments

Comments
 (0)