mirror of
https://gitlab.freedesktop.org/wlroots/wlroots.git
synced 2026-05-09 09:28:08 +02:00
ext_image_capture_source_v1: render hidden sRGB for ICC correct
screencopy When a compositor uses color transforms (ICC profiles), the output's post-render buffer is in the display's color space, not sRGB. A screenshot client like grim receives this buffer and saves it as an untagged PNG, which then appears over-saturated in non-color-managed viewers. To avoid this, the output capture source now creates a hidden headless output that re-renders the same scene graph with an identity color transform (sRGB). The hidden output is driven entirely within the capture source and does not affect the real output or cause any visual flicker.
This commit is contained in:
parent
57441ded02
commit
5c1ba2cf04
2 changed files with 149 additions and 25 deletions
|
|
@ -12,6 +12,7 @@
|
|||
#include <pixman.h>
|
||||
#include <wayland-server-core.h>
|
||||
#include <wlr/render/drm_format_set.h>
|
||||
#include <wlr/backend.h>
|
||||
|
||||
struct wlr_scene_node;
|
||||
struct wlr_allocator;
|
||||
|
|
@ -77,12 +78,19 @@ 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 {
|
||||
struct wl_listener display_destroy;
|
||||
} WLR_PRIVATE;
|
||||
struct wlr_backend *headless_backend;
|
||||
};
|
||||
|
||||
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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@
|
|||
#include <wlr/types/wlr_ext_image_copy_capture_v1.h>
|
||||
#include <wlr/types/wlr_output.h>
|
||||
#include <wlr/util/addon.h>
|
||||
#include <wlr/backend/headless.h>
|
||||
#include <wlr/types/wlr_scene.h>
|
||||
#include "types/wlr_scene.h"
|
||||
#include <wayland-server-core.h>
|
||||
#include "ext-image-capture-source-v1-protocol.h"
|
||||
#include <wlr/util/log.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,137 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
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->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 +226,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 +304,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 +359,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 +376,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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue