diff --git a/include/wlr/types/wlr_input_device.h b/include/wlr/types/wlr_input_device.h index 7f78cf2b2..585e3bbe0 100644 --- a/include/wlr/types/wlr_input_device.h +++ b/include/wlr/types/wlr_input_device.h @@ -10,6 +10,7 @@ #define WLR_TYPES_WLR_INPUT_DEVICE_H #include +#include enum wlr_button_state { WLR_BUTTON_RELEASED, @@ -45,6 +46,8 @@ struct wlr_input_device { struct wl_signal destroy; } events; + struct wlr_addon_set addons; + void *data; }; diff --git a/include/wlr/types/wlr_input_mapper.h b/include/wlr/types/wlr_input_mapper.h new file mode 100644 index 000000000..027b7f1d0 --- /dev/null +++ b/include/wlr/types/wlr_input_mapper.h @@ -0,0 +1,117 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_INPUT_MAPPER_H +#define WLR_TYPES_WLR_INPUT_MAPPER_H + +#include +#include +#include +#include +#include + +struct wlr_input_device; + +struct wlr_input_constraint { + struct wlr_output *output; // NULL if unset + struct wlr_box box; // Empty if unset + + // private state + + struct wl_listener output_destroy; +}; + +/** +* A helper for converting absolute coordinates received from input devices to layout-local +* coordinates and applyingcoordinate constraints. + * + * The constraints precendence is as follows: + * 1) Device-specific box + * 2) Device-specific output + * 3) Global box + * 4) Global output + * + * If no output layout is attached to the input mapper, all output constraints are ignored. + */ +struct wlr_input_mapper { + struct wlr_output_layout *layout; + struct wlr_input_constraint global; + + struct wl_list mappings; // wlr_input_mapping.link + + struct { + struct wl_signal destroy; + } events; + + // private state + + struct wl_listener layout_destroy; +}; + +struct wlr_input_mapping { + struct wlr_input_constraint constraint; + struct wl_list link; // wlr_input_mapper.mappings + + // private state + + struct wlr_addon addon; // wlr_input_device.addons +}; + +struct wlr_input_mapper *wlr_input_mapper_create(void); + +void wlr_input_mapper_destroy(struct wlr_input_mapper *mapper); + +/** + * Attach an output layout to the input mapper. This detaches the previous output layout, if any. + * + * layout may be NULL. + */ +void wlr_input_mapper_attach_output_layout(struct wlr_input_mapper *mapper, + struct wlr_output_layout *layout); + +/** + * Convert absolute coordinates in 0..1 range to layout-local coordinates. + * + * If device is not NULL, its constraints are used, if any. If no matching constraint is found, the + * absolute coordinates are mapped to the entire layout, unless none is attached, in which case lx + * and ly are set to 0. + */ +void wlr_input_mapper_absolute_to_layout(struct wlr_input_mapper *mapper, + struct wlr_input_device *device, double x, double y, double *lx, double *ly); + +/** + * Get the closest point satisfying constraints from the given point. + * + * If device is not NULL, its constraints are used, if any. If no matching constraint is found, get + * the closest point from the layout, unless it's not attached, in which case the original + * coordinates are returned. + */ +void wlr_input_mapper_closest_point(struct wlr_input_mapper *mapper, + struct wlr_input_device *device, double lx, double ly, double *closest_lx, double *closest_ly); + +/** + * Map device to output. If device is NULL, sets the default output constraint. If output is NULL, + * the output constraint is reset. + * + * When the output is destroyed, the output constraint is reset. + * + * Returns true on success, false on memory allocation error. + */ +bool wlr_input_mapper_map_to_output(struct wlr_input_mapper *mapper, + struct wlr_input_device *device, struct wlr_output *output); + +/** + * Map device to box. If device is NULL, sets the default box constraint. If box is NULL or empty, + * the box constraint is reset. + * + * Returns true on success, false on memory allocation error. + */ +bool wlr_input_mapper_map_to_box(struct wlr_input_mapper *mapper, struct wlr_input_device *device, + const struct wlr_box *box); + +#endif diff --git a/types/meson.build b/types/meson.build index 237d41f53..49074722e 100644 --- a/types/meson.build +++ b/types/meson.build @@ -50,6 +50,7 @@ wlr_files += files( 'wlr_idle_inhibit_v1.c', 'wlr_idle_notify_v1.c', 'wlr_input_device.c', + 'wlr_input_mapper.c', 'wlr_input_method_v2.c', 'wlr_keyboard.c', 'wlr_keyboard_group.c', diff --git a/types/wlr_input_device.c b/types/wlr_input_device.c index ede2e04d8..44dd332a6 100644 --- a/types/wlr_input_device.c +++ b/types/wlr_input_device.c @@ -1,6 +1,7 @@ #include #include +#include #include "interfaces/wlr_input_device.h" void wlr_input_device_init(struct wlr_input_device *dev, @@ -10,6 +11,7 @@ void wlr_input_device_init(struct wlr_input_device *dev, .name = strdup(name), }; + wlr_addon_set_init(&dev->addons); wl_signal_init(&dev->events.destroy); } @@ -20,6 +22,7 @@ void wlr_input_device_finish(struct wlr_input_device *wlr_device) { wl_signal_emit_mutable(&wlr_device->events.destroy, wlr_device); + wlr_addon_set_finish(&wlr_device->addons); wl_list_remove(&wlr_device->events.destroy.listener_list); free(wlr_device->name); diff --git a/types/wlr_input_mapper.c b/types/wlr_input_mapper.c new file mode 100644 index 000000000..c961ed675 --- /dev/null +++ b/types/wlr_input_mapper.c @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void constraint_detach_output(struct wlr_input_constraint *constraint) { + constraint->output = NULL; + wl_list_remove(&constraint->output_destroy.link); + wl_list_init(&constraint->output_destroy.link); +} + +static void constraint_handle_output_destroy(struct wl_listener *listener, void *data) { + struct wlr_input_constraint *constraint = wl_container_of(listener, constraint, output_destroy); + constraint_detach_output(constraint); +} + +static void constraint_init(struct wlr_input_constraint *constraint) { + *constraint = (struct wlr_input_constraint){0}; + + constraint->output_destroy.notify = constraint_handle_output_destroy; + wl_list_init(&constraint->output_destroy.link); +} + +static void constraint_finish(struct wlr_input_constraint *constraint) { + wl_list_remove(&constraint->output_destroy.link); +} + +static void mapping_destroy(struct wlr_input_mapping *mapping) { + constraint_finish(&mapping->constraint); + wlr_addon_finish(&mapping->addon); + wl_list_remove(&mapping->link); + free(mapping); +} + +static void device_addon_destroy(struct wlr_addon *addon) { + struct wlr_input_mapping *mapping = wl_container_of(addon, mapping, addon); + mapping_destroy(mapping); +} + +static const struct wlr_addon_interface device_addon_impl = { + .name = "wlr_input_mapping", + .destroy = device_addon_destroy, +}; + +static struct wlr_input_mapping *mapping_create(struct wlr_input_mapper *mapper, + struct wlr_input_device *device) { + struct wlr_input_mapping *mapping = calloc(1, sizeof(*mapping)); + if (mapping == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return NULL; + } + + constraint_init(&mapping->constraint); + wlr_addon_init(&mapping->addon, &device->addons, mapper, &device_addon_impl); + wl_list_insert(&mapper->mappings, &mapping->link); + + return mapping; +} + +static void detach_output_layout(struct wlr_input_mapper *mapper) { + mapper->layout = NULL; + wl_list_remove(&mapper->layout_destroy.link); + wl_list_init(&mapper->layout_destroy.link); +} + +static void handle_layout_destroy(struct wl_listener *listener, void *data) { + struct wlr_input_mapper *mapper = wl_container_of(listener, mapper, layout_destroy); + detach_output_layout(mapper); +} + +static struct wlr_input_constraint *get_constraint(struct wlr_input_mapper *mapper, + struct wlr_input_device *device, bool create) { + if (device != NULL) { + struct wlr_input_mapping *mapping = NULL; + struct wlr_addon *addon = wlr_addon_find(&device->addons, mapper, &device_addon_impl); + if (addon != NULL) { + mapping = wl_container_of(addon, mapping, addon); + } + + if (mapping != NULL) { + return &mapping->constraint; + } + + if (create) { + mapping = mapping_create(mapper, device); + if (mapping == NULL) { + return NULL; + } + } + } + + return &mapper->global; +} + +static void get_constraint_box(struct wlr_input_mapper *mapper, struct wlr_input_device *device, + struct wlr_box *box) { + *box = (struct wlr_box){0}; + + struct wlr_input_constraint *constraint = get_constraint(mapper, device, false); + if (!wlr_box_empty(&constraint->box)) { + *box = constraint->box; + } else if (mapper->layout != NULL && constraint->output != NULL) { + wlr_output_layout_get_box(mapper->layout, constraint->output, box); + assert(!wlr_box_empty(box)); + } +} + +struct wlr_input_mapper *wlr_input_mapper_create(void) { + struct wlr_input_mapper *mapper = calloc(1, sizeof(*mapper)); + if (mapper == NULL) { + return NULL; + } + + constraint_init(&mapper->global); + + wl_list_init(&mapper->mappings); + wl_signal_init(&mapper->events.destroy); + + mapper->layout_destroy.notify = handle_layout_destroy; + wl_list_init(&mapper->layout_destroy.link); + + return mapper; +} + +void wlr_input_mapper_destroy(struct wlr_input_mapper *mapper) { + if (mapper == NULL) { + return; + } + + wl_signal_emit_mutable(&mapper->events.destroy, NULL); + + struct wlr_input_mapping *mapping, *tmp; + wl_list_for_each_safe(mapping, tmp, &mapper->mappings, link) { + mapping_destroy(mapping); + } + + constraint_finish(&mapper->global); + + wl_list_remove(&mapper->layout_destroy.link); + free(mapper); +} + +void wlr_input_mapper_attach_output_layout(struct wlr_input_mapper *mapper, + struct wlr_output_layout *layout) { + detach_output_layout(mapper); + mapper->layout = layout; + if (layout != NULL) { + wl_signal_add(&layout->events.destroy, &mapper->layout_destroy); + } +} + +void wlr_input_mapper_absolute_to_layout(struct wlr_input_mapper *mapper, + struct wlr_input_device *device, double x, double y, double *lx, double *ly) { + struct wlr_box box; + get_constraint_box(mapper, device, &box); + if (wlr_box_empty(&box) && mapper->layout != NULL) { + wlr_output_layout_get_box(mapper->layout, NULL, &box); + } + + // At this point, if no matching constraint was found and the layout is NULL or empty, box is + // filled with zeroes. + *lx = x * box.width + box.x; + *ly = y * box.height + box.y; +} + +void wlr_input_mapper_closest_point(struct wlr_input_mapper *mapper, + struct wlr_input_device *device, double lx, double ly, + double *closest_lx, double *closest_ly) { + struct wlr_box box; + get_constraint_box(mapper, device, &box); + if (!wlr_box_empty(&box)) { + wlr_box_closest_point(&box, lx, ly, closest_lx, closest_ly); + } else if (mapper->layout != NULL) { + wlr_output_layout_closest_point(mapper->layout, NULL, lx, ly, closest_lx, closest_ly); + } else { + *closest_lx = lx; + *closest_ly = ly; + } +} + +bool wlr_input_mapper_map_to_output(struct wlr_input_mapper *mapper, + struct wlr_input_device *device, struct wlr_output *output) { + struct wlr_input_constraint *constraint = get_constraint(mapper, device, true); + if (constraint == NULL) { + return false; + } + + constraint_detach_output(constraint); + constraint->output = output; + if (output != NULL) { + wl_signal_add(&output->events.destroy, &constraint->output_destroy); + } + + return true; +} + +bool wlr_input_mapper_map_to_box(struct wlr_input_mapper *mapper, struct wlr_input_device *device, + const struct wlr_box *box) { + struct wlr_input_constraint *constraint = get_constraint(mapper, device, true); + if (constraint == NULL) { + return false; + } + + if (!wlr_box_empty(box)) { + constraint->box = *box; + } else { + constraint->box = (struct wlr_box){0}; + } + + return true; +}