From 73755ad348657fdf8d9e0f9a48ee13a2ab947e35 Mon Sep 17 00:00:00 2001 From: emersion Date: Tue, 19 Jun 2018 19:07:29 +0100 Subject: [PATCH 1/9] screencopy-v1: add basic implementation --- examples/meson.build | 6 + examples/screencopy.c | 232 ++++++++++++++++++++++ include/rootston/desktop.h | 2 + include/wlr/types/wlr_screencopy_v1.h | 36 ++++ protocol/meson.build | 4 +- protocol/wlr-screencopy-unstable-v1.xml | 122 ++++++++++++ rootston/desktop.c | 3 + types/meson.build | 1 + types/wlr_screencopy_v1.c | 250 ++++++++++++++++++++++++ 9 files changed, 655 insertions(+), 1 deletion(-) create mode 100644 examples/screencopy.c create mode 100644 include/wlr/types/wlr_screencopy_v1.h create mode 100644 protocol/wlr-screencopy-unstable-v1.xml create mode 100644 types/wlr_screencopy_v1.c diff --git a/examples/meson.build b/examples/meson.build index d8df0bfc..a290dc82 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -56,3 +56,9 @@ if libavutil.found() and libavcodec.found() and libavformat.found() libavformat, wlroots, threads ] ) endif + +executable( + 'screencopy', + 'screencopy.c', + dependencies: [wayland_client, wlr_protos, wlroots] +) diff --git a/examples/screencopy.c b/examples/screencopy.c new file mode 100644 index 00000000..98df09b5 --- /dev/null +++ b/examples/screencopy.c @@ -0,0 +1,232 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wlr-screencopy-unstable-v1-client-protocol.h" + +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; + int width, height, stride; +} buffer; +bool buffer_copy_done = 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(int width, int height, + int *stride_out, void **data_out) { + int stride = width * 4; + 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, WL_SHM_FORMAT_XRGB8888); + wl_shm_pool_destroy(pool); + + *data_out = data; + *stride_out = stride; + return buffer; +} + +static void frame_handle_buffer(void *data, + struct zwlr_screencopy_frame_v1 *frame, uint32_t width, uint32_t height, + uint32_t flags, uint32_t format, uint32_t stride) { + buffer.width = width; + buffer.height = height; + buffer.wl_buffer = + create_shm_buffer(width, height, &buffer.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_ready(void *data, + struct zwlr_screencopy_frame_v1 *frame) { + 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, + .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(const char *filename, int width, int height, int stride, + void *data) { + char size[10 + 1 + 10 + 2 + 1]; // int32_t are max 10 digits + sprintf(size, "%dx%d+0", width, height); + + int fd[2]; + if (pipe(fd) != 0) { + fprintf(stderr, "cannot create pipe: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + pid_t child = fork(); + if (child < 0) { + fprintf(stderr, "fork() failed\n"); + exit(EXIT_FAILURE); + } else if (child != 0) { + close(fd[0]); + if (write(fd[1], data, stride * height) < 0) { + fprintf(stderr, "write() failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + close(fd[1]); + waitpid(child, NULL, 0); + } else { + close(fd[1]); + if (dup2(fd[0], 0) != 0) { + fprintf(stderr, "cannot dup the pipe\n"); + exit(EXIT_FAILURE); + } + close(fd[0]); + // We requested WL_SHM_FORMAT_XRGB8888 in little endian, so that's BGRA + // in big endian. + execlp("convert", "convert", "-depth", "8", "-size", size, "bgra:-", + "-alpha", "opaque", filename, NULL); + fprintf(stderr, "cannot execute convert\n"); + exit(EXIT_FAILURE); + } +} + +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, ®istry_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, output); + zwlr_screencopy_frame_v1_add_listener(frame, &frame_listener, NULL); + + while (!buffer_copy_done) { + wl_display_roundtrip(display); + } + + write_image("wayland-screenshot.png", buffer.width, buffer.height, + buffer.stride, buffer.data); + wl_buffer_destroy(buffer.wl_buffer); + + return EXIT_SUCCESS; +} diff --git a/include/rootston/desktop.h b/include/rootston/desktop.h index dfe070ca..88c5ca90 100644 --- a/include/rootston/desktop.h +++ b/include/rootston/desktop.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "rootston/view.h" #include "rootston/config.h" #include "rootston/output.h" @@ -54,6 +55,7 @@ struct roots_desktop { struct wlr_linux_dmabuf *linux_dmabuf; struct wlr_layer_shell *layer_shell; struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard; + struct wlr_screencopy_manager_v1 *screencopy; struct wl_listener new_output; struct wl_listener layout_change; diff --git a/include/wlr/types/wlr_screencopy_v1.h b/include/wlr/types/wlr_screencopy_v1.h new file mode 100644 index 00000000..fbcc1ce2 --- /dev/null +++ b/include/wlr/types/wlr_screencopy_v1.h @@ -0,0 +1,36 @@ +#ifndef WLR_TYPES_WLR_SCREENCOPY_V1_H +#define WLR_TYPES_WLR_SCREENCOPY_V1_H + +#include + +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; + + int32_t width, height; + + struct wl_shm_buffer *buffer; + + 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 diff --git a/protocol/meson.build b/protocol/meson.build index ca0d82b5..5ab82d84 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -42,6 +42,7 @@ protocols = [ 'wlr-export-dmabuf-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml', + 'wlr-screencopy-unstable-v1.xml', ] client_protocols = [ @@ -51,8 +52,9 @@ client_protocols = [ 'idle.xml', 'screenshooter.xml', 'wlr-export-dmabuf-unstable-v1.xml', - 'wlr-layer-shell-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml', + 'wlr-layer-shell-unstable-v1.xml', + 'wlr-screencopy-unstable-v1.xml', ] wl_protos_src = [] diff --git a/protocol/wlr-screencopy-unstable-v1.xml b/protocol/wlr-screencopy-unstable-v1.xml new file mode 100644 index 00000000..6bd5a75f --- /dev/null +++ b/protocol/wlr-screencopy-unstable-v1.xml @@ -0,0 +1,122 @@ + + + + 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. + + + + This protocol allows clients to ask the compositor to copy part of the + screen content to a client buffer. + + + + + This object is a manager which offers requests to start capturing from a + source. + + + + + Request to start capturing from the given output. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object represents a frame which is ready to have its resources + fetched and used. + + + + + + + + + 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 width and + height, and send a copy request. It can optionally create a buffer with + the preferred format and stride. + + + + + + + + + + + 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 ready event is sent. Otherwise, + an abort event is sent. + + + + + + + + + + + + + Called as soon as the frame is copied, indicating it is available + for reading. + + Upon receiving this event, the client should destroy the object. + + + + + + This event indicates that the attempted frame copy has failed. + + Upon receiving this event, the client should destroy the object. + + + + + + Destroys the frame. + + + + diff --git a/rootston/desktop.c b/rootston/desktop.c index 2bba06e2..d670c33b 100644 --- a/rootston/desktop.c +++ b/rootston/desktop.c @@ -875,6 +875,9 @@ struct roots_desktop *desktop_create(struct roots_server *server, wl_signal_add(&desktop->virtual_keyboard->events.new_virtual_keyboard, &desktop->virtual_keyboard_new); desktop->virtual_keyboard_new.notify = handle_virtual_keyboard; + + desktop->screencopy = wlr_screencopy_manager_v1_create(server->wl_display); + return desktop; } diff --git a/types/meson.build b/types/meson.build index 6d6e0fa3..49f87bf8 100644 --- a/types/meson.build +++ b/types/meson.build @@ -50,6 +50,7 @@ lib_wlr_types = static_library( 'wlr_wl_shell.c', 'wlr_xcursor_manager.c', 'wlr_xdg_output.c', + 'wlr_screencopy_v1.c', ), include_directories: wlr_inc, dependencies: [pixman, xkbcommon, wayland_server, wlr_protos], diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c new file mode 100644 index 00000000..197ba330 --- /dev/null +++ b/types/wlr_screencopy_v1.c @@ -0,0 +1,250 @@ +#include +#include +#include +#include +#include +#include +#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_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 *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); + + if (output->width != frame->width || output->height != frame->height) { + zwlr_screencopy_frame_v1_send_failed(frame->resource); + return; + } + + 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); + bool ok = wlr_renderer_read_pixels(renderer, fmt, stride, width, height, + 0, 0, 0, 0, data); + wl_shm_buffer_end_access(buffer); + + if (!ok) { + zwlr_screencopy_frame_v1_send_failed(frame->resource); + return; + } + + zwlr_screencopy_frame_v1_send_ready(frame->resource); + + // TODO: make frame resource inert +} + +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); + struct wlr_output *output = frame->output; + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + + struct wl_shm_buffer *buffer = wl_shm_buffer_get(buffer_resource); + if (buffer == NULL) { + zwlr_screencopy_frame_v1_send_failed(frame_resource); + return; + } + + enum wl_shm_format fmt = wl_shm_buffer_get_format(buffer); + if (!wlr_renderer_format_supported(renderer, fmt)) { + wl_resource_post_error(frame->resource, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_FORMAT, + "unsupported format %"PRIu32, fmt); + return; + } + + if (frame->width != wl_shm_buffer_get_width(buffer) || + frame->height != wl_shm_buffer_get_height(buffer)) { + wl_resource_post_error(frame->resource, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_DIMENSIONS, + "invalid width or height"); + 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; + + // Schedule a buffer swap + output->needs_swap = true; + wlr_output_schedule_frame(output); + + // TODO: listen to buffer destroy +} + +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); + wl_list_remove(&frame->link); + wl_list_remove(&frame->output_swap_buffers.link); + free(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 manager_handle_capture_output(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *output_resource) { + struct wlr_screencopy_manager_v1 *manager = + manager_from_resource(manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + struct wlr_screencopy_frame_v1 *frame = + calloc(1, sizeof(struct wlr_screencopy_frame_v1)); + if (frame == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + frame->manager = manager; + + frame->output = output; + + uint32_t version = wl_resource_get_version(manager_resource); + 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->width = output->width; + frame->height = output->height; + // TODO: don't send zero + zwlr_screencopy_frame_v1_send_buffer(frame->resource, + frame->width, frame->height, 0, 0, 0); +} + +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, + .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); +} From bd8be19b792797925f5cca14049a5ab2d0f6d06c Mon Sep 17 00:00:00 2001 From: emersion Date: Fri, 22 Jun 2018 22:31:33 +0100 Subject: [PATCH 2/9] screencopy: add presentation timestamp --- examples/screencopy.c | 5 +-- protocol/wlr-screencopy-unstable-v1.xml | 48 +++++++++++++++++++++---- types/wlr_screencopy_v1.c | 8 +++-- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/examples/screencopy.c b/examples/screencopy.c index 98df09b5..8b064a15 100644 --- a/examples/screencopy.c +++ b/examples/screencopy.c @@ -113,7 +113,8 @@ static void frame_handle_buffer(void *data, } static void frame_handle_ready(void *data, - struct zwlr_screencopy_frame_v1 *frame) { + struct zwlr_screencopy_frame_v1 *frame, uint32_t tv_sec_hi, + uint32_t tv_sec_lo, uint32_t tv_nsec) { buffer_copy_done = true; } @@ -217,7 +218,7 @@ int main(int argc, char *argv[]) { } struct zwlr_screencopy_frame_v1 *frame = - zwlr_screencopy_manager_v1_capture_output(screencopy_manager, output); + zwlr_screencopy_manager_v1_capture_output(screencopy_manager, 0, output); zwlr_screencopy_frame_v1_add_listener(frame, &frame_listener, NULL); while (!buffer_copy_done) { diff --git a/protocol/wlr-screencopy-unstable-v1.xml b/protocol/wlr-screencopy-unstable-v1.xml index 6bd5a75f..52f3385e 100644 --- a/protocol/wlr-screencopy-unstable-v1.xml +++ b/protocol/wlr-screencopy-unstable-v1.xml @@ -26,6 +26,15 @@ 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. @@ -36,9 +45,11 @@ - Request to start capturing from the given output. + Capture the next frame of a an entire output. + @@ -52,8 +63,17 @@ - This object represents a frame which is ready to have its resources - fetched and used. + 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 finally 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. @@ -99,23 +119,37 @@ Called as soon as the frame is copied, indicating it is available - for reading. + for reading. This event includes the time at which presentation happened + at. - Upon receiving this event, the client should destroy the object. + 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. + + + This event indicates that the attempted frame copy has failed. - Upon receiving this event, the client should destroy the object. + After receiving this event, the client should destroy the object. - Destroys the frame. + Destroys the frame. This request can be sent at any time by the client. diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c index 197ba330..6b6472e1 100644 --- a/types/wlr_screencopy_v1.c +++ b/types/wlr_screencopy_v1.c @@ -21,6 +21,7 @@ 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); @@ -51,7 +52,10 @@ static void frame_handle_output_swap_buffers(struct wl_listener *listener, return; } - zwlr_screencopy_frame_v1_send_ready(frame->resource); + 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); // TODO: make frame resource inert } @@ -134,7 +138,7 @@ static struct wlr_screencopy_manager_v1 *manager_from_resource( static void manager_handle_capture_output(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id, - struct wl_resource *output_resource) { + int32_t overlay_cursor, struct wl_resource *output_resource) { struct wlr_screencopy_manager_v1 *manager = manager_from_resource(manager_resource); struct wlr_output *output = wlr_output_from_resource(output_resource); From 2b9cbaddf3d285ce439bd5da9f19995a0ddbac3a Mon Sep 17 00:00:00 2001 From: emersion Date: Sat, 23 Jun 2018 14:02:43 +0100 Subject: [PATCH 3/9] screencopy: add support for frame flags --- backend/drm/drm.c | 2 +- examples/screencopy.c | 32 ++++++++++++++++++------- include/wlr/render/interface.h | 2 +- include/wlr/render/wlr_renderer.h | 9 ++++++- protocol/wlr-screencopy-unstable-v1.xml | 17 +++++++++---- render/gles2/renderer.c | 28 +++++++++++++++------- render/wlr_renderer.c | 6 ++--- types/wlr_screencopy_v1.c | 9 ++++--- types/wlr_screenshooter.c | 4 ++-- 9 files changed, 77 insertions(+), 32 deletions(-) diff --git a/backend/drm/drm.c b/backend/drm/drm.c index f4a971a2..ae462568 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -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_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); swap_drm_surface_buffers(&plane->surf, NULL); diff --git a/examples/screencopy.c b/examples/screencopy.c index 8b064a15..7be06d68 100644 --- a/examples/screencopy.c +++ b/examples/screencopy.c @@ -45,6 +45,7 @@ static struct { struct wl_buffer *wl_buffer; void *data; int width, height, stride; + bool y_invert; } buffer; bool buffer_copy_done = false; @@ -99,7 +100,7 @@ static struct wl_buffer *create_shm_buffer(int width, int height, static void frame_handle_buffer(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t width, uint32_t height, - uint32_t flags, uint32_t format, uint32_t stride) { + uint32_t format, uint32_t stride) { buffer.width = width; buffer.height = height; buffer.wl_buffer = @@ -112,6 +113,11 @@ static void frame_handle_buffer(void *data, 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) { @@ -126,6 +132,7 @@ static void frame_handle_failed(void *data, 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, }; @@ -153,8 +160,8 @@ static const struct wl_registry_listener registry_listener = { .global_remove = handle_global_remove, }; -static void write_image(const char *filename, int width, int height, int stride, - void *data) { +static void write_image(char *filename, int width, int height, int stride, + bool y_invert, void *data) { char size[10 + 1 + 10 + 2 + 1]; // int32_t are max 10 digits sprintf(size, "%dx%d+0", width, height); @@ -183,10 +190,19 @@ static void write_image(const char *filename, int width, int height, int stride, exit(EXIT_FAILURE); } close(fd[0]); + // We requested WL_SHM_FORMAT_XRGB8888 in little endian, so that's BGRA // in big endian. - execlp("convert", "convert", "-depth", "8", "-size", size, "bgra:-", - "-alpha", "opaque", filename, NULL); + char *argv[11] = {"convert", "-depth", "8", "-size", size, "bgra:-", + "-alpha", "opaque", filename, NULL}; + if (y_invert) { + argv[8] = "-flip"; + argv[9] = filename; + argv[10] = NULL; + } + + execvp("convert", argv); + fprintf(stderr, "cannot execute convert\n"); exit(EXIT_FAILURE); } @@ -221,12 +237,12 @@ int main(int argc, char *argv[]) { 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_roundtrip(display); + while (!buffer_copy_done && wl_display_dispatch(display) != -1) { + // This space is intentionally left blank } write_image("wayland-screenshot.png", buffer.width, buffer.height, - buffer.stride, buffer.data); + buffer.stride, buffer.y_invert, buffer.data); wl_buffer_destroy(buffer.wl_buffer); return EXIT_SUCCESS; diff --git a/include/wlr/render/interface.h b/include/wlr/render/interface.h index fb427c89..1b138ea3 100644 --- a/include/wlr/render/interface.h +++ b/include/wlr/render/interface.h @@ -34,7 +34,7 @@ struct wlr_renderer_impl { int (*get_dmabuf_modifiers)(struct wlr_renderer *renderer, int format, uint64_t **modifiers); 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, void *data); bool (*format_supported)(struct wlr_renderer *renderer, diff --git a/include/wlr/render/wlr_renderer.h b/include/wlr/render/wlr_renderer.h index c715e4b0..0650bf1b 100644 --- a/include/wlr/render/wlr_renderer.h +++ b/include/wlr/render/wlr_renderer.h @@ -7,6 +7,10 @@ #include #include +enum wlr_renderer_read_pixels_flags { + WLR_RENDERER_READ_PIXELS_Y_INVERT = 1, +}; + struct wlr_renderer_impl; 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 * 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, - 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); /** * Checks if a format is supported. diff --git a/protocol/wlr-screencopy-unstable-v1.xml b/protocol/wlr-screencopy-unstable-v1.xml index 52f3385e..e62e3ff0 100644 --- a/protocol/wlr-screencopy-unstable-v1.xml +++ b/protocol/wlr-screencopy-unstable-v1.xml @@ -76,10 +76,6 @@ destroy the frame. - - - - Provides information about the frame's buffer. This event is sent once @@ -91,7 +87,6 @@ - @@ -116,6 +111,18 @@ summary="invalid width or height"/> + + + + + + + Provides flags about the frame. This event is sent once before the + "ready" event. + + + + Called as soon as the frame is copied, indicating it is available diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c index 05426fa9..0c57bfa1 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -250,9 +250,9 @@ static int gles2_get_dmabuf_modifiers(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, - uint32_t height, uint32_t src_x, uint32_t src_y, uint32_t dst_x, - uint32_t dst_y, void *data) { + enum wl_shm_format wl_fmt, 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) { gles2_get_renderer_in_context(wlr_renderer); const struct wlr_gles2_pixel_format *fmt = get_gles2_format_from_wl(wl_fmt); @@ -266,12 +266,24 @@ static bool gles2_read_pixels(struct wlr_renderer *wlr_renderer, // Make sure any pending drawing is finished before we try to read it glFinish(); - // Unfortunately GLES2 doesn't support GL_PACK_*, so we have to read - // the lines out row by row unsigned char *p = data + dst_y * stride; - 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); + uint32_t pack_stride = width * fmt->bpp / 8; + if (pack_stride == stride && dst_x == 0 && flags != NULL) { + // Under these particular conditions, we can read the pixels with only + // one glReadPixels call + glReadPixels(src_x, 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; diff --git a/render/wlr_renderer.c b/render/wlr_renderer.c index 00f1e411..d66edc13 100644 --- a/render/wlr_renderer.c +++ b/render/wlr_renderer.c @@ -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, - 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) { if (!r->impl->read_pixels) { return false; } - return r->impl->read_pixels(r, fmt, stride, width, height, src_x, src_y, - dst_x, dst_y, data); + return r->impl->read_pixels(r, fmt, flags, stride, width, height, + src_x, src_y, dst_x, dst_y, data); } bool wlr_renderer_format_supported(struct wlr_renderer *r, diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c index 6b6472e1..89680a48 100644 --- a/types/wlr_screencopy_v1.c +++ b/types/wlr_screencopy_v1.c @@ -43,8 +43,9 @@ static void frame_handle_output_swap_buffers(struct wl_listener *listener, wl_shm_buffer_begin_access(buffer); void *data = wl_shm_buffer_get_data(buffer); - bool ok = wlr_renderer_read_pixels(renderer, fmt, stride, width, height, - 0, 0, 0, 0, data); + uint32_t flags = 0; + bool ok = wlr_renderer_read_pixels(renderer, fmt, &flags, stride, + width, height, 0, 0, 0, 0, data); wl_shm_buffer_end_access(buffer); if (!ok) { @@ -52,6 +53,8 @@ static void frame_handle_output_swap_buffers(struct wl_listener *listener, 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, @@ -172,7 +175,7 @@ static void manager_handle_capture_output(struct wl_client *client, frame->height = output->height; // TODO: don't send zero zwlr_screencopy_frame_v1_send_buffer(frame->resource, - frame->width, frame->height, 0, 0, 0); + frame->width, frame->height, 0, 0); } static void manager_handle_destroy(struct wl_client *client, diff --git a/types/wlr_screenshooter.c b/types/wlr_screenshooter.c index e1386be6..5d9e9de3 100644 --- a/types/wlr_screenshooter.c +++ b/types/wlr_screenshooter.c @@ -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); wl_shm_buffer_begin_access(shm_buffer); void *data = wl_shm_buffer_get_data(shm_buffer); - bool ok = wlr_renderer_read_pixels(renderer, format, stride, width, height, - 0, 0, 0, 0, data); + bool ok = wlr_renderer_read_pixels(renderer, format, NULL, stride, + width, height, 0, 0, 0, 0, data); wl_shm_buffer_end_access(shm_buffer); if (!ok) { From bf7560b7cda5d3df8cdc7d24e969bfd91cf0ded5 Mon Sep 17 00:00:00 2001 From: emersion Date: Sat, 23 Jun 2018 15:12:17 +0100 Subject: [PATCH 4/9] screencopy: add capture_output_region support --- include/wlr/types/wlr_screencopy_v1.h | 2 +- protocol/wlr-screencopy-unstable-v1.xml | 29 ++++++++- render/gles2/renderer.c | 11 ++-- types/wlr_screencopy_v1.c | 78 +++++++++++++++++++------ 4 files changed, 93 insertions(+), 27 deletions(-) diff --git a/include/wlr/types/wlr_screencopy_v1.h b/include/wlr/types/wlr_screencopy_v1.h index fbcc1ce2..4766d680 100644 --- a/include/wlr/types/wlr_screencopy_v1.h +++ b/include/wlr/types/wlr_screencopy_v1.h @@ -18,7 +18,7 @@ struct wlr_screencopy_frame_v1 { struct wlr_screencopy_manager_v1 *manager; struct wl_list link; - int32_t width, height; + struct wlr_box buffer_box; struct wl_shm_buffer *buffer; diff --git a/protocol/wlr-screencopy-unstable-v1.xml b/protocol/wlr-screencopy-unstable-v1.xml index e62e3ff0..c54b7268 100644 --- a/protocol/wlr-screencopy-unstable-v1.xml +++ b/protocol/wlr-screencopy-unstable-v1.xml @@ -43,16 +43,39 @@ source. + + + + - - Capture the next frame of a an entire output. + + Capture the next frame of an entire output. + summary="composite cursor onto the frame"/> + + + Capture the next frame of an output's region. + + The region is given in output logical coordinates, see + xdg_output.logical_size. Trying to capture a region spanning outside the + output extents is a protocol error. + + + + + + + + + + All objects created by the manager will still remain valid, until their diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c index 0c57bfa1..e8b0b27d 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -253,7 +253,8 @@ static bool gles2_read_pixels(struct wlr_renderer *wlr_renderer, enum wl_shm_format wl_fmt, 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) { - 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); if (fmt == NULL) { @@ -266,13 +267,15 @@ static bool gles2_read_pixels(struct wlr_renderer *wlr_renderer, // Make sure any pending drawing is finished before we try to read it glFinish(); + glGetError(); // Clear the error flag + unsigned char *p = data + dst_y * stride; uint32_t pack_stride = width * fmt->bpp / 8; if (pack_stride == stride && dst_x == 0 && flags != NULL) { // Under these particular conditions, we can read the pixels with only // one glReadPixels call - glReadPixels(src_x, src_y, width, height, fmt->gl_format, - fmt->gl_type, p); + 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 @@ -288,7 +291,7 @@ static bool gles2_read_pixels(struct wlr_renderer *wlr_renderer, POP_GLES2_DEBUG; - return true; + return (glGetError() == GL_NO_ERROR); } static bool gles2_format_supported(struct wlr_renderer *wlr_renderer, diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c index 89680a48..a600a716 100644 --- a/types/wlr_screencopy_v1.c +++ b/types/wlr_screencopy_v1.c @@ -28,10 +28,8 @@ static void frame_handle_output_swap_buffers(struct wl_listener *listener, wl_list_remove(&frame->output_swap_buffers.link); wl_list_init(&frame->output_swap_buffers.link); - if (output->width != frame->width || output->height != frame->height) { - zwlr_screencopy_frame_v1_send_failed(frame->resource); - return; - } + int x = frame->buffer_box.x; + int y = frame->buffer_box.y; struct wl_shm_buffer *buffer = frame->buffer; assert(buffer != NULL); @@ -45,7 +43,7 @@ static void frame_handle_output_swap_buffers(struct wl_listener *listener, void *data = wl_shm_buffer_get_data(buffer); uint32_t flags = 0; bool ok = wlr_renderer_read_pixels(renderer, fmt, &flags, stride, - width, height, 0, 0, 0, 0, data); + width, height, x, y, 0, 0, data); wl_shm_buffer_end_access(buffer); if (!ok) { @@ -84,8 +82,8 @@ static void frame_handle_copy(struct wl_client *client, return; } - if (frame->width != wl_shm_buffer_get_width(buffer) || - frame->height != wl_shm_buffer_get_height(buffer)) { + if (frame->buffer_box.width != wl_shm_buffer_get_width(buffer) || + frame->buffer_box.height != wl_shm_buffer_get_height(buffer)) { wl_resource_post_error(frame->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_DIMENSIONS, "invalid width or height"); @@ -139,24 +137,37 @@ static struct wlr_screencopy_manager_v1 *manager_from_resource( return wl_resource_get_user_data(resource); } -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); - struct wlr_output *output = wlr_output_from_resource(output_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_resource_post_no_memory(manager_resource); + wl_client_post_no_memory(client); return; } frame->manager = manager; frame->output = output; - uint32_t version = wl_resource_get_version(manager_resource); frame->resource = wl_resource_create(client, &zwlr_screencopy_frame_v1_interface, version, id); if (frame->resource == NULL) { @@ -171,11 +182,39 @@ static void manager_handle_capture_output(struct wl_client *client, wl_list_init(&frame->output_swap_buffers.link); - frame->width = output->width; - frame->height = output->height; - // TODO: don't send zero + frame->buffer_box = buffer_box; zwlr_screencopy_frame_v1_send_buffer(frame->resource, - frame->width, frame->height, 0, 0); + frame->buffer_box.width, frame->buffer_box.height, + WL_SHM_FORMAT_XRGB8888, 4 * frame->buffer_box.width); +} + +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, @@ -185,6 +224,7 @@ static void manager_handle_destroy(struct wl_client *client, 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, }; From c421700f3da6333652488e2951c2b0cbb8e35e51 Mon Sep 17 00:00:00 2001 From: emersion Date: Sat, 23 Jun 2018 18:41:40 +0100 Subject: [PATCH 5/9] screncopy: update protocol --- examples/screencopy.c | 56 ++++++++++++++++++------- include/wlr/types/wlr_screencopy_v1.h | 4 +- protocol/wlr-screencopy-unstable-v1.xml | 29 +++++-------- types/wlr_screencopy_v1.c | 37 ++++++++-------- 4 files changed, 72 insertions(+), 54 deletions(-) diff --git a/examples/screencopy.c b/examples/screencopy.c index 7be06d68..dc1984e6 100644 --- a/examples/screencopy.c +++ b/examples/screencopy.c @@ -44,11 +44,24 @@ 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, ImageMagick uses big-endian +// formats. +static const struct { + enum wl_shm_format wl_format; + char *str_format; +} formats[] = { + {WL_SHM_FORMAT_XRGB8888, "BGRA"}, + {WL_SHM_FORMAT_ARGB8888, "BGRA"}, + {WL_SHM_FORMAT_XBGR8888, "RGBA"}, + {WL_SHM_FORMAT_ABGR8888, "RGBA"}, +}; + static int backingfile(off_t size) { char template[] = "/tmp/wlroots-shared-XXXXXX"; int fd = mkstemp(template); @@ -69,9 +82,8 @@ static int backingfile(off_t size) { return fd; } -static struct wl_buffer *create_shm_buffer(int width, int height, - int *stride_out, void **data_out) { - int stride = width * 4; +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); @@ -90,21 +102,22 @@ static struct wl_buffer *create_shm_buffer(int width, int height, 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, WL_SHM_FORMAT_XRGB8888); + stride, fmt); wl_shm_pool_destroy(pool); *data_out = data; - *stride_out = stride; return buffer; } static void frame_handle_buffer(void *data, - struct zwlr_screencopy_frame_v1 *frame, uint32_t width, uint32_t height, - uint32_t format, uint32_t stride) { + 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(width, height, &buffer.stride, &buffer.data); + create_shm_buffer(format, width, height, stride, &buffer.data); if (buffer.wl_buffer == NULL) { fprintf(stderr, "failed to create buffer\n"); exit(EXIT_FAILURE); @@ -160,11 +173,26 @@ static const struct wl_registry_listener registry_listener = { .global_remove = handle_global_remove, }; -static void write_image(char *filename, int width, int height, int stride, - bool y_invert, void *data) { +static void write_image(char *filename, enum wl_shm_format wl_fmt, int width, + int height, int stride, bool y_invert, void *data) { char size[10 + 1 + 10 + 2 + 1]; // int32_t are max 10 digits sprintf(size, "%dx%d+0", width, height); + const char *fmt_str = NULL; + for (size_t i = 0; i < sizeof(formats) / sizeof(formats[0]); ++i) { + if (formats[i].wl_format == wl_fmt) { + fmt_str = formats[i].str_format; + break; + } + } + if (fmt_str == NULL) { + fprintf(stderr, "unsupported format %"PRIu32"\n", wl_fmt); + exit(EXIT_FAILURE); + } + char convert[strlen(fmt_str) + 3]; + memcpy(convert, fmt_str, strlen(fmt_str) + 1); + strcat(convert, ":-"); + int fd[2]; if (pipe(fd) != 0) { fprintf(stderr, "cannot create pipe: %s\n", strerror(errno)); @@ -191,9 +219,7 @@ static void write_image(char *filename, int width, int height, int stride, } close(fd[0]); - // We requested WL_SHM_FORMAT_XRGB8888 in little endian, so that's BGRA - // in big endian. - char *argv[11] = {"convert", "-depth", "8", "-size", size, "bgra:-", + char *argv[11] = {"convert", "-depth", "8", "-size", size, convert, "-alpha", "opaque", filename, NULL}; if (y_invert) { argv[8] = "-flip"; @@ -241,8 +267,8 @@ int main(int argc, char *argv[]) { // This space is intentionally left blank } - write_image("wayland-screenshot.png", buffer.width, buffer.height, - buffer.stride, buffer.y_invert, buffer.data); + 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; diff --git a/include/wlr/types/wlr_screencopy_v1.h b/include/wlr/types/wlr_screencopy_v1.h index 4766d680..69f62437 100644 --- a/include/wlr/types/wlr_screencopy_v1.h +++ b/include/wlr/types/wlr_screencopy_v1.h @@ -18,7 +18,9 @@ struct wlr_screencopy_frame_v1 { struct wlr_screencopy_manager_v1 *manager; struct wl_list link; - struct wlr_box buffer_box; + enum wl_shm_format format; + struct wlr_box box; + int stride; struct wl_shm_buffer *buffer; diff --git a/protocol/wlr-screencopy-unstable-v1.xml b/protocol/wlr-screencopy-unstable-v1.xml index c54b7268..a7a2d172 100644 --- a/protocol/wlr-screencopy-unstable-v1.xml +++ b/protocol/wlr-screencopy-unstable-v1.xml @@ -43,11 +43,6 @@ source. - - - - Capture the next frame of an entire output. @@ -63,8 +58,8 @@ Capture the next frame of an output's region. The region is given in output logical coordinates, see - xdg_output.logical_size. Trying to capture a region spanning outside the - output extents is a protocol error. + xdg_output.logical_size. The region will be clipped to the output's + extents. + - - + @@ -120,8 +114,8 @@ correct size, see zwlr_screencopy_frame_v1.buffer. The buffer needs to have a supported format. - If the frame is successfully copied, a ready event is sent. Otherwise, - an abort event is sent. + If the frame is successfully copied, a "flags" and a "ready" events are + sent. Otherwise, a "failed" event is sent. @@ -129,9 +123,8 @@ - - + diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c index a600a716..027d4b51 100644 --- a/types/wlr_screencopy_v1.c +++ b/types/wlr_screencopy_v1.c @@ -28,8 +28,8 @@ static void frame_handle_output_swap_buffers(struct wl_listener *listener, wl_list_remove(&frame->output_swap_buffers.link); wl_list_init(&frame->output_swap_buffers.link); - int x = frame->buffer_box.x; - int y = frame->buffer_box.y; + int x = frame->box.x; + int y = frame->box.y; struct wl_shm_buffer *buffer = frame->buffer; assert(buffer != NULL); @@ -66,27 +66,24 @@ static void frame_handle_copy(struct wl_client *client, struct wl_resource *buffer_resource) { struct wlr_screencopy_frame_v1 *frame = frame_from_resource(frame_resource); struct wlr_output *output = frame->output; - struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); struct wl_shm_buffer *buffer = wl_shm_buffer_get(buffer_resource); if (buffer == NULL) { - zwlr_screencopy_frame_v1_send_failed(frame_resource); + 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); - if (!wlr_renderer_format_supported(renderer, fmt)) { + 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_FORMAT, - "unsupported format %"PRIu32, fmt); - return; - } - - if (frame->buffer_box.width != wl_shm_buffer_get_width(buffer) || - frame->buffer_box.height != wl_shm_buffer_get_height(buffer)) { - wl_resource_post_error(frame->resource, - ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_DIMENSIONS, - "invalid width or height"); + ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, + "invalid buffer attributes"); return; } @@ -165,7 +162,6 @@ static void capture_output(struct wl_client *client, return; } frame->manager = manager; - frame->output = output; frame->resource = wl_resource_create(client, @@ -182,10 +178,11 @@ static void capture_output(struct wl_client *client, wl_list_init(&frame->output_swap_buffers.link); - frame->buffer_box = buffer_box; - zwlr_screencopy_frame_v1_send_buffer(frame->resource, - frame->buffer_box.width, frame->buffer_box.height, - WL_SHM_FORMAT_XRGB8888, 4 * frame->buffer_box.width); + 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, From 9aaa9ba477f98f0eb827969f80bb845cfef2dd4b Mon Sep 17 00:00:00 2001 From: emersion Date: Sat, 23 Jun 2018 18:55:37 +0100 Subject: [PATCH 6/9] screencopy: make frame resource inert after copy --- types/wlr_screencopy_v1.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c index 027d4b51..c897e41e 100644 --- a/types/wlr_screencopy_v1.c +++ b/types/wlr_screencopy_v1.c @@ -17,6 +17,17 @@ static struct wlr_screencopy_frame_v1 *frame_from_resource( 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); + // 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 = @@ -48,6 +59,7 @@ static void frame_handle_output_swap_buffers(struct wl_listener *listener, if (!ok) { zwlr_screencopy_frame_v1_send_failed(frame->resource); + frame_destroy(frame); return; } @@ -58,13 +70,17 @@ static void frame_handle_output_swap_buffers(struct wl_listener *listener, zwlr_screencopy_frame_v1_send_ready(frame->resource, tv_sec_hi, tv_sec_lo, event->when->tv_nsec); - // TODO: make frame resource inert + 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); @@ -119,9 +135,7 @@ static const struct zwlr_screencopy_frame_v1_interface frame_impl = { static void frame_handle_resource_destroy(struct wl_resource *frame_resource) { struct wlr_screencopy_frame_v1 *frame = frame_from_resource(frame_resource); - wl_list_remove(&frame->link); - wl_list_remove(&frame->output_swap_buffers.link); - free(frame); + frame_destroy(frame); } From dbb01cbcd01d191e406d99d2837c84a9d73d34a0 Mon Sep 17 00:00:00 2001 From: emersion Date: Sat, 23 Jun 2018 19:06:10 +0100 Subject: [PATCH 7/9] screencopy: listen to buffer destroy --- include/wlr/types/wlr_screencopy_v1.h | 1 + types/wlr_screencopy_v1.c | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/wlr/types/wlr_screencopy_v1.h b/include/wlr/types/wlr_screencopy_v1.h index 69f62437..892687aa 100644 --- a/include/wlr/types/wlr_screencopy_v1.h +++ b/include/wlr/types/wlr_screencopy_v1.h @@ -23,6 +23,7 @@ struct wlr_screencopy_frame_v1 { int stride; struct wl_shm_buffer *buffer; + struct wl_listener buffer_destroy; struct wlr_output *output; struct wl_listener output_swap_buffers; diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c index c897e41e..0760eaaf 100644 --- a/types/wlr_screencopy_v1.c +++ b/types/wlr_screencopy_v1.c @@ -23,6 +23,7 @@ static void frame_destroy(struct wlr_screencopy_frame_v1 *frame) { } 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); @@ -73,6 +74,14 @@ static void frame_handle_output_swap_buffers(struct wl_listener *listener, 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) { @@ -116,11 +125,12 @@ static void frame_handle_copy(struct wl_client *client, 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); - - // TODO: listen to buffer destroy } static void frame_handle_destroy(struct wl_client *client, From a7a96d7644ee00b29f9376576a0da8a26cf3cba6 Mon Sep 17 00:00:00 2001 From: emersion Date: Sat, 30 Jun 2018 23:16:53 +0100 Subject: [PATCH 8/9] examples/screencopy: use libpng --- examples/meson.build | 14 ++++-- examples/screencopy.c | 103 ++++++++++++++++++++---------------------- 2 files changed, 58 insertions(+), 59 deletions(-) diff --git a/examples/meson.build b/examples/meson.build index a290dc82..18f44fde 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -1,6 +1,8 @@ threads = dependency('threads') wayland_cursor = dependency('wayland-cursor') +libpng = dependency('libpng', required: false) + # These versions correspond to ffmpeg 4.0 libavutil = dependency('libavutil', version: '>=56.14.100', required: false) libavcodec = dependency('libavcodec', version: '>=58.18.100', required: false) @@ -57,8 +59,10 @@ if libavutil.found() and libavcodec.found() and libavformat.found() ) endif -executable( - 'screencopy', - 'screencopy.c', - dependencies: [wayland_client, wlr_protos, wlroots] -) +if libpng.found() + executable( + 'screencopy', + 'screencopy.c', + dependencies: [wayland_client, wlr_protos, wlroots, libpng] + ) +endif diff --git a/examples/screencopy.c b/examples/screencopy.c index dc1984e6..4e58cd3b 100644 --- a/examples/screencopy.c +++ b/examples/screencopy.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,11 @@ #include #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; @@ -50,16 +56,13 @@ static struct { } buffer; bool buffer_copy_done = false; -// wl_shm_format describes little-endian formats, ImageMagick uses big-endian -// formats. -static const struct { - enum wl_shm_format wl_format; - char *str_format; -} formats[] = { - {WL_SHM_FORMAT_XRGB8888, "BGRA"}, - {WL_SHM_FORMAT_ARGB8888, "BGRA"}, - {WL_SHM_FORMAT_XBGR8888, "RGBA"}, - {WL_SHM_FORMAT_ABGR8888, "RGBA"}, +// 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) { @@ -156,8 +159,8 @@ static void handle_global(void *data, struct wl_registry *registry, 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) { + } else if (strcmp(interface, + zwlr_screencopy_manager_v1_interface.name) == 0) { screencopy_manager = wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, 1); } @@ -174,64 +177,56 @@ static const struct wl_registry_listener registry_listener = { }; static void write_image(char *filename, enum wl_shm_format wl_fmt, int width, - int height, int stride, bool y_invert, void *data) { - char size[10 + 1 + 10 + 2 + 1]; // int32_t are max 10 digits - sprintf(size, "%dx%d+0", width, height); - - const char *fmt_str = NULL; + 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_str = formats[i].str_format; + fmt = &formats[i]; break; } } - if (fmt_str == NULL) { + if (fmt == NULL) { fprintf(stderr, "unsupported format %"PRIu32"\n", wl_fmt); exit(EXIT_FAILURE); } - char convert[strlen(fmt_str) + 3]; - memcpy(convert, fmt_str, strlen(fmt_str) + 1); - strcat(convert, ":-"); - int fd[2]; - if (pipe(fd) != 0) { - fprintf(stderr, "cannot create pipe: %s\n", strerror(errno)); + FILE *f = fopen(filename, "wb"); + if (f == NULL) { + fprintf(stderr, "failed to open output file\n"); exit(EXIT_FAILURE); } - pid_t child = fork(); - if (child < 0) { - fprintf(stderr, "fork() failed\n"); - exit(EXIT_FAILURE); - } else if (child != 0) { - close(fd[0]); - if (write(fd[1], data, stride * height) < 0) { - fprintf(stderr, "write() failed: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } - close(fd[1]); - waitpid(child, NULL, 0); - } else { - close(fd[1]); - if (dup2(fd[0], 0) != 0) { - fprintf(stderr, "cannot dup the pipe\n"); - exit(EXIT_FAILURE); - } - close(fd[0]); + png_structp png = + png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_infop info = png_create_info_struct(png); - char *argv[11] = {"convert", "-depth", "8", "-size", size, convert, - "-alpha", "opaque", filename, NULL}; + 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) { - argv[8] = "-flip"; - argv[9] = filename; - argv[10] = NULL; + row = data + (height - i - 1) * stride; + } else { + row = data + i * stride; } - - execvp("convert", argv); - - fprintf(stderr, "cannot execute convert\n"); - exit(EXIT_FAILURE); + png_write_row(png, row); } + + png_write_end(png, NULL); + + png_destroy_write_struct(&png, &info); + + fclose(f); } int main(int argc, char *argv[]) { From cc9b198f9e4e00e59773e78ab8afe1c4c0fd1ee1 Mon Sep 17 00:00:00 2001 From: emersion Date: Sat, 30 Jun 2018 23:19:02 +0100 Subject: [PATCH 9/9] render/gles2: ditch extra parens --- render/gles2/renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c index e8b0b27d..5233ed1b 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -291,7 +291,7 @@ static bool gles2_read_pixels(struct wlr_renderer *wlr_renderer, POP_GLES2_DEBUG; - return (glGetError() == GL_NO_ERROR); + return glGetError() == GL_NO_ERROR; } static bool gles2_format_supported(struct wlr_renderer *wlr_renderer,