diff --git a/backend/drm/drm.c b/backend/drm/drm.c index a20442d4..a01ec805 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -1129,7 +1129,6 @@ static bool drm_connector_set_cursor(struct wlr_output *output, conn->cursor_height = buffer->height; } - wlr_output_update_needs_frame(output); return true; } @@ -1159,7 +1158,6 @@ static bool drm_connector_move_cursor(struct wlr_output *output, conn->cursor_x = box.x; conn->cursor_y = box.y; - wlr_output_update_needs_frame(output); return true; } diff --git a/backend/headless/output.c b/backend/headless/output.c index 5aaf1bd8..1f81f0b9 100644 --- a/backend/headless/output.c +++ b/backend/headless/output.c @@ -79,6 +79,15 @@ static bool output_commit(struct wlr_output *wlr_output, return true; } +static bool output_set_cursor(struct wlr_output *wlr_output, + struct wlr_buffer *buffer, int hotspot_x, int hotspot_y) { + return true; +} + +static bool output_move_cursor(struct wlr_output *wlr_output, int x, int y) { + return true; +} + static void output_destroy(struct wlr_output *wlr_output) { struct wlr_headless_output *output = headless_output_from_output(wlr_output); @@ -90,6 +99,8 @@ static void output_destroy(struct wlr_output *wlr_output) { static const struct wlr_output_impl output_impl = { .destroy = output_destroy, .commit = output_commit, + .set_cursor = output_set_cursor, + .move_cursor = output_move_cursor, }; bool wlr_output_is_headless(struct wlr_output *wlr_output) { diff --git a/include/wlr/interfaces/wlr_ext_image_capture_source_v1.h b/include/wlr/interfaces/wlr_ext_image_capture_source_v1.h new file mode 100644 index 00000000..2aa7653f --- /dev/null +++ b/include/wlr/interfaces/wlr_ext_image_capture_source_v1.h @@ -0,0 +1,45 @@ +/* + * 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_INTERFACES_WLR_EXT_IMAGE_CAPTURE_SOURCE_V1_H +#define WLR_INTERFACES_WLR_EXT_IMAGE_CAPTURE_SOURCE_V1_H + +#include +#include + +struct wlr_ext_image_copy_capture_frame_v1; +struct wlr_swapchain; +struct wlr_renderer; +struct wlr_seat; + +struct wlr_ext_image_capture_source_v1_interface { + // TODO: drop with_cursors flag + void (*start)(struct wlr_ext_image_capture_source_v1 *source, bool with_cursors); + void (*stop)(struct wlr_ext_image_capture_source_v1 *source); + void (*schedule_frame)(struct wlr_ext_image_capture_source_v1 *source); + void (*copy_frame)(struct wlr_ext_image_capture_source_v1 *source, + struct wlr_ext_image_copy_capture_frame_v1 *dst_frame, + struct wlr_ext_image_capture_source_v1_frame_event *frame_event); + struct wlr_ext_image_capture_source_v1_cursor *(*get_pointer_cursor)( + struct wlr_ext_image_capture_source_v1 *source, struct wlr_seat *seat); +}; + +void wlr_ext_image_capture_source_v1_init(struct wlr_ext_image_capture_source_v1 *source, + const struct wlr_ext_image_capture_source_v1_interface *impl); +void wlr_ext_image_capture_source_v1_finish(struct wlr_ext_image_capture_source_v1 *source); +bool wlr_ext_image_capture_source_v1_create_resource(struct wlr_ext_image_capture_source_v1 *source, + struct wl_client *client, uint32_t new_id); +void wlr_ext_image_capture_source_v1_set_constraints_from_swapchain( + struct wlr_ext_image_capture_source_v1 *source, + struct wlr_swapchain *swapchain, struct wlr_renderer *renderer); + +void wlr_ext_image_capture_source_v1_cursor_init(struct wlr_ext_image_capture_source_v1_cursor *source_cursor, + const struct wlr_ext_image_capture_source_v1_interface *impl); +void wlr_ext_image_capture_source_v1_cursor_finish(struct wlr_ext_image_capture_source_v1_cursor *source_cursor); + +#endif diff --git a/include/wlr/types/wlr_ext_image_capture_source_v1.h b/include/wlr/types/wlr_ext_image_capture_source_v1.h new file mode 100644 index 00000000..2fd43502 --- /dev/null +++ b/include/wlr/types/wlr_ext_image_capture_source_v1.h @@ -0,0 +1,94 @@ +/* + * 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_EXT_IMAGE_CAPTURE_SOURCE_V1_H +#define WLR_TYPES_WLR_EXT_IMAGE_CAPTURE_SOURCE_V1_H + +#include +#include +#include + +/** + * A screen capture source. + * + * When the size, device or formats change, the constraints_update event is + * emitted. + * + * The device and formats advertised are suitable for copying into a + * struct wlr_buffer. + */ +struct wlr_ext_image_capture_source_v1 { + const struct wlr_ext_image_capture_source_v1_interface *impl; + struct wl_list resources; // wl_resource_get_link() + + uint32_t width, height; + + uint32_t *shm_formats; + size_t shm_formats_len; + + dev_t dmabuf_device; + struct wlr_drm_format_set dmabuf_formats; + + struct { + struct wl_signal constraints_update; + struct wl_signal frame; // struct wlr_ext_image_capture_source_v1_frame_event + struct wl_signal destroy; + } events; +}; + +/** + * Event indicating that the source has produced a new frame. + */ +struct wlr_ext_image_capture_source_v1_frame_event { + const pixman_region32_t *damage; +}; + +/** + * A cursor capture source. + * + * Provides additional cursor-specific functionality on top of + * struct wlr_ext_image_capture_source_v1. + */ +struct wlr_ext_image_capture_source_v1_cursor { + struct wlr_ext_image_capture_source_v1 base; + + bool entered; + int32_t x, y; + struct { + int32_t x, y; + } hotspot; + + struct { + struct wl_signal update; + } events; +}; + +/** + * Interface exposing one screen capture source per output. + */ +struct wlr_ext_output_image_capture_source_manager_v1 { + struct wl_global *global; + + // private state + + struct wl_listener display_destroy; +}; + +/** + * Obtain a struct wlr_ext_image_capture_source_v1 from an ext_image_capture_source_v1 + * resource. + * + * Asserts that the resource has the correct type. Returns NULL if the resource + * is inert. + */ +struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_from_resource(struct wl_resource *resource); + +struct wlr_ext_output_image_capture_source_manager_v1 *wlr_ext_output_image_capture_source_manager_v1_create( + struct wl_display *display, uint32_t version); + +#endif diff --git a/include/wlr/types/wlr_ext_image_copy_capture_v1.h b/include/wlr/types/wlr_ext_image_copy_capture_v1.h new file mode 100644 index 00000000..68f0579f --- /dev/null +++ b/include/wlr/types/wlr_ext_image_copy_capture_v1.h @@ -0,0 +1,49 @@ +/* + * 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_EXT_IMAGE_COPY_CAPTURE_V1_H +#define WLR_TYPES_WLR_EXT_IMAGE_COPY_CAPTURE_V1_H + +#include +#include +#include "ext-image-copy-capture-v1-protocol.h" + +struct wlr_ext_image_copy_capture_manager_v1 { + struct wl_global *global; + + // private state + + struct wl_listener display_destroy; +}; + +struct wlr_ext_image_copy_capture_frame_v1 { + struct wl_resource *resource; + bool capturing; + struct wlr_buffer *buffer; + pixman_region32_t buffer_damage; + + struct { + struct wl_signal destroy; + } events; + + // private state + + struct wlr_ext_image_copy_capture_session_v1 *session; +}; + +struct wlr_ext_image_copy_capture_manager_v1 *wlr_ext_image_copy_capture_manager_v1_create( + struct wl_display *display, uint32_t version); + +void wlr_ext_image_copy_capture_frame_v1_ready(struct wlr_ext_image_copy_capture_frame_v1 *frame, + enum wl_output_transform transform, const struct timespec *presentation_time); +bool wlr_ext_image_copy_capture_frame_v1_copy_buffer(struct wlr_ext_image_copy_capture_frame_v1 *frame, + struct wlr_buffer *src, struct wlr_renderer *renderer); +void wlr_ext_image_copy_capture_frame_v1_fail(struct wlr_ext_image_copy_capture_frame_v1 *frame, + enum ext_image_copy_capture_frame_v1_failure_reason reason); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index a4476918..1aac4da4 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,5 +1,5 @@ wayland_protos = dependency('wayland-protocols', - version: '>=1.35', + version: '>=1.37', fallback: 'wayland-protocols', default_options: ['tests=false'], ) @@ -26,6 +26,8 @@ protocols = { 'drm-lease-v1': wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', 'ext-foreign-toplevel-list-v1': wl_protocol_dir / 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml', 'ext-idle-notify-v1': wl_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', + 'ext-image-capture-source-v1': wl_protocol_dir / 'staging/ext-image-capture-source/ext-image-capture-source-v1.xml', + 'ext-image-copy-capture-v1': wl_protocol_dir / 'staging/ext-image-copy-capture/ext-image-copy-capture-v1.xml', 'ext-session-lock-v1': wl_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', 'fractional-scale-v1': wl_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', 'linux-drm-syncobj-v1': wl_protocol_dir / 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml', diff --git a/types/ext_image_capture_source_v1/base.c b/types/ext_image_capture_source_v1/base.c new file mode 100644 index 00000000..e4b03f85 --- /dev/null +++ b/types/ext_image_capture_source_v1/base.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ext-image-capture-source-v1-protocol.h" + +static void source_handle_destroy(struct wl_client *client, + struct wl_resource *source_resource) { + wl_resource_destroy(source_resource); +} + +static const struct ext_image_capture_source_v1_interface source_impl = { + .destroy = source_handle_destroy, +}; + +struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_image_capture_source_v1_interface, &source_impl)); + return wl_resource_get_user_data(resource); +} + +static void source_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +void wlr_ext_image_capture_source_v1_init(struct wlr_ext_image_capture_source_v1 *source, + const struct wlr_ext_image_capture_source_v1_interface *impl) { + *source = (struct wlr_ext_image_capture_source_v1){ + .impl = impl, + }; + wl_list_init(&source->resources); + wl_signal_init(&source->events.destroy); + wl_signal_init(&source->events.constraints_update); + wl_signal_init(&source->events.frame); +} + +void wlr_ext_image_capture_source_v1_finish(struct wlr_ext_image_capture_source_v1 *source) { + wl_signal_emit_mutable(&source->events.destroy, NULL); + + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &source->resources) { + wl_resource_set_user_data(resource, NULL); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } + + free(source->shm_formats); + wlr_drm_format_set_finish(&source->dmabuf_formats); +} + +bool wlr_ext_image_capture_source_v1_create_resource(struct wlr_ext_image_capture_source_v1 *source, + struct wl_client *client, uint32_t new_id) { + struct wl_resource *resource = wl_resource_create(client, + &ext_image_capture_source_v1_interface, 1, new_id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return false; + } + wl_resource_set_implementation(resource, &source_impl, source, + source_handle_resource_destroy); + if (source != NULL) { + wl_list_insert(&source->resources, wl_resource_get_link(resource)); + } else { + wl_list_init(wl_resource_get_link(resource)); + } + return true; +} + +static uint32_t get_swapchain_shm_format(struct wlr_swapchain *swapchain, + struct wlr_renderer *renderer) { + struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain); + if (buffer == NULL) { + return DRM_FORMAT_INVALID; + } + + struct wlr_texture *texture = wlr_texture_from_buffer(renderer, buffer); + wlr_buffer_unlock(buffer); + if (texture == NULL) { + return DRM_FORMAT_INVALID; + } + + uint32_t format = wlr_texture_preferred_read_format(texture); + wlr_texture_destroy(texture); + + return format; +} + +void wlr_ext_image_capture_source_v1_set_constraints_from_swapchain(struct wlr_ext_image_capture_source_v1 *source, + struct wlr_swapchain *swapchain, struct wlr_renderer *renderer) { + source->width = swapchain->width; + source->height = swapchain->height; + + uint32_t shm_format = get_swapchain_shm_format(swapchain, renderer); + if (shm_format != DRM_FORMAT_INVALID) { + uint32_t *shm_formats = calloc(1, sizeof(shm_formats[0])); + if (shm_formats == NULL) { + return; + } + shm_formats[0] = shm_format; + + source->shm_formats_len = 1; + free(source->shm_formats); + source->shm_formats = shm_formats; + } + + int drm_fd = wlr_renderer_get_drm_fd(renderer); + if (swapchain->allocator != NULL && + (swapchain->allocator->buffer_caps & WLR_BUFFER_CAP_DMABUF) && + drm_fd >= 0) { + struct stat dev_stat; + if (fstat(drm_fd, &dev_stat) != 0) { + return; + } + + source->dmabuf_device = dev_stat.st_rdev; + source->dmabuf_formats.len = 0; + for (size_t i = 0; i < swapchain->format.len; i++) { + wlr_drm_format_set_add(&source->dmabuf_formats, + swapchain->format.format, swapchain->format.modifiers[i]); + } + } + + wl_signal_emit_mutable(&source->events.constraints_update, NULL); +} + +void wlr_ext_image_capture_source_v1_cursor_init(struct wlr_ext_image_capture_source_v1_cursor *source_cursor, + const struct wlr_ext_image_capture_source_v1_interface *impl) { + *source_cursor = (struct wlr_ext_image_capture_source_v1_cursor){0}; + wlr_ext_image_capture_source_v1_init(&source_cursor->base, impl); + wl_signal_init(&source_cursor->events.update); +} + +void wlr_ext_image_capture_source_v1_cursor_finish(struct wlr_ext_image_capture_source_v1_cursor *source_cursor) { + wlr_ext_image_capture_source_v1_finish(&source_cursor->base); +} diff --git a/types/ext_image_capture_source_v1/output.c b/types/ext_image_capture_source_v1/output.c new file mode 100644 index 00000000..17b69578 --- /dev/null +++ b/types/ext_image_capture_source_v1/output.c @@ -0,0 +1,383 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "render/wlr_renderer.h" +#include "ext-image-capture-source-v1-protocol.h" + +#define OUTPUT_IMAGE_SOURCE_MANAGER_V1_VERSION 1 + +struct output_cursor_source { + struct wlr_ext_image_capture_source_v1_cursor base; + + struct wlr_output *output; + struct wlr_buffer *prev_buffer; + bool initialized; + + struct wl_listener output_commit; + struct wl_listener prev_buffer_release; +}; + +struct wlr_ext_output_image_capture_source_v1 { + struct wlr_ext_image_capture_source_v1 base; + struct wlr_addon addon; + + struct wlr_output *output; + + struct wl_listener output_commit; + + struct output_cursor_source cursor; + + size_t num_started; + bool software_cursors_locked; +}; + +struct wlr_ext_output_image_capture_source_v1_frame_event { + struct wlr_ext_image_capture_source_v1_frame_event base; + struct wlr_buffer *buffer; + struct timespec *when; +}; + +static void output_source_start(struct wlr_ext_image_capture_source_v1 *base, + bool with_cursors) { + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(base, source, base); + source->num_started++; + if (source->num_started > 1) { + return; + } + wlr_output_lock_attach_render(source->output, true); + if (with_cursors) { + wlr_output_lock_software_cursors(source->output, true); + } + source->software_cursors_locked = with_cursors; +} + +static void output_source_stop(struct wlr_ext_image_capture_source_v1 *base) { + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(base, source, base); + assert(source->num_started > 0); + if (source->num_started > 0) { + return; + } + wlr_output_lock_attach_render(source->output, false); + if (source->software_cursors_locked) { + wlr_output_lock_software_cursors(source->output, false); + } +} + +static void output_source_schedule_frame(struct wlr_ext_image_capture_source_v1 *base) { + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(base, source, base); + wlr_output_update_needs_frame(source->output); +} + +static void output_source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, + struct wlr_ext_image_copy_capture_frame_v1 *frame, + struct wlr_ext_image_capture_source_v1_frame_event *base_event) { + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(base, source, base); + struct wlr_ext_output_image_capture_source_v1_frame_event *event = + wl_container_of(base_event, event, base); + + if (wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame, + event->buffer, source->output->renderer)) { + wlr_ext_image_copy_capture_frame_v1_ready(frame, + source->output->transform, event->when); + } +} + +static struct wlr_ext_image_capture_source_v1_cursor *output_source_get_pointer_cursor( + struct wlr_ext_image_capture_source_v1 *base, struct wlr_seat *seat) { + // TODO: handle seat + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(base, source, base); + return &source->cursor.base; +} + +static const struct wlr_ext_image_capture_source_v1_interface output_source_impl = { + .start = output_source_start, + .stop = output_source_stop, + .schedule_frame = output_source_schedule_frame, + .copy_frame = output_source_copy_frame, + .get_pointer_cursor = output_source_get_pointer_cursor, +}; + +static void source_update_buffer_constraints(struct wlr_ext_output_image_capture_source_v1 *source) { + struct wlr_output *output = source->output; + + if (!wlr_output_configure_primary_swapchain(output, NULL, &output->swapchain)) { + return; + } + + wlr_ext_image_capture_source_v1_set_constraints_from_swapchain(&source->base, + output->swapchain, output->renderer); +} + +static void source_handle_output_commit(struct wl_listener *listener, + void *data) { + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(listener, source, output_commit); + struct wlr_output_event_commit *event = data; + + if (event->state->committed & (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_RENDER_FORMAT)) { + source_update_buffer_constraints(source); + } + + if (event->state->committed & WLR_OUTPUT_STATE_BUFFER) { + struct wlr_buffer *buffer = event->state->buffer; + + pixman_region32_t full_damage; + pixman_region32_init_rect(&full_damage, 0, 0, buffer->width, buffer->height); + + const pixman_region32_t *damage; + if (event->state->committed & WLR_OUTPUT_STATE_DAMAGE) { + damage = &event->state->damage; + } else { + damage = &full_damage; + } + + struct wlr_ext_output_image_capture_source_v1_frame_event frame_event = { + .base = { + .damage = damage, + }, + .buffer = buffer, + .when = event->when, // TODO: predict next presentation time instead + }; + wl_signal_emit_mutable(&source->base.events.frame, &frame_event); + + pixman_region32_fini(&full_damage); + } +} + +static void output_cursor_source_init(struct output_cursor_source *cursor_source, + struct wlr_output *output); +static void output_cursor_source_finish(struct output_cursor_source *cursor_source); + +static void output_addon_destroy(struct wlr_addon *addon) { + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(addon, source, addon); + wlr_ext_image_capture_source_v1_finish(&source->base); + output_cursor_source_finish(&source->cursor); + wl_list_remove(&source->output_commit.link); + wlr_addon_finish(&source->addon); + free(source); +} + +static const struct wlr_addon_interface output_addon_impl = { + .name = "wlr_ext_output_image_capture_source_v1", + .destroy = output_addon_destroy, +}; + +static void output_manager_handle_create_source(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t new_id, + struct wl_resource *output_resource) { + struct wlr_output *output = wlr_output_from_resource(output_resource); + if (output == NULL) { + wlr_ext_image_capture_source_v1_create_resource(NULL, client, new_id); + return; + } + + struct wlr_ext_output_image_capture_source_v1 *source; + struct wlr_addon *addon = wlr_addon_find(&output->addons, NULL, &output_addon_impl); + if (addon != NULL) { + source = wl_container_of(addon, source, addon); + } else { + source = calloc(1, sizeof(*source)); + if (source == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + wlr_ext_image_capture_source_v1_init(&source->base, &output_source_impl); + wlr_addon_init(&source->addon, &output->addons, NULL, &output_addon_impl); + source->output = output; + + source->output_commit.notify = source_handle_output_commit; + wl_signal_add(&output->events.commit, &source->output_commit); + + source_update_buffer_constraints(source); + + output_cursor_source_init(&source->cursor, output); + } + + if (!wlr_ext_image_capture_source_v1_create_resource(&source->base, client, new_id)) { + return; + } +} + +static void output_manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct ext_output_image_capture_source_manager_v1_interface output_manager_impl = { + .create_source = output_manager_handle_create_source, + .destroy = output_manager_handle_destroy, +}; + +static void output_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_ext_output_image_capture_source_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &ext_output_image_capture_source_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &output_manager_impl, manager, NULL); +} + +static void output_manager_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_output_image_capture_source_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_ext_output_image_capture_source_manager_v1 *wlr_ext_output_image_capture_source_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= OUTPUT_IMAGE_SOURCE_MANAGER_V1_VERSION); + + struct wlr_ext_output_image_capture_source_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &ext_output_image_capture_source_manager_v1_interface, version, manager, output_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = output_manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +static void output_cursor_source_schedule_frame(struct wlr_ext_image_capture_source_v1 *base) { + struct output_cursor_source *cursor_source = wl_container_of(base, cursor_source, base); + wlr_output_update_needs_frame(cursor_source->output); +} + +static void output_cursor_source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, + struct wlr_ext_image_copy_capture_frame_v1 *frame, + struct wlr_ext_image_capture_source_v1_frame_event *base_event) { + struct output_cursor_source *cursor_source = wl_container_of(base, cursor_source, base); + + struct wlr_buffer *src_buffer = cursor_source->output->cursor_front_buffer; + if (src_buffer == NULL) { + wlr_ext_image_copy_capture_frame_v1_fail(frame, EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); + return; + } + + if (!wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame, + src_buffer, cursor_source->output->renderer)) { + return; + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + wlr_ext_image_copy_capture_frame_v1_ready(frame, WL_OUTPUT_TRANSFORM_NORMAL, &now); +} + +static const struct wlr_ext_image_capture_source_v1_interface output_cursor_source_impl = { + .schedule_frame = output_cursor_source_schedule_frame, + .copy_frame = output_cursor_source_copy_frame, +}; + +static void output_cursor_source_update(struct output_cursor_source *cursor_source) { + struct wlr_output *output = cursor_source->output; + + if (output->cursor_swapchain != NULL && !cursor_source->initialized) { + wlr_ext_image_capture_source_v1_set_constraints_from_swapchain(&cursor_source->base.base, + output->cursor_swapchain, output->renderer); + cursor_source->initialized = true; + } + + struct wlr_output_cursor *output_cursor = output->hardware_cursor; + if (output_cursor == NULL || !output_cursor->visible) { + cursor_source->base.entered = false; + wl_signal_emit_mutable(&cursor_source->base.events.update, NULL); + return; + } + + if (output->cursor_swapchain != NULL && + ((int)cursor_source->base.base.width != output->cursor_swapchain->width || + (int)cursor_source->base.base.height != output->cursor_swapchain->height)) { + cursor_source->base.base.width = output->cursor_swapchain->width; + cursor_source->base.base.height = output->cursor_swapchain->height; + wl_signal_emit_mutable(&cursor_source->base.base.events.constraints_update, NULL); + } + + cursor_source->base.entered = true; + cursor_source->base.x = round(output_cursor->x); + cursor_source->base.y = round(output_cursor->y); + cursor_source->base.hotspot.x = output_cursor->hotspot_x; + cursor_source->base.hotspot.y = output_cursor->hotspot_y; + wl_signal_emit_mutable(&cursor_source->base.events.update, NULL); +} + +static void output_cursor_source_handle_prev_buffer_release(struct wl_listener *listener, + void *data) { + struct output_cursor_source *cursor_source = wl_container_of(listener, cursor_source, prev_buffer_release); + wl_list_remove(&cursor_source->prev_buffer_release.link); + wl_list_init(&cursor_source->prev_buffer_release.link); + cursor_source->prev_buffer = NULL; +} + +static void output_cursor_source_handle_output_commit(struct wl_listener *listener, + void *data) { + struct output_cursor_source *cursor_source = wl_container_of(listener, cursor_source, output_commit); + struct wlr_output_event_commit *event = data; + + output_cursor_source_update(cursor_source); + + struct wlr_buffer *buffer = cursor_source->output->cursor_front_buffer; + if (buffer != NULL && buffer != cursor_source->prev_buffer) { + pixman_region32_t full_damage; + pixman_region32_init_rect(&full_damage, 0, 0, buffer->width, buffer->height); + + struct wlr_ext_output_image_capture_source_v1_frame_event frame_event = { + .base = { + .damage = &full_damage, + }, + .buffer = buffer, + .when = event->when, // TODO: predict next presentation time instead + }; + wl_signal_emit_mutable(&cursor_source->base.base.events.frame, &frame_event); + + pixman_region32_fini(&full_damage); + + assert(buffer->n_locks > 0); + cursor_source->prev_buffer = buffer; + wl_list_remove(&cursor_source->prev_buffer_release.link); + cursor_source->prev_buffer_release.notify = output_cursor_source_handle_prev_buffer_release; + wl_signal_add(&buffer->events.release, &cursor_source->prev_buffer_release); + } +} + +static void output_cursor_source_init(struct output_cursor_source *cursor_source, + struct wlr_output *output) { + wlr_ext_image_capture_source_v1_cursor_init(&cursor_source->base, &output_cursor_source_impl); + + // Caller is responsible for destroying the output cursor source when the + // output is destroyed + cursor_source->output = output; + + cursor_source->output_commit.notify = output_cursor_source_handle_output_commit; + wl_signal_add(&output->events.commit, &cursor_source->output_commit); + + wl_list_init(&cursor_source->prev_buffer_release.link); + + output_cursor_source_update(cursor_source); +} + +static void output_cursor_source_finish(struct output_cursor_source *cursor_source) { + wlr_ext_image_capture_source_v1_cursor_finish(&cursor_source->base); + wl_list_remove(&cursor_source->output_commit.link); + wl_list_remove(&cursor_source->prev_buffer_release.link); +} diff --git a/types/meson.build b/types/meson.build index ec70d4b7..6a00af0e 100644 --- a/types/meson.build +++ b/types/meson.build @@ -3,6 +3,8 @@ wlr_files += files( 'data_device/wlr_data_offer.c', 'data_device/wlr_data_source.c', 'data_device/wlr_drag.c', + 'ext_image_capture_source_v1/base.c', + 'ext_image_capture_source_v1/output.c', 'output/cursor.c', 'output/output.c', 'output/render.c', @@ -43,6 +45,7 @@ wlr_files += files( 'wlr_drm.c', 'wlr_export_dmabuf_v1.c', 'wlr_foreign_toplevel_management_v1.c', + 'wlr_ext_image_copy_capture_v1.c', 'wlr_ext_foreign_toplevel_list_v1.c', 'wlr_fullscreen_shell_v1.c', 'wlr_gamma_control_v1.c', diff --git a/types/output/cursor.c b/types/output/cursor.c index 7bc8d0d6..f8f5eb64 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -23,6 +23,8 @@ static bool output_set_hardware_cursor(struct wlr_output *output, return false; } + wlr_output_update_needs_frame(output); + wlr_buffer_unlock(output->cursor_front_buffer); output->cursor_front_buffer = NULL; @@ -33,6 +35,15 @@ static bool output_set_hardware_cursor(struct wlr_output *output, return true; } +static bool output_move_hardware_cursor(struct wlr_output *output, int x, int y) { + assert(output->impl->move_cursor); + if (!output->impl->move_cursor(output, x, y)) { + return false; + } + wlr_output_update_needs_frame(output); + return true; +} + static void output_cursor_damage_whole(struct wlr_output_cursor *cursor); static void output_disable_hardware_cursor(struct wlr_output *output) { @@ -302,8 +313,7 @@ static bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) { // If the cursor was hidden or was a software cursor, the hardware // cursor position is outdated - output->impl->move_cursor(cursor->output, - (int)cursor->x, (int)cursor->y); + output_move_hardware_cursor(cursor->output, (int)cursor->x, (int)cursor->y); struct wlr_buffer *buffer = NULL; if (texture != NULL) { @@ -455,8 +465,7 @@ bool wlr_output_cursor_move(struct wlr_output_cursor *cursor, return true; } - assert(cursor->output->impl->move_cursor); - return cursor->output->impl->move_cursor(cursor->output, (int)x, (int)y); + return output_move_hardware_cursor(cursor->output, (int)x, (int)y); } struct wlr_output_cursor *wlr_output_cursor_create(struct wlr_output *output) { diff --git a/types/wlr_ext_image_copy_capture_v1.c b/types/wlr_ext_image_copy_capture_v1.c new file mode 100644 index 00000000..321c6f99 --- /dev/null +++ b/types/wlr_ext_image_copy_capture_v1.c @@ -0,0 +1,685 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define IMAGE_COPY_CAPTURE_MANAGER_V1_VERSION 1 + +struct wlr_ext_image_copy_capture_session_v1 { + struct wl_resource *resource; + struct wlr_ext_image_capture_source_v1 *source; + struct wlr_ext_image_copy_capture_frame_v1 *frame; + + struct wl_listener source_destroy; + struct wl_listener source_constraints_update; + struct wl_listener source_frame; + + pixman_region32_t damage; +}; + +struct wlr_ext_image_copy_capture_cursor_session_v1 { + struct wl_resource *resource; + struct wlr_ext_image_capture_source_v1_cursor *source; + bool capture_session_created; + + struct { + bool entered; + int32_t x, y; + struct { + int32_t x, y; + } hotspot; + } prev; + + struct wl_listener source_destroy; + struct wl_listener source_update; +}; + +static const struct ext_image_copy_capture_frame_v1_interface frame_impl; +static const struct ext_image_copy_capture_session_v1_interface session_impl; +static const struct ext_image_copy_capture_cursor_session_v1_interface cursor_session_impl; + +static struct wlr_ext_image_copy_capture_frame_v1 *frame_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_image_copy_capture_frame_v1_interface, &frame_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_ext_image_copy_capture_session_v1 *session_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_image_copy_capture_session_v1_interface, &session_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_image_copy_capture_cursor_session_v1_interface, &cursor_session_impl)); + return wl_resource_get_user_data(resource); +} + +static void frame_destroy(struct wlr_ext_image_copy_capture_frame_v1 *frame) { + if (frame == NULL) { + return; + } + wl_signal_emit_mutable(&frame->events.destroy, NULL); + wl_resource_set_user_data(frame->resource, NULL); + wlr_buffer_unlock(frame->buffer); + pixman_region32_fini(&frame->buffer_damage); + if (frame->session->frame == frame) { + frame->session->frame = NULL; + } + free(frame); +} + +static void frame_handle_resource_destroy(struct wl_resource *resource) { + frame_destroy(wl_resource_get_user_data(resource)); +} + +void wlr_ext_image_copy_capture_frame_v1_ready(struct wlr_ext_image_copy_capture_frame_v1 *frame, + enum wl_output_transform transform, + const struct timespec *presentation_time) { + assert(frame->capturing); + + int rects_len = 0; + const pixman_box32_t *rects = + pixman_region32_rectangles(&frame->session->damage, &rects_len); + for (int i = 0; i < rects_len; i++) { + const pixman_box32_t *rect = &rects[i]; + ext_image_copy_capture_frame_v1_send_damage(frame->resource, + rect->x1, rect->y1, rect->x2 - rect->x1, rect->y2 - rect->y1); + } + + ext_image_copy_capture_frame_v1_send_transform(frame->resource, transform); + ext_image_copy_capture_frame_v1_send_presentation_time(frame->resource, + presentation_time->tv_sec >> 32, presentation_time->tv_sec, + presentation_time->tv_nsec); + ext_image_copy_capture_frame_v1_send_ready(frame->resource); + frame_destroy(frame); +} + +static bool copy_dmabuf(struct wlr_buffer *dst, + struct wlr_buffer *src, struct wlr_renderer *renderer, + const pixman_region32_t *clip) { + struct wlr_texture *texture = wlr_texture_from_buffer(renderer, src); + if (texture == NULL) { + return false; + } + + bool ok = false; + struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, dst, NULL); + if (!pass) { + goto out; + } + + wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options) { + .texture = texture, + .clip = clip, + .blend_mode = WLR_RENDER_BLEND_MODE_NONE, + }); + + ok = wlr_render_pass_submit(pass); + +out: + wlr_texture_destroy(texture); + return ok; +} + +static bool copy_shm(void *data, uint32_t format, size_t stride, + struct wlr_buffer *src, struct wlr_renderer *renderer) { + // TODO: bypass renderer if source buffer supports data ptr access + struct wlr_texture *texture = wlr_texture_from_buffer(renderer, src); + if (!texture) { + return false; + } + + // TODO: only copy damaged region + bool ok = wlr_texture_read_pixels(texture, &(struct wlr_texture_read_pixels_options){ + .data = data, + .format = format, + .stride = stride, + }); + + wlr_texture_destroy(texture); + + return ok; +} + +bool wlr_ext_image_copy_capture_frame_v1_copy_buffer(struct wlr_ext_image_copy_capture_frame_v1 *frame, + struct wlr_buffer *src, struct wlr_renderer *renderer) { + struct wlr_buffer *dst = frame->buffer; + + if (src->width != dst->width || src->height != dst->height) { + wlr_ext_image_copy_capture_frame_v1_fail(frame, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); + return false; + } + + bool ok = false; + enum ext_image_copy_capture_frame_v1_failure_reason failure_reason = + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN; + struct wlr_dmabuf_attributes dmabuf; + void *data; + uint32_t format; + size_t stride; + if (wlr_buffer_get_dmabuf(dst, &dmabuf)) { + if (frame->session->source->dmabuf_formats.len == 0) { + ok = false; + failure_reason = EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS; + } else { + ok = copy_dmabuf(dst, src, renderer, &frame->buffer_damage); + } + } else if (wlr_buffer_begin_data_ptr_access(dst, + WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) { + if (frame->session->source->shm_formats_len == 0) { + ok = false; + failure_reason = EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS; + } else { + ok = copy_shm(data, format, stride, src, renderer); + } + wlr_buffer_end_data_ptr_access(dst); + } + if (!ok) { + wlr_ext_image_copy_capture_frame_v1_fail(frame, failure_reason); + return false; + } + + return true; +} + +void wlr_ext_image_copy_capture_frame_v1_fail(struct wlr_ext_image_copy_capture_frame_v1 *frame, + enum ext_image_copy_capture_frame_v1_failure_reason reason) { + ext_image_copy_capture_frame_v1_send_failed(frame->resource, reason); + frame_destroy(frame); +} + +static void frame_handle_destroy(struct wl_client *client, + struct wl_resource *frame_resource) { + wl_resource_destroy(frame_resource); +} + +static void frame_handle_attach_buffer(struct wl_client *client, + struct wl_resource *frame_resource, struct wl_resource *buffer_resource) { + struct wlr_ext_image_copy_capture_frame_v1 *frame = frame_from_resource(frame_resource); + if (frame == NULL) { + return; + } + + if (frame->capturing) { + wl_resource_post_error(frame->resource, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, + "attach_buffer sent after capture"); + return; + } + + struct wlr_buffer *buffer = wlr_buffer_try_from_resource(buffer_resource); + if (buffer == NULL) { + wl_resource_post_no_memory(frame_resource); + return; + } + + wlr_buffer_unlock(frame->buffer); + frame->buffer = buffer; +} + +static void frame_handle_damage_buffer(struct wl_client *client, + struct wl_resource *frame_resource, int32_t x, int32_t y, + int32_t width, int32_t height) { + struct wlr_ext_image_copy_capture_frame_v1 *frame = frame_from_resource(frame_resource); + if (frame == NULL) { + return; + } + + if (frame->capturing) { + wl_resource_post_error(frame->resource, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, + "damage_buffer sent after capture"); + return; + } + + if (x < 0 || y < 0 || width <= 0 || height <= 0) { + wl_resource_post_error(frame->resource, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_INVALID_BUFFER_DAMAGE, + "Invalid buffer damage coordinates"); + return; + } + + pixman_region32_union_rect(&frame->buffer_damage, &frame->buffer_damage, + x, y, width, height); +} + +static void frame_handle_capture(struct wl_client *client, + struct wl_resource *frame_resource) { + struct wlr_ext_image_copy_capture_frame_v1 *frame = frame_from_resource(frame_resource); + if (frame == NULL) { + return; + } + + if (frame->capturing) { + wl_resource_post_error(frame->resource, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, + "capture sent twice"); + return; + } + + if (frame->buffer == NULL) { + wl_resource_post_error(frame->resource, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_NO_BUFFER, + "No buffer attached"); + return; + } + + frame->capturing = true; + + bool need_frame = pixman_region32_not_empty(&frame->session->damage); + struct wlr_ext_image_capture_source_v1 *source = frame->session->source; + if (need_frame && source->impl->schedule_frame) { + source->impl->schedule_frame(source); + } +} + +static const struct ext_image_copy_capture_frame_v1_interface frame_impl = { + .destroy = frame_handle_destroy, + .attach_buffer = frame_handle_attach_buffer, + .damage_buffer = frame_handle_damage_buffer, + .capture = frame_handle_capture, +}; + +static void session_handle_destroy(struct wl_client *client, + struct wl_resource *session_resource) { + wl_resource_destroy(session_resource); +} + +static void session_handle_create_frame(struct wl_client *client, + struct wl_resource *session_resource, uint32_t new_id) { + struct wlr_ext_image_copy_capture_session_v1 *session = session_from_resource(session_resource); + + if (session->frame != NULL) { + wl_resource_post_error(session_resource, + EXT_IMAGE_COPY_CAPTURE_SESSION_V1_ERROR_DUPLICATE_FRAME, + "session already has a frame object"); + return; + } + + uint32_t version = wl_resource_get_version(session_resource); + struct wl_resource *frame_resource = wl_resource_create(client, + &ext_image_copy_capture_frame_v1_interface, version, new_id); + if (frame_resource == NULL) { + wl_resource_post_no_memory(frame_resource); + return; + } + wl_resource_set_implementation(frame_resource, &frame_impl, NULL, + frame_handle_resource_destroy); + + if (session == NULL) { + ext_image_copy_capture_frame_v1_send_failed(frame_resource, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); + return; + } + + struct wlr_ext_image_copy_capture_frame_v1 *frame = calloc(1, sizeof(*frame)); + if (frame == NULL) { + wl_resource_post_no_memory(session_resource); + return; + } + + frame->resource = frame_resource; + frame->session = session; + pixman_region32_init(&frame->buffer_damage); + wl_signal_init(&frame->events.destroy); + + wl_resource_set_user_data(frame_resource, frame); + + session->frame = frame; +} + +static const struct ext_image_copy_capture_session_v1_interface session_impl = { + .destroy = session_handle_destroy, + .create_frame = session_handle_create_frame, +}; + +static void session_send_constraints(struct wlr_ext_image_copy_capture_session_v1 *session) { + struct wlr_ext_image_capture_source_v1 *source = session->source; + + ext_image_copy_capture_session_v1_send_buffer_size(session->resource, + source->width, source->height); + + for (size_t i = 0; i < source->shm_formats_len; i++) { + ext_image_copy_capture_session_v1_send_shm_format(session->resource, + source->shm_formats[i]); + } + + if (source->dmabuf_formats.len > 0) { + struct wl_array dev_id_array = { + .data = &source->dmabuf_device, + .size = sizeof(source->dmabuf_device), + }; + ext_image_copy_capture_session_v1_send_dmabuf_device(session->resource, + &dev_id_array); + } + for (size_t i = 0; i < source->dmabuf_formats.len; i++) { + struct wlr_drm_format *fmt = &source->dmabuf_formats.formats[i]; + struct wl_array modifiers_array = { + .data = fmt->modifiers, + .size = fmt->len * sizeof(fmt->modifiers[0]), + }; + ext_image_copy_capture_session_v1_send_dmabuf_format(session->resource, + fmt->format, &modifiers_array); + } + + ext_image_copy_capture_session_v1_send_done(session->resource); +} + +static void session_destroy(struct wlr_ext_image_copy_capture_session_v1 *session) { + if (session == NULL) { + return; + } + + if (session->frame != NULL) { + wlr_ext_image_copy_capture_frame_v1_fail(session->frame, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); + } + + if (session->source->impl->stop) { + session->source->impl->stop(session->source); + } + + ext_image_copy_capture_session_v1_send_stopped(session->resource); + wl_resource_set_user_data(session->resource, NULL); + + pixman_region32_fini(&session->damage); + wl_list_remove(&session->source_destroy.link); + wl_list_remove(&session->source_constraints_update.link); + wl_list_remove(&session->source_frame.link); + free(session); +} + +static void session_handle_source_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_image_copy_capture_session_v1 *session = wl_container_of(listener, session, source_destroy); + session_destroy(session); +} + +static void session_handle_source_constraints_update(struct wl_listener *listener, + void *data) { + struct wlr_ext_image_copy_capture_session_v1 *session = + wl_container_of(listener, session, source_constraints_update); + session_send_constraints(session); +} + +static void session_handle_source_frame(struct wl_listener *listener, void *data) { + struct wlr_ext_image_copy_capture_session_v1 *session = wl_container_of(listener, session, source_frame); + struct wlr_ext_image_capture_source_v1_frame_event *event = data; + + pixman_region32_union(&session->damage, &session->damage, event->damage); + + struct wlr_ext_image_copy_capture_frame_v1 *frame = session->frame; + if (frame != NULL && frame->capturing && + pixman_region32_not_empty(&session->damage)) { + pixman_region32_union(&frame->buffer_damage, + &frame->buffer_damage, &session->damage); + + struct wlr_ext_image_capture_source_v1 *source = frame->session->source; + source->impl->copy_frame(source, frame, event); + + // TODO: don't clear on copy error + pixman_region32_clear(&session->damage); + } +} + +static void session_handle_resource_destroy(struct wl_resource *resource) { + session_destroy(session_from_resource(resource)); +} + +static void session_create(struct wl_resource *parent_resource, uint32_t new_id, + struct wlr_ext_image_capture_source_v1 *source, uint32_t options) { + struct wl_client *client = wl_resource_get_client(parent_resource); + uint32_t version = wl_resource_get_version(parent_resource); + struct wl_resource *session_resource = wl_resource_create(client, + &ext_image_copy_capture_session_v1_interface, version, new_id); + if (session_resource == NULL) { + wl_resource_post_no_memory(parent_resource); + return; + } + wl_resource_set_implementation(session_resource, &session_impl, NULL, + session_handle_resource_destroy); + + if (source == NULL) { + ext_image_copy_capture_session_v1_send_stopped(session_resource); + return; + } + + struct wlr_ext_image_copy_capture_session_v1 *session = calloc(1, sizeof(*session)); + if (session == NULL) { + wl_resource_post_no_memory(parent_resource); + return; + } + + if (source->impl->start) { + source->impl->start(source, options & EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS); + } + + session->resource = session_resource; + session->source = source; + pixman_region32_init_rect(&session->damage, 0, 0, source->width, + source->height); + + session->source_destroy.notify = session_handle_source_destroy; + wl_signal_add(&source->events.destroy, &session->source_destroy); + + session->source_constraints_update.notify = session_handle_source_constraints_update; + wl_signal_add(&source->events.constraints_update, &session->source_constraints_update); + + session->source_frame.notify = session_handle_source_frame; + wl_signal_add(&source->events.frame, &session->source_frame); + + wl_resource_set_user_data(session_resource, session); + session_send_constraints(session); +} + +static void cursor_session_destroy(struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session) { + if (cursor_session == NULL) { + return; + } + if (cursor_session->source->entered) { + ext_image_copy_capture_cursor_session_v1_send_leave(cursor_session->resource); + } + wl_resource_set_user_data(cursor_session->resource, NULL); + wl_list_remove(&cursor_session->source_destroy.link); + wl_list_remove(&cursor_session->source_update.link); + free(cursor_session); +} + +static void cursor_session_handle_destroy(struct wl_client *client, + struct wl_resource *cursor_session_resource) { + wl_resource_destroy(cursor_session_resource); +} + +static void cursor_session_handle_get_capture_session(struct wl_client *client, + struct wl_resource *cursor_session_resource, uint32_t new_id) { + struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session = + cursor_session_from_resource(cursor_session_resource); + + if (cursor_session != NULL && cursor_session->capture_session_created) { + wl_resource_post_error(cursor_session_resource, + EXT_IMAGE_COPY_CAPTURE_CURSOR_SESSION_V1_ERROR_DUPLICATE_SESSION, + "get_capture_session sent twice"); + return; + } + + struct wlr_ext_image_capture_source_v1 *source = NULL; + if (cursor_session != NULL) { + source = &cursor_session->source->base; + } + + session_create(cursor_session_resource, new_id, source, 0); +} + +static const struct ext_image_copy_capture_cursor_session_v1_interface cursor_session_impl = { + .destroy = cursor_session_handle_destroy, + .get_capture_session = cursor_session_handle_get_capture_session, +}; + +static void cursor_session_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session = + cursor_session_from_resource(resource); + cursor_session_destroy(cursor_session); +} + +static void cursor_session_handle_source_destroy(struct wl_listener *listener, + void *data) { + struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session = + wl_container_of(listener, cursor_session, source_destroy); + cursor_session_destroy(cursor_session); +} + +static void cursor_session_update( + struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session) { + struct wlr_ext_image_capture_source_v1_cursor *cursor_source = cursor_session->source; + + if (cursor_source->entered && !cursor_session->prev.entered) { + ext_image_copy_capture_cursor_session_v1_send_enter(cursor_session->resource); + } + if (!cursor_source->entered && cursor_session->prev.entered) { + ext_image_copy_capture_cursor_session_v1_send_leave(cursor_session->resource); + } + + if (cursor_source->x != cursor_session->prev.x || + cursor_source->y != cursor_session->prev.y) { + ext_image_copy_capture_cursor_session_v1_send_position(cursor_session->resource, + cursor_source->x, cursor_source->y); + } + + if (cursor_source->hotspot.x != cursor_session->prev.hotspot.x || + cursor_source->hotspot.y != cursor_session->prev.hotspot.y) { + ext_image_copy_capture_cursor_session_v1_send_hotspot(cursor_session->resource, + cursor_source->hotspot.x, cursor_source->hotspot.y); + } + + cursor_session->prev.entered = cursor_source->entered; + cursor_session->prev.x = cursor_source->x; + cursor_session->prev.y = cursor_source->y; + cursor_session->prev.hotspot.y = cursor_source->hotspot.y; + cursor_session->prev.hotspot.y = cursor_source->hotspot.y; +} + +static void cursor_session_handle_source_update(struct wl_listener *listener, + void *data) { + struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session = + wl_container_of(listener, cursor_session, source_update); + cursor_session_update(cursor_session); +} + +static void manager_handle_create_session(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t new_id, + struct wl_resource *source_resource, uint32_t options) { + struct wlr_ext_image_capture_source_v1 *source = + wlr_ext_image_capture_source_v1_from_resource(source_resource); + session_create(manager_resource, new_id, source, options); +} + +static void manager_handle_create_pointer_cursor_session(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t new_id, + struct wl_resource *source_resource, struct wl_resource *pointer_resource) { + struct wlr_ext_image_capture_source_v1 *source = wlr_ext_image_capture_source_v1_from_resource(source_resource); + struct wlr_seat_client *seat_client = wlr_seat_client_from_pointer_resource(pointer_resource); + + struct wlr_seat *seat = NULL; + if (seat_client != NULL) { + seat = seat_client->seat; + } + + struct wlr_ext_image_capture_source_v1_cursor *source_cursor = NULL; + if (source != NULL && seat != NULL && source->impl->get_pointer_cursor) { + source_cursor = source->impl->get_pointer_cursor(source, seat); + } + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *cursor_session_resource = wl_resource_create(client, + &ext_image_copy_capture_cursor_session_v1_interface, version, new_id); + if (cursor_session_resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(cursor_session_resource, &cursor_session_impl, NULL, + cursor_session_handle_resource_destroy); + + if (source_cursor == NULL) { + return; // leave inert + } + + struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session = calloc(1, sizeof(*cursor_session)); + if (cursor_session == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + cursor_session->resource = cursor_session_resource; + cursor_session->source = source_cursor; + + cursor_session->source_destroy.notify = cursor_session_handle_source_destroy; + wl_signal_add(&source_cursor->base.events.destroy, &cursor_session->source_destroy); + + cursor_session->source_update.notify = cursor_session_handle_source_update; + wl_signal_add(&source_cursor->events.update, &cursor_session->source_update); + + wl_resource_set_user_data(cursor_session_resource, cursor_session); + + cursor_session_update(cursor_session); +} + +static void manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct ext_image_copy_capture_manager_v1_interface manager_impl = { + .create_session = manager_handle_create_session, + .create_pointer_cursor_session = manager_handle_create_pointer_cursor_session, + .destroy = manager_handle_destroy, +}; + +static void manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wl_resource *resource = wl_resource_create(client, + &ext_image_copy_capture_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, NULL, NULL); +} + +static void manager_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_image_copy_capture_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_ext_image_copy_capture_manager_v1 *wlr_ext_image_copy_capture_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= IMAGE_COPY_CAPTURE_MANAGER_V1_VERSION); + + struct wlr_ext_image_copy_capture_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &ext_image_copy_capture_manager_v1_interface, version, manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +}