diff --git a/include/wlr/types/wlr_ext_image_capture_source_v1.h b/include/wlr/types/wlr_ext_image_capture_source_v1.h index 21c74cdec..99c8e9d7b 100644 --- a/include/wlr/types/wlr_ext_image_capture_source_v1.h +++ b/include/wlr/types/wlr_ext_image_capture_source_v1.h @@ -12,10 +12,12 @@ #include #include #include +#include struct wlr_scene_node; struct wlr_allocator; struct wlr_renderer; +struct wlr_output_layout; /** * A screen capture source. @@ -77,12 +79,24 @@ struct wlr_ext_image_capture_source_v1_cursor { */ struct wlr_ext_output_image_capture_source_manager_v1 { struct wl_global *global; + struct wl_display *display; + struct wlr_scene *scene; + struct wlr_output_layout *layout; struct { struct wl_listener display_destroy; } WLR_PRIVATE; + struct wlr_backend *headless_backend; }; +void wlr_ext_output_image_capture_source_manager_v1_set_layout( + struct wlr_ext_output_image_capture_source_manager_v1 *manager, + struct wlr_output_layout *layout); + +void wlr_ext_output_image_capture_source_manager_v1_set_scene( + struct wlr_ext_output_image_capture_source_manager_v1 *manager, + struct wlr_scene *scene); + /** * Interface exposing one screen capture source per foreign toplevel. */ diff --git a/types/ext_image_capture_source_v1/output.c b/types/ext_image_capture_source_v1/output.c index 0e3a57823..65b99691c 100644 --- a/types/ext_image_capture_source_v1/output.c +++ b/types/ext_image_capture_source_v1/output.c @@ -6,7 +6,12 @@ #include #include #include +#include #include +#include +#include +#include "types/wlr_scene.h" +#include #include "ext-image-capture-source-v1-protocol.h" #define OUTPUT_IMAGE_SOURCE_MANAGER_V1_VERSION 1 @@ -35,6 +40,13 @@ struct wlr_ext_output_image_capture_source_v1 { size_t num_started; bool software_cursors_locked; + + // headless output to avoid icc profile stickiness + struct wlr_ext_output_image_capture_source_manager_v1 *manager; + struct wlr_output *hidden_output; + struct wlr_scene_output *hidden_scene_output; + struct wl_listener hidden_output_frame; + struct wl_listener hidden_output_commit; }; struct wlr_ext_output_image_capture_source_v1_frame_event { @@ -43,30 +55,150 @@ struct wlr_ext_output_image_capture_source_v1_frame_event { struct timespec when; }; +static void handle_hidden_commit(struct wl_listener *listener, void *data) { + struct wlr_ext_output_image_capture_source_v1 *source = + wl_container_of(listener, source, hidden_output_commit); + struct wlr_output_event_commit *event = data; + + if (!(event->state->committed & WLR_OUTPUT_STATE_BUFFER)) + return; + struct wlr_buffer *buffer = event->state->buffer; + pixman_region32_t damage; + pixman_region32_init_rect(&damage, 0, 0, buffer->width, buffer->height); + + struct wlr_ext_output_image_capture_source_v1_frame_event frame_event = { + .base = { .damage = &damage }, + .buffer = buffer, + .when = event->when, + }; + wl_signal_emit_mutable(&source->base.events.frame, &frame_event); + pixman_region32_fini(&damage); +} + +static void handle_hidden_frame(struct wl_listener *listener, void *data) { + struct wlr_ext_output_image_capture_source_v1 *source = + wl_container_of(listener, source, hidden_output_frame); + + + pixman_region32_t damage; + pixman_region32_init_rect(&damage, 0, 0, + source->hidden_output->width, source->hidden_output->height); + pixman_region32_copy(&source->hidden_scene_output->pending_commit_damage, &damage); + pixman_region32_fini(&damage); + + struct wlr_scene_output_state_options opts = { + .color_transform = NULL, // sRGB + }; + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + if (!wlr_scene_output_build_state(source->hidden_scene_output, + &state, &opts)) { + wlr_output_state_finish(&state); + return; + } + wlr_output_commit_state(source->hidden_output, &state); + wlr_output_state_finish(&state); +} + +static struct wlr_backend *ensure_headless_backend( + struct wlr_ext_output_image_capture_source_manager_v1 *manager) { + if (manager->headless_backend) + return manager->headless_backend; + + struct wl_event_loop *loop = + wl_display_get_event_loop(manager->display); + manager->headless_backend = wlr_headless_backend_create(loop); + if (manager->headless_backend) + wlr_backend_start(manager->headless_backend); + return manager->headless_backend; +} + +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); +} + +void wlr_ext_output_image_capture_source_manager_v1_set_layout( + struct wlr_ext_output_image_capture_source_manager_v1 *manager, + struct wlr_output_layout *layout) { + manager->layout = layout; +} + 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); + 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); + + // Stop the real output from sending its ICC buffer to the capture session + wl_list_remove(&source->output_commit.link); + + struct wlr_output *real = source->output; + struct wlr_backend *headless = ensure_headless_backend(source->manager); + if (!headless) { + return; } - source->software_cursors_locked = with_cursors; + + source->hidden_output = wlr_headless_add_output(headless, + real->width, real->height); + if (!source->hidden_output) { + return; + } + wlr_output_init_render(source->hidden_output, + real->allocator, real->renderer); + source->hidden_scene_output = wlr_scene_output_create( + source->manager->scene, source->hidden_output); + if (source->manager->layout) { + struct wlr_box box; + wlr_output_layout_get_box(source->manager->layout, + source->output, &box); + wlr_scene_output_set_position(source->hidden_scene_output, + box.x, box.y); + } + + if (!source->hidden_scene_output) { + wlr_output_destroy(source->hidden_output); + source->hidden_output = NULL; + return; + } + source->hidden_output_frame.notify = handle_hidden_frame; + wl_signal_add(&source->hidden_output->events.frame, + &source->hidden_output_frame); + source->hidden_output_commit.notify = handle_hidden_commit; + wl_signal_add(&source->hidden_output->events.commit, + &source->hidden_output_commit); + + source_update_buffer_constraints(source); + wl_signal_emit_mutable(&source->hidden_output->events.frame, source->hidden_output); } 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); + struct wlr_ext_output_image_capture_source_v1 *source = + wl_container_of(base, source, base); source->num_started--; 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); + + // Let real output commit event flow once more + wl_signal_add(&source->output->events.commit, &source->output_commit); + + if (source->hidden_output) { + wl_list_remove(&source->hidden_output_frame.link); + wl_list_remove(&source->hidden_output_commit.link); + wlr_scene_output_destroy(source->hidden_scene_output); + wlr_output_destroy(source->hidden_output); + source->hidden_scene_output = NULL; + source->hidden_output = NULL; } } @@ -107,21 +239,6 @@ static const struct wlr_ext_image_capture_source_v1_interface output_source_impl .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 (!output->enabled) { - return; - } - - 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); @@ -200,6 +317,8 @@ static void output_manager_handle_create_source(struct wl_client *client, wlr_addon_init(&source->addon, &output->addons, NULL, &output_addon_impl); source->output = output; + source->manager = wl_resource_get_user_data(manager_resource); + source->output_commit.notify = source_handle_output_commit; wl_signal_add(&output->events.commit, &source->output_commit); @@ -253,6 +372,10 @@ struct wlr_ext_output_image_capture_source_manager_v1 *wlr_ext_output_image_capt return NULL; } + manager->display = display; + manager->headless_backend = NULL; + + manager->global = wl_global_create(display, &ext_output_image_capture_source_manager_v1_interface, version, manager, output_manager_bind); if (manager->global == NULL) { @@ -266,6 +389,12 @@ struct wlr_ext_output_image_capture_source_manager_v1 *wlr_ext_output_image_capt return manager; } +void wlr_ext_output_image_capture_source_manager_v1_set_scene( + struct wlr_ext_output_image_capture_source_manager_v1 *manager, + struct wlr_scene *scene) { + manager->scene = scene; +} + static void output_cursor_source_request_frame(struct wlr_ext_image_capture_source_v1 *base, bool schedule_frame) { struct output_cursor_source *cursor_source = wl_container_of(base, cursor_source, base);