From 73755ad348657fdf8d9e0f9a48ee13a2ab947e35 Mon Sep 17 00:00:00 2001 From: emersion Date: Tue, 19 Jun 2018 19:07:29 +0100 Subject: [PATCH] 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 d8df0bfc9..a290dc822 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 000000000..98df09b56 --- /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 dfe070ca1..88c5ca900 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 000000000..fbcc1ce20 --- /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 ca0d82b5c..5ab82d840 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 000000000..6bd5a75ff --- /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 2bba06e2c..d670c33b6 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 6d6e0fa32..49f87bf88 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 000000000..197ba3309 --- /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); +}