Skip to content

Commit

Permalink
Add pointer constraint functionality.
Browse files Browse the repository at this point in the history
  • Loading branch information
vanfanel committed Feb 12, 2024
1 parent 8a00921 commit 3f72f1e
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 6 deletions.
10 changes: 10 additions & 0 deletions cage.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <wlr/types/wlr_idle_notify_v1.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_output_management_v1.h>
#include <wlr/types/wlr_pointer_constraints_v1.h>
#include <wlr/types/wlr_presentation_time.h>
#include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_scene.h>
Expand Down Expand Up @@ -501,6 +502,15 @@ main(int argc, char *argv[])
goto end;
}

server.pointer_constraints = wlr_pointer_constraints_v1_create(server.wl_display);
if (!server.pointer_constraints) {
wlr_log(WLR_ERROR, "Unable to create the pointer constraints");
ret = 1;
goto end;
}
server.pointer_constraint.notify = handle_pointer_constraint;
wl_signal_add(&server.pointer_constraints->events.new_constraint, &server.pointer_constraint);

#if CAGE_HAS_XWAYLAND
struct wlr_xcursor_manager *xcursor_manager = NULL;
struct wlr_xwayland *xwayland = wlr_xwayland_create(server.wl_display, compositor, true);
Expand Down
3 changes: 3 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ wlroots = dependency('wlroots', version: '>= 0.17.0', fallback: ['wlroots
wayland_protos = dependency('wayland-protocols', version: '>=1.14')
wayland_server = dependency('wayland-server')
xkbcommon = dependency('xkbcommon')
pixman = dependency('pixman-1')
math = cc.find_library('m')

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

server_protocols = [
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
[wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'],
]

server_protos_headers = []
Expand Down Expand Up @@ -153,6 +155,7 @@ executable(
wlroots,
xkbcommon,
math,
pixman,
],
install: true,
)
Expand Down
206 changes: 200 additions & 6 deletions seat.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <wlr/types/wlr_virtual_pointer_v1.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/util/log.h>
#include <wlr/util/region.h>
#if CAGE_HAS_XWAYLAND
#include <wlr/xwayland.h>
#endif
Expand Down Expand Up @@ -608,7 +609,7 @@ handle_cursor_button(struct wl_listener *listener, void *data)
}

static void
process_cursor_motion(struct cg_seat *seat, uint32_t time_msec, double dx, double dy, double dx_unaccel,
process_cursor_motion(struct cg_seat *seat, uint32_t time_msec, double *dx, double *dy, double dx_unaccel,
double dy_unaccel)
{
double sx, sy;
Expand All @@ -625,10 +626,30 @@ process_cursor_motion(struct cg_seat *seat, uint32_t time_msec, double dx, doubl

if (dx != 0 || dy != 0) {
wlr_relative_pointer_manager_v1_send_relative_motion(seat->server->relative_pointer_manager, wlr_seat,
(uint64_t) time_msec * 1000, dx, dy, dx_unaccel,
(uint64_t) time_msec * 1000, *dx, *dy, dx_unaccel,
dy_unaccel);
}

/* Apply pointer constraints. It has to be done before calling wlr_cursor_move() */
if (seat->active_constraint) {
double sx_confined, sy_confined;
/* wlr_region_confine() checks if the current position is within a confinement region.
* If it is, returns true and takes the next position after a move and ajusts it
* to the confinement region.
* It it's not, it returns false and does nothing.
* seat->confine: confinement region.
* sx/sy:current position.
* sx+dx/sy+dy: next position after a move.
* sx_confined/sy_confined: next position, but confined to the region.
*/
if (!wlr_region_confine(&seat->confine, sx, sy, sx + *dx, sy + *dy, &sx_confined, &sy_confined)) {
return;
}

*dx = sx_confined - sx;
*dy = sy_confined - sy;
}

struct cg_drag_icon *drag_icon;
wl_list_for_each (drag_icon, &seat->drag_icons, link) {
drag_icon_update_position(drag_icon);
Expand All @@ -637,6 +658,173 @@ process_cursor_motion(struct cg_seat *seat, uint32_t time_msec, double dx, doubl
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

static void
warp_to_constraint_cursor_hint(struct cg_seat *seat)
{
struct wlr_pointer_constraint_v1 *constraint = seat->active_constraint;

if (constraint->current.committed & WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT) {
double sx = constraint->current.cursor_hint.x;
double sy = constraint->current.cursor_hint.y;

struct cg_view *view = view_from_wlr_surface(constraint->surface);
if (!view) {
return;
}

wlr_cursor_warp(seat->cursor, NULL, sx, sy);

// Warp the pointer as well, so that on the next pointer rebase we don't
// send an unexpected synthetic motion event to clients.
wlr_seat_pointer_warp(constraint->seat, sx, sy);
}
}

static void
check_constraint_region(struct cg_seat *seat)
{
struct wlr_pointer_constraint_v1 *constraint = seat->active_constraint;
pixman_region32_t *region = &constraint->region;
if (seat->active_confine_requires_warp) {
seat->active_confine_requires_warp = false;

double sx = seat->cursor->x;
double sy = seat->cursor->y;

if (!pixman_region32_contains_point(region, floor(sx), floor(sy), NULL)) {
int nboxes;
pixman_box32_t *boxes = pixman_region32_rectangles(region, &nboxes);
if (nboxes > 0) {
double sx = (boxes[0].x1 + boxes[0].x2) / 2.;
double sy = (boxes[0].y1 + boxes[0].y2) / 2.;

wlr_cursor_warp_closest(seat->cursor, NULL, sx, sy);
}
}
}

// A locked pointer will result in an empty region, thus disallowing all movement
if (constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED) {
pixman_region32_copy(&seat->confine, region);
} else {
pixman_region32_clear(&seat->confine);
}
}

static void
handle_constraint_commit(struct wl_listener *listener, void *data)
{
struct cg_seat *seat = wl_container_of(listener, seat, constraint_commit);
struct wlr_pointer_constraint_v1 *constraint = seat->active_constraint;
assert(constraint->surface == data);

check_constraint_region(seat);
}

/* This is where active_constraint is assigned. Also remember that everything
* regarding cursor is in cg_seat, because in Cage there's only a cursor per seat. */
void
cg_cursor_constrain(struct cg_seat *seat, struct wlr_pointer_constraint_v1 *constraint)
{

if (seat->active_constraint == constraint) {
return;
}

wl_list_remove(&seat->constraint_commit.link);
if (seat->active_constraint) {
if (constraint == NULL) {
warp_to_constraint_cursor_hint(seat);
}
wlr_pointer_constraint_v1_send_deactivated(seat->active_constraint);
}

seat->active_constraint = constraint;

if (constraint == NULL) {
wl_list_init(&seat->constraint_commit.link);
return;
}

seat->active_confine_requires_warp = true;

// FIXME: Big hack, stolen from wlr_pointer_constraints_v1.c:121.
// This is necessary because the focus may be set before the surface
// has finished committing, which means that warping won't work properly,
// since this code will be run *after* the focus has been set.
// That is why we duplicate the code here.
if (pixman_region32_not_empty(&constraint->current.region)) {
pixman_region32_intersect(&constraint->region, &constraint->surface->input_region,
&constraint->current.region);
} else {
pixman_region32_copy(&constraint->region, &constraint->surface->input_region);
}

check_constraint_region(seat);

wlr_pointer_constraint_v1_send_activated(constraint);

seat->constraint_commit.notify = handle_constraint_commit;
wl_signal_add(&constraint->surface->events.commit, &seat->constraint_commit);
}

static void
handle_pointer_constraint_set_region(struct wl_listener *listener, void *data)
{
struct cg_pointer_constraint *cg_constraint = wl_container_of(listener, cg_constraint, set_region);
struct cg_seat *seat = cg_constraint->seat;

seat->active_confine_requires_warp = true;
}

void
handle_constraint_destroy(struct wl_listener *listener, void *data)
{
struct cg_pointer_constraint *cg_constraint = wl_container_of(listener, cg_constraint, destroy);
struct wlr_pointer_constraint_v1 *constraint = data;
struct cg_seat *seat = cg_constraint->seat;

wl_list_remove(&cg_constraint->set_region.link);
wl_list_remove(&cg_constraint->destroy.link);

if (seat->active_constraint == constraint) {
warp_to_constraint_cursor_hint(seat);

if (seat->constraint_commit.link.next != NULL) {
wl_list_remove(&seat->constraint_commit.link);
}
wl_list_init(&seat->constraint_commit.link);
seat->active_constraint = NULL;
}

free(cg_constraint);
}

void
handle_pointer_constraint(struct wl_listener *listener, void *data)
{
struct wlr_pointer_constraint_v1 *constraint = data;

/* Recover cg_seat from a wlr_seat's data field. Every wlr_seat has it's data field
set to the cg_seat containing it, in seat_create(). */
struct cg_seat *seat = constraint->seat->data;

struct cg_pointer_constraint *cg_constraint = calloc(1, sizeof(struct cg_pointer_constraint));
cg_constraint->constraint = constraint;
cg_constraint->seat = seat;

cg_constraint->set_region.notify = handle_pointer_constraint_set_region;
wl_signal_add(&constraint->events.set_region, &cg_constraint->set_region);

cg_constraint->destroy.notify = handle_constraint_destroy;
wl_signal_add(&constraint->events.destroy, &cg_constraint->destroy);

struct wlr_surface *surface = seat->seat->keyboard_state.focused_surface;
if (surface && surface == constraint->surface) {
cg_cursor_constrain(seat, constraint);
}
}

static void
handle_cursor_motion_absolute(struct wl_listener *listener, void *data)
{
Expand All @@ -650,7 +838,7 @@ handle_cursor_motion_absolute(struct wl_listener *listener, void *data)
double dy = ly - seat->cursor->y;

wlr_cursor_warp_absolute(seat->cursor, &event->pointer->base, event->x, event->y);
process_cursor_motion(seat, event->time_msec, dx, dy, dx, dy);
process_cursor_motion(seat, event->time_msec, &dx, &dy, dx, dy);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

Expand All @@ -659,10 +847,12 @@ handle_cursor_motion_relative(struct wl_listener *listener, void *data)
{
struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion_relative);
struct wlr_pointer_motion_event *event = data;
double dx = event->delta_x;
double dy = event->delta_y;

wlr_cursor_move(seat->cursor, &event->pointer->base, event->delta_x, event->delta_y);
process_cursor_motion(seat, event->time_msec, event->delta_x, event->delta_y, event->unaccel_dx,
process_cursor_motion(seat, event->time_msec, &dx, &dy, event->unaccel_dx,
event->unaccel_dy);
wlr_cursor_move(seat->cursor, &event->pointer->base, dx, dy);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}

Expand Down Expand Up @@ -812,6 +1002,7 @@ seat_create(struct cg_server *server, struct wlr_backend *backend)
free(seat);
return NULL;
}
seat->seat->data = seat;
seat->server = server;
seat->destroy.notify = handle_destroy;
wl_signal_add(&seat->seat->events.destroy, &seat->destroy);
Expand Down Expand Up @@ -880,6 +1071,8 @@ seat_create(struct cg_server *server, struct wlr_backend *backend)
seat->start_drag.notify = handle_start_drag;
wl_signal_add(&seat->seat->events.start_drag, &seat->start_drag);

wl_list_init(&seat->constraint_commit.link);

return seat;
}

Expand Down Expand Up @@ -955,7 +1148,8 @@ seat_set_focus(struct cg_seat *seat, struct cg_view *view)
wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, NULL, 0, NULL);
}

process_cursor_motion(seat, -1, 0, 0, 0, 0);
double dx = 0, dy = 0;
process_cursor_motion(seat, -1, &dx, &dy, 0, 0);
}

void
Expand Down
17 changes: 17 additions & 0 deletions seat.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_pointer_constraints_v1.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_xcursor_manager.h>

Expand Down Expand Up @@ -48,6 +49,20 @@ struct cg_seat {
struct wl_listener request_set_cursor;
struct wl_listener request_set_selection;
struct wl_listener request_set_primary_selection;

struct wl_listener constraint_commit;
struct wlr_pointer_constraint_v1 *active_constraint;
pixman_region32_t confine; // invalid if active_constraint == NULL
bool active_confine_requires_warp;
};

struct cg_pointer_constraint {
struct cg_seat *seat;

struct wlr_pointer_constraint_v1 *constraint;

struct wl_listener set_region;
struct wl_listener destroy;
};

struct cg_keyboard_group {
Expand Down Expand Up @@ -93,4 +108,6 @@ struct cg_view *seat_get_focus(struct cg_seat *seat);
void seat_set_focus(struct cg_seat *seat, struct cg_view *view);
void seat_center_cursor(struct cg_seat *seat);

void handle_pointer_constraint(struct wl_listener *listener, void *data);

#endif
3 changes: 3 additions & 0 deletions server.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ struct cg_server {

struct wlr_relative_pointer_manager_v1 *relative_pointer_manager;

struct wlr_pointer_constraints_v1 *pointer_constraints;
struct wl_listener pointer_constraint;

bool xdg_decoration;
bool allow_vt_switch;
bool return_app_code;
Expand Down

0 comments on commit 3f72f1e

Please sign in to comment.