aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorprogandy <code@progandy>2020-04-30 20:25:25 +0200
committerprogandy <code@progandy>2020-04-30 20:25:25 +0200
commit260cd732767185a2781173905b16454140005fea (patch)
treee57b26d2fd24550a70ff285b1c88c59ce7e2ceaf
Create an output mirror prototype for wlroots
To mirror an output its dmabuf is exported and then submitted to a new xdg surface which can be placed on another output.
-rw-r--r--LICENSE19
-rw-r--r--main.c606
-rw-r--r--meson.build42
-rw-r--r--protocol/meson.build49
-rw-r--r--protocol/wlr-export-dmabuf-unstable-v1.xml203
5 files changed, 919 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d2881f3
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2020 ProgAndy
+
+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 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.
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..d172056
--- /dev/null
+++ b/main.c
@@ -0,0 +1,606 @@
+/* SPDX-License-Identifier: MIT */
+/* Copyright (c) 2020 ProgAndy */
+
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wayland-client.h>
+#include "wlr-export-dmabuf-unstable-v1-client-protocol.h"
+#include "linux-dmabuf-unstable-v1-client-protocol.h"
+#include "xdg-shell-client-protocol.h"
+
+struct wayland_output {
+ struct wl_list link;
+ uint32_t id;
+ struct wl_output *output;
+ char *make;
+ char *model;
+ int width;
+ int height;
+ int framerate;
+};
+
+struct frame_object {
+ uint32_t index;
+ int32_t fd;
+ uint32_t size;
+ uint32_t offset;
+ uint32_t stride;
+ uint32_t plane_index;
+};
+
+struct frame {
+ struct zwlr_export_dmabuf_frame_v1 *frame;
+ struct wl_buffer *buffer;
+ uint32_t width;
+ uint32_t height;
+ uint32_t offset_x;
+ uint32_t offset_y;
+ uint32_t buffer_flags;
+ uint32_t flags;
+ uint32_t format;
+ uint32_t mod_high;
+ uint32_t mod_low;
+ uint32_t num_objects;
+ struct frame_object objects[];
+};
+
+struct window {
+ struct wl_surface *surface;
+ struct xdg_surface *xdg_surface;
+ struct xdg_toplevel *xdg_toplevel;
+ bool init;
+ int width, height;
+};
+
+struct mirror_context {
+ struct wl_display *display;
+ struct wl_registry *registry;
+ struct zwlr_export_dmabuf_manager_v1 *export_manager;
+ struct wl_compositor *compositor;
+ struct xdg_wm_base *xdg_wm_base;
+ struct zwp_linux_dmabuf_v1 *dmabuf;
+
+ struct wl_list output_list;
+
+ struct window *window;
+ /* Target */
+ struct wl_output *source_output;
+ int w;
+ int h;
+ bool with_cursor;
+
+ /* Main frame callback */
+ struct zwlr_export_dmabuf_frame_v1 *frame_callback;
+
+ /* If something happens during capture */
+ int err;
+ bool quit;
+
+ /* dmabuf frames */
+ struct frame *incomplete_frame;
+ struct frame *next_frame;
+
+
+};
+struct mirror_context *q_ctx = NULL;
+
+void window_destroy(struct window *window);
+int window_create(struct mirror_context *ctx);
+
+static void frame_free(struct frame *f) {
+
+ if (f) {
+ if (f->buffer)
+ wl_buffer_destroy(f->buffer);
+ for (uint32_t i = 0; i < f->num_objects; ++i) {
+ close(f->objects[i].fd);
+ }
+ if (f->frame)
+ zwlr_export_dmabuf_frame_v1_destroy(f->frame);
+ free(f);
+ }
+
+}
+
+static void output_handle_geometry(void *data, struct wl_output *wl_output,
+ int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
+ int32_t subpixel, const char *make, const char *model,
+ int32_t transform) {
+ struct wayland_output *output = data;
+ output->make = strdup(make);
+ output->model = strdup(model);
+}
+
+static void output_handle_mode(void *data, struct wl_output *wl_output,
+ uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
+ if (flags & WL_OUTPUT_MODE_CURRENT) {
+ struct wayland_output *output = data;
+ output->width = width;
+ output->height = height;
+ output->framerate = refresh;
+ }
+}
+
+static void output_handle_done(void* data, struct wl_output *wl_output) {
+ /* Nothing to do */
+}
+
+static void output_handle_scale(void* data, struct wl_output *wl_output,
+ int32_t factor) {
+ /* Nothing to do */
+}
+
+static const struct wl_output_listener output_listener = {
+ .geometry = output_handle_geometry,
+ .mode = output_handle_mode,
+ .done = output_handle_done,
+ .scale = output_handle_scale,
+};
+
+static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
+{
+ xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+ xdg_wm_base_ping,
+};
+
+
+static void registry_handle_add(void *data, struct wl_registry *reg,
+ uint32_t id, const char *interface, uint32_t ver) {
+ struct mirror_context *ctx = data;
+
+ if (!strcmp(interface, wl_output_interface.name)) {
+ struct wayland_output *output = calloc(1,sizeof(*output));
+
+ output->id = id;
+ output->output = wl_registry_bind(reg, id, &wl_output_interface, 1);
+
+ wl_output_add_listener(output->output, &output_listener, output);
+ wl_list_insert(&ctx->output_list, &output->link);
+ }
+
+ if (!strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) {
+ ctx->export_manager = wl_registry_bind(reg, id,
+ &zwlr_export_dmabuf_manager_v1_interface, 1);
+ }
+
+ if (!strcmp(interface, wl_compositor_interface.name)) {
+ ctx->compositor = wl_registry_bind(reg, id, &wl_compositor_interface, 1);
+ }
+
+ if (!strcmp(interface, xdg_wm_base_interface.name)) {
+ ctx->xdg_wm_base = wl_registry_bind(reg, id, &xdg_wm_base_interface, 1);
+ xdg_wm_base_add_listener(ctx->xdg_wm_base, &xdg_wm_base_listener, ctx);
+ }
+
+ if (!strcmp(interface, zwp_linux_dmabuf_v1_interface.name)) {
+ if (ver < 3) {
+ return;
+ }
+ ctx->dmabuf = wl_registry_bind(reg, id, &zwp_linux_dmabuf_v1_interface, 3);
+ /* assume we get a compatible format from dmabuf export */
+ }
+}
+
+static void remove_output(struct wayland_output *out) {
+ wl_list_remove(&out->link);
+ free(out->make);
+ free(out->model);
+ free(out);
+}
+
+static struct wayland_output *find_output(struct mirror_context *ctx,
+ struct wl_output *out, uint32_t id) {
+ struct wayland_output *output, *tmp;
+ wl_list_for_each_safe(output, tmp, &ctx->output_list, link) {
+ if ((output->output == out) || (output->id == id)) {
+ return output;
+ }
+ }
+ return NULL;
+}
+
+static void registry_handle_remove(void *data, struct wl_registry *reg,
+ uint32_t id) {
+ remove_output(find_output((struct mirror_context *)data, NULL, id));
+}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = registry_handle_add,
+ .global_remove = registry_handle_remove,
+};
+
+static void frame_start(void *data, struct zwlr_export_dmabuf_frame_v1 *frame,
+ uint32_t width, uint32_t height, uint32_t offset_x, uint32_t offset_y,
+ uint32_t buffer_flags, uint32_t flags, uint32_t format,
+ uint32_t mod_high, uint32_t mod_low, uint32_t num_objects) {
+ struct mirror_context *ctx = data;
+ int err = 0;
+
+ /* Allocate DRM specific struct */
+ struct frame *f = calloc(1,sizeof(*f)+num_objects*sizeof(struct frame_object));
+ if (!f) {
+ err = ENOMEM;
+ goto fail;
+ }
+ // suppress y-invert flag.
+ flags &= ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT;
+ f->frame = frame;
+ f->width = width;
+ f->height = height;
+ f->offset_x = offset_x;
+ f->offset_y = offset_y;
+ f->buffer_flags = buffer_flags;
+ f->flags = flags;
+ f->format = format;
+ f->mod_high = mod_high;
+ f->mod_low = mod_low;
+ f->num_objects = num_objects;
+
+ f->width = width;
+ f->height = height;
+
+ ctx->incomplete_frame = f;
+
+ return;
+
+fail:
+ ctx->err = err;
+ if (f) frame_free(f);
+}
+
+static void frame_object(void *data, struct zwlr_export_dmabuf_frame_v1 *frame,
+ uint32_t index, int32_t fd, uint32_t size, uint32_t offset,
+ uint32_t stride, uint32_t plane_index) {
+ struct mirror_context *ctx = data;
+ struct frame *f = ctx->incomplete_frame;
+ f->objects[index].index = index;
+ f->objects[index].fd = fd;
+ f->objects[index].size = size;
+ f->objects[index].offset = offset;
+ f->objects[index].stride = stride;
+ f->objects[index].plane_index = plane_index;
+
+}
+
+static void
+buffer_release(void *data, struct wl_buffer *buffer)
+{
+ struct frame *f = data;
+
+ frame_free(f);
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+ buffer_release
+};
+
+
+static void display_frame(struct mirror_context *ctx);
+static void request_frame(struct mirror_context *ctx);
+
+static void frame_ready(void *data, struct zwlr_export_dmabuf_frame_v1 *frame,
+ uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
+ struct mirror_context *ctx = data;
+ struct frame *f = ctx->incomplete_frame;
+ ctx->incomplete_frame = NULL;
+ struct zwp_linux_buffer_params_v1 *params;
+ params = zwp_linux_dmabuf_v1_create_params(ctx->dmabuf);
+
+ for (uint32_t i = 0; i < f->num_objects; ++i) {
+ zwp_linux_buffer_params_v1_add(params,
+ f->objects[i].fd,
+ f->objects[i].plane_index,
+ f->objects[i].offset,
+ f->objects[i].stride,
+ f->mod_high,
+ f->mod_low);
+ }
+
+
+ // TODO: handle failed creation
+ f->buffer =
+ zwp_linux_buffer_params_v1_create_immed(params,
+ f->width,
+ f->height,
+ f->format,
+ f->flags);
+ zwp_linux_buffer_params_v1_destroy(params);
+ wl_buffer_add_listener(f->buffer, &buffer_listener, f);
+
+ if (ctx->next_frame) {
+ frame_free(ctx->next_frame);
+ }
+ ctx->next_frame = f;
+
+
+ //* Frames will not be requested in the render loop
+ if (!ctx->quit && !ctx->err) {
+ if (ctx->window && ctx->window->init) {
+ display_frame(ctx);
+ }
+ request_frame(ctx);
+ }
+ return;
+}
+
+static void frame_cancel(void *data, struct zwlr_export_dmabuf_frame_v1 *frame,
+ uint32_t reason) {
+ struct mirror_context *ctx = data;
+ frame_free(ctx->incomplete_frame);
+ if (reason == ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT) {
+ /* Permanent error. Exit! */
+ ctx->err = true;
+ } else {
+ request_frame(ctx);
+ }
+}
+
+static const struct zwlr_export_dmabuf_frame_v1_listener frame_listener = {
+ .frame = frame_start,
+ .object = frame_object,
+ .ready = frame_ready,
+ .cancel = frame_cancel,
+};
+
+static void request_frame(struct mirror_context *ctx) {
+ ctx->frame_callback = zwlr_export_dmabuf_manager_v1_capture_output(
+ ctx->export_manager, ctx->with_cursor, ctx->source_output);
+
+ zwlr_export_dmabuf_frame_v1_add_listener(ctx->frame_callback,
+ &frame_listener, ctx);
+}
+
+
+
+
+static void xdg_surface_handle_configure(void *data,
+ struct xdg_surface *surface, uint32_t serial) {
+ struct mirror_context *ctx = data;
+
+ xdg_surface_ack_configure(surface, serial);
+ /* mark window ready */
+ ctx->window->init = true;
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+ xdg_surface_handle_configure,
+};
+
+static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel,
+ int32_t width, int32_t height, struct wl_array *states) {
+ /* Empty */
+}
+
+static void
+xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel)
+{
+ struct mirror_context *ctx = data;
+ ctx->quit = true;
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ xdg_toplevel_handle_configure,
+ xdg_toplevel_handle_close,
+};
+
+
+static void on_quit_signal(int signo) {
+ if (q_ctx) {
+ q_ctx->quit = true;
+ }
+}
+
+
+static int main_loop(struct mirror_context *ctx) {
+
+ q_ctx = ctx;
+
+ if (signal(SIGINT, on_quit_signal) == SIG_ERR) {
+ /*av_log(ctx, AV_LOG_ERROR, "Unable to install signal handler!\n");*/
+ return EINVAL;
+ }
+
+ request_frame(ctx);
+
+ while (wl_display_dispatch(ctx->display) != -1 && !ctx->err && !ctx->quit);
+
+ return ctx->err;
+}
+
+static int init(struct mirror_context *ctx) {
+ wl_list_init(&ctx->output_list);
+
+ ctx->display = wl_display_connect(NULL);
+ if (!ctx->display) {
+ puts("Failed to connect to display");
+ return EINVAL;
+ }
+
+
+ ctx->registry = wl_display_get_registry(ctx->display);
+ wl_registry_add_listener(ctx->registry, &registry_listener, ctx);
+
+ wl_display_roundtrip(ctx->display);
+ wl_display_dispatch(ctx->display);
+ assert(ctx->compositor);
+
+ if (!ctx->export_manager) {
+ printf("Compositor doesn't support %s!\n",
+ zwlr_export_dmabuf_manager_v1_interface.name);
+ return -1;
+ }
+ if (!ctx->dmabuf) {
+ printf("Compositor doesn't support %s!\n",
+ zwp_linux_dmabuf_v1_interface.name);
+ return -1;
+ }
+ if (!ctx->xdg_wm_base) {
+ printf("Compositor doesn't support %s!\n",
+ xdg_wm_base_interface.name);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+void window_destroy(struct window *window) {
+ if (window->xdg_toplevel) {
+ xdg_toplevel_destroy(window->xdg_toplevel);
+ }
+ if (window->xdg_surface) {
+ xdg_surface_destroy(window->xdg_surface);
+ }
+ if (window->surface) {
+ wl_surface_destroy(window->surface);
+ }
+ free(window);
+}
+
+int window_create(struct mirror_context *ctx) {
+ struct window *window;
+ int err = 0;
+
+ assert(!ctx->window);
+
+ window = calloc(1,sizeof(*window));
+ if (!window) {
+ return 1;
+ }
+
+ window->width = ctx->w;
+ window->height = ctx->h;
+
+ window->surface = wl_compositor_create_surface(ctx->compositor);
+
+ window->xdg_surface = xdg_wm_base_get_xdg_surface(ctx->xdg_wm_base, window->surface);
+ xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, ctx);
+
+ window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface);
+ xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, ctx);
+
+ xdg_toplevel_set_title(window->xdg_toplevel, "wlr dmabuf output mirror");
+
+ wl_surface_commit(window->surface);
+
+ ctx->window = window;
+
+ return err;
+}
+
+
+static void display_frame(struct mirror_context *ctx) {
+
+ struct frame *next = ctx->next_frame;
+ ctx->next_frame = NULL;
+ if (!next) return;
+
+
+ wl_surface_attach(ctx->window->surface, next->buffer, 0, 0);
+ wl_surface_damage(ctx->window->surface, 0, 0, ctx->window->width, ctx->window->height);
+
+ wl_surface_commit(ctx->window->surface);
+}
+
+
+static void uninit(struct mirror_context *ctx);
+
+int main(int argc, char *argv[]) {
+ int err;
+ struct mirror_context ctx = { 0 };
+ struct wayland_output *o, *tmp_o;
+
+
+ err = init(&ctx);
+ if (err) {
+ puts("Could not initialize wayland");
+ goto end;
+ }
+
+ if (argc != 2 || !strcmp(argv[1], "-h")) {
+ printf("wdomirror SOURCE_ID\n"
+ "Mirror wlroots output with dmabuf protocols.\n\n");
+ wl_list_for_each_reverse_safe(o, tmp_o, &ctx.output_list, link) {
+ printf("Mirrorable output: %s Model: %s: ID: %i\n",
+ o->make, o->model, o->id);
+ }
+ err = 1;
+ goto end;
+ }
+
+ const int o_id = strtol(argv[1], NULL, 10);
+ o = find_output(&ctx, NULL, o_id);
+ if (!o) {
+ printf("Unable to find output with ID %i!\n", o_id);
+ err = 1;
+ goto end;
+ }
+ printf("Mirroring output: %s Model: %s: ID: %i\n",
+ o->make, o->model, o->id);
+
+ ctx.source_output = o->output;
+ ctx.w = o->width;
+ ctx.h = o->height;
+ ctx.with_cursor = true;
+
+
+ err = window_create(&ctx);
+ if (err) {
+ goto end;
+ }
+
+ err = main_loop(&ctx);
+ if (err) {
+ goto end;
+ }
+
+end:
+ uninit(&ctx);
+ return err;
+}
+
+static void uninit(struct mirror_context *ctx) {
+ struct wayland_output *output, *tmp_o;
+ wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) {
+ remove_output(output);
+ }
+ if (ctx->window)
+ window_destroy(ctx->window);
+
+ if (ctx->export_manager) {
+ zwlr_export_dmabuf_manager_v1_destroy(ctx->export_manager);
+ }
+ if (ctx->dmabuf) {
+ zwp_linux_dmabuf_v1_destroy(ctx->dmabuf);
+ }
+ if (ctx->xdg_wm_base) {
+ xdg_wm_base_destroy(ctx->xdg_wm_base);
+ }
+ if (ctx->compositor) {
+ wl_compositor_destroy(ctx->compositor);
+ }
+ if (ctx->registry) {
+ wl_registry_destroy(ctx->registry);
+ }
+ if (ctx->display) {
+ wl_display_flush(ctx->display);
+ wl_display_disconnect(ctx->display);
+ }
+
+ if (ctx->next_frame) {
+ frame_free(ctx->next_frame);
+ }
+
+}
+
+/* vim: set ts=2 sw=2 et: */
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..52b01ec
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,42 @@
+project(
+ 'wdomirror',
+ 'c',
+ version: '0.1.0',
+ license: 'MIT',
+ meson_version: '>=0.54.0',
+ default_options: [
+ 'c_std=c11',
+ 'warning_level=2',
+ 'werror=true',
+ ],
+)
+
+cc = meson.get_compiler('c')
+
+add_project_arguments(cc.get_supported_arguments([
+ '-Wno-missing-braces',
+ '-Wno-unused-parameter',
+]), language: 'c')
+
+wayland_client = dependency('wayland-client')
+wayland_protos = dependency('wayland-protocols', version: '>=1.17')
+
+subdir('protocol')
+
+mirror_files = [ 'main.c' ]
+mirror_deps = [
+ wayland_client,
+ wayland_protos,
+ client_protos,
+]
+
+
+proto_inc = include_directories('protocol')
+
+executable(
+ meson.project_name(),
+ mirror_files,
+ dependencies: mirror_deps,
+ include_directories: proto_inc,
+ install: true
+)
diff --git a/protocol/meson.build b/protocol/meson.build
new file mode 100644
index 0000000..2b274a2
--- /dev/null
+++ b/protocol/meson.build
@@ -0,0 +1,49 @@
+wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir')
+
+wayland_scanner = find_program('wayland-scanner')
+
+# should check wayland_scanner's version, but it is hard to get
+if wayland_client.version().version_compare('>=1.14.91')
+ code_type = 'private-code'
+else
+ code_type = 'code'
+endif
+
+wayland_scanner_code = generator(
+ wayland_scanner,
+ output: '@BASENAME@-protocol.c',
+ arguments: [code_type, '@INPUT@', '@OUTPUT@'],
+)
+
+wayland_scanner_client = generator(
+ wayland_scanner,
+ output: '@BASENAME@-client-protocol.h',
+ arguments: ['client-header', '@INPUT@', '@OUTPUT@'],
+)
+
+client_protocols = [
+ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
+ [wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'],
+ 'wlr-export-dmabuf-unstable-v1.xml',
+]
+
+
+client_protos_src = []
+client_protos_headers = []
+
+foreach p : client_protocols
+ xml = join_paths(p)
+ client_protos_src += wayland_scanner_code.process(xml)
+ client_protos_headers += wayland_scanner_client.process(xml)
+endforeach
+
+lib_client_protos = static_library(
+ 'client_protos',
+ client_protos_src + client_protos_headers,
+ dependencies: [wayland_client]
+) # for the include directory
+
+client_protos = declare_dependency(
+ link_with: lib_client_protos,
+ sources: client_protos_headers,
+)
diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml
new file mode 100644
index 0000000..751f7ef
--- /dev/null
+++ b/protocol/wlr-export-dmabuf-unstable-v1.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_export_dmabuf_unstable_v1">
+ <copyright>
+ Copyright © 2018 Rostislav Pehlivanov
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <description summary="a protocol for low overhead screen content capturing">
+ An interface to capture surfaces in an efficient way by exporting DMA-BUFs.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+ </description>
+
+ <interface name="zwlr_export_dmabuf_manager_v1" version="1">
+ <description summary="manager to inform clients and begin capturing">
+ This object is a manager with which to start capturing from sources.
+ </description>
+
+ <request name="capture_output">
+ <description summary="capture a frame from an output">
+ Capture the next frame of a an entire output.
+ </description>
+ <arg name="frame" type="new_id" interface="zwlr_export_dmabuf_frame_v1"/>
+ <arg name="overlay_cursor" type="int"
+ summary="include custom client hardware cursor on top of the frame"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the manager">
+ All objects created by the manager will still remain valid, until their
+ appropriate destroy request has been called.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_export_dmabuf_frame_v1" version="1">
+ <description summary="a DMA-BUF frame">
+ This object represents a single DMA-BUF frame.
+
+ If the capture is successful, the compositor will first send a "frame"
+ event, followed by one or several "object". When the frame is available
+ for readout, the "ready" event is sent.
+
+ If the capture failed, the "cancel" event is sent. This can happen anytime
+ before the "ready" event.
+
+ Once either a "ready" or a "cancel" event is received, the client should
+ destroy the frame. Once an "object" event is received, the client is
+ responsible for closing the associated file descriptor.
+
+ All frames are read-only and may not be written into or altered.
+ </description>
+
+ <enum name="flags">
+ <description summary="frame flags">
+ Special flags that should be respected by the client.
+ </description>
+ <entry name="transient" value="0x1"
+ summary="clients should copy frame before processing"/>
+ </enum>
+
+ <event name="frame">
+ <description summary="a frame description">
+ Main event supplying the client with information about the frame. If the
+ capture didn't fail, this event is always emitted first before any other
+ events.
+
+ This event is followed by a number of "object" as specified by the
+ "num_objects" argument.
+ </description>
+ <arg name="width" type="uint"
+ summary="frame width in pixels"/>
+ <arg name="height" type="uint"
+ summary="frame height in pixels"/>
+ <arg name="offset_x" type="uint"
+ summary="crop offset for the x axis"/>
+ <arg name="offset_y" type="uint"
+ summary="crop offset for the y axis"/>
+ <arg name="buffer_flags" type="uint"
+ summary="flags which indicate properties (invert, interlacing),
+ has the same values as zwp_linux_buffer_params_v1:flags"/>
+ <arg name="flags" type="uint" enum="flags"
+ summary="indicates special frame features"/>
+ <arg name="format" type="uint"
+ summary="format of the frame (DRM_FORMAT_*)"/>
+ <arg name="mod_high" type="uint"
+ summary="drm format modifier, high"/>
+ <arg name="mod_low" type="uint"
+ summary="drm format modifier, low"/>
+ <arg name="num_objects" type="uint"
+ summary="indicates how many objects (FDs) the frame has (max 4)"/>
+ </event>
+
+ <event name="object">
+ <description summary="an object description">
+ Event which serves to supply the client with the file descriptors
+ containing the data for each object.
+
+ After receiving this event, the client must always close the file
+ descriptor as soon as they're done with it and even if the frame fails.
+ </description>
+ <arg name="index" type="uint"
+ summary="index of the current object"/>
+ <arg name="fd" type="fd"
+ summary="fd of the current object"/>
+ <arg name="size" type="uint"
+ summary="size in bytes for the current object"/>
+ <arg name="offset" type="uint"
+ summary="starting point for the data in the object's fd"/>
+ <arg name="stride" type="uint"
+ summary="line size in bytes"/>
+ <arg name="plane_index" type="uint"
+ summary="index of the the plane the data in the object applies to"/>
+ </event>
+
+ <event name="ready">
+ <description summary="indicates frame is available for reading">
+ This event is sent as soon as the frame is presented, 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 this object.
+ </description>
+ <arg name="tv_sec_hi" type="uint"
+ summary="high 32 bits of the seconds part of the timestamp"/>
+ <arg name="tv_sec_lo" type="uint"
+ summary="low 32 bits of the seconds part of the timestamp"/>
+ <arg name="tv_nsec" type="uint"
+ summary="nanoseconds part of the timestamp"/>
+ </event>
+
+ <enum name="cancel_reason">
+ <description summary="cancel reason">
+ Indicates reason for cancelling the frame.
+ </description>
+ <entry name="temporary" value="0"
+ summary="temporary error, source will produce more frames"/>
+ <entry name="permanent" value="1"
+ summary="fatal error, source will not produce frames"/>
+ <entry name="resizing" value="2"
+ summary="temporary error, source will produce more frames"/>
+ </enum>
+
+ <event name="cancel">
+ <description summary="indicates the frame is no longer valid">
+ If the capture failed or if the frame is no longer valid after the
+ "frame" event has been emitted, this event will be used to inform the
+ client to scrap the frame.
+
+ If the failure is temporary, the client may capture again the same
+ source. If the failure is permanent, any further attempts to capture the
+ same source will fail again.
+
+ After receiving this event, the client should destroy this object.
+ </description>
+ <arg name="reason" type="uint" enum="cancel_reason"
+ summary="indicates a reason for cancelling this frame capture"/>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="delete this object, used or not">
+ Unreferences the frame. This request must be called as soon as its no
+ longer used.
+
+ It can be called at any time by the client. The client will still have
+ to close any FDs it has been given.
+ </description>
+ </request>
+ </interface>
+</protocol>