/*
* 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 .
*/
#ifdef USE_WAYLAND
#include "capture_wlr.h"
#include
#include
#include
#include
#include
#include "gpu.h"
#include "toolbox/utils.h"
#include "wlr-export-dmabuf-unstable-v1.h"
struct CaptureContextWlr {
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);
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