Skip to content
Commits on Source (13)
  • Pekka Paalanen's avatar
    protocol: new screenshooter protocol · d0eca43b
    Pekka Paalanen authored
    
    
    This is a completely new screenshooting protocol designed to support:
    - color management testing by adding the "blending" source
    - KMS testing by adding the "writeback" source
    - output decorations testing by adding the "full_framebuffer" source
    - proper buffer size negotiation instead of guessing from wl_output
    - compositor chosen pixel format, primarily for "blending" source
    - proper indication of screenshot failure
    - dmabuf target buffers, linear only
    
    This new protocol should be good enough to publish as a Weston public
    extension. Hence install it.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    d0eca43b
  • Pekka Paalanen's avatar
    libweston: implement new screenshooting protocol base · 3700c781
    Pekka Paalanen authored
    
    
    This implements the basics of the new screenshooting protocol. The
    actual pixel operations will be implemented separately in the renderers
    and DRM-backend.
    
    See the previous commit "protocol: new screenshooter protocol" for why.
    
    If DRM-backend needs more from weston_capture_task when it implements
    writeback screenshooting, it will be easy to add user_data or expose
    weston_capture_task::link for the backend to use. Those were not added
    yet because it is uncertain what is actually needed.
    
    The DRM-backend no-damage optimization requires special handling here as
    well. See also 7f1a113c .
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    3700c781
  • Pekka Paalanen's avatar
    libweston: add pixel_format_get_shm_format() · c7f5de6d
    Pekka Paalanen authored
    
    
    This will be useful for client code that wants to create a wl_shm buffer
    with a DRM format code.
    
    The test suite will be using this.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    c7f5de6d
  • Pekka Paalanen's avatar
    pixman-renderer: implement output capture · 0c8c2c40
    Pekka Paalanen authored
    
    
    This services output capture tasks for the 'framebuffer' and 'blending'
    pixel sources.
    
    Just like the old screenshooting path, the 'framebuffer' pixel source is
    the hardware buffer, whether a shadow is used or not. This may not be
    the best for performance, but you do get the real framebuffer contents.
    Maybe it's rgb565, or even less.
    
    When the shadow buffer is used, I realized it is effectively the same as
    the intermediate blending buffer in GL-renderer when color management is
    used. Pixman-renderer does non-linear blending only, so the shadow
    buffer is in the blending space. The shadow buffer is also always 8 bpc
    regardless of the hardware framebuffer, so the read-back may be
    different from the hardware framebuffer. Read-back from the shadow is
    optimal for performance, but not what the hardware gets.
    
    'full-framebuffer' source cannot yet be implemented, because backends do
    not tell Pixman-renderer about the margins where the wayland-backend
    blits the output decorations. The target "hardware" buffer handed to
    pixman-renderer does not allow accessing the decorations area.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    0c8c2c40
  • Pekka Paalanen's avatar
    gl-renderer: implement output capture · f60c9cc1
    Pekka Paalanen authored
    
    
    This services output capture tasks for the 'framebuffer' and 'full
    framebuffer' pixel sources.
    
    Both pixel sources come from the same source: the EGLSurface. The only
    difference is the area. The EGLSurface contains the borders used for
    output decorations, hence 'full framebuffer' is possible to capture.
    
    We use GL_ANGLE_pack_reverse_row_order extension to make glReadPixels
    return the image data in the layout we need for wl_shm buffers directly.
    Without the extension we have to flip manually.
    
    Another extension to the same effect is MESA_pack_invert, but this is
    not specified for GL ES. It also uses a different token value, so it
    cannot be directly substituted even if supported.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    f60c9cc1
  • Pekka Paalanen's avatar
    compositor: make new screenhots free for all on --debug · e40a7eae
    Pekka Paalanen authored
    
    
    Replicating the policy of the old screenshooting interface, allow all
    screenshot to anyone with the new interface as well when --debug is
    used.
    
    Looks like there was one stray trailing space in unrelated code that my
    editor deleted. Better this way.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    e40a7eae
  • Pekka Paalanen's avatar
    tests: use pixel-formats.h in create_shm_buffer() · c93b18ea
    Pekka Paalanen authored
    
    
    Instead of starting yet another hand-crafted pixel format mapping table,
    use the one we have.
    
    Following patches want to be able to create XRGB8888 buffers, and later
    even other formats.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    c93b18ea
  • Pekka Paalanen's avatar
    tests: add output-capture-protocol test · e3046b12
    Pekka Paalanen authored
    
    
    For exercising various code paths in the server side protocol
    implementation.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    e3046b12
  • Pekka Paalanen's avatar
    tests: rewrite screenshooting to use new protocol · 849b87e2
    Pekka Paalanen authored
    
    
    Migrate the tst suite to using the new screenshooting protocol. This
    ensures the new protocol and implementation work, and removes a user of
    the old protocol so that the old protocol can be removed in the future.
    
    Now that the compositor chooses the pixel format,
    capture_screenshot_of_output() needs to convert to the expected format
    in the tests when necessary.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    849b87e2
  • Pekka Paalanen's avatar
    compositor: authorize new screenshooter protocol · 93a94e76
    Pekka Paalanen authored
    
    
    This is necessary in order to convert clients/screenshot.c into the
    protocol.
    
    We authorize a client the same way as before: if the wl_client is the
    one we spawned, it's allowed.
    
    Allowing screenshots on --debug was authorized by an earlier patch in
    compositor/main.c.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    93a94e76
  • Pekka Paalanen's avatar
    clients: rewrite screenshot.c for new protocol · 949b2eb7
    Pekka Paalanen authored
    
    
    The functionality of this screenshooting helper client is kept exactly
    the same as before: if you have multiple outputs, some transformed, some
    scale, in any layout, this will create a "multi-image" where the
    framebuffer (the physical image) of each output is pasted into a row of
    images in the order the outputs were advertised thrugh wl_registry.
    Output transform or scale are not accounted for. If you have a monitor
    rotated sideways, the screenshot will have the image of that monitor
    reverse-sideways.
    
    Otherwise the client is almost completely re-written, so trying to read
    the diff is not that useful.
    
    The old screenshooting protocol is replaced with the new
    weston-output-capture protocol. This makes it unnecessary to listen for
    wl_output information (since we do not handle output transform or scale
    anyway).
    
    The buffer sizes and formats are dictated by the compositor, which also
    means we cannot hardcode the format. Hence, use Pixman for the blitting,
    in case it needs to do format conversion. It is good to get rid of
    hand-crafted pixel data manipulation code too.
    
    For that reason we also need a pixel format database to convert between
    DRM fourcc, wl_shm and Pixman codes. We link to libweston to borrow its
    database instead of inventing another partial copy of it. It's weird to
    use compositor library private API in a client, but better than the
    alternative.
    
    The original code had no tear-down code at all. Now, if everything
    succeeds, the program ends with no unfreed memory according to ASan. If
    something fails, it still YOLO's it (doesn't free stuff). That's how far
    my pedantry carried.
    
    I also did not bother taking output transform or scale into account,
    since the old code did not either. It would be nice to create a seamless
    image of the desktop with shots rotated and scaled to align, in the max
    scale over all outputs. Meh.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    949b2eb7
  • Pekka Paalanen's avatar
    compositor: remove weston-screenshooter protocol · 17df553b
    Pekka Paalanen authored
    
    
    There are no internal users left for this protocol, they have been
    migrated to the new weston-output-capture protocol. There are no
    external users, because this protocol was private and never installed.
    
    Remove this dead code.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    17df553b
  • Pekka Paalanen's avatar
    libweston: deprecate weston_screenshooter_shoot() · fdaf4cf3
    Pekka Paalanen authored
    
    
    Nothing in-tree uses this function, and its functionality has been
    replaced with the weston-output-capture protocol extension which is
    implemented in libweston core.
    
    Users of this function should migrate to
    weston_compositor_add_screenshot_authority() and replace custom
    screenshooting protocols with weston-output-capture.xml installed by
    libweston.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    fdaf4cf3
......@@ -364,10 +364,14 @@ if get_option('shell-desktop')
exe_shooter = executable(
'weston-screenshooter',
'screenshot.c',
weston_screenshooter_client_protocol_h,
weston_screenshooter_protocol_c,
weston_output_capture_client_protocol_h,
weston_output_capture_protocol_c,
include_directories: common_inc,
dependencies: dep_toytoolkit,
dependencies: [
dep_toytoolkit,
dep_libweston_private, # for pixel-formats.h
dep_pixman,
],
install_dir: get_option('bindir'),
install: true
)
......
/*
* Copyright © 2008 Kristian Høgsberg
* Copyright 2022 Collabora, Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
......@@ -24,6 +25,7 @@
#include "config.h"
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
......@@ -33,25 +35,49 @@
#include <limits.h>
#include <sys/param.h>
#include <sys/mman.h>
#include <pixman.h>
#include <cairo.h>
#include <assert.h>
#include <wayland-client.h>
#include "weston-screenshooter-client-protocol.h"
#include "weston-output-capture-client-protocol.h"
#include "shared/os-compatibility.h"
#include "shared/xalloc.h"
#include "shared/file-util.h"
#include "pixel-formats.h"
/* The screenshooter is a good example of a custom object exposed by
* the compositor and serves as a test bed for implementing client
* side marshalling outside libwayland.so */
struct screenshooter_app {
struct wl_registry *registry;
struct wl_shm *shm;
struct weston_capture_v1 *capture_factory;
struct wl_list output_list; /* struct screenshooter_output::link */
struct screenshooter_output {
struct wl_output *output;
struct wl_buffer *buffer;
int width, height, offset_x, offset_y;
bool retry;
bool failed;
int waitcount;
};
struct screenshooter_buffer {
size_t len;
void *data;
struct wl_list link;
struct wl_buffer *wl_buffer;
pixman_image_t *image;
};
struct screenshooter_output {
struct screenshooter_app *app;
struct wl_list link; /* struct screenshooter_app::output_list */
struct wl_output *wl_output;
int offset_x, offset_y;
struct weston_capture_source_v1 *source;
int buffer_width;
int buffer_height;
const struct pixel_format_info *fmt;
struct screenshooter_buffer *buffer;
};
struct buffer_size {
......@@ -61,97 +87,204 @@ struct buffer_size {
int max_x, max_y;
};
struct screenshooter_data {
struct wl_shm *shm;
struct wl_list output_list;
static struct screenshooter_buffer *
screenshot_create_shm_buffer(struct screenshooter_app *app,
size_t width, size_t height,
const struct pixel_format_info *fmt)
{
struct screenshooter_buffer *buffer;
struct wl_shm_pool *pool;
int fd;
size_t bytes_pp;
size_t stride;
struct weston_screenshooter *screenshooter;
int buffer_copy_done;
};
assert(width > 0);
assert(height > 0);
assert(fmt && fmt->bpp > 0);
assert(fmt->pixman_format);
buffer = xzalloc(sizeof *buffer);
bytes_pp = fmt->bpp / 8;
stride = width * bytes_pp;
buffer->len = stride * height;
assert(width == stride / bytes_pp);
assert(height == buffer->len / stride);
fd = os_create_anonymous_file(buffer->len);
if (fd < 0) {
fprintf(stderr, "creating a buffer file for %zd B failed: %s\n",
buffer->len, strerror(errno));
free(buffer);
return NULL;
}
buffer->data = mmap(NULL, buffer->len, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (buffer->data == MAP_FAILED) {
fprintf(stderr, "mmap failed: %s\n", strerror(errno));
close(fd);
free(buffer);
return NULL;
}
pool = wl_shm_create_pool(app->shm, fd, buffer->len);
close(fd);
buffer->wl_buffer =
wl_shm_pool_create_buffer(pool, 0, width, height, stride,
pixel_format_get_shm_format(fmt));
wl_shm_pool_destroy(pool);
buffer->image = pixman_image_create_bits(fmt->pixman_format,
width, height,
buffer->data, stride);
abort_oom_if_null(buffer->image);
return buffer;
}
static void
display_handle_geometry(void *data,
struct wl_output *wl_output,
int x,
int y,
int physical_width,
int physical_height,
int subpixel,
const char *make,
const char *model,
int transform)
screenshooter_buffer_destroy(struct screenshooter_buffer *buffer)
{
struct screenshooter_output *output;
if (!buffer)
return;
output = wl_output_get_user_data(wl_output);
pixman_image_unref(buffer->image);
munmap(buffer->data, buffer->len);
wl_buffer_destroy(buffer->wl_buffer);
free(buffer);
}
if (wl_output == output->output) {
output->offset_x = x;
output->offset_y = y;
}
static void
capture_source_handle_format(void *data,
struct weston_capture_source_v1 *proxy,
uint32_t drm_format)
{
struct screenshooter_output *output = data;
assert(output->source == proxy);
output->fmt = pixel_format_get_info(drm_format);
}
static void
display_handle_mode(void *data,
struct wl_output *wl_output,
uint32_t flags,
int width,
int height,
int refresh)
capture_source_handle_size(void *data,
struct weston_capture_source_v1 *proxy,
int32_t width, int32_t height)
{
struct screenshooter_output *output;
struct screenshooter_output *output = data;
output = wl_output_get_user_data(wl_output);
assert(width > 0);
assert(height > 0);
if (wl_output == output->output && (flags & WL_OUTPUT_MODE_CURRENT)) {
output->width = width;
output->height = height;
}
output->buffer_width = width;
output->buffer_height = height;
}
static const struct wl_output_listener output_listener = {
display_handle_geometry,
display_handle_mode
};
static void
capture_source_handle_complete(void *data,
struct weston_capture_source_v1 *proxy)
{
struct screenshooter_output *output = data;
output->app->waitcount--;
}
static void
screenshot_done(void *data, struct weston_screenshooter *screenshooter)
capture_source_handle_retry(void *data,
struct weston_capture_source_v1 *proxy)
{
struct screenshooter_data *sh_data = data;
sh_data->buffer_copy_done = 1;
struct screenshooter_output *output = data;
output->app->waitcount--;
output->app->retry = true;
}
static const struct weston_screenshooter_listener screenshooter_listener = {
screenshot_done
static void
capture_source_handle_failed(void *data,
struct weston_capture_source_v1 *proxy,
const char *msg)
{
struct screenshooter_output *output = data;
output->app->waitcount--;
output->app->failed = true;
if (msg)
fprintf(stderr, "Output capture error: %s\n", msg);
}
static const struct weston_capture_source_v1_listener capture_source_handlers = {
.format = capture_source_handle_format,
.size = capture_source_handle_size,
.complete = capture_source_handle_complete,
.retry = capture_source_handle_retry,
.failed = capture_source_handle_failed,
};
static void
create_output(struct screenshooter_app *app, uint32_t output_name, uint32_t version)
{
struct screenshooter_output *output;
version = MIN(version, 4);
output = xzalloc(sizeof *output);
output->app = app;
output->wl_output = wl_registry_bind(app->registry, output_name,
&wl_output_interface, version);
abort_oom_if_null(output->wl_output);
output->source = weston_capture_v1_create(app->capture_factory,
output->wl_output,
WESTON_CAPTURE_V1_SOURCE_FRAMEBUFFER);
abort_oom_if_null(output->source);
weston_capture_source_v1_add_listener(output->source,
&capture_source_handlers, output);
wl_list_insert(&app->output_list, &output->link);
}
static void
destroy_output(struct screenshooter_output *output)
{
weston_capture_source_v1_destroy(output->source);
if (wl_output_get_version(output->wl_output) >= WL_OUTPUT_RELEASE_SINCE_VERSION)
wl_output_release(output->wl_output);
else
wl_output_destroy(output->wl_output);
screenshooter_buffer_destroy(output->buffer);
wl_list_remove(&output->link);
free(output);
}
static void
handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
static struct screenshooter_output *output;
struct screenshooter_data *sh_data = data;
if (strcmp(interface, "wl_output") == 0) {
output = xmalloc(sizeof *output);
output->output = wl_registry_bind(registry, name,
&wl_output_interface, 1);
wl_list_insert(&sh_data->output_list, &output->link);
wl_output_add_listener(output->output, &output_listener, output);
} else if (strcmp(interface, "wl_shm") == 0) {
sh_data->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
} else if (strcmp(interface, "weston_screenshooter") == 0) {
sh_data->screenshooter = wl_registry_bind(registry, name,
&weston_screenshooter_interface,
1);
struct screenshooter_app *app = data;
if (strcmp(interface, wl_output_interface.name) == 0) {
create_output(app, name, version);
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
app->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
/*
* Not listening for format advertisements,
* weston_capture_source_v1.format event tells us what to use.
*/
} else if (strcmp(interface, weston_capture_v1_interface.name) == 0) {
app->capture_factory = wl_registry_bind(registry, name,
&weston_capture_v1_interface,
1);
}
}
static void
handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
{
/* XXX: unimplemented */
/* Dynamic output removals will just fail the respective shot. */
}
static const struct wl_registry_listener registry_listener = {
......@@ -159,80 +292,52 @@ static const struct wl_registry_listener registry_listener = {
handle_global_remove
};
static struct wl_buffer *
screenshot_create_shm_buffer(int width, int height, void **data_out,
struct wl_shm *shm)
static void
screenshooter_output_capture(struct screenshooter_output *output)
{
struct wl_shm_pool *pool;
struct wl_buffer *buffer;
int fd, size, stride;
void *data;
stride = width * 4;
size = stride * height;
fd = os_create_anonymous_file(size);
if (fd < 0) {
fprintf(stderr, "creating a buffer file for %d B failed: %s\n",
size, strerror(errno));
return NULL;
}
data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
fprintf(stderr, "mmap failed: %s\n", strerror(errno));
close(fd);
return NULL;
}
pool = wl_shm_create_pool(shm, fd, size);
close(fd);
buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride,
WL_SHM_FORMAT_XRGB8888);
wl_shm_pool_destroy(pool);
*data_out = data;
return buffer;
screenshooter_buffer_destroy(output->buffer);
output->buffer = screenshot_create_shm_buffer(output->app,
output->buffer_width,
output->buffer_height,
output->fmt);
abort_oom_if_null(output->buffer);
weston_capture_source_v1_capture(output->source,
output->buffer->wl_buffer);
output->app->waitcount++;
}
static void
screenshot_write_png(const struct buffer_size *buff_size,
struct wl_list *output_list)
{
int output_stride, buffer_stride, i;
pixman_image_t *shot;
cairo_surface_t *surface;
void *data, *d, *s;
struct screenshooter_output *output, *next;
struct screenshooter_output *output;
FILE *fp;
char filepath[PATH_MAX];
buffer_stride = buff_size->width * 4;
data = xmalloc(buffer_stride * buff_size->height);
if (!data)
return;
wl_list_for_each_safe(output, next, output_list, link) {
output_stride = output->width * 4;
s = output->data;
d = data + (output->offset_y - buff_size->min_y) * buffer_stride +
(output->offset_x - buff_size->min_x) * 4;
shot = pixman_image_create_bits(PIXMAN_a8r8g8b8,
buff_size->width, buff_size->height,
NULL, 0);
abort_oom_if_null(shot);
for (i = 0; i < output->height; i++) {
memcpy(d, s, output_stride);
d += buffer_stride;
s += output_stride;
}
free(output);
wl_list_for_each(output, output_list, link) {
pixman_image_composite32(PIXMAN_OP_SRC,
output->buffer->image, /* src */
NULL, /* mask */
shot, /* dest */
0, 0, /* src x,y */
0, 0, /* mask x,y */
output->offset_x, output->offset_y, /* dst x,y */
output->buffer_width, output->buffer_height);
}
surface = cairo_image_surface_create_for_data(data,
surface = cairo_image_surface_create_for_data((void *)pixman_image_get_data(shot),
CAIRO_FORMAT_ARGB32,
buff_size->width,
buff_size->height,
buffer_stride);
pixman_image_get_width(shot),
pixman_image_get_height(shot),
pixman_image_get_stride(shot));
fp = file_create_dated(getenv("XDG_PICTURES_DIR"), "wayland-screenshot-",
".png", filepath, sizeof(filepath));
......@@ -241,11 +346,12 @@ screenshot_write_png(const struct buffer_size *buff_size,
cairo_surface_write_to_png(surface, filepath);
}
cairo_surface_destroy(surface);
free(data);
pixman_image_unref(shot);
}
static int
screenshot_set_buffer_size(struct buffer_size *buff_size, struct wl_list *output_list)
screenshot_set_buffer_size(struct buffer_size *buff_size,
struct wl_list *output_list)
{
struct screenshooter_output *output;
buff_size->min_x = buff_size->min_y = INT_MAX;
......@@ -254,16 +360,16 @@ screenshot_set_buffer_size(struct buffer_size *buff_size, struct wl_list *output
wl_list_for_each_reverse(output, output_list, link) {
output->offset_x = position;
position += output->width;
position += output->buffer_width;
}
wl_list_for_each(output, output_list, link) {
buff_size->min_x = MIN(buff_size->min_x, output->offset_x);
buff_size->min_y = MIN(buff_size->min_y, output->offset_y);
buff_size->max_x =
MAX(buff_size->max_x, output->offset_x + output->width);
MAX(buff_size->max_x, output->offset_x + output->buffer_width);
buff_size->max_y =
MAX(buff_size->max_y, output->offset_y + output->height);
MAX(buff_size->max_y, output->offset_y + output->buffer_height);
}
if (buff_size->max_x <= buff_size->min_x ||
......@@ -276,13 +382,16 @@ screenshot_set_buffer_size(struct buffer_size *buff_size, struct wl_list *output
return 0;
}
int main(int argc, char *argv[])
int
main(int argc, char *argv[])
{
struct wl_display *display;
struct wl_registry *registry;
struct screenshooter_output *output;
struct screenshooter_output *tmp_output;
struct buffer_size buff_size = {};
struct screenshooter_data sh_data = {};
struct screenshooter_app app = {};
wl_list_init(&app.output_list);
display = wl_display_connect(NULL);
if (display == NULL) {
......@@ -291,39 +400,52 @@ int main(int argc, char *argv[])
return -1;
}
wl_list_init(&sh_data.output_list);
registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, &sh_data);
wl_display_dispatch(display);
app.registry = wl_display_get_registry(display);
wl_registry_add_listener(app.registry, &registry_listener, &app);
/* Process wl_registry advertisements */
wl_display_roundtrip(display);
if (sh_data.screenshooter == NULL) {
fprintf(stderr, "display doesn't support screenshooter\n");
if (!app.shm) {
fprintf(stderr, "Error: display does not support wl_shm\n");
return -1;
}
if (!app.capture_factory) {
fprintf(stderr, "Error: display does not support weston_capture_v1\n");
return -1;
}
weston_screenshooter_add_listener(sh_data.screenshooter,
&screenshooter_listener,
&sh_data);
/* Process initial events for wl_output and weston_capture_source_v1 */
wl_display_roundtrip(display);
if (screenshot_set_buffer_size(&buff_size, &sh_data.output_list))
return -1;
do {
app.retry = false;
wl_list_for_each(output, &app.output_list, link)
screenshooter_output_capture(output);
wl_list_for_each(output, &sh_data.output_list, link) {
output->buffer =
screenshot_create_shm_buffer(output->width,
output->height,
&output->data,
sh_data.shm);
weston_screenshooter_take_shot(sh_data.screenshooter,
output->output,
output->buffer);
sh_data.buffer_copy_done = 0;
while (!sh_data.buffer_copy_done)
wl_display_roundtrip(display);
while (app.waitcount > 0 && !app.failed) {
if (wl_display_dispatch(display) < 0)
app.failed = true;
assert(app.waitcount >= 0);
}
} while (app.retry && !app.failed);
if (!app.failed) {
if (screenshot_set_buffer_size(&buff_size, &app.output_list) < 0)
return -1;
screenshot_write_png(&buff_size, &app.output_list);
} else {
fprintf(stderr, "Error: screenshot or protocol failure\n");
}
screenshot_write_png(&buff_size, &sh_data.output_list);
wl_list_for_each_safe(output, tmp_output, &app.output_list, link)
destroy_output(output);
weston_capture_v1_destroy(app.capture_factory);
wl_shm_destroy(app.shm);
wl_registry_destroy(app.registry);
wl_display_disconnect(display);
return 0;
}
......@@ -130,6 +130,7 @@ struct wet_compositor {
pid_t autolaunch_pid;
bool autolaunch_watch;
bool use_color_manager;
struct wl_listener screenshot_auth;
};
static FILE *weston_logfile = NULL;
......@@ -2046,7 +2047,7 @@ drm_backend_output_configure(struct weston_output *output,
mode = WESTON_DRM_BACKEND_OUTPUT_CURRENT;
/* If mode=current and no max-bpc was specfied on the .ini file,
use current max_bpc so full modeset is not done. */
if (!max_bpc_specified)
if (!max_bpc_specified)
max_bpc = 0;
} else if (strcmp(s, "preferred") != 0) {
modeline = s;
......@@ -3575,6 +3576,17 @@ weston_log_subscribe_to_scopes(struct weston_log_context *log_ctx,
weston_log_setup_scopes(log_ctx, flight_rec, flight_rec_scopes);
}
static void
screenshot_allow_all(struct wl_listener *l,
struct weston_output_capture_attempt *att)
{
/*
* The effect of --debug option: indiscriminately allow everyone to
* take screenshots of any output.
*/
att->authorized = true;
}
static void
sigint_helper(int sig)
{
......@@ -3779,8 +3791,12 @@ wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data)
protologger = wl_display_add_protocol_logger(display,
protocol_log_fn,
NULL);
if (debug_protocol)
if (debug_protocol) {
weston_compositor_enable_debug_protocol(wet.compositor);
weston_compositor_add_screenshot_authority(wet.compositor,
&wet.screenshot_auth,
screenshot_allow_all);
}
if (flight_rec)
weston_compositor_add_debug_binding(wet.compositor, KEY_D,
......
......@@ -7,8 +7,6 @@ srcs_weston = [
text_input_unstable_v1_protocol_c,
input_method_unstable_v1_server_protocol_h,
input_method_unstable_v1_protocol_c,
weston_screenshooter_server_protocol_h,
weston_screenshooter_protocol_c,
]
deps_weston = [
dep_libshared,
......
/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright 2022 Collabora, Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
......@@ -30,87 +31,17 @@
#include <libweston/libweston.h>
#include "weston.h"
#include "weston-screenshooter-server-protocol.h"
#include "shared/helpers.h"
#include <libweston/weston-log.h>
struct screenshooter {
struct weston_compositor *ec;
struct wl_global *global;
struct wl_client *client;
struct weston_process process;
struct wl_listener destroy_listener;
struct weston_recorder *recorder;
struct wl_listener authorization;
};
static void
screenshooter_done(void *data, enum weston_screenshooter_outcome outcome)
{
struct wl_resource *resource = data;
switch (outcome) {
case WESTON_SCREENSHOOTER_SUCCESS:
weston_screenshooter_send_done(resource);
break;
case WESTON_SCREENSHOOTER_NO_MEMORY:
wl_resource_post_no_memory(resource);
break;
default:
break;
}
}
static void
screenshooter_take_shot(struct wl_client *client,
struct wl_resource *resource,
struct wl_resource *output_resource,
struct wl_resource *buffer_resource)
{
struct weston_output *output =
weston_head_from_resource(output_resource)->output;
struct weston_compositor *ec = output->compositor;
struct weston_buffer *buffer =
weston_buffer_from_resource(ec, buffer_resource);
if (buffer == NULL) {
wl_resource_post_no_memory(resource);
return;
}
weston_screenshooter_shoot(output, buffer, screenshooter_done, resource);
}
struct weston_screenshooter_interface screenshooter_implementation = {
screenshooter_take_shot
};
static void
bind_shooter(struct wl_client *client,
void *data, uint32_t version, uint32_t id)
{
struct screenshooter *shooter = data;
struct wl_resource *resource;
bool debug_enabled =
weston_compositor_is_debug_protocol_enabled(shooter->ec);
resource = wl_resource_create(client,
&weston_screenshooter_interface, 1, id);
if (!debug_enabled && !shooter->client) {
wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT,
"screenshooter failed: permission denied. "\
"Debug protocol must be enabled");
return;
} else if (!debug_enabled && client != shooter->client) {
wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT,
"screenshooter failed: permission denied.");
return;
}
wl_resource_set_implementation(resource, &screenshooter_implementation,
data, NULL);
}
static void
screenshooter_sigchld(struct weston_process *process, int status)
{
......@@ -165,6 +96,17 @@ recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time,
}
}
static void
authorize_screenshooter(struct wl_listener *l,
struct weston_output_capture_attempt *att)
{
struct screenshooter *shooter = wl_container_of(l, shooter,
authorization);
if (shooter->client && att->who->client == shooter->client)
att->authorized = true;
}
static void
screenshooter_destroy(struct wl_listener *listener, void *data)
{
......@@ -172,8 +114,8 @@ screenshooter_destroy(struct wl_listener *listener, void *data)
container_of(listener, struct screenshooter, destroy_listener);
wl_list_remove(&shooter->destroy_listener.link);
wl_list_remove(&shooter->authorization.link);
wl_global_destroy(shooter->global);
free(shooter);
}
......@@ -188,9 +130,6 @@ screenshooter_create(struct weston_compositor *ec)
shooter->ec = ec;
shooter->global = wl_global_create(ec->wl_display,
&weston_screenshooter_interface, 1,
shooter, bind_shooter);
weston_compositor_add_key_binding(ec, KEY_S, MODIFIER_SUPER,
screenshooter_binding, shooter);
weston_compositor_add_key_binding(ec, KEY_R, MODIFIER_SUPER,
......@@ -198,4 +137,7 @@ screenshooter_create(struct weston_compositor *ec)
shooter->destroy_listener.notify = screenshooter_destroy;
wl_signal_add(&ec->destroy_signal, &shooter->destroy_listener);
weston_compositor_add_screenshot_authority(ec, &shooter->authorization,
authorize_screenshooter);
}
......@@ -91,6 +91,7 @@ struct ro_anonymous_file;
struct weston_color_profile;
struct weston_color_transform;
struct pixel_format_info;
struct weston_output_capture_info;
enum weston_keyboard_modifier {
MODIFIER_CTRL = (1 << 0),
......@@ -502,6 +503,7 @@ struct weston_output {
int disable_planes;
int destroying;
struct wl_list feedback_list;
struct weston_output_capture_info *capture_info;
uint32_t transform;
int32_t native_scale;
......@@ -1328,6 +1330,12 @@ struct weston_compositor {
struct weston_log_pacer unmapped_surface_or_view_pacer;
struct weston_log_pacer presentation_clock_failure_pacer;
/** Screenshooting global state, see output-capture.c */
struct {
struct wl_global *weston_capture_v1;
struct wl_signal ask_auth;
} output_capture;
};
struct weston_solid_buffer_values {
......@@ -2110,7 +2118,7 @@ typedef void (*weston_screenshooter_done_func_t)(void *data,
enum weston_screenshooter_outcome outcome);
int
weston_screenshooter_shoot(struct weston_output *output, struct weston_buffer *buffer,
weston_screenshooter_done_func_t done, void *data);
weston_screenshooter_done_func_t done, void *data) WL_DEPRECATED;
struct weston_recorder *
weston_recorder_start(struct weston_output *output, const char *filename);
void
......@@ -2363,6 +2371,31 @@ struct weston_color_profile *
weston_compositor_load_icc_file(struct weston_compositor *compositor,
const char *path);
/** Describes who is trying to capture and which output */
struct weston_output_capture_client {
struct wl_client *client;
struct weston_output *output;
};
/** Arguments asking to authorize a screenshot/capture
*
* \sa weston_compositor_add_screenshot_authority
*/
struct weston_output_capture_attempt {
const struct weston_output_capture_client *const who;
/** Set to true to authorize the screenshot. */
bool authorized;
/** Set to true to deny the screenshot. */
bool denied;
};
void
weston_compositor_add_screenshot_authority(struct weston_compositor *compositor,
struct wl_listener *listener,
void (*auth)(struct wl_listener *l,
struct weston_output_capture_attempt *att));
#ifdef __cplusplus
}
#endif
......
......@@ -55,6 +55,7 @@
#include "shared/timespec-util.h"
#include "shared/string-helpers.h"
#include "shared/weston-drm-fourcc.h"
#include "output-capture.h"
#include "pixman-renderer.h"
#include "pixel-formats.h"
#include "libbacklight.h"
......@@ -385,6 +386,7 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage)
*/
if (!pixman_region32_not_empty(damage) &&
wl_list_empty(&output->base.frame_signal.listener_list) &&
!weston_output_has_capture_tasks(&output->base) &&
scanout_plane->state_cur->fb &&
(scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE ||
scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB)) {
......
......@@ -79,6 +79,7 @@
#include "backend.h"
#include "libweston-internal.h"
#include "color.h"
#include "output-capture.h"
#include "weston-log-internal.h"
......@@ -3076,6 +3077,8 @@ weston_output_repaint(struct weston_output *output)
animation->frame(animation, output, &output->frame_time);
}
weston_output_capture_info_repaint_done(output->capture_info);
TL_POINT(ec, "core_repaint_posted", TLP_OUTPUT(output), TLP_END);
return r;
......@@ -6658,6 +6661,8 @@ weston_compositor_remove_output(struct weston_output *output)
wl_list_for_each(head, &output->head_list, output_link)
weston_head_remove_global(head);
weston_output_capture_info_destroy(&output->capture_info);
compositor->output_id_pool &= ~(1u << output->id);
output->id = 0xffffffff; /* invalid */
}
......@@ -7149,6 +7154,9 @@ weston_output_enable(struct weston_output *output)
if (!weston_output_set_color_outcome(output))
return -1;
output->capture_info = weston_output_capture_info_create();
assert(output->capture_info);
/* Enable the output (set up the crtc or create a
* window representing the output, set up the
* renderer, etc)
......@@ -7156,6 +7164,7 @@ weston_output_enable(struct weston_output *output)
if (output->enable(output) < 0) {
weston_log("Enabling output \"%s\" failed.\n", output->name);
weston_output_color_outcome_destroy(&output->color_outcome);
weston_output_capture_info_destroy(&output->capture_info);
return -1;
}
......@@ -8175,6 +8184,7 @@ weston_compositor_create(struct wl_display *display,
wl_signal_init(&ec->heads_changed_signal);
wl_signal_init(&ec->output_heads_changed_signal);
wl_signal_init(&ec->session_signal);
wl_signal_init(&ec->output_capture.ask_auth);
ec->session_active = true;
ec->output_id_pool = 0;
......@@ -8214,6 +8224,8 @@ weston_compositor_create(struct wl_display *display,
if (weston_input_init(ec) != 0)
goto fail;
weston_compositor_install_capture_protocol(ec);
wl_list_init(&ec->view_list);
wl_list_init(&ec->plane_list);
wl_list_init(&ec->layer_list);
......
......@@ -25,6 +25,7 @@ srcs_libweston = [
'linux-sync-file.c',
'log.c',
'noop-renderer.c',
'output-capture.c',
'pixel-formats.c',
'pixman-renderer.c',
'plugin-registry.c',
......@@ -50,8 +51,6 @@ srcs_libweston = [
pointer_constraints_unstable_v1_server_protocol_h,
relative_pointer_unstable_v1_protocol_c,
relative_pointer_unstable_v1_server_protocol_h,
weston_screenshooter_protocol_c,
weston_screenshooter_server_protocol_h,
single_pixel_buffer_v1_protocol_c,
single_pixel_buffer_v1_server_protocol_h,
text_cursor_position_protocol_c,
......@@ -70,6 +69,8 @@ srcs_libweston = [
weston_debug_server_protocol_h,
weston_direct_display_protocol_c,
weston_direct_display_server_protocol_h,
weston_output_capture_protocol_c,
weston_output_capture_server_protocol_h,
]
subdir('desktop')
......
This diff is collapsed.
/*
* Copyright 2022 Collabora, Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <libweston/libweston.h>
/** Copy of weston_capture_v1.source enum from protocol */
enum weston_output_capture_source {
/** DRM KMS hardware writeback */
WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK = 0,
/** framebuffer desktop area */
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
/** complete framebuffer, including borders if any */
WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER,
/** blending buffer, potentially light-linear */
WESTON_OUTPUT_CAPTURE_SOURCE_BLENDING,
};
#define WESTON_OUTPUT_CAPTURE_SOURCE__COUNT (WESTON_OUTPUT_CAPTURE_SOURCE_BLENDING + 1)
struct weston_output_capture_info;
/*
* For weston_output core implementation:
*/
struct weston_output_capture_info *
weston_output_capture_info_create(void);
void
weston_output_capture_info_destroy(struct weston_output_capture_info **cip);
void
weston_output_capture_info_repaint_done(struct weston_output_capture_info *ci);
/*
* For actual capturing implementations (renderers, DRM-backend):
*/
void
weston_output_update_capture_info(struct weston_output *output,
enum weston_output_capture_source src,
int width, int height,
const struct pixel_format_info *format);
bool
weston_output_has_capture_tasks(struct weston_output *output);
struct weston_capture_task;
struct weston_capture_task *
weston_output_pull_capture_task(struct weston_output *output,
enum weston_output_capture_source src,
int width, int height,
const struct pixel_format_info *format);
struct weston_buffer *
weston_capture_task_get_buffer(struct weston_capture_task *ct);
void
weston_capture_task_retire_failed(struct weston_capture_task *ct,
const char *err_msg);
void
weston_capture_task_retire_complete(struct weston_capture_task *ct);
/*
* entry point for weston_compositor
*/
void
weston_compositor_install_capture_protocol(struct weston_compositor *compositor);
......@@ -760,3 +760,19 @@ pixel_format_get_modifier(uint64_t modifier)
return mod_str;
}
WL_EXPORT uint32_t
pixel_format_get_shm_format(const struct pixel_format_info *info)
{
/* Only these two format codes differ between wl_shm and DRM fourcc */
switch (info->format) {
case DRM_FORMAT_ARGB8888:
return WL_SHM_FORMAT_ARGB8888;
case DRM_FORMAT_XRGB8888:
return WL_SHM_FORMAT_XRGB8888;
default:
break;
}
return info->format;
}
......@@ -345,3 +345,12 @@ pixel_format_height_for_plane(const struct pixel_format_info *format,
*/
char *
pixel_format_get_modifier(uint64_t modifier);
/**
* Return the wl_shm format code
*
* @param info Pixel format info structure
* @returns The wl_shm format code for this pixel format.
*/
uint32_t
pixel_format_get_shm_format(const struct pixel_format_info *info);
......@@ -35,9 +35,11 @@
#include "pixman-renderer.h"
#include "color.h"
#include "pixel-formats.h"
#include "output-capture.h"
#include "shared/helpers.h"
#include "shared/signal.h"
#include "shared/weston-drm-fourcc.h"
#include "shared/xalloc.h"
#include <linux/input.h>
......@@ -45,6 +47,7 @@ struct pixman_output_state {
pixman_image_t *shadow_image;
const struct pixel_format_info *shadow_format;
pixman_image_t *hw_buffer;
const struct pixel_format_info *hw_format;
pixman_region32_t *hw_extra_damage;
struct weston_size fb_size;
};
......@@ -578,6 +581,63 @@ copy_to_hw_buffer(struct weston_output *output, pixman_region32_t *region)
pixman_image_set_clip_region32 (po->hw_buffer, NULL);
}
static void
pixman_renderer_do_capture(struct weston_buffer *into, pixman_image_t *from)
{
struct wl_shm_buffer *shm = into->shm_buffer;
pixman_image_t *dest;
assert(into->type == WESTON_BUFFER_SHM);
assert(shm);
wl_shm_buffer_begin_access(shm);
dest = pixman_image_create_bits(into->pixel_format->pixman_format,
into->width, into->height,
wl_shm_buffer_get_data(shm),
wl_shm_buffer_get_stride(shm));
abort_oom_if_null(dest);
pixman_image_composite32(PIXMAN_OP_SRC, from, NULL /* mask */, dest,
0, 0, /* src_x, src_y */
0, 0, /* mask_x, mask_y */
0, 0, /* dest_x, dest_y */
into->width, into->height);
pixman_image_unref(dest);
wl_shm_buffer_end_access(shm);
}
static void
pixman_renderer_do_capture_tasks(struct weston_output *output,
enum weston_output_capture_source source,
pixman_image_t *from,
const struct pixel_format_info *pfmt)
{
int width = pixman_image_get_width(from);
int height = pixman_image_get_height(from);
struct weston_capture_task *ct;
while ((ct = weston_output_pull_capture_task(output, source,
width, height,
pfmt))) {
struct weston_buffer *buffer = weston_capture_task_get_buffer(ct);
assert(buffer->width == width);
assert(buffer->height == height);
assert(buffer->pixel_format->format == pfmt->format);
if (buffer->type != WESTON_BUFFER_SHM) {
weston_capture_task_retire_failed(ct, "pixman: unsupported buffer");
continue;
}
pixman_renderer_do_capture(buffer, from);
weston_capture_task_retire_complete(ct);
}
}
static void
pixman_renderer_repaint_output(struct weston_output *output,
pixman_region32_t *output_damage)
......@@ -604,10 +664,16 @@ pixman_renderer_repaint_output(struct weston_output *output,
if (po->shadow_image) {
repaint_surfaces(output, output_damage);
pixman_renderer_do_capture_tasks(output,
WESTON_OUTPUT_CAPTURE_SOURCE_BLENDING,
po->shadow_image, po->shadow_format);
copy_to_hw_buffer(output, &hw_damage);
} else {
repaint_surfaces(output, &hw_damage);
}
pixman_renderer_do_capture_tasks(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
po->hw_buffer, po->hw_format);
pixman_region32_fini(&hw_damage);
wl_signal_emit(&output->frame_signal, output_damage);
......@@ -866,6 +932,18 @@ pixman_renderer_resize_output(struct weston_output *output,
po->fb_size = *fb_size;
/*
* Have a hw_format only after the first call to
* pixman_renderer_output_set_buffer().
*/
if (po->hw_format) {
weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
po->fb_size.width,
po->fb_size.height,
po->hw_format);
}
if (!po->shadow_format)
return true;
......@@ -877,6 +955,12 @@ pixman_renderer_resize_output(struct weston_output *output,
fb_size->width, fb_size->height,
NULL, 0);
weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_BLENDING,
po->fb_size.width,
po->fb_size.height,
po->shadow_format);
return !!po->shadow_image;
}
......@@ -955,6 +1039,7 @@ WL_EXPORT void
pixman_renderer_output_set_buffer(struct weston_output *output,
pixman_image_t *buffer)
{
struct weston_compositor *compositor = output->compositor;
struct pixman_output_state *po = get_output_state(output);
pixman_format_code_t pixman_format;
......@@ -962,15 +1047,28 @@ pixman_renderer_output_set_buffer(struct weston_output *output,
pixman_image_unref(po->hw_buffer);
po->hw_buffer = buffer;
if (po->hw_buffer) {
pixman_format = pixman_image_get_format(po->hw_buffer);
output->compositor->read_format =
pixel_format_get_info_by_pixman(pixman_format);
pixman_image_ref(po->hw_buffer);
if (!po->hw_buffer)
return;
assert(po->fb_size.width == pixman_image_get_width(po->hw_buffer));
assert(po->fb_size.height == pixman_image_get_height(po->hw_buffer));
}
pixman_format = pixman_image_get_format(po->hw_buffer);
po->hw_format = pixel_format_get_info_by_pixman(pixman_format);
compositor->read_format = po->hw_format;
assert(po->hw_format);
pixman_image_ref(po->hw_buffer);
assert(po->fb_size.width == pixman_image_get_width(po->hw_buffer));
assert(po->fb_size.height == pixman_image_get_height(po->hw_buffer));
/*
* The size cannot change, but the format might, or we did not have
* hw_format in pixman_renderer_resize_output() yet.
*/
weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
po->fb_size.width,
po->fb_size.height,
po->hw_format);
}
WL_EXPORT void
......
......@@ -177,6 +177,7 @@ struct gl_renderer {
bool has_texture_type_2_10_10_10_rev;
bool has_gl_texture_rg;
bool has_texture_norm16;
bool has_pack_reverse;
struct gl_shader *current_shader;
struct gl_shader *fallback_shader;
......
......@@ -52,6 +52,7 @@
#include "linux-dmabuf.h"
#include "linux-dmabuf-unstable-v1-server-protocol.h"
#include "linux-explicit-synchronization.h"
#include "output-capture.h"
#include "pixel-formats.h"
#include "shared/fd-util.h"
......@@ -61,6 +62,7 @@
#include "shared/timespec-util.h"
#include "shared/weston-drm-fourcc.h"
#include "shared/weston-egl-ext.h"
#include "shared/xalloc.h"
#define BUFFER_DAMAGE_COUNT 2
......@@ -673,6 +675,138 @@ gl_fbo_texture_fini(struct gl_fbo_texture *fbotex)
fbotex->tex = 0;
}
static bool
gl_renderer_do_capture(struct gl_renderer *gr, struct weston_buffer *into,
const struct weston_geometry *rect)
{
struct wl_shm_buffer *shm = into->shm_buffer;
const struct pixel_format_info *fmt = into->pixel_format;
void *shm_pixels;
void *read_target;
int32_t stride;
pixman_image_t *tmp = NULL;
assert(fmt->gl_type != 0);
assert(fmt->gl_format != 0);
assert(into->type == WESTON_BUFFER_SHM);
assert(shm);
stride = wl_shm_buffer_get_stride(shm);
if (stride % 4 != 0)
return false;
glPixelStorei(GL_PACK_ALIGNMENT, 4);
shm_pixels = wl_shm_buffer_get_data(shm);
if (gr->has_pack_reverse) {
/* Make glReadPixels() return top row first. */
glPixelStorei(GL_PACK_REVERSE_ROW_ORDER_ANGLE, GL_TRUE);
read_target = shm_pixels;
} else {
/*
* glReadPixels() returns bottom row first. We need to
* read into a temporary buffer and y-flip it.
*/
tmp = pixman_image_create_bits(fmt->pixman_format,
rect->width, rect->height,
NULL, 0);
if (!tmp)
return false;
read_target = pixman_image_get_data(tmp);
}
wl_shm_buffer_begin_access(shm);
glReadPixels(rect->x, rect->y, rect->width, rect->height,
fmt->gl_format, fmt->gl_type, read_target);
if (tmp) {
pixman_image_t *shm_image;
pixman_transform_t flip;
shm_image = pixman_image_create_bits_no_clear(fmt->pixman_format,
rect->width,
rect->height,
shm_pixels,
stride);
abort_oom_if_null(shm_image);
pixman_transform_init_scale(&flip, pixman_fixed_1,
pixman_fixed_minus_1);
pixman_transform_translate(&flip, NULL, 0,
pixman_int_to_fixed(rect->height));
pixman_image_set_transform(tmp, &flip);
pixman_image_composite32(PIXMAN_OP_SRC,
tmp, /* src */
NULL, /* mask */
shm_image, /* dest */
0, 0, /* src x,y */
0, 0, /* mask x,y */
0, 0, /* dest x,y */
rect->width, rect->height);
pixman_image_unref(shm_image);
pixman_image_unref(tmp);
}
wl_shm_buffer_end_access(shm);
return true;
}
static void
gl_renderer_do_capture_tasks(struct gl_renderer *gr,
struct weston_output *output,
enum weston_output_capture_source source)
{
struct gl_output_state *go = get_output_state(output);
const struct pixel_format_info *format;
struct weston_capture_task *ct;
struct weston_geometry rect;
switch (source) {
case WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER:
format = output->compositor->read_format;
rect = go->area;
/* Because glReadPixels has bottom-left origin */
rect.y = go->fb_size.height - go->area.y - go->area.height;
break;
case WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER:
format = output->compositor->read_format;
rect.x = 0;
rect.y = 0;
rect.width = go->fb_size.width;
rect.height = go->fb_size.height;
break;
default:
assert(0);
return;
}
while ((ct = weston_output_pull_capture_task(output, source, rect.width,
rect.height, format))) {
struct weston_buffer *buffer = weston_capture_task_get_buffer(ct);
assert(buffer->width == rect.width);
assert(buffer->height == rect.height);
assert(buffer->pixel_format->format == format->format);
if (buffer->type != WESTON_BUFFER_SHM ||
buffer->buffer_origin != ORIGIN_TOP_LEFT) {
weston_capture_task_retire_failed(ct, "GL: unsupported buffer");
continue;
}
if (gl_renderer_do_capture(gr, buffer, &rect))
weston_capture_task_retire_complete(ct);
else
weston_capture_task_retire_failed(ct, "GL: capture failed");
}
}
static void
gl_renderer_send_shader_error(struct weston_view *view)
{
......@@ -1704,6 +1838,10 @@ gl_renderer_repaint_output(struct weston_output *output,
draw_output_borders(output, border_status);
gl_renderer_do_capture_tasks(gr, output,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER);
gl_renderer_do_capture_tasks(gr, output,
WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER);
wl_signal_emit(&output->frame_signal, output_damage);
go->end_render_sync = create_render_sync(gr);
......@@ -1753,6 +1891,7 @@ gl_renderer_read_pixels(struct weston_output *output,
uint32_t width, uint32_t height)
{
struct gl_output_state *go = get_output_state(output);
struct gl_renderer *gr = get_renderer(output->compositor);
x += go->area.x;
y += go->fb_size.height - go->area.y - go->area.height;
......@@ -1763,6 +1902,8 @@ gl_renderer_read_pixels(struct weston_output *output,
if (use_output(output) < 0)
return -1;
if (gr->has_pack_reverse)
glPixelStorei(GL_PACK_REVERSE_ROW_ORDER_ANGLE, GL_FALSE);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(x, y, width, height, format->gl_format,
format->gl_type, pixels);
......@@ -3070,6 +3211,8 @@ gl_renderer_surface_copy_content(struct weston_surface *surface,
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(0);
if (gr->has_pack_reverse)
glPixelStorei(GL_PACK_REVERSE_ROW_ORDER_ANGLE, GL_FALSE);
glPixelStorei(GL_PACK_ALIGNMENT, bytespp);
glReadPixels(src_x, src_y, width, height, gl_format,
GL_UNSIGNED_BYTE, target);
......@@ -3276,6 +3419,16 @@ gl_renderer_resize_output(struct weston_output *output,
go->fb_size = *fb_size;
go->area = *area;
weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER,
area->width, area->height,
output->compositor->read_format);
weston_output_update_capture_info(output,
WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER,
fb_size->width, fb_size->height,
output->compositor->read_format);
if (!shfmt)
return true;
......@@ -3923,6 +4076,9 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface)
if (weston_check_egl_extension(extensions, "GL_EXT_texture_norm16"))
gr->has_texture_norm16 = true;
if (weston_check_egl_extension(extensions, "GL_ANGLE_pack_reverse_row_order"))
gr->has_pack_reverse = true;
if (gr->gl_version >= gr_gl_version(3, 0) ||
weston_check_egl_extension(extensions, "GL_EXT_texture_rg"))
gr->has_gl_texture_rg = true;
......@@ -3959,6 +4115,8 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface)
gr_gl_version_minor(gr->gl_version));
weston_log_continue(STAMP_SPACE "read-back format: %s\n",
ec->read_format->drm_format_name);
weston_log_continue(STAMP_SPACE "glReadPixels supports y-flip: %s\n",
yesno(gr->has_pack_reverse));
weston_log_continue(STAMP_SPACE "wl_shm 10 bpc formats: %s\n",
yesno(gr->has_texture_type_2_10_10_10_rev));
weston_log_continue(STAMP_SPACE "wl_shm 16 bpc formats: %s\n",
......
......@@ -10,6 +10,7 @@ install_data(
'weston-content-protection.xml',
'weston-debug.xml',
'weston-direct-display.xml',
'weston-output-capture.xml',
],
install_dir: join_paths(dir_data, dir_protocol_libweston)
)
......@@ -32,7 +33,7 @@ generated_protocols = [
[ 'viewporter', 'stable' ],
[ 'weston-debug', 'internal' ],
[ 'weston-desktop-shell', 'internal' ],
[ 'weston-screenshooter', 'internal' ],
[ 'weston-output-capture', 'internal' ],
[ 'weston-content-protection', 'internal' ],
[ 'weston-test', 'internal' ],
[ 'weston-touch-calibration', 'internal' ],
......
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="weston_output_capture">
<copyright>
Copyright 2020, 2022 Collabora, Ltd.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="weston_capture_v1" version="1">
<description summary="image capture factory">
The global interface exposing Weston screenshooting functionality
intended for single shots.
This is a privileged inteface.
</description>
<request name="destroy" type="destructor">
<description summary="unbind image capture factory">
Affects no other protocol objects in any way.
</description>
</request>
<enum name="error">
<entry name="invalid_source" value="0"
summary="invalid source enum value"/>
</enum>
<enum name="source">
<entry name="writeback" value="0" summary="use hardware writeback"/>
<entry name="framebuffer" value="1"
summary="copy from framebuffer, desktop area"/>
<entry name="full_framebuffer" value="2"
summary="copy whole framebuffer, including borders"/>
<entry name="blending" value="3" summary="copy from blending space"/>
</enum>
<request name="create">
<description summary="create an object for capturing output images">
This creates a weston_capture_source_v1 object corresponding to the
given wl_output. The object delivers information for allocating
suitable buffers, and exposes the capture function.
The object will be using the given pixel source for capturing images.
If the source is not available, all attempts to capture will fail
gracefully.
'writeback' source will use hardware writeback feature of DRM KMS for
capturing. This may allow hardware planes to remain used
during the capture. This source is often not available.
'framebuffer' source copies the contents of the final framebuffer.
Using this source temporarily disables all use of hardware planes and
DRM KMS color pipeline features. This source is always available.
'full_framebuffer' is otherwise the same as 'framebuffer' except it
will include also any borders (decorations) that the framebuffer may
contain.
'blending' source copies the contents of the intermediate blending
buffer, which should be in linear-light format. Using this source
temporarily disables all use of hardware planes. This source is only
available when a blending buffer exists, e.g. when color management
is active on the output.
If the pixel source is not one of the defined enumeration values,
'invalid_source' protocol error is raised.
</description>
<arg name="output" type="object" interface="wl_output"
summary="output to shoot"/>
<arg name="source" type="uint" enum="source" summary="pixel source"/>
<arg name="capture_source_new_id" type="new_id"
interface="weston_capture_source_v1" summary="new object"/>
</request>
</interface>
<interface name="weston_capture_source_v1" version="1">
<description summary="image capturing source">
An object representing image capturing functionality for a single
source. When created, it sends the initial events if and only if the
output still exists and the specified pixel source is available on
the output.
</description>
<enum name="error">
<entry name="bad_buffer" value="0"
summary="the wl_buffer is not writable"/>
<entry name="sequence" value="1"
summary="capture requested again before previous retired"/>
</enum>
<request name="destroy" type="destructor">
<description summary="cancel the capture, and destroy">
If a capture is on-going on this object, this will cancel it and
make the image buffer contents undefined.
This object is destroyed.
</description>
</request>
<request name="capture">
<description summary="capture an image">
If the given wl_buffer is compatible, the associated output will go
through a repaint some time after this request has been processed,
and that repaint will execute the capture.
Once the capture is complete, 'complete' event is emitted.
If the given wl_buffer is incompatible, the event 'retry' is
emitted.
If the capture fails or the buffer type is unsupported, the event
'failed' is emitted.
The client must wait for one of these events before attempting
'capture' on this object again. If 'capture' is requested again before
any of those events, 'sequence' protocol error is raised.
The wl_buffer object will not emit wl_buffer.release event due to
this request.
The wl_buffer must refer to compositor-writable storage. If buffer
storage is not writable, either the protocol error bad_buffer or
wl_shm.error.invalid_fd is raised.
If the wl_buffer is destroyed before any event is emitted, the buffer
contents become undefined.
A compositor is required to implement capture into wl_shm buffers.
Other buffer types may or may not be supported.
</description>
<arg name="buffer" type="object" interface="wl_buffer"
summary="a writable image buffer"/>
</request>
<event name="format">
<description summary="pixel format for a buffer">
This event delivers the pixel format that should be used for the
image buffer. Any buffer is incompatible if it does not have
this pixel format.
The format modifier is linear (DRM_FORMAT_MOD_LINEAR).
This is an initial event, and sent whenever the required format
changes.
</description>
<arg name="drm_format" type="uint" summary="DRM pixel format code"/>
</event>
<event name="size">
<description summary="dimensions for a buffer">
This event delivers the size that should be used for the
image buffer. Any buffer is incompatible if it does not have
this size.
Row alignment of the buffer must be 4 bytes, and it must not contain
further row padding. Otherwise the buffer is unsupported.
This is an initial event, and sent whenever the required size
changes.
</description>
<arg name="width" type="int" summary="width in pixels"/>
<arg name="height" type="int" summary="height in pixels"/>
</event>
<event name="complete">
<description summary="capture has completed">
This event is emitted as a response to 'capture' request when it
has successfully completed.
If the buffer used in the shot is a dmabuf, the client also needs to
wait for any implicit fences on it before accessing the contents.
</description>
</event>
<event name="retry">
<description summary="retry image capture with a different buffer">
This event is emitted as a response to 'capture' request when it
cannot succeed due to an incompatible buffer. The client has already
received the events delivering the new buffer parameters. The client
should retry the capture with the new buffer parameters.
</description>
</event>
<event name="failed">
<description summary="capture failed">
This event is emitted as a response to 'capture' request when it
has failed for reasons other than an incompatible buffer. The reasons
may include: unsupported buffer type, unsupported buffer stride,
unsupported image source, the image source (output) was removed, or
compositor policy denied the capture.
The string 'msg' may contain a human-readable explanation of the
failure to aid debugging.
</description>
<arg name="msg" type="string" allow-null="true"
summary="human-readable hint"/>
</event>
</interface>
</protocol>
<protocol name="weston_screenshooter">
<interface name="weston_screenshooter" version="1">
<request name="take_shot">
<arg name="output" type="object" interface="wl_output"/>
<arg name="buffer" type="object" interface="wl_buffer"/>
</request>
<event name="done">
</event>
</interface>
</protocol>
......@@ -31,8 +31,8 @@ lib_test_client = static_library(
'weston-test-fixture-compositor.c',
weston_test_client_protocol_h,
weston_test_protocol_c,
weston_screenshooter_client_protocol_h,
weston_screenshooter_protocol_c,
weston_output_capture_client_protocol_h,
weston_output_capture_protocol_c,
viewporter_client_protocol_h,
viewporter_protocol_c,
'color_util.h',
......@@ -43,6 +43,8 @@ lib_test_client = static_library(
dep_libshared,
dep_wayland_client,
dep_libexec_weston,
dep_libweston_private,
dep_libdrm_headers,
dep_pixman,
dependency('cairo'),
],
......@@ -57,6 +59,7 @@ dep_test_client = declare_dependency(
dep_wayland_client,
dep_test_runner,
dep_pixman,
dep_libdrm_headers,
dependency('libudev', version: '>= 136'),
]
)
......@@ -180,6 +183,15 @@ tests = [
'name': 'matrix-transform',
'dep_objs': dep_libm,
},
{
'name': 'output-capture-protocol',
'sources': [
'output-capture-protocol-test.c',
weston_output_capture_protocol_c,
weston_output_capture_client_protocol_h,
],
'dep_objs': [ dep_libdrm_headers ],
},
{ 'name': 'output-damage', },
{ 'name': 'output-transforms', },
{ 'name': 'plugin-registry', },
......