aboutsummaryrefslogtreecommitdiff
path: root/main.c
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 /main.c
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.
Diffstat (limited to 'main.c')
-rw-r--r--main.c606
1 files changed, 606 insertions, 0 deletions
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: */