Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement pointer constraint. #300

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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