diff --git a/Makefile b/Makefile index 0c83db2..5c2428c 100644 --- a/Makefile +++ b/Makefile @@ -7,11 +7,14 @@ SOURCES := \ src/render.c \ src/dmabuf.c \ src/strlcpy.c \ + src/shm.c \ + src/screencopy.c \ include common.mk PROTOCOLS := \ $(BUILD_DIR)/proto-wlr-export-dmabuf-unstable-v1.o \ + $(BUILD_DIR)/proto-wlr-screencopy-unstable-v1.o \ VERSION=0.0.0 diff --git a/include/screencopy.h b/include/screencopy.h new file mode 100644 index 0000000..10d5c68 --- /dev/null +++ b/include/screencopy.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include "wlr-screencopy-unstable-v1.h" + +struct zwlr_screencopy_manager_v1; +struct zwlr_screencopy_frame_v1; +struct wl_output; +struct wl_buffer; +struct wl_shm; + +enum screencopy_status { + SCREENCOPY_STATUS_CAPTURING = 0, + SCREENCOPY_STATUS_FATAL, + SCREENCOPY_STATUS_FAILED, + SCREENCOPY_STATUS_DONE, +}; + +struct screencopy { + struct wl_shm* wl_shm; + struct wl_buffer* buffer; + struct zwlr_screencopy_manager_v1* manager; + struct zwlr_screencopy_frame_v1* frame; + + bool overlay_cursor; + struct wl_output* output; + enum screencopy_status status; + void (*on_done)(struct screencopy*); +}; diff --git a/include/shm.h b/include/shm.h new file mode 100644 index 0000000..c22327f --- /dev/null +++ b/include/shm.h @@ -0,0 +1,3 @@ +#pragma once + +int shm_alloc_fd(size_t size); diff --git a/protocols/Makefile b/protocols/Makefile index 4d1215d..d5b2268 100644 --- a/protocols/Makefile +++ b/protocols/Makefile @@ -2,7 +2,7 @@ WAYLAND_SCANNER ?= wayland-scanner PROTOCOLS := \ wlr-export-dmabuf-unstable-v1.xml \ - wlr-output-management-unstable-v1.xml \ + wlr-screencopy-unstable-v1.xml \ SOURCES := $(PROTOCOLS:%.xml=build/%.c) HEADERS := $(PROTOCOLS:%.xml=build/%.h) diff --git a/protocols/wlr-screencopy-unstable-v1.xml b/protocols/wlr-screencopy-unstable-v1.xml new file mode 100644 index 0000000..d519d9c --- /dev/null +++ b/protocols/wlr-screencopy-unstable-v1.xml @@ -0,0 +1,206 @@ + + + + Copyright © 2018 Simon Ser + Copyright © 2019 Marel Iceland ehf. + + 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. + + 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. + + + + + This object is a manager which offers requests to start capturing from a + source. + + + + + Capture the next frame of an entire output. + + + + + + + + + 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. + + + + + + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + 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. + + + + + 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. + + + + + + + + + + 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. + + + + + + + + + + + + + + + + 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 + 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. + + + + + + + + + This event indicates that the attempted frame copy has failed. + + After receiving this event, the client should destroy the object. + + + + + + Destroys the frame. This request can be sent at any time by the client. + + + + + + Same as copy, except it waits until there is damage to copy. + + + + + + + This event is sent right before the ready event when copy_with_damage is + requested. It may be generated multiple times for each copy_with_damage + request. + + The arguments describe a box around an area that has changed since the + last copy request that was derived from the current screencopy manager + instance. + + The union of all regions received between the call to copy_with_damage + and a ready event is the total damage since the prior ready event. + + + + + + + + diff --git a/src/main.c b/src/main.c index 390b979..3835eb0 100644 --- a/src/main.c +++ b/src/main.c @@ -30,6 +30,7 @@ #include #include "wlr-export-dmabuf-unstable-v1.h" +#include "wlr-screencopy-unstable-v1.h" #include "render.h" #include "dmabuf.h" #include "strlcpy.h" @@ -56,6 +57,9 @@ struct wayvnc { const struct output* selected_output; struct dmabuf_capture* capture; + struct wl_shm* wl_shm; + struct zwlr_screencopy_manager_v1* screencopy_manager; + uv_poll_t wayland_poller; uv_prepare_t flusher; uv_signal_t signal_handler; @@ -161,11 +165,25 @@ static void registry_add(void* data, struct wl_registry* registry, return; } + if (strcmp(interface, wl_shm_interface.name) == 0) { + self->wl_shm = wl_registry_bind(registry, id, &wl_shm_interface, + 1); + return; + } + if (strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name) == 0) { self->export_manager = wl_registry_bind(registry, id, &zwlr_export_dmabuf_manager_v1_interface, - version); + 1); + return; + } + + if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) { + self->screencopy_manager = + wl_registry_bind(registry, id, + &zwlr_screencopy_manager_v1_interface, + 2); return; } } diff --git a/src/screencopy.c b/src/screencopy.c new file mode 100644 index 0000000..eb905b1 --- /dev/null +++ b/src/screencopy.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019 Andri Yngvason + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "shm.h" +#include "screencopy.h" + +static int screencopy_buffer_init(struct screencopy* self, + enum wl_shm_format format, uint32_t width, + uint32_t height, uint32_t stride) +{ + if (self->buffer) + return 0; + + size_t size = stride * height; + + int fd = shm_alloc_fd(size); + if (fd < 0) + return -1; + + struct wl_shm_pool* pool = wl_shm_create_pool(self->wl_shm, fd, size); + close(fd); + if (!pool) + return -1; + + struct wl_buffer* buffer = + wl_shm_pool_create_buffer(pool, 0, width, height, stride, + format); + wl_shm_pool_destroy(pool); + + self->buffer = buffer; + return 0; +} + +void screencopy_stop(struct screencopy* self) +{ + if (self->frame) { + zwlr_screencopy_frame_v1_destroy(self->frame); + self->frame = NULL; + } +} + +static void screencopy_buffer(void* data, + struct zwlr_screencopy_frame_v1* frame, + enum wl_shm_format format, uint32_t width, + uint32_t height, uint32_t stride) +{ + struct screencopy* self = data; + + if (screencopy_buffer_init(self, format, width, height, stride) < 0) { + self->status = SCREENCOPY_STATUS_FATAL; + screencopy_stop(self); + self->on_done(self); + } +} + +static void screencopy_flags(void* data, + struct zwlr_screencopy_frame_v1* frame, + uint32_t flags) +{ + (void)data; + (void)frame; + (void)flags; + + /* TODO. Assume y-invert for now */ +} + +static void screencopy_ready(void* data, + struct zwlr_screencopy_frame_v1* frame, + uint32_t sec_hi, uint32_t sec_lo, uint32_t nsec) +{ + (void)sec_hi; + (void)sec_lo; + (void)nsec; + + struct screencopy* self = data; + screencopy_stop(self); + self->status = SCREENCOPY_STATUS_DONE; + self->on_done(self); +} + +static void screencopy_failed(void* data, + struct zwlr_screencopy_frame_v1* frame) +{ + struct screencopy* self = data; + screencopy_stop(self); + self->status = SCREENCOPY_STATUS_FAILED; + self->on_done(self); +} + +static void screencopy_damage(void* data, + struct zwlr_screencopy_frame_v1* frame, + uint32_t x, uint32_t y, + uint32_t width, uint32_t height) +{ + struct screencopy* self = data; + + /* TODO */ +} + +int screencopy_start(struct screencopy* self) +{ + static const struct zwlr_screencopy_frame_v1_listener frame_listener = { + .buffer = screencopy_buffer, + .flags = screencopy_flags, + .ready = screencopy_ready, + .failed = screencopy_failed, + .damage = screencopy_damage, + }; + + self->frame = + zwlr_screencopy_manager_v1_capture_output(self->manager, + self->overlay_cursor, + self->output); + if (!self->frame) + return -1; + + zwlr_screencopy_frame_v1_add_listener(self->frame, &frame_listener, + self); + + self->status = SCREENCOPY_STATUS_CAPTURING; + return 0; +} diff --git a/src/shm.c b/src/shm.c new file mode 100644 index 0000000..af1e06a --- /dev/null +++ b/src/shm.c @@ -0,0 +1,59 @@ +/* The following is based on code puublished under public domain by Drew DeVault + * here: https://wayland-book.com/book/surfaces/shared-memory.html + */ + +#include +#include +#include +#include +#include + +static void randname(char *buf) +{ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + + for (int i = 0; i < 6; ++i) { + buf[i] = 'A'+(r&15)+(r&16)*2; + r >>= 5; + } +} + +static int create_shm_file(void) +{ + int retries = 100; + + do { + char name[] = "/wl_shm-XXXXXX"; + randname(name + sizeof(name) - 7); + --retries; + + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +int shm_alloc_fd(size_t size) +{ + int fd = create_shm_file(); + if (fd < 0) + return -1; + + int ret; + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + close(fd); + return -1; + } + + return fd; +}