diff --git a/examples/meson.build b/examples/meson.build index b5ad6c98..86c0ddbb 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -70,6 +70,10 @@ examples = { 'src': 'gamma-control.c', 'dep': [wayland_client, wayland_cursor, wlr_protos, wlroots], }, + 'pointer-constraints': { + 'src': 'pointer-constraints.c', + 'dep': [wayland_client, wlr_protos, wlroots], + }, 'dmabuf-capture': { 'src': 'dmabuf-capture.c', 'dep': [ diff --git a/examples/pointer-constraints.c b/examples/pointer-constraints.c new file mode 100644 index 00000000..8738d475 --- /dev/null +++ b/examples/pointer-constraints.c @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "xdg-shell-client-protocol.h" +#include "pointer-constraints-unstable-v1-client-protocol.h" + +static int width = 512, height = 512; + +static struct wl_compositor *compositor = NULL; +static struct wl_seat *seat = NULL; +static struct xdg_wm_base *wm_base = NULL; +static struct zwp_pointer_constraints_v1 *pointer_constraints = NULL; + +struct wlr_egl egl; +struct wl_egl_window *egl_window; +struct wlr_egl_surface *egl_surface; +struct zwp_locked_pointer_v1* locked_pointer; +struct zwp_confined_pointer_v1* confined_pointer; + +enum { + REGION_TYPE_NONE, + REGION_TYPE_DISJOINT, + REGION_TYPE_JOINT, + REGION_TYPE_MAX +} region_type = REGION_TYPE_NONE; + +struct wl_region *regions[3]; + +static void draw(void) { + eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context); + + float color[] = {1.0, 1.0, 0.0, 1.0}; + + glViewport(0, 0, width, height); + glClearColor(color[0], color[1], color[2], 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(egl.display, egl_surface); +} + +static void pointer_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial, + uint32_t time, uint32_t button, uint32_t state_w) { + struct wl_surface *surface = data; + + if (button == BTN_LEFT && state_w == WL_POINTER_BUTTON_STATE_PRESSED) { + region_type = (region_type + 1) % REGION_TYPE_MAX; + + if (locked_pointer) { + zwp_locked_pointer_v1_set_region(locked_pointer, regions[region_type]); + } else if (confined_pointer) { + zwp_confined_pointer_v1_set_region(confined_pointer, regions[region_type]); + } + + wl_surface_commit(surface); + } +} + +static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) { + // This space intentionally left blank +} + +static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface) { + // This space intentionally left blank +} + +static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { + // This space intentionally left blank +} + +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) { + // This space intentionally left blank +} + +static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) { + // This space intentionally left blank +} + +static void pointer_handle_axis_source(void *data, + struct wl_pointer *wl_pointer, uint32_t axis_source) { + // This space intentionally left blank +} + +static void pointer_handle_axis_stop(void *data, + struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) { + // This space intentionally left blank +} + +static void pointer_handle_axis_discrete(void *data, + struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { + // This space intentionally left blank +} + +static const struct wl_pointer_listener pointer_listener = { + .enter = pointer_handle_enter, + .leave = pointer_handle_leave, + .motion = pointer_handle_motion, + .button = pointer_handle_button, + .axis = pointer_handle_axis, + .frame = pointer_handle_frame, + .axis_source = pointer_handle_axis_source, + .axis_stop = pointer_handle_axis_stop, + .axis_discrete = pointer_handle_axis_discrete, +}; + +static void xdg_surface_handle_configure(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) { + xdg_surface_ack_configure(xdg_surface, serial); + wl_egl_window_resize(egl_window, width, height, 0, 0); + draw(); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_handle_configure, +}; + +static void xdg_toplevel_handle_configure(void *data, + struct xdg_toplevel *xdg_toplevel, int32_t w, int32_t h, + struct wl_array *states) { + width = w; + height = h; +} + +static void xdg_toplevel_handle_close(void *data, + struct xdg_toplevel *xdg_toplevel) { + exit(EXIT_SUCCESS); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, + .close = xdg_toplevel_handle_close, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + if (strcmp(interface, wl_compositor_interface.name) == 0) { + compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, 1); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + seat = wl_registry_bind(registry, name, &wl_seat_interface, version); + } else if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) { + pointer_constraints = wl_registry_bind(registry, name, + &zwp_pointer_constraints_v1_interface, version); + } +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = NULL, +}; + +int main(int argc, char **argv) { + if (argc != 4) { + goto invalid_args; + } + + bool lock; + if (strcmp(argv[1], "lock") == 0) { + lock = true; + } else if (strcmp(argv[1], "confine") == 0) { + lock = false; + } else { + goto invalid_args; + } + + enum zwp_pointer_constraints_v1_lifetime lifetime; + if (strcmp(argv[2], "oneshot") == 0) { + lifetime = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT; + } else if (strcmp(argv[2], "persistent") == 0) { + lifetime = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT; + } else { + goto invalid_args; + } + + if (strcmp(argv[3], "no-region") == 0) { + region_type = REGION_TYPE_NONE; + } else if (strcmp(argv[3], "disjoint-region") == 0) { + region_type = REGION_TYPE_DISJOINT; + } else if (strcmp(argv[3], "joint-region") == 0) { + region_type = REGION_TYPE_JOINT; + } + + struct wl_display *display = wl_display_connect(NULL); + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + struct wl_region *disjoint_region = wl_compositor_create_region(compositor); + wl_region_add(disjoint_region, 0, 0, 255, 256); + wl_region_add(disjoint_region, 257, 0, 255, 256); + regions[REGION_TYPE_DISJOINT] = disjoint_region; + + struct wl_region *joint_region = wl_compositor_create_region(compositor); + wl_region_add(joint_region, 0, 0, 256, 256); + wl_region_add(joint_region, 256, 0, 256, 256); + wl_region_add(joint_region, 256, 256, 256, 256); + regions[REGION_TYPE_JOINT] = joint_region; + + wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, NULL, + WL_SHM_FORMAT_ARGB8888); + + struct wl_surface *surface = wl_compositor_create_surface(compositor); + struct xdg_surface *xdg_surface = + xdg_wm_base_get_xdg_surface(wm_base, surface); + struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); + + xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL); + xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL); + + struct wl_pointer *pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(pointer, &pointer_listener, surface); + + if (lock) { + locked_pointer = zwp_pointer_constraints_v1_lock_pointer( + pointer_constraints, surface, pointer, regions[region_type], lifetime); + + zwp_locked_pointer_v1_set_cursor_position_hint(locked_pointer, wl_fixed_from_int(128), wl_fixed_from_int(128)); + } else { + confined_pointer = zwp_pointer_constraints_v1_confine_pointer( + pointer_constraints, surface, pointer, regions[region_type], lifetime); + } + + wl_surface_commit(surface); + + egl_window = wl_egl_window_create(surface, width, height); + egl_surface = wlr_egl_create_surface(&egl, egl_window); + + wl_display_roundtrip(display); + + draw(); + + while (wl_display_dispatch(display) != -1) {} + + return EXIT_SUCCESS; + + invalid_args: { + fprintf(stderr, "pointer-constraints " + "\n"); + exit(EXIT_FAILURE); + } +} diff --git a/include/rootston/cursor.h b/include/rootston/cursor.h index 547fe333..b5bb682f 100644 --- a/include/rootston/cursor.h +++ b/include/rootston/cursor.h @@ -1,6 +1,7 @@ #ifndef ROOTSTON_CURSOR_H #define ROOTSTON_CURSOR_H +#include #include "rootston/seat.h" enum roots_cursor_mode { @@ -14,6 +15,9 @@ struct roots_cursor { struct roots_seat *seat; struct wlr_cursor *cursor; + struct wlr_pointer_constraint_v1 *active_constraint; + pixman_region32_t confine; // invalid if active_constraint == NULL + const char *default_xcursor; enum roots_cursor_mode mode; @@ -45,6 +49,10 @@ struct roots_cursor { struct wl_listener tool_button; struct wl_listener request_set_cursor; + + struct wl_listener focus_change; + + struct wl_listener constraint_commit; }; struct roots_cursor *roots_cursor_create(struct roots_seat *seat); @@ -81,9 +89,17 @@ void roots_cursor_handle_tool_tip(struct roots_cursor *cursor, void roots_cursor_handle_request_set_cursor(struct roots_cursor *cursor, struct wlr_seat_pointer_request_set_cursor_event *event); +void roots_cursor_handle_focus_change(struct roots_cursor *cursor, + struct wlr_seat_pointer_focus_change_event *event); + +void roots_cursor_handle_constraint_commit(struct roots_cursor *cursor); + void roots_cursor_update_position(struct roots_cursor *cursor, uint32_t time); void roots_cursor_update_focus(struct roots_cursor *cursor); +void roots_cursor_constrain(struct roots_cursor *cursor, + struct wlr_pointer_constraint_v1 *constraint, double sx, double sy); + #endif diff --git a/include/rootston/desktop.h b/include/rootston/desktop.h index 89d8af4a..dac694d1 100644 --- a/include/rootston/desktop.h +++ b/include/rootston/desktop.h @@ -56,6 +56,7 @@ struct roots_desktop { struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard; struct wlr_screencopy_manager_v1 *screencopy; struct wlr_tablet_manager_v2 *tablet_v2; + struct wlr_pointer_constraints_v1 *pointer_constraints; struct wl_listener new_output; struct wl_listener layout_change; @@ -67,6 +68,8 @@ struct roots_desktop { struct wl_listener input_inhibit_activate; struct wl_listener input_inhibit_deactivate; struct wl_listener virtual_keyboard_new; + struct wl_listener constraint_create; + struct wl_listener constraint_destroy; #ifdef WLR_HAS_XWAYLAND struct wlr_xwayland *xwayland; diff --git a/include/rootston/input.h b/include/rootston/input.h index 2cdb13e6..31810b4d 100644 --- a/include/rootston/input.h +++ b/include/rootston/input.h @@ -16,7 +16,7 @@ struct roots_input { struct wl_listener new_input; - struct wl_list seats; + struct wl_list seats; // roots_seat::link }; struct roots_input *input_create(struct roots_server *server, diff --git a/include/rootston/seat.h b/include/rootston/seat.h index c5e584b6..a3192d26 100644 --- a/include/rootston/seat.h +++ b/include/rootston/seat.h @@ -10,7 +10,7 @@ struct roots_seat { struct roots_input *input; struct wlr_seat *seat; struct roots_cursor *cursor; - struct wl_list link; + struct wl_list link; // roots_input::seats // coordinates of the first touch point if it exists int32_t touch_id; diff --git a/include/wlr/types/wlr_box.h b/include/wlr/types/wlr_box.h index 11a53b56..8e25918b 100644 --- a/include/wlr/types/wlr_box.h +++ b/include/wlr/types/wlr_box.h @@ -9,6 +9,7 @@ #ifndef WLR_TYPES_WLR_BOX_H #define WLR_TYPES_WLR_BOX_H +#include #include #include @@ -40,4 +41,6 @@ void wlr_box_transform(const struct wlr_box *box, void wlr_box_rotated_bounds(const struct wlr_box *box, float rotation, struct wlr_box *dest); +void wlr_box_from_pixman_box32(const pixman_box32_t box, struct wlr_box *dest); + #endif diff --git a/include/wlr/types/wlr_cursor.h b/include/wlr/types/wlr_cursor.h index ba582be9..44ced1f0 100644 --- a/include/wlr/types/wlr_cursor.h +++ b/include/wlr/types/wlr_cursor.h @@ -91,6 +91,18 @@ bool wlr_cursor_warp(struct wlr_cursor *cur, struct wlr_input_device *dev, void wlr_cursor_absolute_to_layout_coords(struct wlr_cursor *cur, struct wlr_input_device *dev, double x, double y, double *lx, double *ly); + +/** + * Warp the cursor to the given x and y coordinates. If the given point is out + * of the layout boundaries or constraints, the closest point will be used. + * If one coordinate is NAN, it will be ignored. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + */ +void wlr_cursor_warp_closest(struct wlr_cursor *cur, + struct wlr_input_device *dev, double x, double y); + /** * Warp the cursor to the given x and y in absolute 0..1 coordinates. If the * given point is out of the layout boundaries or constraints, the closest point diff --git a/include/wlr/types/wlr_pointer_constraints_v1.h b/include/wlr/types/wlr_pointer_constraints_v1.h new file mode 100644 index 00000000..b8015a57 --- /dev/null +++ b/include/wlr/types/wlr_pointer_constraints_v1.h @@ -0,0 +1,88 @@ +#ifndef WLR_TYPES_WLR_POINTER_CONSTRAINTS_V1_H +#define WLR_TYPES_WLR_POINTER_CONSTRAINTS_V1_H + +#include +#include +#include +#include +#include +#include "pointer-constraints-unstable-v1-protocol.h" + +struct wlr_seat; + +enum wlr_pointer_constraint_v1_type { + WLR_POINTER_CONSTRAINT_V1_LOCKED, + WLR_POINTER_CONSTRAINT_V1_CONFINED, +}; + +struct wlr_pointer_constraint_v1_state { + pixman_region32_t *region; + + // only valid for locked_pointer + struct { + double x, y; + bool valid; + } cursor_hint; +}; + +struct wlr_pointer_constraint_v1 { + struct wlr_pointer_constraints_v1 *pointer_constraints; + + struct wl_resource *resource; + struct wlr_surface *surface; + struct wlr_seat *seat; + enum zwp_pointer_constraints_v1_lifetime lifetime; + enum wlr_pointer_constraint_v1_type type; + pixman_region32_t region; + + struct wlr_pointer_constraint_v1_state current, pending; + + struct wl_listener surface_commit; + struct wl_listener surface_destroy; + struct wl_listener seat_destroy; + + struct wl_list link; // wlr_pointer_constraints_v1::constraints + + void *data; +}; + +struct wlr_pointer_constraints_v1 { + struct wl_list wl_resources; // wl_resource_get_link + struct wl_global *wl_global; + + struct { + /** + * Called when a new pointer constraint is created. + * + * data: wlr_pointer_constraint_v1* + */ + struct wl_signal constraint_create; + /** + * Called when a pointer constraint is destroyed. + * + * data: wlr_pointer_constraint_v1* + */ + struct wl_signal constraint_destroy; + } events; + + struct wl_list constraints; // wlr_pointer_constraint_v1::link + + void *data; +}; + +struct wlr_pointer_constraints_v1 *wlr_pointer_constraints_v1_create( + struct wl_display *display); +void wlr_pointer_constraints_v1_destroy( + struct wlr_pointer_constraints_v1 *wlr_pointer_constraints_v1); + +struct wlr_pointer_constraint_v1 *wlr_pointer_constraints_v1_constraint_for_surface( + struct wlr_pointer_constraints_v1 *wlr_pointer_constraints_v1, + struct wlr_surface *surface, struct wlr_seat *seat); + +void wlr_pointer_constraint_v1_send_activated( + struct wlr_pointer_constraint_v1 *constraint); +void wlr_pointer_constraint_v1_send_deactivated( + struct wlr_pointer_constraint_v1 *constraint); + + +#endif diff --git a/include/wlr/types/wlr_region.h b/include/wlr/types/wlr_region.h index 3c4a0532..ec7f73aa 100644 --- a/include/wlr/types/wlr_region.h +++ b/include/wlr/types/wlr_region.h @@ -10,8 +10,7 @@ #define WLR_TYPES_WLR_REGION_H #include - -struct wl_resource; +#include /* * Creates a new region resource with the provided new ID. If `resource_list` is diff --git a/include/wlr/util/region.h b/include/wlr/util/region.h index 32387bfb..4aca07e1 100644 --- a/include/wlr/util/region.h +++ b/include/wlr/util/region.h @@ -16,6 +16,8 @@ #ifndef WLR_UTIL_REGION_H #define WLR_UTIL_REGION_H + +#include #include #include @@ -48,4 +50,7 @@ void wlr_region_expand(pixman_region32_t *dst, pixman_region32_t *src, void wlr_region_rotated_bounds(pixman_region32_t *dst, pixman_region32_t *src, float rotation, int ox, int oy); +bool wlr_region_confine(pixman_region32_t *region, double x1, double y1, double x2, + double y2, double *x2_out, double *y2_out); + #endif diff --git a/protocol/meson.build b/protocol/meson.build index 2d2a73ed..a02ca5f5 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -18,6 +18,7 @@ protocols = [ [wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'], [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], + [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], 'gamma-control.xml', 'gtk-primary-selection.xml', 'idle.xml', @@ -36,6 +37,7 @@ client_protocols = [ [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], [wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'], [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], + [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], 'idle.xml', 'screenshooter.xml', 'wlr-export-dmabuf-unstable-v1.xml', diff --git a/rootston/config.c b/rootston/config.c index 92d90de1..5c168679 100644 --- a/rootston/config.c +++ b/rootston/config.c @@ -506,6 +506,7 @@ struct roots_config *roots_config_create_from_args(int argc, char *argv[]) { add_binding_config(&config->bindings, "Logo+Shift+E", "exit"); add_binding_config(&config->bindings, "Ctrl+q", "close"); add_binding_config(&config->bindings, "Alt+Tab", "next_window"); + add_binding_config(&config->bindings, "Logo+Escape", "break_pointer_constraint"); struct roots_keyboard_config *kc = calloc(1, sizeof(struct roots_keyboard_config)); kc->meta_key = WLR_MODIFIER_LOGO; diff --git a/rootston/cursor.c b/rootston/cursor.c index ee9e5c6b..0aaff132 100644 --- a/rootston/cursor.c +++ b/rootston/cursor.c @@ -1,9 +1,12 @@ #define _XOPEN_SOURCE 700 +#include #include #include +#include #include #include #include +#include #ifdef __linux__ #include #elif __FreeBSD__ @@ -11,6 +14,7 @@ #endif #include "rootston/cursor.h" #include "rootston/desktop.h" +#include "rootston/view.h" #include "rootston/xcursor.h" struct roots_cursor *roots_cursor_create(struct roots_seat *seat) { @@ -303,15 +307,86 @@ static void roots_cursor_press_button(struct roots_cursor *cursor, void roots_cursor_handle_motion(struct roots_cursor *cursor, struct wlr_event_pointer_motion *event) { - wlr_cursor_move(cursor->cursor, event->device, - event->delta_x, event->delta_y); + double dx = event->delta_x; + double dy = event->delta_y; + + if (cursor->active_constraint) { + struct roots_view *view = cursor->pointer_view->view; + assert(view); + + double center_x = view->x + view->width / 2.; + double center_y = view->y + view->height / 2.; + + double lx1 = cursor->cursor->x; + double ly1 = cursor->cursor->y; + + double lx2 = lx1 + dx; + double ly2 = ly1 + dy; + + // Optimization for most common case. + // This also makes sure that we don't encounter + // precision bugs in the most common case. + if (view->rotation == 0.0) { + double sx1 = lx1 - view->x; + double sy1 = ly1 - view->y; + + double sx2 = lx2 - view->x; + double sy2 = ly2 - view->y; + + double sx2_confined, sy2_confined; + if (!wlr_region_confine(&cursor->confine, sx1, sy1, sx2, sy2, &sx2_confined, &sy2_confined)) { + return; + } + + dx = sx2_confined - sx1; + dy = sy2_confined - sy1; + } else { + assert(false); + double c = cos(view->rotation); + double s = sin(view->rotation); + + double sx1 = c * (lx1 - center_x) - s * (ly1 - center_y) + view->width / 2.; + double sy1 = s * (lx1 - center_x) + c * (ly1 - center_y) + view->height / 2.; + + double sx2 = c * (lx2 - center_x) - s * (ly2 - center_y) + view->width / 2.; + double sy2 = s * (lx2 - center_x) + c * (ly2 - center_y) + view->height / 2.; + + double sx2_confined, sy2_confined; + if (!wlr_region_confine(&cursor->confine, sx1, sy1, sx2, sy2, &sx2_confined, &sy2_confined)) { + return; + } + + // avoid NaNs + double fraction = (sx2 - sx1) > (sy2 - sy1) ? + (sx2_confined - sx1) / (sx2 - sx1) : + (sy2_confined - sy1) / (sy2 - sy1); + + dx *= fraction; + dy *= fraction; + } + } + + wlr_cursor_move(cursor->cursor, event->device, dx, dy); roots_cursor_update_position(cursor, event->time_msec); } void roots_cursor_handle_motion_absolute(struct roots_cursor *cursor, struct wlr_event_pointer_motion_absolute *event) { - wlr_cursor_warp_absolute(cursor->cursor, - event->device, event->x, event->y); + double lx, ly; + wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device, event->x, + event->y, &lx, &ly); + + if (cursor->pointer_view) { + struct roots_view *view = cursor->pointer_view->view; + + if (cursor->active_constraint && + !pixman_region32_contains_point(&cursor->confine, + lx - view->x, ly - view->y, NULL)) { + return; + } + } + + wlr_cursor_warp_closest(cursor->cursor, event->device, lx, ly); roots_cursor_update_position(cursor, event->time_msec); } @@ -405,18 +480,34 @@ void roots_cursor_handle_touch_motion(struct roots_cursor *cursor, void roots_cursor_handle_tool_axis(struct roots_cursor *cursor, struct wlr_event_tablet_tool_axis *event) { + double x = NAN, y = NAN; if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X) && (event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) { - wlr_cursor_warp_absolute(cursor->cursor, event->device, - event->x, event->y); - roots_cursor_update_position(cursor, event->time_msec); + x = event->x; + y = event->y; } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X)) { - wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, -1); - roots_cursor_update_position(cursor, event->time_msec); + x = event->x; } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) { - wlr_cursor_warp_absolute(cursor->cursor, event->device, -1, event->y); - roots_cursor_update_position(cursor, event->time_msec); + y = event->y; + } + + double lx, ly; + wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device, + x, y, &lx, &ly); + + + if (cursor->pointer_view) { + struct roots_view *view = cursor->pointer_view->view; + + if (cursor->active_constraint && + !pixman_region32_contains_point(&cursor->confine, + lx - view->x, ly - view->y, NULL)) { + return; + } } + + wlr_cursor_warp_closest(cursor->cursor, event->device, lx, ly); + roots_cursor_update_position(cursor, event->time_msec); } void roots_cursor_handle_tool_tip(struct roots_cursor *cursor, @@ -446,3 +537,102 @@ void roots_cursor_handle_request_set_cursor(struct roots_cursor *cursor, event->hotspot_y); cursor->cursor_client = event->seat_client->client; } + +void roots_cursor_handle_focus_change(struct roots_cursor *cursor, + struct wlr_seat_pointer_focus_change_event *event) { + double sx = event->sx; + double sy = event->sy; + + double lx = cursor->cursor->x; + double ly = cursor->cursor->y; + + wlr_log(WLR_DEBUG, "entered surface %p, lx: %f, ly: %f, sx: %f, sy: %f", + event->new_surface, lx, ly, sx, sy); + + roots_cursor_constrain(cursor, + wlr_pointer_constraints_v1_constraint_for_surface( + cursor->seat->input->server->desktop->pointer_constraints, + event->new_surface, cursor->seat->seat), + sx, sy); +} + +void roots_cursor_handle_constraint_commit(struct roots_cursor *cursor) { + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + + struct roots_view *view; + double sx, sy; + struct wlr_surface *surface = desktop_surface_at(desktop, + cursor->cursor->x, cursor->cursor->y, &sx, &sy, &view); + // This should never happen but views move around right when they're + // created from (0, 0) to their actual coordinates. + if (surface != cursor->active_constraint->surface) { + roots_cursor_update_focus(cursor); + } else { + roots_cursor_constrain(cursor, cursor->active_constraint, sx, sy); + } +} + +void roots_cursor_constrain(struct roots_cursor *cursor, + struct wlr_pointer_constraint_v1 *constraint, double sx, double sy) { + if (cursor->active_constraint != constraint) { + wlr_log(WLR_DEBUG, "roots_cursor_constrain(%p, %p)", cursor, constraint); + wlr_log(WLR_DEBUG, "cursor->active_constraint: %p", cursor->active_constraint); + + if (cursor->active_constraint) { + wlr_pointer_constraint_v1_send_deactivated(cursor->active_constraint); + if (cursor->constraint_commit.link.next) { + wl_list_remove(&cursor->constraint_commit.link); + } + } + + cursor->active_constraint = constraint; + + if (!constraint) { + return; + } + + wlr_pointer_constraint_v1_send_activated(constraint); + wl_signal_add(&constraint->surface->events.commit, + &cursor->constraint_commit); + } else if (constraint == NULL) { + return; + } + + pixman_region32_clear(&cursor->confine); + + pixman_region32_t *region = &constraint->region; + + if (!pixman_region32_contains_point(region, sx, sy, NULL)) { + // Warp into region if possible + int nboxes; + pixman_box32_t *boxes = pixman_region32_rectangles(region, &nboxes); + if (nboxes > 0) { + struct roots_view *view = cursor->pointer_view->view; + + double sx = (boxes[0].x1 + boxes[0].x2) / 2.; + double sy = (boxes[0].y1 + boxes[0].y2) / 2.; + + double lx, ly; + if (view->rotation == 0.0) { + lx = sx + view->x; + ly = sy + view->y; + } else { + double c = cos(view->rotation); + double s = sin(view->rotation); + + double center_x = view->width / 2.; + double center_y = view->height / 2.; + + lx = c * (sx - center_x) - s * (sy - center_y) + center_x + view->x; + ly = s * (sx - center_x) + c * (sy - center_y) + center_y + view->y; + } + + wlr_cursor_warp_closest(cursor->cursor, NULL, lx, ly); + } + } + + // 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); + } +} diff --git a/rootston/desktop.c b/rootston/desktop.c index e120508d..d469c263 100644 --- a/rootston/desktop.c +++ b/rootston/desktop.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -776,6 +777,53 @@ static void input_inhibit_deactivate(struct wl_listener *listener, void *data) { } } +static void handle_constraint_create( + struct wl_listener *listener, + struct wlr_pointer_constraint_v1 *constraint) { + struct roots_seat* seat = constraint->seat->data; + + double sx, sy; + struct wlr_surface *surface = desktop_surface_at(seat->input->server->desktop, + seat->cursor->cursor->x, seat->cursor->cursor->y, &sx, &sy, NULL); + + if (surface == constraint->surface) { + assert(!seat->cursor->active_constraint); + roots_cursor_constrain(seat->cursor, constraint, sx, sy); + } +} + +static void handle_constraint_destroy( + struct wl_listener *listener, + struct wlr_pointer_constraint_v1 *constraint) { + struct roots_seat* seat = constraint->seat->data; + if (seat->cursor->active_constraint == constraint) { + roots_cursor_constrain(seat->cursor, NULL, NAN, NAN); + if (constraint->current.cursor_hint.valid && seat->cursor->pointer_view) { + double sx = constraint->current.cursor_hint.x; + double sy = constraint->current.cursor_hint.y; + + struct roots_view *view = seat->cursor->pointer_view->view; + + double lx, ly; + if (view->rotation == 0.0) { + lx = sx + view->x; + ly = sy + view->y; + } else { + double c = cos(view->rotation); + double s = sin(view->rotation); + + double center_x = view->width / 2.; + double center_y = view->height / 2.; + + lx = c * (sx - center_x) - s * (sy - center_y) + center_x + view->x; + ly = s * (sx - center_x) + c * (sy - center_y) + center_y + view->y; + } + + wlr_cursor_warp(seat->cursor->cursor, NULL, lx, ly); + } + } +} + struct roots_desktop *desktop_create(struct roots_server *server, struct roots_config *config) { wlr_log(WLR_DEBUG, "Initializing roots desktop"); @@ -906,6 +954,14 @@ struct roots_desktop *desktop_create(struct roots_server *server, &desktop->xdg_toplevel_decoration); desktop->xdg_toplevel_decoration.notify = handle_xdg_toplevel_decoration; + desktop->pointer_constraints = wlr_pointer_constraints_v1_create(server->wl_display); + desktop->constraint_destroy.notify = (wl_notify_func_t)handle_constraint_destroy; + wl_signal_add(&desktop->pointer_constraints->events.constraint_destroy, + &desktop->constraint_destroy); + desktop->constraint_create.notify = (wl_notify_func_t)handle_constraint_create; + wl_signal_add(&desktop->pointer_constraints->events.constraint_create, + &desktop->constraint_create); + return desktop; } diff --git a/rootston/keyboard.c b/rootston/keyboard.c index b5a8093b..6ba0bd6d 100644 --- a/rootston/keyboard.c +++ b/rootston/keyboard.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -176,6 +177,13 @@ static void keyboard_binding_execute(struct roots_keyboard *keyboard, decoration->wlr_decoration, mode); } } + } else if (strcmp(command, "break_pointer_constraint") == 0) { + struct wl_list *list = + &keyboard->input->seats; + struct roots_seat *seat; + wl_list_for_each(seat, list, link) { + roots_cursor_constrain(seat->cursor, NULL, NAN, NAN); + } } else { wlr_log(WLR_ERROR, "unknown binding command: %s", command); } diff --git a/rootston/rootston.ini.example b/rootston/rootston.ini.example index bb0efa44..4b75e9c6 100644 --- a/rootston/rootston.ini.example +++ b/rootston/rootston.ini.example @@ -53,9 +53,11 @@ meta-key = Logo # - "close" to close the current view # - "next_window" to cycle through windows # - "alpha" to cycle a window's alpha channel +# - "break_pointer_constraint" to decline and deactivate all pointer constraints [bindings] Logo+Shift+e = exit Logo+q = close Logo+m = maximize +Logo+Escape = break_pointer_constraint Alt+Tab = next_window Ctrl+Shift+a = alpha diff --git a/rootston/seat.c b/rootston/seat.c index 929fe504..466f1429 100644 --- a/rootston/seat.c +++ b/rootston/seat.c @@ -299,6 +299,22 @@ static void handle_request_set_cursor(struct wl_listener *listener, roots_cursor_handle_request_set_cursor(cursor, event); } +static void handle_pointer_focus_change(struct wl_listener *listener, + void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, focus_change); + struct wlr_seat_pointer_focus_change_event *event = data; + roots_cursor_handle_focus_change(cursor, event); +} + +static void handle_constraint_commit(struct wl_listener *listener, + void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, constraint_commit); + assert(cursor->active_constraint->surface == data); + roots_cursor_handle_constraint_commit(cursor); +} + static void seat_reset_device_mappings(struct roots_seat *seat, struct wlr_input_device *device) { struct wlr_cursor *cursor = seat->cursor->cursor; @@ -434,6 +450,13 @@ static void roots_seat_init_cursor(struct roots_seat *seat) { wl_signal_add(&seat->seat->events.request_set_cursor, &seat->cursor->request_set_cursor); seat->cursor->request_set_cursor.notify = handle_request_set_cursor; + + wl_signal_add(&seat->seat->pointer_state.events.focus_change, + &seat->cursor->focus_change); + seat->cursor->focus_change.notify = handle_pointer_focus_change; + + wl_list_init(&seat->cursor->constraint_commit.link); + seat->cursor->constraint_commit.notify = handle_constraint_commit; } static void roots_drag_icon_handle_surface_commit(struct wl_listener *listener, @@ -1188,7 +1211,9 @@ void roots_seat_set_focus(struct roots_seat *seat, struct roots_view *view) { NULL, 0, NULL); } - roots_cursor_update_focus(seat->cursor); + if (seat->cursor) { + roots_cursor_update_focus(seat->cursor); + } } /** @@ -1224,7 +1249,10 @@ void roots_seat_set_focus_layer(struct roots_seat *seat, NULL, 0, NULL); } - roots_cursor_update_focus(seat->cursor); + + if (seat->cursor) { + roots_cursor_update_focus(seat->cursor); + } } void roots_seat_set_exclusive_client(struct roots_seat *seat, diff --git a/types/meson.build b/types/meson.build index 1329060e..038f7de4 100644 --- a/types/meson.build +++ b/types/meson.build @@ -39,6 +39,7 @@ lib_wlr_types = static_library( 'wlr_output_layout.c', 'wlr_output.c', 'wlr_pointer.c', + 'wlr_pointer_constraints_v1.c', 'wlr_primary_selection.c', 'wlr_region.c', 'wlr_screenshooter.c', diff --git a/types/wlr_box.c b/types/wlr_box.c index ada6a733..44a80dc2 100644 --- a/types/wlr_box.c +++ b/types/wlr_box.c @@ -143,3 +143,12 @@ void wlr_box_rotated_bounds(const struct wlr_box *box, float rotation, dest->y = floor(fmin(y1, y2)); dest->height = ceil(fmax(y1, y2) - fmin(y1, y2)); } + +void wlr_box_from_pixman_box32(const pixman_box32_t box, struct wlr_box *dest) { + *dest = (struct wlr_box){ + .x = box.x1, + .y = box.y1, + .width = box.x2 - box.x1, + .height = box.y2 - box.y1, + }; +} diff --git a/types/wlr_cursor.c b/types/wlr_cursor.c index 49f11b20..e97dd840 100644 --- a/types/wlr_cursor.c +++ b/types/wlr_cursor.c @@ -252,7 +252,7 @@ bool wlr_cursor_warp(struct wlr_cursor *cur, struct wlr_input_device *dev, return result; } -static void cursor_warp_closest(struct wlr_cursor *cur, +void wlr_cursor_warp_closest(struct wlr_cursor *cur, struct wlr_input_device *dev, double lx, double ly) { struct wlr_box *mapping = get_mapping(cur, dev); if (mapping) { @@ -286,7 +286,7 @@ void wlr_cursor_warp_absolute(struct wlr_cursor *cur, double lx, ly; wlr_cursor_absolute_to_layout_coords(cur, dev, x, y, &lx, &ly); - cursor_warp_closest(cur, dev, lx, ly); + wlr_cursor_warp_closest(cur, dev, lx, ly); } void wlr_cursor_move(struct wlr_cursor *cur, struct wlr_input_device *dev, @@ -296,7 +296,7 @@ void wlr_cursor_move(struct wlr_cursor *cur, struct wlr_input_device *dev, double lx = !isnan(delta_x) ? cur->x + delta_x : cur->x; double ly = !isnan(delta_y) ? cur->y + delta_y : cur->y; - cursor_warp_closest(cur, dev, lx, ly); + wlr_cursor_warp_closest(cur, dev, lx, ly); } void wlr_cursor_set_image(struct wlr_cursor *cur, const uint8_t *pixels, diff --git a/types/wlr_pointer_constraints_v1.c b/types/wlr_pointer_constraints_v1.c new file mode 100644 index 00000000..2d2750b5 --- /dev/null +++ b/types/wlr_pointer_constraints_v1.c @@ -0,0 +1,362 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util/signal.h" + +static const struct zwp_locked_pointer_v1_interface locked_pointer_impl; +static const struct zwp_confined_pointer_v1_interface confined_pointer_impl; +static const struct zwp_pointer_constraints_v1_interface pointer_constraints_impl; +static void pointer_constraint_destroy(struct wlr_pointer_constraint_v1 *constraint); + +static struct wlr_pointer_constraint_v1 *pointer_constraint_from_resource( + struct wl_resource *resource) { + assert( + wl_resource_instance_of( + resource, &zwp_confined_pointer_v1_interface, + &confined_pointer_impl) || + wl_resource_instance_of( + resource, &zwp_locked_pointer_v1_interface, + &locked_pointer_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_pointer_constraints_v1 *pointer_constraints_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_pointer_constraints_v1_interface, + &pointer_constraints_impl)); + return wl_resource_get_user_data(resource); +} + +static void resource_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void pointer_constraint_destroy(struct wlr_pointer_constraint_v1 *constraint) { + if (constraint == NULL) { + return; + } + + wlr_log(WLR_DEBUG, "destroying constraint %p", constraint); + + wl_resource_set_user_data(constraint->resource, NULL); + wl_list_remove(&constraint->link); + wl_list_remove(&constraint->surface_commit.link); + wl_list_remove(&constraint->surface_destroy.link); + wl_list_remove(&constraint->seat_destroy.link); + + wlr_signal_emit_safe( + &constraint->pointer_constraints->events.constraint_destroy, + constraint); + + if (constraint->current.region) { + pixman_region32_fini(constraint->current.region); + free(constraint->current.region); + } + if (constraint->pending.region && + constraint->current.region != constraint->pending.region) { + pixman_region32_fini(constraint->pending.region); + free(constraint->pending.region); + } + pixman_region32_fini(&constraint->region); + free(constraint); +} + +static void pointer_constraint_destroy_resource(struct wl_resource *resource) { + struct wlr_pointer_constraint_v1 *constraint = + pointer_constraint_from_resource(resource); + + pointer_constraint_destroy(constraint); +} + +static void pointer_constraint_set_region( + struct wlr_pointer_constraint_v1 *constraint, + struct wl_resource *region_resource) { + if (constraint->pending.region && + constraint->current.region != constraint->pending.region) { + pixman_region32_fini(constraint->pending.region); + free(constraint->pending.region); + } + + if (region_resource) { + constraint->pending.region = calloc(1, sizeof(pixman_region32_t)); + pixman_region32_init(constraint->pending.region); + + pixman_region32_t *region = wlr_region_from_resource(region_resource); + pixman_region32_copy(constraint->pending.region, region); + } else { + constraint->pending.region = NULL; + } +} + +static void pointer_constraint_set_region_wrapper(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *region_resource) { + struct wlr_pointer_constraint_v1 *constraint = + pointer_constraint_from_resource(resource); + + pointer_constraint_set_region(constraint, region_resource); +} + +static void pointer_constraint_set_cursor_position_hint(struct wl_client *client, + struct wl_resource *resource, wl_fixed_t x, wl_fixed_t y) { + struct wlr_pointer_constraint_v1 *constraint = + pointer_constraint_from_resource(resource); + + constraint->pending.cursor_hint.x = wl_fixed_to_double(x); + constraint->pending.cursor_hint.y = wl_fixed_to_double(y); + constraint->pending.cursor_hint.valid = true; +} + +static void pointer_constraint_handle_commit( + struct wlr_pointer_constraint_v1 *constraint) { + constraint->current.cursor_hint = constraint->pending.cursor_hint; + + if (constraint->current.region != constraint->pending.region) { + if (constraint->current.region) { + pixman_region32_fini(constraint->current.region); + free(constraint->current.region); + } + + constraint->current.region = constraint->pending.region; + } + + pixman_region32_clear(&constraint->region); + + if (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); + } +} + +static void handle_surface_commit(struct wl_listener *listener, void *data) { + struct wlr_pointer_constraint_v1 *constraint = + wl_container_of(listener, constraint, surface_commit); + + pointer_constraint_handle_commit(constraint); +} + +static void handle_surface_destroy(struct wl_listener *listener, void *data) { + struct wlr_pointer_constraint_v1 *constraint = + wl_container_of(listener, constraint, surface_destroy); + + pointer_constraint_destroy(constraint); +} + +static void handle_seat_destroy(struct wl_listener *listener, void *data) { + struct wlr_pointer_constraint_v1 *constraint = + wl_container_of(listener, constraint, seat_destroy); + + pointer_constraint_destroy(constraint); +} + +static const struct zwp_confined_pointer_v1_interface confined_pointer_impl = { + .destroy = resource_destroy, + .set_region = pointer_constraint_set_region_wrapper, +}; + +static const struct zwp_locked_pointer_v1_interface locked_pointer_impl = { + .destroy = resource_destroy, + .set_region = pointer_constraint_set_region_wrapper, + .set_cursor_position_hint = pointer_constraint_set_cursor_position_hint, +}; + +static void pointer_constraint_create(struct wl_client *client, + struct wl_resource *pointer_constraints_resource, uint32_t id, + struct wl_resource *surface_resource, struct wl_resource *pointer_resource, + struct wl_resource *region_resource, + enum zwp_pointer_constraints_v1_lifetime lifetime, + enum wlr_pointer_constraint_v1_type type) { + struct wlr_pointer_constraints_v1 *pointer_constraints = + pointer_constraints_from_resource(pointer_constraints_resource); + + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + struct wlr_seat *seat = wlr_seat_client_from_pointer_resource(pointer_resource)->seat; + + if (wlr_pointer_constraints_v1_constraint_for_surface(pointer_constraints, + surface, seat)) { + wl_resource_post_error(pointer_constraints_resource, + ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED, + "a pointer constraint with a wl_pointer of the same wl_seat" + " is already on this surface"); + return; + } + + uint32_t version = wl_resource_get_version(pointer_constraints_resource); + + bool locked_pointer = type == WLR_POINTER_CONSTRAINT_V1_LOCKED; + + struct wl_resource *resource = locked_pointer ? + wl_resource_create(client, &zwp_locked_pointer_v1_interface, version, id) : + wl_resource_create(client, &zwp_confined_pointer_v1_interface, version, id); + + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + struct wlr_pointer_constraint_v1 *constraint = calloc(1, sizeof(*constraint)); + if (constraint == NULL) { + wl_client_post_no_memory(client); + return; + } + + constraint->resource = resource; + constraint->surface = surface; + constraint->seat = seat; + constraint->lifetime = lifetime; + constraint->type = type; + constraint->pointer_constraints = pointer_constraints; + + pixman_region32_init(&constraint->region); + + pointer_constraint_set_region(constraint, region_resource); + pointer_constraint_handle_commit(constraint); + + constraint->surface_commit.notify = handle_surface_commit; + wl_signal_add(&surface->events.commit, &constraint->surface_commit); + + constraint->surface_destroy.notify = handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &constraint->surface_destroy); + + constraint->seat_destroy.notify = handle_seat_destroy; + wl_signal_add(&seat->events.destroy, &constraint->seat_destroy); + + wl_resource_set_implementation(constraint->resource, + locked_pointer ? (void*)&locked_pointer_impl : (void*)&confined_pointer_impl, + constraint, pointer_constraint_destroy_resource); + + wlr_log(WLR_DEBUG, "new %s_pointer %p (res %p)", + locked_pointer ? "locked" : "confined", constraint, constraint->resource); + + wl_list_insert(&pointer_constraints->constraints, &constraint->link); + + wlr_signal_emit_safe(&pointer_constraints->events.constraint_create, + constraint); +} + +static void pointer_constraints_lock_pointer(struct wl_client *client, + struct wl_resource *cons_resource, uint32_t id, + struct wl_resource *surface, struct wl_resource *pointer, + struct wl_resource *region, enum zwp_pointer_constraints_v1_lifetime lifetime) { + pointer_constraint_create(client, cons_resource, id, surface, pointer, + region, lifetime, WLR_POINTER_CONSTRAINT_V1_LOCKED); +} + +static void pointer_constraints_confine_pointer(struct wl_client *client, + struct wl_resource *cons_resource, uint32_t id, + struct wl_resource *surface, struct wl_resource *pointer, + struct wl_resource *region, enum zwp_pointer_constraints_v1_lifetime lifetime) { + pointer_constraint_create(client, cons_resource, id, surface, pointer, + region, lifetime, WLR_POINTER_CONSTRAINT_V1_CONFINED); +} + +static const struct zwp_pointer_constraints_v1_interface pointer_constraints_impl = { + .destroy = resource_destroy, + .lock_pointer = pointer_constraints_lock_pointer, + .confine_pointer = pointer_constraints_confine_pointer, +}; + +static void pointer_constraints_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void pointer_constraints_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_pointer_constraints_v1 *pointer_constraints = data; + assert(client && pointer_constraints); + + struct wl_resource *resource = wl_resource_create(client, + &zwp_pointer_constraints_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_list_insert(&pointer_constraints->wl_resources, wl_resource_get_link(resource)); + wl_resource_set_implementation(resource, &pointer_constraints_impl, + pointer_constraints, pointer_constraints_destroy); +} + +struct wlr_pointer_constraints_v1 *wlr_pointer_constraints_v1_create( + struct wl_display *display) { + struct wlr_pointer_constraints_v1 *pointer_constraints = + calloc(1, sizeof(*pointer_constraints)); + + if (!pointer_constraints) { + return NULL; + } + + struct wl_global *wl_global = wl_global_create(display, + &zwp_pointer_constraints_v1_interface, 1, pointer_constraints, + pointer_constraints_bind); + if (!wl_global) { + free(pointer_constraints); + return NULL; + } + pointer_constraints->wl_global = wl_global; + + wl_list_init(&pointer_constraints->wl_resources); + wl_list_init(&pointer_constraints->constraints); + wl_signal_init(&pointer_constraints->events.constraint_create); + wl_signal_init(&pointer_constraints->events.constraint_destroy); + + return pointer_constraints; +} + +void wlr_pointer_constraints_v1_destroy( + struct wlr_pointer_constraints_v1 *pointer_constraints) { + struct wl_resource *resource, *_tmp_res; + wl_resource_for_each_safe(resource, _tmp_res, &pointer_constraints->wl_resources) { + wl_resource_destroy(resource); + } + + struct wlr_pointer_constraint_v1 *constraint, *_tmp_cons; + wl_list_for_each_safe(constraint, _tmp_cons, &pointer_constraints->constraints, link) { + wl_resource_destroy(constraint->resource); + } + + wl_global_destroy(pointer_constraints->wl_global); + free(pointer_constraints); +} + +struct wlr_pointer_constraint_v1 *wlr_pointer_constraints_v1_constraint_for_surface( + struct wlr_pointer_constraints_v1 *pointer_constraints, + struct wlr_surface *surface, struct wlr_seat *seat) { + struct wlr_pointer_constraint_v1 *constraint; + wl_list_for_each(constraint, &pointer_constraints->constraints, link) { + if (constraint->surface == surface && constraint->seat == seat) { + return constraint; + } + } + + return NULL; +} + +// Thankfully zwp_confined_pointer_v1_send_{un,}confined work +// on both locked and confined pointer constraints. +void wlr_pointer_constraint_v1_send_activated( + struct wlr_pointer_constraint_v1 *constraint) { + wlr_log(WLR_DEBUG, "constrained %p", constraint); + zwp_confined_pointer_v1_send_confined(constraint->resource); +} + +void wlr_pointer_constraint_v1_send_deactivated(struct wlr_pointer_constraint_v1 *constraint) { + if (wl_resource_get_user_data(constraint->resource)) { + wlr_log(WLR_DEBUG, "unconstrained %p", constraint); + zwp_confined_pointer_v1_send_unconfined(constraint->resource); + if (constraint->lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT) { + pointer_constraint_destroy(constraint); + } + } +} diff --git a/util/region.c b/util/region.c index 38f84c5e..76df71f3 100644 --- a/util/region.c +++ b/util/region.c @@ -1,5 +1,8 @@ +#include #include +#include #include +#include #include void wlr_region_scale(pixman_region32_t *dst, pixman_region32_t *src, @@ -177,3 +180,71 @@ void wlr_region_rotated_bounds(pixman_region32_t *dst, pixman_region32_t *src, pixman_region32_init_rects(dst, dst_rects, nrects); free(dst_rects); } + +static void region_confine(pixman_region32_t *region, double x1, double y1, double x2, + double y2, double *x2_out, double *y2_out, pixman_box32_t box) { + double x_clamped = fmax(fmin(x2, box.x2 - 1), box.x1); + double y_clamped = fmax(fmin(y2, box.y2 - 1), box.y1); + + // If the target coordinates are above box.{x,y}2 - 1, but less than + // box.{x,y}2, then they are still within the box. + if (floor(x_clamped) == floor(x2) && floor(y_clamped) == floor(y2)) { + *x2_out = x2; + *y2_out = y2; + return; + } + + double dx = x2 - x1; + double dy = y2 - y1; + + // We use fabs to avoid negative zeroes and thus avoid a bug + // with negative infinity. + double delta = fmin(fabs(x_clamped - x1) / fabs(dx), fabs(y_clamped - y1) / fabs(dy)); + + // We clamp it again due to precision errors. + double x = fmax(fmin(delta * dx + x1, box.x2 - 1), box.x1); + double y = fmax(fmin(delta * dy + y1, box.y2 - 1), box.y1); + + // Go one unit past the boundary to find an adjacent box. + int x_ext = floor(x) + (dx == 0 ? 0 : dx > 0 ? 1 : -1); + int y_ext = floor(y) + (dy == 0 ? 0 : dy > 0 ? 1 : -1); + + if (pixman_region32_contains_point(region, x_ext, y_ext, &box)) { + return region_confine(region, x1, y1, x2, y2, x2_out, y2_out, box); + } else if (dx == 0 || dy == 0) { + *x2_out = x; + *y2_out = y; + } else { + bool bordering_x = x == box.x1 || x == box.x2 - 1; + bool bordering_y = y == box.y1 || y == box.y2 - 1; + + if ((bordering_x && bordering_y) || (!bordering_x && !bordering_y)) { + double x2_potential, y2_potential; + double tmp1, tmp2; + region_confine(region, x, y, x, y2, &tmp1, &y2_potential, box); + region_confine(region, x, y, x2, y, &x2_potential, &tmp2, box); + if (fabs(x2_potential - x) > fabs(y2_potential - y)) { + *x2_out = x2_potential; + *y2_out = y; + } else { + *x2_out = x; + *y2_out = y2_potential; + } + } else if (bordering_x) { + return region_confine(region, x, y, x, y2, x2_out, y2_out, box); + } else if (bordering_y) { + return region_confine(region, x, y, x2, y, x2_out, y2_out, box); + } + } +} + +bool wlr_region_confine(pixman_region32_t *region, double x1, double y1, double x2, + double y2, double *x2_out, double *y2_out) { + pixman_box32_t box; + if (pixman_region32_contains_point(region, x1, y1, &box)) { + region_confine(region, x1, y1, x2, y2, x2_out, y2_out, box); + return true; + } else { + return false; + } +}