diff --git a/include/sway/input/cursor.h b/include/sway/input/cursor.h index c87d8332..072a56ca 100644 --- a/include/sway/input/cursor.h +++ b/include/sway/input/cursor.h @@ -2,6 +2,7 @@ #define _SWAY_INPUT_CURSOR_H #include #include +#include #include #include "sway/input/seat.h" @@ -26,6 +27,9 @@ struct sway_cursor { struct wlr_surface *image_surface; int hotspot_x, hotspot_y; + struct wlr_pointer_constraint_v1 *active_constraint; + pixman_region32_t confine; // invalid if active_constraint == NULL + struct wl_listener motion; struct wl_listener motion_absolute; struct wl_listener button; @@ -43,6 +47,8 @@ struct sway_cursor { struct wl_listener request_set_cursor; + struct wl_listener constraint_commit; + struct wl_event_source *hide_source; bool hidden; @@ -75,7 +81,8 @@ int cursor_get_timeout(struct sway_cursor *cursor); * Like cursor_rebase, but also allows focus to change when the cursor enters a * new container. */ -void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec); +void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, + struct sway_node *node, struct wlr_surface *surface, double sx, double sy); void dispatch_cursor_button(struct sway_cursor *cursor, struct wlr_input_device *device, uint32_t time_msec, uint32_t button, @@ -97,6 +104,10 @@ void cursor_warp_to_container(struct sway_cursor *cursor, void cursor_warp_to_workspace(struct sway_cursor *cursor, struct sway_workspace *workspace); + +void sway_cursor_constrain(struct sway_cursor *cursor, + struct wlr_pointer_constraint_v1 *constraint); + uint32_t get_mouse_bindsym(const char *name, char **error); uint32_t get_mouse_bindcode(const char *name, char **error); diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h index ef85b67f..1c9354df 100644 --- a/include/sway/input/seat.h +++ b/include/sway/input/seat.h @@ -84,6 +84,12 @@ struct sway_seat { struct wl_list link; // input_manager::seats }; +struct sway_pointer_constraint { + struct wlr_pointer_constraint_v1 *constraint; + + struct wl_listener destroy; +}; + struct sway_seat *seat_create(const char *seat_name); void seat_destroy(struct sway_seat *seat); diff --git a/include/sway/server.h b/include/sway/server.h index 9242ceb7..fa2c6557 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -61,6 +61,9 @@ struct sway_server { struct wlr_presentation *presentation; + struct wlr_pointer_constraints_v1 *pointer_constraints; + struct wl_listener pointer_constraint; + size_t txn_timeout_ms; list_t *transactions; list_t *dirty_nodes; @@ -86,5 +89,6 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data); #endif void handle_server_decoration(struct wl_listener *listener, void *data); void handle_xdg_decoration(struct wl_listener *listener, void *data); +void handle_pointer_constraint(struct wl_listener *listener, void *data); #endif diff --git a/protocols/meson.build b/protocols/meson.build index a031245c..c438b078 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -39,6 +39,7 @@ server_protocols = [ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], ['wlr-layer-shell-unstable-v1.xml'], ['wlr-input-inhibitor-unstable-v1.xml'], ] diff --git a/sway/input/cursor.c b/sway/input/cursor.c index c84d6c40..c87efc2b 100644 --- a/sway/input/cursor.c +++ b/sway/input/cursor.c @@ -1,4 +1,5 @@ #define _POSIX_C_SOURCE 200809L +#include #include #include #include @@ -6,10 +7,11 @@ #include #include #include -#include -#include -#include #include +#include +#include +#include +#include #include "list.h" #include "log.h" #include "config.h" @@ -327,8 +329,9 @@ void cursor_unhide(struct sway_cursor *cursor) { cursor_rebase(cursor); } -void cursor_send_pointer_motion(struct sway_cursor *cursor, - uint32_t time_msec) { +void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, + struct sway_node *node, struct wlr_surface *surface, + double sx, double sy) { if (time_msec == 0) { time_msec = get_current_time_msec(); } @@ -343,12 +346,7 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, return; } - struct wlr_surface *surface = NULL; - double sx, sy; - struct sway_node *prev_node = cursor->previous.node; - struct sway_node *node = node_at_coords(seat, - cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); // Update the stored previous position cursor->previous.x = cursor->cursor->x; @@ -401,9 +399,62 @@ static void handle_cursor_motion(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, motion); struct wlr_event_pointer_motion *event = data; cursor_handle_activity(cursor); - wlr_cursor_move(cursor->cursor, event->device, - event->delta_x, event->delta_y); - cursor_send_pointer_motion(cursor, event->time_msec); + + double dx = event->delta_x; + double dy = event->delta_y; + + struct wlr_surface *surface = NULL; + double sx, sy; + struct sway_node *node = node_at_coords(cursor->seat, + cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); + + if (cursor->active_constraint) { + if (cursor->active_constraint->surface != surface) { + return; + } + + double sx_confined, sy_confined; + if (!wlr_region_confine(&cursor->confine, sx, sy, sx + dx, sy + dy, + &sx_confined, &sy_confined)) { + return; + } + + dx = sx_confined - sx; + dy = sy_confined - sy; + } + + wlr_cursor_move(cursor->cursor, event->device, dx, dy); + cursor_send_pointer_motion(cursor, event->time_msec, node, surface, + sx + dx, sy + dy); + transaction_commit_dirty(); +} + +static void cursor_motion_absolute(struct sway_cursor *cursor, + uint32_t time_msec, struct wlr_input_device *dev, + double x, double y) { + cursor_handle_activity(cursor); + + double lx, ly; + wlr_cursor_absolute_to_layout_coords(cursor->cursor, dev, + x, y, &lx, &ly); + + struct wlr_surface *surface = NULL; + double sx, sy; + struct sway_node *node = node_at_coords(cursor->seat, + lx, ly, &surface, &sx, &sy); + + if (cursor->active_constraint) { + if (cursor->active_constraint->surface != surface) { + return; + } + if (!pixman_region32_contains_point(&cursor->confine, + floor(sx), floor(sy), NULL)) { + return; + } + } + + wlr_cursor_warp_closest(cursor->cursor, dev, lx, ly); + cursor_send_pointer_motion(cursor, time_msec, node, surface, sx, sy); transaction_commit_dirty(); } @@ -412,10 +463,9 @@ static void handle_cursor_motion_absolute( struct sway_cursor *cursor = wl_container_of(listener, cursor, motion_absolute); struct wlr_event_pointer_motion_absolute *event = data; - cursor_handle_activity(cursor); - wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, event->y); - cursor_send_pointer_motion(cursor, event->time_msec); - transaction_commit_dirty(); + + cursor_motion_absolute(cursor, event->time_msec, event->device, + event->x, event->y); } /** @@ -961,9 +1011,7 @@ static void handle_tool_axis(struct wl_listener *listener, void *data) { apply_mapping_from_region(event->device, ic->mapped_from_region, &x, &y); } - wlr_cursor_warp_absolute(cursor->cursor, event->device, x, y); - cursor_send_pointer_motion(cursor, event->time_msec); - transaction_commit_dirty(); + cursor_motion_absolute(cursor, event->time_msec, event->device, x, y); } static void handle_tool_tip(struct wl_listener *listener, void *data) { @@ -1001,6 +1049,49 @@ static void handle_tool_button(struct wl_listener *listener, void *data) { transaction_commit_dirty(); } +static void check_constraint_region(struct sway_cursor *cursor) { + struct wlr_pointer_constraint_v1 *constraint = cursor->active_constraint; + pixman_region32_t *region = &constraint->region; + struct sway_view *view = view_from_wlr_surface(constraint->surface); + if (view) { + struct sway_container *con = view->container; + + double sx = cursor->cursor->x - con->content_x + view->geometry.x; + double sy = cursor->cursor->y - con->content_y + view->geometry.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(cursor->cursor, NULL, + sx + con->content_x - view->geometry.x, + sy + con->content_y - view->geometry.y); + } + } + } + + // A locked pointer will result in an empty region, thus disallowing all movement + if (constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED) { + pixman_region32_copy(&cursor->confine, region); + } else { + pixman_region32_clear(&cursor->confine); + } +} + +static void handle_constraint_commit(struct wl_listener *listener, + void *data) { + struct sway_cursor *cursor = + wl_container_of(listener, cursor, constraint_commit); + struct wlr_pointer_constraint_v1 *constraint = cursor->active_constraint; + assert(constraint->surface == data); + + check_constraint_region(cursor); +} + static void handle_request_set_cursor(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = @@ -1162,6 +1253,8 @@ struct sway_cursor *sway_cursor_create(struct sway_seat *seat) { &cursor->request_set_cursor); cursor->request_set_cursor.notify = handle_request_set_cursor; + wl_list_init(&cursor->constraint_commit.link); + cursor->cursor = wlr_cursor; return cursor; @@ -1284,3 +1377,107 @@ const char *get_mouse_button_name(uint32_t button) { } return name; } + +static void warp_to_constraint_cursor_hint(struct sway_cursor *cursor) { + struct wlr_pointer_constraint_v1 *constraint = cursor->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 sway_view *view = view_from_wlr_surface(constraint->surface); + struct sway_container *con = view->container; + + double lx = sx + con->content_x - view->geometry.x; + double ly = sy + con->content_y - view->geometry.y; + + wlr_cursor_warp(cursor->cursor, NULL, lx, ly); + } +} + +void handle_constraint_destroy(struct wl_listener *listener, void *data) { + struct sway_pointer_constraint *sway_constraint = + wl_container_of(listener, sway_constraint, destroy); + struct wlr_pointer_constraint_v1 *constraint = data; + struct sway_seat *seat = constraint->seat->data; + struct sway_cursor *cursor = seat->cursor; + + wl_list_remove(&sway_constraint->destroy.link); + + if (cursor->active_constraint == constraint) { + warp_to_constraint_cursor_hint(cursor); + + if (cursor->constraint_commit.link.next != NULL) { + wl_list_remove(&cursor->constraint_commit.link); + } + wl_list_init(&cursor->constraint_commit.link); + cursor->active_constraint = NULL; + } + + free(sway_constraint); +} + +void handle_pointer_constraint(struct wl_listener *listener, void *data) { + struct wlr_pointer_constraint_v1 *constraint = data; + struct sway_seat *seat = constraint->seat->data; + + struct sway_pointer_constraint *sway_constraint = + calloc(1, sizeof(struct sway_pointer_constraint)); + sway_constraint->constraint = constraint; + + sway_constraint->destroy.notify = handle_constraint_destroy; + wl_signal_add(&constraint->events.destroy, &sway_constraint->destroy); + + struct sway_node *focus = seat_get_focus(seat); + if (focus && focus->type == N_CONTAINER && focus->sway_container->view) { + struct wlr_surface *surface = focus->sway_container->view->surface; + if (surface == constraint->surface) { + sway_cursor_constrain(seat->cursor, constraint); + } + } +} + +void sway_cursor_constrain(struct sway_cursor *cursor, + struct wlr_pointer_constraint_v1 *constraint) { + if (cursor->active_constraint == constraint) { + return; + } + + wl_list_remove(&cursor->constraint_commit.link); + if (cursor->active_constraint) { + if (constraint == NULL) { + warp_to_constraint_cursor_hint(cursor); + } + wlr_pointer_constraint_v1_send_deactivated( + cursor->active_constraint); + } + + cursor->active_constraint = constraint; + + if (constraint == NULL) { + wl_list_init(&cursor->constraint_commit.link); + return; + } + + // 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(cursor); + + wlr_pointer_constraint_v1_send_activated(constraint); + + cursor->constraint_commit.notify = handle_constraint_commit; + wl_signal_add(&constraint->surface->events.commit, + &cursor->constraint_commit); +} diff --git a/sway/input/seat.c b/sway/input/seat.c index 8cb1d8e9..d159da22 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -105,6 +105,11 @@ static void seat_send_focus(struct sway_node *node, struct sway_seat *seat) { wlr_seat_keyboard_notify_enter( seat->wlr_seat, view->surface, NULL, 0, NULL); } + + struct wlr_pointer_constraint_v1 *constraint = + wlr_pointer_constraints_v1_constraint_for_surface( + server.pointer_constraints, view->surface, seat->wlr_seat); + sway_cursor_constrain(seat->cursor, constraint); } } @@ -684,6 +689,7 @@ static void send_unfocus(struct sway_container *con, void *data) { // Unfocus the container and any children (eg. when leaving `focus parent`) static void seat_send_unfocus(struct sway_node *node, struct sway_seat *seat) { + sway_cursor_constrain(seat->cursor, NULL); wlr_seat_keyboard_clear_focus(seat->wlr_seat); if (node->type == N_WORKSPACE) { workspace_for_each_container(node->sway_workspace, send_unfocus, seat); diff --git a/sway/input/seatop_down.c b/sway/input/seatop_down.c index c491e9c2..c2256c9a 100644 --- a/sway/input/seatop_down.c +++ b/sway/input/seatop_down.c @@ -26,14 +26,19 @@ static void handle_motion(struct sway_seat *seat, uint32_t time_msec) { static void handle_finish(struct sway_seat *seat) { struct seatop_down_event *e = seat->seatop_data; + struct sway_cursor *cursor = seat->cursor; // Set the cursor's previous coords to the x/y at the start of the // operation, so the container change will be detected if using // focus_follows_mouse and the cursor moved off the original container // during the operation. - seat->cursor->previous.x = e->ref_lx; - seat->cursor->previous.y = e->ref_ly; + cursor->previous.x = e->ref_lx; + cursor->previous.y = e->ref_ly; if (e->moved) { - cursor_send_pointer_motion(seat->cursor, 0); + struct wlr_surface *surface = NULL; + double sx, sy; + struct sway_node *node = node_at_coords(seat, + cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); + cursor_send_pointer_motion(cursor, 0, node, surface, sx, sy); } } diff --git a/sway/server.c b/sway/server.c index 43dc3900..82262585 100644 --- a/sway/server.c +++ b/sway/server.c @@ -10,11 +10,12 @@ #include #include #include -#include #include +#include #include #include #include +#include #include #include #include @@ -105,6 +106,12 @@ bool server_init(struct sway_server *server) { server->xdg_decoration.notify = handle_xdg_decoration; wl_list_init(&server->xdg_decorations); + server->pointer_constraints = + wlr_pointer_constraints_v1_create(server->wl_display); + server->pointer_constraint.notify = handle_pointer_constraint; + wl_signal_add(&server->pointer_constraints->events.new_constraint, + &server->pointer_constraint); + server->presentation = wlr_presentation_create(server->wl_display, server->backend);