diff --git a/include/wlr/types/wlr_cursor_shape_v1.h b/include/wlr/types/wlr_cursor_shape_v1.h new file mode 100644 index 000000000..2fef1e042 --- /dev/null +++ b/include/wlr/types/wlr_cursor_shape_v1.h @@ -0,0 +1,58 @@ +/* + * 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_CURSOR_SHAPE_V1_H +#define WLR_TYPES_WLR_CURSOR_SHAPE_V1_H + +#include +#include "cursor-shape-v1-protocol.h" + +/** + * Manager for the cursor-shape-v1 protocol. + * + * Compositors should listen to the request_set_shape event and handle it in + * the same way as wlr_seat.events.request_set_cursor. + */ +struct wlr_cursor_shape_manager_v1 { + struct wl_global *global; + + struct { + struct wl_signal request_set_shape; // struct wlr_cursor_shape_manager_v1_request_set_shape_event + struct wl_signal destroy; + } events; + + void *data; + + // private state + + struct wl_listener display_destroy; +}; + +enum wlr_cursor_shape_manager_v1_device_type { + WLR_CURSOR_SHAPE_MANAGER_V1_DEVICE_TYPE_POINTER, + WLR_CURSOR_SHAPE_MANAGER_V1_DEVICE_TYPE_TABLET_TOOL, +}; + +struct wlr_cursor_shape_manager_v1_request_set_shape_event { + struct wlr_seat_client *seat_client; + enum wlr_cursor_shape_manager_v1_device_type device_type; + uint32_t serial; + enum wp_cursor_shape_device_v1_shape shape; +}; + +struct wlr_cursor_shape_manager_v1 *wlr_cursor_shape_manager_v1_create( + struct wl_display *display, uint32_t version); + +/** + * Get the name of a cursor shape. + * + * The name can be used to load a cursor from an XCursor theme. + */ +const char *wlr_cursor_shape_v1_name(enum wp_cursor_shape_device_v1_shape shape); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index f4a91b948..3f37f3262 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,5 +1,5 @@ wayland_protos = dependency('wayland-protocols', - version: '>=1.31', + version: '>=1.32', fallback: 'wayland-protocols', default_options: ['tests=false'], ) @@ -19,6 +19,7 @@ protocols = { # Staging upstream protocols 'content-type-v1': wl_protocol_dir / 'staging/content-type/content-type-v1.xml', + 'cursor-shape-v1': wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', 'drm-lease-v1': wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', 'ext-idle-notify-v1': wl_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', 'ext-session-lock-v1': wl_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', diff --git a/types/meson.build b/types/meson.build index 9f305e4ab..de33a8b50 100644 --- a/types/meson.build +++ b/types/meson.build @@ -36,6 +36,7 @@ wlr_files += files( 'buffer/resource.c', 'wlr_compositor.c', 'wlr_content_type_v1.c', + 'wlr_cursor_shape_v1.c', 'wlr_cursor.c', 'wlr_damage_ring.c', 'wlr_data_control_v1.c', diff --git a/types/wlr_cursor_shape_v1.c b/types/wlr_cursor_shape_v1.c new file mode 100644 index 000000000..d9f26f92b --- /dev/null +++ b/types/wlr_cursor_shape_v1.c @@ -0,0 +1,230 @@ +#include +#include +#include +#include +#include "types/wlr_tablet_v2.h" + +#define CURSOR_SHAPE_MANAGER_V1_VERSION 1 + +struct wlr_cursor_shape_device_v1 { + struct wl_resource *resource; + struct wlr_cursor_shape_manager_v1 *manager; + enum wlr_cursor_shape_manager_v1_device_type type; + struct wlr_seat_client *seat_client; + + struct wl_listener seat_client_destroy; +}; + +static const struct wp_cursor_shape_device_v1_interface device_impl; +static const struct wp_cursor_shape_manager_v1_interface manager_impl; + +// Returns NULL if the resource is inert +static struct wlr_cursor_shape_device_v1 *device_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_cursor_shape_device_v1_interface, &device_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_cursor_shape_manager_v1 *manager_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_cursor_shape_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void resource_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void device_handle_set_shape(struct wl_client *client, struct wl_resource *device_resource, + uint32_t serial, uint32_t shape) { + struct wlr_cursor_shape_device_v1 *device = device_from_resource(device_resource); + if (device == NULL) { + return; + } + + if (shape == 0 || shape > WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT) { + wl_resource_post_error(device_resource, WP_CURSOR_SHAPE_DEVICE_V1_ERROR_INVALID_SHAPE, + "Invalid shape %"PRIu32, shape); + return; + } + + struct wlr_cursor_shape_manager_v1_request_set_shape_event event = { + .seat_client = device->seat_client, + .device_type = device->type, + .serial = serial, + .shape = shape, + }; + wl_signal_emit_mutable(&device->manager->events.request_set_shape, &event); +} + +static const struct wp_cursor_shape_device_v1_interface device_impl = { + .destroy = resource_handle_destroy, + .set_shape = device_handle_set_shape, +}; + +static void device_destroy(struct wlr_cursor_shape_device_v1 *device) { + if (device == NULL) { + return; + } + wl_list_remove(&device->seat_client_destroy.link); + wl_resource_set_user_data(device->resource, NULL); // make inert + free(device); +} + +static void device_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_cursor_shape_device_v1 *device = device_from_resource(resource); + device_destroy(device); +} + +static void device_handle_seat_client_destroy(struct wl_listener *listener, void *data) { + struct wlr_cursor_shape_device_v1 *device = wl_container_of(listener, device, seat_client_destroy); + device_destroy(device); +} + +static void create_device(struct wl_resource *manager_resource, uint32_t id, + struct wlr_seat_client *seat_client, + enum wlr_cursor_shape_manager_v1_device_type type) { + struct wlr_cursor_shape_manager_v1 *manager = manager_from_resource(manager_resource); + + struct wl_client *client = wl_resource_get_client(manager_resource); + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *device_resource = wl_resource_create(client, + &wp_cursor_shape_device_v1_interface, version, id); + if (device_resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(device_resource, + &device_impl, NULL, device_handle_resource_destroy); + + if (seat_client == NULL) { + return; // leave the resource inert + } + + struct wlr_cursor_shape_device_v1 *device = calloc(1, sizeof(*device)); + if (device == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + device->resource = device_resource; + device->manager = manager; + device->type = type; + device->seat_client = seat_client; + + device->seat_client_destroy.notify = device_handle_seat_client_destroy; + wl_signal_add(&seat_client->events.destroy, &device->seat_client_destroy); + + wl_resource_set_user_data(device_resource, device); +} + +static void manager_handle_get_pointer(struct wl_client *client, struct wl_resource *manager_resource, + uint32_t id, struct wl_resource *pointer_resource) { + struct wlr_seat_client *seat_client = wlr_seat_client_from_pointer_resource(pointer_resource); + create_device(manager_resource, id, seat_client, + WLR_CURSOR_SHAPE_MANAGER_V1_DEVICE_TYPE_POINTER); +} + +static void manager_handle_get_tablet_tool_v2(struct wl_client *client, struct wl_resource *manager_resource, + uint32_t id, struct wl_resource *tablet_tool_resource) { + struct wlr_tablet_tool_client_v2 *tablet_tool_client = tablet_tool_client_from_resource(tablet_tool_resource); + struct wlr_seat_client *seat_client = tablet_tool_client->seat->seat_client; + create_device(manager_resource, id, seat_client, + WLR_CURSOR_SHAPE_MANAGER_V1_DEVICE_TYPE_TABLET_TOOL); +} + +static const struct wp_cursor_shape_manager_v1_interface manager_impl = { + .destroy = resource_handle_destroy, + .get_pointer = manager_handle_get_pointer, + .get_tablet_tool_v2 = manager_handle_get_tablet_tool_v2, +}; + +static void manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_cursor_shape_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_cursor_shape_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_cursor_shape_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + wl_global_destroy(manager->global); + wl_list_remove(&manager->display_destroy.link); + free(manager); +} + +struct wlr_cursor_shape_manager_v1 *wlr_cursor_shape_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= CURSOR_SHAPE_MANAGER_V1_VERSION); + + struct wlr_cursor_shape_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &wp_cursor_shape_manager_v1_interface, version, manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.request_set_shape); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +static const char *const shape_names[] = { + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT] = "default", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU] = "context-menu", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP] = "help", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER] = "pointer", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS] = "progress", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT] = "wait", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL] = "cell", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR] = "crosshair", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT] = "text", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT] = "vertical-text", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS] = "alias", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY] = "copy", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE] = "move", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP] = "no-drop", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED] = "not-allowed", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB] = "grab", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING] = "grabbing", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE] = "e-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE] = "n-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE] = "ne-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE] = "nw-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE] = "s-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE] = "se-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE] = "sw-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE] = "w-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE] = "ew-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE] = "ns-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE] = "nesw-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE] = "nwse-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE] = "col-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE] = "row-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL] = "all-scroll", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN] = "zoom-in", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT] = "zoom-out", +}; + +const char *wlr_cursor_shape_v1_name(enum wp_cursor_shape_device_v1_shape shape) { + assert(shape < sizeof(shape_names) / sizeof(shape_names[0])); + return shape_names[shape]; +}