summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--capture_wlr.c331
-rw-r--r--main.c10
3 files changed, 341 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index 0302032..0fdd237 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
compile_commands.json
compile_flags.txt
streamer
+wlr-export-dmabuf-unstable-v1.h
diff --git a/capture_wlr.c b/capture_wlr.c
new file mode 100644
index 0000000..0bd8f17
--- /dev/null
+++ b/capture_wlr.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
+ *
+ * streamer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * streamer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with streamer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifdef USE_WAYLAND
+
+#include "capture_wlr.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-client.h>
+
+#include "gpu.h"
+#include "toolbox/utils.h"
+#include "wlr-export-dmabuf-unstable-v1.h"
+
+struct CaptureContextWlr {
+ struct IoMuxer* io_muxer;
+ struct GpuContext* gpu_context;
+ const struct CaptureContextCallbacks* callbacks;
+ void* user;
+
+ // Wayland globals
+ struct wl_display* wl_display;
+ struct wl_registry* wl_registry;
+ struct wl_output* wl_output;
+ struct zwlr_export_dmabuf_manager_v1* zwlr_export_dmabuf_manager_v1;
+
+ // Wayland dynamics
+ struct zwlr_export_dmabuf_frame_v1* zwlr_export_dmabuf_frame_v1;
+
+ // Volatile states
+ uint32_t gpu_frame_width;
+ uint32_t gpu_frame_height;
+ uint32_t gpu_frame_fourcc;
+ size_t gpu_frame_nplanes;
+ struct GpuFramePlane gpu_frame_planes[4];
+};
+
+static bool CaptureOutput(struct CaptureContextWlr* capture_context);
+
+static void OnWlRegistryGlobal(void* data, struct wl_registry* wl_registry,
+ uint32_t name, const char* interface,
+ uint32_t version) {
+#define MAYBE_BIND(what) \
+ if (!strcmp(interface, what##_interface.name)) do { \
+ capture_context->what = \
+ wl_registry_bind(wl_registry, name, &what##_interface, version); \
+ if (!capture_context->what) \
+ LOG("Failed to bind " #what " (%s)", strerror(errno)); \
+ return; \
+ } while (false)
+ struct CaptureContextWlr* capture_context = data;
+ if (!capture_context->wl_output) MAYBE_BIND(wl_output);
+ MAYBE_BIND(zwlr_export_dmabuf_manager_v1);
+#undef MAYBE_BIND
+}
+
+static void OnWlRegistryGlobalRemove(void* data, struct wl_registry* registry,
+ uint32_t name) {
+ (void)data;
+ (void)registry;
+ (void)name;
+}
+
+static bool InitWaylandGlobals(struct CaptureContextWlr* capture_context) {
+ capture_context->wl_display =
+ wl_display_connect(/*NULL*/ "/run/user/1000/wayland-1");
+ if (!capture_context->wl_display) {
+ LOG("Failed to connect wl_display (%s)", strerror(errno));
+ return false;
+ }
+
+ capture_context->wl_registry =
+ wl_display_get_registry(capture_context->wl_display);
+ if (!capture_context->wl_registry) {
+ LOG("Failed to get wl_registry (%s)", strerror(errno));
+ goto rollback_wl_display;
+ }
+
+ static const struct wl_registry_listener wl_registry_listener = {
+ .global = OnWlRegistryGlobal,
+ .global_remove = OnWlRegistryGlobalRemove,
+ };
+ if (wl_registry_add_listener(capture_context->wl_registry,
+ &wl_registry_listener, capture_context)) {
+ LOG("Failed to add wl_registry listener (%s)", strerror(errno));
+ goto rollback_wl_registry;
+ }
+ if (wl_display_roundtrip(capture_context->wl_display) == -1) {
+ LOG("Failed to roundtrip wl_display (%s)", strerror(errno));
+ goto rollback_wl_registry;
+ }
+
+ if (!capture_context->wl_output ||
+ !capture_context->zwlr_export_dmabuf_manager_v1) {
+ LOG("Some required wayland globals are missing");
+ goto rollback_wayland_globals;
+ }
+ return true;
+
+rollback_wayland_globals:
+ if (capture_context->zwlr_export_dmabuf_manager_v1)
+ zwlr_export_dmabuf_manager_v1_destroy(
+ capture_context->zwlr_export_dmabuf_manager_v1);
+ if (capture_context->wl_output) wl_output_destroy(capture_context->wl_output);
+rollback_wl_registry:
+ wl_registry_destroy(capture_context->wl_registry);
+rollback_wl_display:
+ wl_display_disconnect(capture_context->wl_display);
+ return false;
+}
+
+static void DeinitWaylandGlobals(struct CaptureContextWlr* capture_context) {
+ zwlr_export_dmabuf_manager_v1_destroy(
+ capture_context->zwlr_export_dmabuf_manager_v1);
+ wl_output_destroy(capture_context->wl_output);
+ wl_registry_destroy(capture_context->wl_registry);
+ wl_display_disconnect(capture_context->wl_display);
+}
+
+static void OnExportDmabufFrameFrame(
+ void* data, struct zwlr_export_dmabuf_frame_v1* zwlr_export_dmabuf_frame_v1,
+ 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) {
+ (void)zwlr_export_dmabuf_frame_v1;
+ (void)offset_x;
+ (void)offset_y;
+ (void)buffer_flags;
+ (void)flags;
+ struct CaptureContextWlr* capture_context = data;
+ capture_context->gpu_frame_width = width;
+ capture_context->gpu_frame_height = height;
+ capture_context->gpu_frame_fourcc = format;
+ capture_context->gpu_frame_nplanes = num_objects;
+ for (size_t i = 0; i < LENGTH(capture_context->gpu_frame_planes); i++) {
+ capture_context->gpu_frame_planes[i].dmabuf_fd = -1;
+ capture_context->gpu_frame_planes[i].modifier =
+ (uint64_t)mod_high << 32 | mod_low;
+ }
+}
+
+static void OnExportDmabufFrameObject(
+ void* data, struct zwlr_export_dmabuf_frame_v1* zwlr_export_dmabuf_frame_v1,
+ uint32_t index, int32_t fd, uint32_t size, uint32_t offset, uint32_t stride,
+ uint32_t plane_index) {
+ (void)zwlr_export_dmabuf_frame_v1;
+ (void)size;
+ (void)index;
+ struct CaptureContextWlr* capture_context = data;
+ capture_context->gpu_frame_planes[plane_index].dmabuf_fd = fd;
+ capture_context->gpu_frame_planes[plane_index].pitch = stride;
+ capture_context->gpu_frame_planes[plane_index].offset = offset;
+}
+
+static void OnExportDmabufFrameReady(
+ void* data, struct zwlr_export_dmabuf_frame_v1* zwlr_export_dmabuf_frame_v1,
+ uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
+ (void)zwlr_export_dmabuf_frame_v1;
+ (void)tv_sec_hi;
+ (void)tv_sec_lo;
+ (void)tv_nsec;
+ struct CaptureContextWlr* capture_context = data;
+
+ struct GpuFrame* gpu_frame = GpuContextCreateFrame(
+ capture_context->gpu_context, capture_context->gpu_frame_width,
+ capture_context->gpu_frame_height, capture_context->gpu_frame_fourcc,
+ capture_context->gpu_frame_nplanes, capture_context->gpu_frame_planes);
+ if (!gpu_frame) {
+ // TODO(mburakov): ... then what?
+ abort();
+ }
+
+ // TODO(mburakov): Callee could theoretically drop client, and then below code
+ // in this function would probably fail miserably. Do something about this!
+ capture_context->callbacks->OnFrameReady(capture_context->user, gpu_frame);
+
+ CloseUniqueFds((int[4]){capture_context->gpu_frame_planes[0].dmabuf_fd,
+ capture_context->gpu_frame_planes[1].dmabuf_fd,
+ capture_context->gpu_frame_planes[2].dmabuf_fd,
+ capture_context->gpu_frame_planes[3].dmabuf_fd});
+ zwlr_export_dmabuf_frame_v1_destroy(
+ capture_context->zwlr_export_dmabuf_frame_v1);
+ capture_context->zwlr_export_dmabuf_frame_v1 = NULL;
+
+ if (!CaptureOutput(capture_context)) {
+ // TODO(mburakov): ... then what?
+ abort();
+ }
+}
+
+static void OnExportDmabufFrameCancel(
+ void* data, struct zwlr_export_dmabuf_frame_v1* zwlr_export_dmabuf_frame_v1,
+ uint32_t reason) {
+ (void)zwlr_export_dmabuf_frame_v1;
+ struct CaptureContextWlr* capture_context = data;
+ CloseUniqueFds((int[4]){capture_context->gpu_frame_planes[0].dmabuf_fd,
+ capture_context->gpu_frame_planes[1].dmabuf_fd,
+ capture_context->gpu_frame_planes[2].dmabuf_fd,
+ capture_context->gpu_frame_planes[3].dmabuf_fd});
+ zwlr_export_dmabuf_frame_v1_destroy(
+ capture_context->zwlr_export_dmabuf_frame_v1);
+ capture_context->zwlr_export_dmabuf_frame_v1 = NULL;
+ bool result;
+ switch (reason) {
+ case ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_TEMPORARY:
+ result = CaptureOutput(capture_context);
+ break;
+ case ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT:
+ result = false;
+ break;
+ case ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_RESIZING:
+ result = CaptureOutput(capture_context);
+ break;
+ default:
+ __builtin_unreachable();
+ }
+ if (!result) {
+ // TODO(mburakov): ... then what?
+ abort();
+ }
+}
+
+static bool CaptureOutput(struct CaptureContextWlr* capture_context) {
+ capture_context->zwlr_export_dmabuf_frame_v1 =
+ zwlr_export_dmabuf_manager_v1_capture_output(
+ capture_context->zwlr_export_dmabuf_manager_v1, 1,
+ capture_context->wl_output);
+ if (!capture_context->zwlr_export_dmabuf_frame_v1) {
+ LOG("Failed to capture zwlr_export_dmabuf_manager_v1 (%s)",
+ strerror(errno));
+ return false;
+ }
+
+ static const struct zwlr_export_dmabuf_frame_v1_listener
+ zwlr_export_dmabuf_frame_v1_listener = {
+ .frame = OnExportDmabufFrameFrame,
+ .object = OnExportDmabufFrameObject,
+ .ready = OnExportDmabufFrameReady,
+ .cancel = OnExportDmabufFrameCancel,
+ };
+ if (zwlr_export_dmabuf_frame_v1_add_listener(
+ capture_context->zwlr_export_dmabuf_frame_v1,
+ &zwlr_export_dmabuf_frame_v1_listener, capture_context)) {
+ LOG("Failed to add zwlr_export_dmabuf_frame_v1 listener (%s)",
+ strerror(errno));
+ goto rollback_frame;
+ }
+
+ if (wl_display_flush(capture_context->wl_display) == -1) {
+ LOG("Failed to flush wl_display (%s)", strerror(errno));
+ goto rollback_frame;
+ }
+ return true;
+
+rollback_frame:
+ zwlr_export_dmabuf_frame_v1_destroy(
+ capture_context->zwlr_export_dmabuf_frame_v1);
+ capture_context->zwlr_export_dmabuf_frame_v1 = NULL;
+ return false;
+}
+
+struct CaptureContextWlr* CaptureContextWlrCreate(
+ struct GpuContext* gpu_context,
+ const struct CaptureContextCallbacks* callbacks, void* user) {
+ struct CaptureContextWlr* capture_context =
+ malloc(sizeof(struct CaptureContextWlr));
+ if (!capture_context) {
+ LOG("Failed to allocate capture context (%s)", strerror(errno));
+ return NULL;
+ }
+ *capture_context = (struct CaptureContextWlr){
+ .gpu_context = gpu_context,
+ .callbacks = callbacks,
+ .user = user,
+ };
+
+ if (!InitWaylandGlobals(capture_context)) {
+ LOG("Failed to initialize wayland globals");
+ goto rollback_capture_context;
+ }
+ if (!CaptureOutput(capture_context)) {
+ LOG("Failed to capture output");
+ goto rollback_wayland_globals;
+ }
+ return capture_context;
+
+rollback_wayland_globals:
+ DeinitWaylandGlobals(capture_context);
+rollback_capture_context:
+ free(capture_context);
+ return NULL;
+}
+
+int CaptureContextWlrGetEventsFd(struct CaptureContextWlr* capture_context) {
+ int events_fd = wl_display_get_fd(capture_context->wl_display);
+ if (events_fd == -1) LOG("Failed to get wl_display fd (%s)", strerror(errno));
+ return events_fd;
+}
+
+bool CaptureContextWlrProcessEvents(struct CaptureContextWlr* capture_context) {
+ bool result = wl_display_dispatch(capture_context->wl_display) != -1;
+ if (!result) LOG("Failed to dispatch wl_display (%s)", strerror(errno));
+ return result;
+}
+
+void CaptureContextWlrDestroy(struct CaptureContextWlr* capture_context) {
+ if (capture_context->zwlr_export_dmabuf_frame_v1)
+ zwlr_export_dmabuf_frame_v1_destroy(
+ capture_context->zwlr_export_dmabuf_frame_v1);
+ DeinitWaylandGlobals(capture_context);
+ free(capture_context);
+}
+
+#endif // USE_WAYLAND
diff --git a/main.c b/main.c
index 2e0ddc4..e848536 100644
--- a/main.c
+++ b/main.c
@@ -52,6 +52,7 @@ struct Contexts {
struct InputHandler* input_handler;
struct CaptureContext* capture_context;
struct EncodeContext* encode_context;
+ bool drop_client;
};
static int CreateServerSocket(const char* arg) {
@@ -144,7 +145,10 @@ static void OnCaptureContextFrameReady(void* user,
return;
drop_client:
- MaybeDropClient(contexts);
+ // TODO(mburakov): Can't drop client here, because leftover code in capturing
+ // functions would fail in this case. Instead just schedule dropping client
+ // here, and execute that in the event loop of the main function.
+ contexts->drop_client = true;
}
static void OnClientWriting(void* user) {
@@ -296,6 +300,10 @@ int main(int argc, char* argv[]) {
LOG("Failed to iterate io muxer (%s)", strerror(errno));
g_signal = SIGABRT;
}
+ if (contexts.drop_client) {
+ MaybeDropClient(&contexts);
+ contexts.drop_client = false;
+ }
}
MaybeDropClient(&contexts);