Merge pull request #1069 from emersion/screencopy

Add wlr-screencopy-unstable-v1 support
This commit is contained in:
Drew DeVault 2018-06-30 15:27:45 -07:00 committed by GitHub
commit 015ebc5750
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 865 additions and 19 deletions

View file

@ -683,7 +683,7 @@ static bool drm_connector_set_cursor(struct wlr_output *output,
wlr_render_texture_with_matrix(rend, texture, matrix, 1.0); wlr_render_texture_with_matrix(rend, texture, matrix, 1.0);
wlr_renderer_end(rend); wlr_renderer_end(rend);
wlr_renderer_read_pixels(rend, WL_SHM_FORMAT_ARGB8888, bo_stride, wlr_renderer_read_pixels(rend, WL_SHM_FORMAT_ARGB8888, NULL, bo_stride,
plane->surf.width, plane->surf.height, 0, 0, 0, 0, bo_data); plane->surf.width, plane->surf.height, 0, 0, 0, 0, bo_data);
swap_drm_surface_buffers(&plane->surf, NULL); swap_drm_surface_buffers(&plane->surf, NULL);

View file

@ -1,6 +1,8 @@
threads = dependency('threads') threads = dependency('threads')
wayland_cursor = dependency('wayland-cursor') wayland_cursor = dependency('wayland-cursor')
libpng = dependency('libpng', required: false)
# These versions correspond to ffmpeg 4.0 # These versions correspond to ffmpeg 4.0
libavutil = dependency('libavutil', version: '>=56.14.100', required: false) libavutil = dependency('libavutil', version: '>=56.14.100', required: false)
libavcodec = dependency('libavcodec', version: '>=58.18.100', required: false) libavcodec = dependency('libavcodec', version: '>=58.18.100', required: false)
@ -56,3 +58,11 @@ if libavutil.found() and libavcodec.found() and libavformat.found()
libavformat, wlroots, threads ] libavformat, wlroots, threads ]
) )
endif endif
if libpng.found()
executable(
'screencopy',
'screencopy.c',
dependencies: [wayland_client, wlr_protos, wlroots, libpng]
)
endif

270
examples/screencopy.c Normal file
View file

@ -0,0 +1,270 @@
/*
* Copyright © 2008 Kristian Høgsberg
*
* 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.
*/
#define _XOPEN_SOURCE 700
#define _POSIX_C_SOURCE 199309L
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <png.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <unistd.h>
#include <wayland-client-protocol.h>
#include "wlr-screencopy-unstable-v1-client-protocol.h"
struct format {
enum wl_shm_format wl_format;
bool is_bgr;
};
static struct wl_shm *shm = NULL;
static struct zwlr_screencopy_manager_v1 *screencopy_manager = NULL;
static struct wl_output *output = NULL;
static struct {
struct wl_buffer *wl_buffer;
void *data;
enum wl_shm_format format;
int width, height, stride;
bool y_invert;
} buffer;
bool buffer_copy_done = false;
// wl_shm_format describes little-endian formats, libpng uses big-endian
// formats (so Wayland's ABGR is libpng's RGBA).
static const struct format formats[] = {
{WL_SHM_FORMAT_XRGB8888, true},
{WL_SHM_FORMAT_ARGB8888, true},
{WL_SHM_FORMAT_XBGR8888, false},
{WL_SHM_FORMAT_ABGR8888, false},
};
static int backingfile(off_t size) {
char template[] = "/tmp/wlroots-shared-XXXXXX";
int fd = mkstemp(template);
if (fd < 0) {
return -1;
}
int ret;
while ((ret = ftruncate(fd, size)) == EINTR) {
// No-op
}
if (ret < 0) {
close(fd);
return -1;
}
unlink(template);
return fd;
}
static struct wl_buffer *create_shm_buffer(enum wl_shm_format fmt,
int width, int height, int stride, void **data_out) {
int size = stride * height;
int fd = backingfile(size);
if (fd < 0) {
fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size);
return NULL;
}
void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
fprintf(stderr, "mmap failed: %m\n");
close(fd);
return NULL;
}
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
close(fd);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
stride, fmt);
wl_shm_pool_destroy(pool);
*data_out = data;
return buffer;
}
static void frame_handle_buffer(void *data,
struct zwlr_screencopy_frame_v1 *frame, uint32_t format,
uint32_t width, uint32_t height, uint32_t stride) {
buffer.format = format;
buffer.width = width;
buffer.height = height;
buffer.stride = stride;
buffer.wl_buffer =
create_shm_buffer(format, width, height, stride, &buffer.data);
if (buffer.wl_buffer == NULL) {
fprintf(stderr, "failed to create buffer\n");
exit(EXIT_FAILURE);
}
zwlr_screencopy_frame_v1_copy(frame, buffer.wl_buffer);
}
static void frame_handle_flags(void *data,
struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) {
buffer.y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT;
}
static void frame_handle_ready(void *data,
struct zwlr_screencopy_frame_v1 *frame, uint32_t tv_sec_hi,
uint32_t tv_sec_lo, uint32_t tv_nsec) {
buffer_copy_done = true;
}
static void frame_handle_failed(void *data,
struct zwlr_screencopy_frame_v1 *frame) {
fprintf(stderr, "failed to copy frame\n");
exit(EXIT_FAILURE);
}
static const struct zwlr_screencopy_frame_v1_listener frame_listener = {
.buffer = frame_handle_buffer,
.flags = frame_handle_flags,
.ready = frame_handle_ready,
.failed = frame_handle_failed,
};
static void handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version) {
if (strcmp(interface, wl_output_interface.name) == 0 && output == NULL) {
output = wl_registry_bind(registry, name, &wl_output_interface, 1);
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
} else if (strcmp(interface,
zwlr_screencopy_manager_v1_interface.name) == 0) {
screencopy_manager = wl_registry_bind(registry, name,
&zwlr_screencopy_manager_v1_interface, 1);
}
}
static void handle_global_remove(void *data, struct wl_registry *registry,
uint32_t name) {
// Who cares?
}
static const struct wl_registry_listener registry_listener = {
.global = handle_global,
.global_remove = handle_global_remove,
};
static void write_image(char *filename, enum wl_shm_format wl_fmt, int width,
int height, int stride, bool y_invert, png_bytep data) {
const struct format *fmt = NULL;
for (size_t i = 0; i < sizeof(formats) / sizeof(formats[0]); ++i) {
if (formats[i].wl_format == wl_fmt) {
fmt = &formats[i];
break;
}
}
if (fmt == NULL) {
fprintf(stderr, "unsupported format %"PRIu32"\n", wl_fmt);
exit(EXIT_FAILURE);
}
FILE *f = fopen(filename, "wb");
if (f == NULL) {
fprintf(stderr, "failed to open output file\n");
exit(EXIT_FAILURE);
}
png_structp png =
png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_infop info = png_create_info_struct(png);
png_init_io(png, f);
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
if (fmt->is_bgr) {
png_set_bgr(png);
}
png_write_info(png, info);
for (size_t i = 0; i < (size_t)height; ++i) {
png_bytep row;
if (y_invert) {
row = data + (height - i - 1) * stride;
} else {
row = data + i * stride;
}
png_write_row(png, row);
}
png_write_end(png, NULL);
png_destroy_write_struct(&png, &info);
fclose(f);
}
int main(int argc, char *argv[]) {
struct wl_display * display = wl_display_connect(NULL);
if (display == NULL) {
fprintf(stderr, "failed to create display: %m\n");
return EXIT_FAILURE;
}
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, NULL);
wl_display_dispatch(display);
wl_display_roundtrip(display);
if (shm == NULL) {
fprintf(stderr, "compositor is missing wl_shm\n");
return EXIT_FAILURE;
}
if (screencopy_manager == NULL) {
fprintf(stderr, "compositor doesn't support wlr-screencopy-unstable-v1\n");
return EXIT_FAILURE;
}
if (output == NULL) {
fprintf(stderr, "no output available\n");
return EXIT_FAILURE;
}
struct zwlr_screencopy_frame_v1 *frame =
zwlr_screencopy_manager_v1_capture_output(screencopy_manager, 0, output);
zwlr_screencopy_frame_v1_add_listener(frame, &frame_listener, NULL);
while (!buffer_copy_done && wl_display_dispatch(display) != -1) {
// This space is intentionally left blank
}
write_image("wayland-screenshot.png", buffer.format, buffer.width,
buffer.height, buffer.stride, buffer.y_invert, buffer.data);
wl_buffer_destroy(buffer.wl_buffer);
return EXIT_SUCCESS;
}

View file

@ -22,6 +22,7 @@
#include <wlr/types/wlr_list.h> #include <wlr/types/wlr_list.h>
#include <wlr/types/wlr_idle.h> #include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_idle_inhibit_v1.h> #include <wlr/types/wlr_idle_inhibit_v1.h>
#include <wlr/types/wlr_screencopy_v1.h>
#include "rootston/view.h" #include "rootston/view.h"
#include "rootston/config.h" #include "rootston/config.h"
#include "rootston/output.h" #include "rootston/output.h"
@ -54,6 +55,7 @@ struct roots_desktop {
struct wlr_linux_dmabuf *linux_dmabuf; struct wlr_linux_dmabuf *linux_dmabuf;
struct wlr_layer_shell *layer_shell; struct wlr_layer_shell *layer_shell;
struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard; struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard;
struct wlr_screencopy_manager_v1 *screencopy;
struct wl_listener new_output; struct wl_listener new_output;
struct wl_listener layout_change; struct wl_listener layout_change;

View file

@ -34,7 +34,7 @@ struct wlr_renderer_impl {
int (*get_dmabuf_modifiers)(struct wlr_renderer *renderer, int format, int (*get_dmabuf_modifiers)(struct wlr_renderer *renderer, int format,
uint64_t **modifiers); uint64_t **modifiers);
bool (*read_pixels)(struct wlr_renderer *renderer, enum wl_shm_format fmt, bool (*read_pixels)(struct wlr_renderer *renderer, enum wl_shm_format fmt,
uint32_t stride, uint32_t width, uint32_t height, uint32_t *flags, uint32_t stride, uint32_t width, uint32_t height,
uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y, uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y,
void *data); void *data);
bool (*format_supported)(struct wlr_renderer *renderer, bool (*format_supported)(struct wlr_renderer *renderer,

View file

@ -7,6 +7,10 @@
#include <wlr/render/wlr_texture.h> #include <wlr/render/wlr_texture.h>
#include <wlr/types/wlr_box.h> #include <wlr/types/wlr_box.h>
enum wlr_renderer_read_pixels_flags {
WLR_RENDERER_READ_PIXELS_Y_INVERT = 1,
};
struct wlr_renderer_impl; struct wlr_renderer_impl;
struct wlr_renderer { struct wlr_renderer {
@ -87,9 +91,12 @@ int wlr_renderer_get_dmabuf_modifiers(struct wlr_renderer *renderer, int format,
/** /**
* Reads out of pixels of the currently bound surface into data. `stride` is in * Reads out of pixels of the currently bound surface into data. `stride` is in
* bytes. * bytes.
*
* If `flags` is not NULl, the caller indicates that it accepts frame flags
* defined in `enum wlr_renderer_read_pixels_flags`.
*/ */
bool wlr_renderer_read_pixels(struct wlr_renderer *r, enum wl_shm_format fmt, bool wlr_renderer_read_pixels(struct wlr_renderer *r, enum wl_shm_format fmt,
uint32_t stride, uint32_t width, uint32_t height, uint32_t *flags, uint32_t stride, uint32_t width, uint32_t height,
uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y, void *data); uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y, void *data);
/** /**
* Checks if a format is supported. * Checks if a format is supported.

View file

@ -0,0 +1,39 @@
#ifndef WLR_TYPES_WLR_SCREENCOPY_V1_H
#define WLR_TYPES_WLR_SCREENCOPY_V1_H
#include <wayland-server.h>
struct wlr_screencopy_manager_v1 {
struct wl_global *global;
struct wl_list resources; // wl_resource
struct wl_list frames; // wlr_screencopy_frame_v1::link
struct wl_listener display_destroy;
void *data;
};
struct wlr_screencopy_frame_v1 {
struct wl_resource *resource;
struct wlr_screencopy_manager_v1 *manager;
struct wl_list link;
enum wl_shm_format format;
struct wlr_box box;
int stride;
struct wl_shm_buffer *buffer;
struct wl_listener buffer_destroy;
struct wlr_output *output;
struct wl_listener output_swap_buffers;
void *data;
};
struct wlr_screencopy_manager_v1 *wlr_screencopy_manager_v1_create(
struct wl_display *display);
void wlr_screencopy_manager_v1_destroy(
struct wlr_screencopy_manager_v1 *screencopy);
#endif

View file

@ -42,6 +42,7 @@ protocols = [
'wlr-export-dmabuf-unstable-v1.xml', 'wlr-export-dmabuf-unstable-v1.xml',
'wlr-input-inhibitor-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml',
'wlr-layer-shell-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml',
'wlr-screencopy-unstable-v1.xml',
] ]
client_protocols = [ client_protocols = [
@ -51,8 +52,9 @@ client_protocols = [
'idle.xml', 'idle.xml',
'screenshooter.xml', 'screenshooter.xml',
'wlr-export-dmabuf-unstable-v1.xml', 'wlr-export-dmabuf-unstable-v1.xml',
'wlr-layer-shell-unstable-v1.xml',
'wlr-input-inhibitor-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml',
'wlr-layer-shell-unstable-v1.xml',
'wlr-screencopy-unstable-v1.xml',
] ]
wl_protos_src = [] wl_protos_src = []

View file

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_screencopy_unstable_v1">
<copyright>
Copyright © 2018 Simon Ser
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>
<description summary="screen content capturing on client buffers">
This protocol allows clients to ask the compositor to copy part of the
screen content to a client buffer.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<interface name="zwlr_screencopy_manager_v1" version="1">
<description summary="manager to inform clients and begin capturing">
This object is a manager which offers requests to start capturing from a
source.
</description>
<request name="capture_output">
<description summary="capture an output">
Capture the next frame of an entire output.
</description>
<arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
<arg name="overlay_cursor" type="int"
summary="composite cursor onto the frame"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
<request name="capture_output_region">
<description summary="capture an output's region">
Capture the next frame of an output's region.
The region is given in output logical coordinates, see
xdg_output.logical_size. The region will be clipped to the output's
extents.
</description>
<arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
<arg name="overlay_cursor" type="int"
summary="composite cursor onto the frame"/>
<arg name="output" type="object" interface="wl_output"/>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
</description>
</request>
</interface>
<interface name="zwlr_screencopy_frame_v1" version="1">
<description summary="a frame ready for copy">
This object represents a single frame.
When created, a "buffer" event will be sent. The client will then be able
to send a "copy" request. If the capture is successful, the compositor
will send a "flags" followed by a "ready" event.
If the capture failed, the "failed" event is sent. This can happen anytime
before the "ready" event.
Once either a "ready" or a "failed" event is received, the client should
destroy the frame.
</description>
<event name="buffer">
<description summary="buffer information">
Provides information about the frame's buffer. This event is sent once
as soon as the frame is created.
The client should then create a buffer with the provided attributes, and
send a "copy" request.
</description>
<arg name="format" type="uint" summary="buffer format"/>
<arg name="width" type="uint" summary="buffer width"/>
<arg name="height" type="uint" summary="buffer height"/>
<arg name="stride" type="uint" summary="buffer stride"/>
</event>
<request name="copy">
<description summary="copy the frame">
Copy the frame to the supplied buffer. The buffer must have a the
correct size, see zwlr_screencopy_frame_v1.buffer. The buffer needs to
have a supported format.
If the frame is successfully copied, a "flags" and a "ready" events are
sent. Otherwise, a "failed" event is sent.
</description>
<arg name="buffer" type="object" interface="wl_buffer"/>
</request>
<enum name="error">
<entry name="already_used" value="0"
summary="the object has already been used to copy a wl_buffer"/>
<entry name="invalid_buffer" value="1"
summary="buffer attributes are invalid"/>
</enum>
<enum name="flags" bitfield="true">
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
</enum>
<event name="flags">
<description summary="frame flags">
Provides flags about the frame. This event is sent once before the
"ready" event.
</description>
<arg name="flags" type="uint" enum="flags" summary="frame flags"/>
</event>
<event name="ready">
<description summary="indicates frame is available for reading">
Called as soon as the frame is copied, indicating it is available
for reading. This event includes the time at which presentation happened
at.
The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
each component being an unsigned 32-bit value. Whole seconds are in
tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
and the additional fractional part in tv_nsec as nanoseconds. Hence,
for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part
may have an arbitrary offset at start.
After receiving this event, the client should destroy the object.
</description>
<arg name="tv_sec_hi" type="uint"
summary="high 32 bits of the seconds part of the timestamp"/>
<arg name="tv_sec_lo" type="uint"
summary="low 32 bits of the seconds part of the timestamp"/>
<arg name="tv_nsec" type="uint"
summary="nanoseconds part of the timestamp"/>
</event>
<event name="failed">
<description summary="frame copy failed">
This event indicates that the attempted frame copy has failed.
After receiving this event, the client should destroy the object.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="delete this object, used or not">
Destroys the frame. This request can be sent at any time by the client.
</description>
</request>
</interface>
</protocol>

View file

@ -250,10 +250,11 @@ static int gles2_get_dmabuf_modifiers(struct wlr_renderer *wlr_renderer,
} }
static bool gles2_read_pixels(struct wlr_renderer *wlr_renderer, static bool gles2_read_pixels(struct wlr_renderer *wlr_renderer,
enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width, enum wl_shm_format wl_fmt, uint32_t *flags, uint32_t stride,
uint32_t height, uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y,
uint32_t dst_y, void *data) { uint32_t dst_x, uint32_t dst_y, void *data) {
gles2_get_renderer_in_context(wlr_renderer); struct wlr_gles2_renderer *renderer =
gles2_get_renderer_in_context(wlr_renderer);
const struct wlr_gles2_pixel_format *fmt = get_gles2_format_from_wl(wl_fmt); const struct wlr_gles2_pixel_format *fmt = get_gles2_format_from_wl(wl_fmt);
if (fmt == NULL) { if (fmt == NULL) {
@ -266,17 +267,31 @@ static bool gles2_read_pixels(struct wlr_renderer *wlr_renderer,
// Make sure any pending drawing is finished before we try to read it // Make sure any pending drawing is finished before we try to read it
glFinish(); glFinish();
// Unfortunately GLES2 doesn't support GL_PACK_*, so we have to read glGetError(); // Clear the error flag
// the lines out row by row
unsigned char *p = data + dst_y * stride; unsigned char *p = data + dst_y * stride;
for (size_t i = src_y; i < src_y + height; ++i) { uint32_t pack_stride = width * fmt->bpp / 8;
glReadPixels(src_x, src_y + height - i - 1, width, 1, fmt->gl_format, if (pack_stride == stride && dst_x == 0 && flags != NULL) {
fmt->gl_type, p + i * stride + dst_x * fmt->bpp / 8); // Under these particular conditions, we can read the pixels with only
// one glReadPixels call
glReadPixels(src_x, renderer->viewport_height - height - src_y,
width, height, fmt->gl_format, fmt->gl_type, p);
*flags = WLR_RENDERER_READ_PIXELS_Y_INVERT;
} else {
// Unfortunately GLES2 doesn't support GL_PACK_*, so we have to read
// the lines out row by row
for (size_t i = src_y; i < src_y + height; ++i) {
glReadPixels(src_x, src_y + height - i - 1, width, 1, fmt->gl_format,
fmt->gl_type, p + i * stride + dst_x * fmt->bpp / 8);
}
if (flags != NULL) {
*flags = 0;
}
} }
POP_GLES2_DEBUG; POP_GLES2_DEBUG;
return true; return glGetError() == GL_NO_ERROR;
} }
static bool gles2_format_supported(struct wlr_renderer *wlr_renderer, static bool gles2_format_supported(struct wlr_renderer *wlr_renderer,

View file

@ -139,14 +139,14 @@ int wlr_renderer_get_dmabuf_modifiers(struct wlr_renderer *r, int format,
} }
bool wlr_renderer_read_pixels(struct wlr_renderer *r, enum wl_shm_format fmt, bool wlr_renderer_read_pixels(struct wlr_renderer *r, enum wl_shm_format fmt,
uint32_t stride, uint32_t width, uint32_t height, uint32_t *flags, uint32_t stride, uint32_t width, uint32_t height,
uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y, uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y,
void *data) { void *data) {
if (!r->impl->read_pixels) { if (!r->impl->read_pixels) {
return false; return false;
} }
return r->impl->read_pixels(r, fmt, stride, width, height, src_x, src_y, return r->impl->read_pixels(r, fmt, flags, stride, width, height,
dst_x, dst_y, data); src_x, src_y, dst_x, dst_y, data);
} }
bool wlr_renderer_format_supported(struct wlr_renderer *r, bool wlr_renderer_format_supported(struct wlr_renderer *r,

View file

@ -875,6 +875,9 @@ struct roots_desktop *desktop_create(struct roots_server *server,
wl_signal_add(&desktop->virtual_keyboard->events.new_virtual_keyboard, wl_signal_add(&desktop->virtual_keyboard->events.new_virtual_keyboard,
&desktop->virtual_keyboard_new); &desktop->virtual_keyboard_new);
desktop->virtual_keyboard_new.notify = handle_virtual_keyboard; desktop->virtual_keyboard_new.notify = handle_virtual_keyboard;
desktop->screencopy = wlr_screencopy_manager_v1_create(server->wl_display);
return desktop; return desktop;
} }

View file

@ -50,6 +50,7 @@ lib_wlr_types = static_library(
'wlr_wl_shell.c', 'wlr_wl_shell.c',
'wlr_xcursor_manager.c', 'wlr_xcursor_manager.c',
'wlr_xdg_output.c', 'wlr_xdg_output.c',
'wlr_screencopy_v1.c',
), ),
include_directories: wlr_inc, include_directories: wlr_inc,
dependencies: [pixman, xkbcommon, wayland_server, wlr_protos], dependencies: [pixman, xkbcommon, wayland_server, wlr_protos],

318
types/wlr_screencopy_v1.c Normal file
View file

@ -0,0 +1,318 @@
#include <assert.h>
#include <stdlib.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_screencopy_v1.h>
#include <wlr/backend.h>
#include "wlr-screencopy-unstable-v1-protocol.h"
#define SCREENCOPY_MANAGER_VERSION 1
static const struct zwlr_screencopy_frame_v1_interface frame_impl;
static struct wlr_screencopy_frame_v1 *frame_from_resource(
struct wl_resource *resource) {
assert(wl_resource_instance_of(resource,
&zwlr_screencopy_frame_v1_interface, &frame_impl));
return wl_resource_get_user_data(resource);
}
static void frame_destroy(struct wlr_screencopy_frame_v1 *frame) {
if (frame == NULL) {
return;
}
wl_list_remove(&frame->link);
wl_list_remove(&frame->output_swap_buffers.link);
wl_list_remove(&frame->buffer_destroy.link);
// Make the frame resource inert
wl_resource_set_user_data(frame->resource, NULL);
free(frame);
}
static void frame_handle_output_swap_buffers(struct wl_listener *listener,
void *_data) {
struct wlr_screencopy_frame_v1 *frame =
wl_container_of(listener, frame, output_swap_buffers);
struct wlr_output_event_swap_buffers *event = _data;
struct wlr_output *output = frame->output;
struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend);
wl_list_remove(&frame->output_swap_buffers.link);
wl_list_init(&frame->output_swap_buffers.link);
int x = frame->box.x;
int y = frame->box.y;
struct wl_shm_buffer *buffer = frame->buffer;
assert(buffer != NULL);
enum wl_shm_format fmt = wl_shm_buffer_get_format(buffer);
int32_t width = wl_shm_buffer_get_width(buffer);
int32_t height = wl_shm_buffer_get_height(buffer);
int32_t stride = wl_shm_buffer_get_stride(buffer);
wl_shm_buffer_begin_access(buffer);
void *data = wl_shm_buffer_get_data(buffer);
uint32_t flags = 0;
bool ok = wlr_renderer_read_pixels(renderer, fmt, &flags, stride,
width, height, x, y, 0, 0, data);
wl_shm_buffer_end_access(buffer);
if (!ok) {
zwlr_screencopy_frame_v1_send_failed(frame->resource);
frame_destroy(frame);
return;
}
zwlr_screencopy_frame_v1_send_flags(frame->resource, flags);
uint32_t tv_sec_hi = event->when->tv_sec >> 32;
uint32_t tv_sec_lo = event->when->tv_sec & 0xFFFFFFFF;
zwlr_screencopy_frame_v1_send_ready(frame->resource,
tv_sec_hi, tv_sec_lo, event->when->tv_nsec);
frame_destroy(frame);
}
static void frame_handle_buffer_destroy(struct wl_listener *listener,
void *data) {
struct wlr_screencopy_frame_v1 *frame =
wl_container_of(listener, frame, buffer_destroy);
zwlr_screencopy_frame_v1_send_failed(frame->resource);
frame_destroy(frame);
}
static void frame_handle_copy(struct wl_client *client,
struct wl_resource *frame_resource,
struct wl_resource *buffer_resource) {
struct wlr_screencopy_frame_v1 *frame = frame_from_resource(frame_resource);
if (frame == NULL) {
return;
}
struct wlr_output *output = frame->output;
struct wl_shm_buffer *buffer = wl_shm_buffer_get(buffer_resource);
if (buffer == NULL) {
wl_resource_post_error(frame->resource,
ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER,
"unsupported buffer type");
return;
}
enum wl_shm_format fmt = wl_shm_buffer_get_format(buffer);
int32_t width = wl_shm_buffer_get_width(buffer);
int32_t height = wl_shm_buffer_get_height(buffer);
int32_t stride = wl_shm_buffer_get_stride(buffer);
if (fmt != frame->format || width != frame->box.width ||
height != frame->box.height || stride != frame->stride) {
wl_resource_post_error(frame->resource,
ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER,
"invalid buffer attributes");
return;
}
if (!wl_list_empty(&frame->output_swap_buffers.link) ||
frame->buffer != NULL) {
wl_resource_post_error(frame->resource,
ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED,
"frame already used");
return;
}
frame->buffer = buffer;
wl_signal_add(&output->events.swap_buffers, &frame->output_swap_buffers);
frame->output_swap_buffers.notify = frame_handle_output_swap_buffers;
wl_resource_add_destroy_listener(buffer_resource, &frame->buffer_destroy);
frame->buffer_destroy.notify = frame_handle_buffer_destroy;
// Schedule a buffer swap
output->needs_swap = true;
wlr_output_schedule_frame(output);
}
static void frame_handle_destroy(struct wl_client *client,
struct wl_resource *frame_resource) {
wl_resource_destroy(frame_resource);
}
static const struct zwlr_screencopy_frame_v1_interface frame_impl = {
.copy = frame_handle_copy,
.destroy = frame_handle_destroy,
};
static void frame_handle_resource_destroy(struct wl_resource *frame_resource) {
struct wlr_screencopy_frame_v1 *frame = frame_from_resource(frame_resource);
frame_destroy(frame);
}
static const struct zwlr_screencopy_manager_v1_interface manager_impl;
static struct wlr_screencopy_manager_v1 *manager_from_resource(
struct wl_resource *resource) {
assert(wl_resource_instance_of(resource,
&zwlr_screencopy_manager_v1_interface, &manager_impl));
return wl_resource_get_user_data(resource);
}
static void capture_output(struct wl_client *client,
struct wlr_screencopy_manager_v1 *manager, uint32_t version, uint32_t id,
int32_t overlay_cursor, struct wlr_output *output,
const struct wlr_box *box) {
struct wlr_box buffer_box = {0};
if (box == NULL) {
buffer_box.width = output->width;
buffer_box.height = output->height;
} else {
int ow, oh;
wlr_output_effective_resolution(output, &ow, &oh);
buffer_box = *box;
wlr_box_transform(&buffer_box, output->transform, ow, oh, &buffer_box);
buffer_box.x *= output->scale;
buffer_box.y *= output->scale;
buffer_box.width *= output->scale;
buffer_box.height *= output->scale;
}
struct wlr_screencopy_frame_v1 *frame =
calloc(1, sizeof(struct wlr_screencopy_frame_v1));
if (frame == NULL) {
wl_client_post_no_memory(client);
return;
}
frame->manager = manager;
frame->output = output;
frame->resource = wl_resource_create(client,
&zwlr_screencopy_frame_v1_interface, version, id);
if (frame->resource == NULL) {
free(frame);
wl_client_post_no_memory(client);
return;
}
wl_resource_set_implementation(frame->resource, &frame_impl, frame,
frame_handle_resource_destroy);
wl_list_insert(&manager->frames, &frame->link);
wl_list_init(&frame->output_swap_buffers.link);
frame->format = WL_SHM_FORMAT_XRGB8888;
frame->box = buffer_box;
frame->stride = 4 * buffer_box.width;
zwlr_screencopy_frame_v1_send_buffer(frame->resource, frame->format,
buffer_box.width, buffer_box.height, frame->stride);
}
static void manager_handle_capture_output(struct wl_client *client,
struct wl_resource *manager_resource, uint32_t id,
int32_t overlay_cursor, struct wl_resource *output_resource) {
struct wlr_screencopy_manager_v1 *manager =
manager_from_resource(manager_resource);
uint32_t version = wl_resource_get_version(manager_resource);
struct wlr_output *output = wlr_output_from_resource(output_resource);
capture_output(client, manager, version, id, overlay_cursor, output, NULL);
}
static void manager_handle_capture_output_region(struct wl_client *client,
struct wl_resource *manager_resource, uint32_t id,
int32_t overlay_cursor, struct wl_resource *output_resource,
int32_t x, int32_t y, int32_t width, int32_t height) {
struct wlr_screencopy_manager_v1 *manager =
manager_from_resource(manager_resource);
uint32_t version = wl_resource_get_version(manager_resource);
struct wlr_output *output = wlr_output_from_resource(output_resource);
struct wlr_box box = {
.x = x,
.y = y,
.width = width,
.height = height,
};
capture_output(client, manager, version, id, overlay_cursor, output, &box);
}
static void manager_handle_destroy(struct wl_client *client,
struct wl_resource *manager_resource) {
wl_resource_destroy(manager_resource);
}
static const struct zwlr_screencopy_manager_v1_interface manager_impl = {
.capture_output = manager_handle_capture_output,
.capture_output_region = manager_handle_capture_output_region,
.destroy = manager_handle_destroy,
};
void manager_handle_resource_destroy(struct wl_resource *resource) {
wl_list_remove(wl_resource_get_link(resource));
}
static void manager_bind(struct wl_client *client, void *data, uint32_t version,
uint32_t id) {
struct wlr_screencopy_manager_v1 *manager = data;
struct wl_resource *resource = wl_resource_create(client,
&zwlr_screencopy_manager_v1_interface, version, id);
if (resource == NULL) {
wl_client_post_no_memory(client);
return;
}
wl_resource_set_implementation(resource, &manager_impl, manager,
manager_handle_resource_destroy);
wl_list_insert(&manager->resources, wl_resource_get_link(resource));
}
static void handle_display_destroy(struct wl_listener *listener, void *data) {
struct wlr_screencopy_manager_v1 *manager =
wl_container_of(listener, manager, display_destroy);
wlr_screencopy_manager_v1_destroy(manager);
}
struct wlr_screencopy_manager_v1 *wlr_screencopy_manager_v1_create(
struct wl_display *display) {
struct wlr_screencopy_manager_v1 *manager =
calloc(1, sizeof(struct wlr_screencopy_manager_v1));
if (manager == NULL) {
return NULL;
}
manager->global = wl_global_create(display,
&zwlr_screencopy_manager_v1_interface, SCREENCOPY_MANAGER_VERSION,
manager, manager_bind);
if (manager->global == NULL) {
free(manager);
return NULL;
}
wl_list_init(&manager->resources);
wl_list_init(&manager->frames);
manager->display_destroy.notify = handle_display_destroy;
wl_display_add_destroy_listener(display, &manager->display_destroy);
return manager;
}
void wlr_screencopy_manager_v1_destroy(
struct wlr_screencopy_manager_v1 *manager) {
if (manager == NULL) {
return;
}
wl_list_remove(&manager->display_destroy.link);
struct wlr_screencopy_frame_v1 *frame, *tmp_frame;
wl_list_for_each_safe(frame, tmp_frame, &manager->frames, link) {
wl_resource_destroy(frame->resource);
}
struct wl_resource *resource, *tmp_resource;
wl_resource_for_each_safe(resource, tmp_resource, &manager->resources) {
wl_resource_destroy(resource);
}
wl_global_destroy(manager->global);
free(manager);
}

View file

@ -50,8 +50,8 @@ static void output_handle_frame(struct wl_listener *listener, void *_data) {
int32_t stride = wl_shm_buffer_get_stride(shm_buffer); int32_t stride = wl_shm_buffer_get_stride(shm_buffer);
wl_shm_buffer_begin_access(shm_buffer); wl_shm_buffer_begin_access(shm_buffer);
void *data = wl_shm_buffer_get_data(shm_buffer); void *data = wl_shm_buffer_get_data(shm_buffer);
bool ok = wlr_renderer_read_pixels(renderer, format, stride, width, height, bool ok = wlr_renderer_read_pixels(renderer, format, NULL, stride,
0, 0, 0, 0, data); width, height, 0, 0, 0, 0, data);
wl_shm_buffer_end_access(shm_buffer); wl_shm_buffer_end_access(shm_buffer);
if (!ok) { if (!ok) {