weston/pipewire/pipewire-plugin.c
Pekka Paalanen 3728d7e99b backend-drm,pipewire,remoting: do not set monitor serial to "unknown"
If serial is unknown, it's best to leave it as NULL. All usage sites
already deal with it possibly being NULL.

This makes DRM-backend consistent with all other backend that leave
serial as NULL, allowing to move the initialization of these fields into
core.

Pipewire and remoting plugins are modified just for consistency.

Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.com>
2023-09-15 06:56:59 +00:00

794 lines
20 KiB
C

/*
* Copyright © 2019 Pengutronix, Michael Olbrich <m.olbrich@pengutronix.de>
*
* 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.
*/
#include "config.h"
#include <libweston/pipewire-plugin.h>
#include "backend.h"
#include "libweston-internal.h"
#include "shared/timespec-util.h"
#include "shared/string-helpers.h"
#include <libweston/backend-drm.h>
#include <libweston/weston-log.h>
#include <sys/mman.h>
#include <errno.h>
#include <unistd.h>
#include <pipewire/pipewire.h>
#include <spa/param/format-utils.h>
#include <spa/param/video/format-utils.h>
#include <spa/utils/defs.h>
#include <spa/buffer/meta.h>
#include <spa/utils/result.h>
#define PROP_RANGE(min, max) 2, (min), (max)
struct weston_pipewire {
struct weston_compositor *compositor;
struct wl_list output_list;
struct wl_listener destroy_listener;
const struct weston_drm_virtual_output_api *virtual_output_api;
struct weston_log_scope *debug;
struct pw_loop *loop;
struct wl_event_source *loop_source;
struct pw_context *context;
struct pw_core *core;
struct pw_type *t;
struct spa_hook core_listener;
};
struct pipewire_output {
struct weston_output *output;
int (*saved_enable)(struct weston_output *output);
int (*saved_disable)(struct weston_output *output);
int (*saved_start_repaint_loop)(struct weston_output *output);
struct weston_head *head;
struct weston_pipewire *pipewire;
uint32_t seq;
struct pw_stream *stream;
struct spa_hook stream_listener;
struct spa_video_info_raw video_format;
struct wl_event_source *finish_frame_timer;
struct wl_list link;
bool submitted_frame;
enum dpms_enum dpms;
};
struct pipewire_frame_data {
struct pipewire_output *output;
int fd;
int stride;
struct drm_fb *drm_buffer;
int fence_sync_fd;
struct wl_event_source *fence_sync_event_source;
};
static void
pipewire_debug_impl(struct weston_pipewire *pipewire,
struct pipewire_output *output,
const char *fmt, va_list ap)
{
FILE *fp;
char *logstr;
size_t logsize;
char timestr[128];
if (!weston_log_scope_is_enabled(pipewire->debug))
return;
fp = open_memstream(&logstr, &logsize);
if (!fp)
return;
weston_log_scope_timestamp(pipewire->debug, timestr, sizeof timestr);
fprintf(fp, "%s", timestr);
if (output)
fprintf(fp, "[%s]", output->output->name);
fprintf(fp, " ");
vfprintf(fp, fmt, ap);
fprintf(fp, "\n");
if (fclose(fp) == 0)
weston_log_scope_write(pipewire->debug, logstr, logsize);
free(logstr);
}
static void
pipewire_output_debug(struct pipewire_output *output, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
pipewire_debug_impl(output->pipewire, output, fmt, ap);
va_end(ap);
}
static struct weston_pipewire *
weston_pipewire_get(struct weston_compositor *compositor);
static struct pipewire_output *
lookup_pipewire_output(struct weston_output *base_output)
{
struct weston_compositor *c = base_output->compositor;
struct weston_pipewire *pipewire = weston_pipewire_get(c);
struct pipewire_output *output;
/* XXX: This could happen on the compositor shutdown path with our
* destroy listener being removed, and pipewire_output_destroy() being
* called as a virtual destructor.
*
* See https://gitlab.freedesktop.org/wayland/weston/-/issues/591 for
* an alternative to the shutdown sequence.
*/
if (!pipewire)
return NULL;
wl_list_for_each(output, &pipewire->output_list, link) {
if (output->output == base_output)
return output;
}
return NULL;
}
static void
pipewire_output_handle_frame(struct pipewire_output *output, int fd,
int stride, struct drm_fb *drm_buffer)
{
const struct weston_drm_virtual_output_api *api =
output->pipewire->virtual_output_api;
size_t size = output->output->height * stride;
struct pw_buffer *buffer;
struct spa_buffer *spa_buffer;
struct spa_meta_header *h;
void *ptr;
if (pw_stream_get_state(output->stream, NULL) !=
PW_STREAM_STATE_STREAMING)
goto out;
buffer = pw_stream_dequeue_buffer(output->stream);
if (!buffer) {
weston_log("Failed to dequeue a pipewire buffer\n");
goto out;
}
spa_buffer = buffer->buffer;
if ((h = spa_buffer_find_meta_data(spa_buffer, SPA_META_Header,
sizeof(struct spa_meta_header)))) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
h->pts = SPA_TIMESPEC_TO_NSEC(&ts);
h->flags = 0;
h->seq = output->seq++;
h->dts_offset = 0;
}
ptr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
memcpy(spa_buffer->datas[0].data, ptr, size);
munmap(ptr, size);
spa_buffer->datas[0].chunk->offset = 0;
spa_buffer->datas[0].chunk->stride = stride;
spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize;
pipewire_output_debug(output, "push frame");
pw_stream_queue_buffer(output->stream, buffer);
out:
close(fd);
output->submitted_frame = true;
api->buffer_released(drm_buffer);
}
static int
pipewire_output_fence_sync_handler(int fd, uint32_t mask, void *data)
{
struct pipewire_frame_data *frame_data = data;
struct pipewire_output *output = frame_data->output;
pipewire_output_handle_frame(output, frame_data->fd, frame_data->stride,
frame_data->drm_buffer);
wl_event_source_remove(frame_data->fence_sync_event_source);
close(frame_data->fence_sync_fd);
free(frame_data);
return 0;
}
static int
pipewire_output_submit_frame(struct weston_output *base_output, int fd,
int stride, struct drm_fb *drm_buffer)
{
struct pipewire_output *output = lookup_pipewire_output(base_output);
struct weston_pipewire *pipewire = output->pipewire;
const struct weston_drm_virtual_output_api *api =
pipewire->virtual_output_api;
struct wl_event_loop *loop;
struct pipewire_frame_data *frame_data;
int fence_sync_fd;
pipewire_output_debug(output, "submit frame: fd = %d drm_fb = %p",
fd, drm_buffer);
fence_sync_fd = api->get_fence_sync_fd(output->output);
if (fence_sync_fd == -1) {
pipewire_output_handle_frame(output, fd, stride, drm_buffer);
return 0;
}
frame_data = zalloc(sizeof *frame_data);
if (!frame_data) {
close(fence_sync_fd);
pipewire_output_handle_frame(output, fd, stride, drm_buffer);
return 0;
}
loop = wl_display_get_event_loop(pipewire->compositor->wl_display);
frame_data->output = output;
frame_data->fd = fd;
frame_data->stride = stride;
frame_data->drm_buffer = drm_buffer;
frame_data->fence_sync_fd = fence_sync_fd;
frame_data->fence_sync_event_source =
wl_event_loop_add_fd(loop, frame_data->fence_sync_fd,
WL_EVENT_READABLE,
pipewire_output_fence_sync_handler,
frame_data);
return 0;
}
static void
pipewire_output_timer_update(struct pipewire_output *output)
{
int64_t msec;
int32_t refresh;
if (pw_stream_get_state(output->stream, NULL) ==
PW_STREAM_STATE_STREAMING)
refresh = output->output->current_mode->refresh;
else
refresh = 1000;
msec = millihz_to_nsec(refresh) / 1000000;
wl_event_source_timer_update(output->finish_frame_timer, msec);
}
static int
pipewire_output_finish_frame_handler(void *data)
{
struct pipewire_output *output = data;
const struct weston_drm_virtual_output_api *api
= output->pipewire->virtual_output_api;
struct timespec now;
if (output->submitted_frame) {
struct weston_compositor *c = output->pipewire->compositor;
output->submitted_frame = false;
weston_compositor_read_presentation_clock(c, &now);
api->finish_frame(output->output, &now, 0);
}
if (output->dpms == WESTON_DPMS_ON)
pipewire_output_timer_update(output);
else
wl_event_source_timer_update(output->finish_frame_timer, 0);
return 0;
}
static void
pipewire_output_destroy(struct weston_output *base_output)
{
struct pipewire_output *output = lookup_pipewire_output(base_output);
struct weston_mode *mode, *next;
if (!output)
return;
weston_head_release(output->head);
wl_list_for_each_safe(mode, next, &base_output->mode_list, link) {
wl_list_remove(&mode->link);
free(mode);
}
pw_stream_destroy(output->stream);
wl_list_remove(&output->link);
free(output->head);
free(output);
}
static int
pipewire_output_start_repaint_loop(struct weston_output *base_output)
{
struct pipewire_output *output = lookup_pipewire_output(base_output);
pipewire_output_debug(output, "start repaint loop");
output->saved_start_repaint_loop(base_output);
pipewire_output_timer_update(output);
return 0;
}
static void
pipewire_set_dpms(struct weston_output *base_output, enum dpms_enum level)
{
struct pipewire_output *output = lookup_pipewire_output(base_output);
if (output->dpms == level)
return;
output->dpms = level;
pipewire_output_finish_frame_handler(output);
}
static int
pipewire_output_connect(struct pipewire_output *output)
{
uint8_t buffer[1024];
struct spa_pod_builder builder =
SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
const struct spa_pod *params[1];
int frame_rate = output->output->current_mode->refresh / 1000;
int width = output->output->width;
int height = output->output->height;
int ret;
params[0] = spa_pod_builder_add_object(&builder,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_BGRx),
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)),
SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION (0, 1)),
SPA_FORMAT_VIDEO_maxFramerate,
SPA_POD_CHOICE_RANGE_Fraction(&SPA_FRACTION(frame_rate, 1),
&SPA_FRACTION(1, 1),
&SPA_FRACTION(frame_rate, 1)));
ret = pw_stream_connect(output->stream, PW_DIRECTION_OUTPUT, SPA_ID_INVALID,
(PW_STREAM_FLAG_DRIVER |
PW_STREAM_FLAG_MAP_BUFFERS),
params, 1);
if (ret != 0) {
weston_log("Failed to connect pipewire stream: %s",
spa_strerror(ret));
return -1;
}
return 0;
}
static int
pipewire_output_enable(struct weston_output *base_output)
{
struct pipewire_output *output = lookup_pipewire_output(base_output);
struct weston_compositor *c = base_output->compositor;
const struct weston_drm_virtual_output_api *api
= output->pipewire->virtual_output_api;
struct wl_event_loop *loop;
int ret;
api->set_submit_frame_cb(base_output, pipewire_output_submit_frame);
ret = pipewire_output_connect(output);
if (ret < 0)
return ret;
ret = output->saved_enable(base_output);
if (ret < 0)
return ret;
output->saved_start_repaint_loop = base_output->start_repaint_loop;
base_output->start_repaint_loop = pipewire_output_start_repaint_loop;
base_output->set_dpms = pipewire_set_dpms;
loop = wl_display_get_event_loop(c->wl_display);
output->finish_frame_timer =
wl_event_loop_add_timer(loop,
pipewire_output_finish_frame_handler,
output);
output->dpms = WESTON_DPMS_ON;
return 0;
}
static int
pipewire_output_disable(struct weston_output *base_output)
{
struct pipewire_output *output = lookup_pipewire_output(base_output);
wl_event_source_remove(output->finish_frame_timer);
pw_stream_disconnect(output->stream);
return output->saved_disable(base_output);
}
static void
pipewire_output_stream_state_changed(void *data, enum pw_stream_state old,
enum pw_stream_state state,
const char *error_message)
{
struct pipewire_output *output = data;
pipewire_output_debug(output, "state changed %s -> %s",
pw_stream_state_as_string(old),
pw_stream_state_as_string(state));
switch (state) {
case PW_STREAM_STATE_STREAMING:
weston_output_schedule_repaint(output->output);
break;
default:
break;
}
}
static void
pipewire_output_stream_param_changed(void *data, uint32_t id, const struct spa_pod *format)
{
struct pipewire_output *output = data;
uint8_t buffer[1024];
struct spa_pod_builder builder =
SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
const struct spa_pod *params[2];
int32_t width, height, stride, size;
const int bpp = 4;
if (!format) {
pipewire_output_debug(output, "format = None");
pw_stream_update_params(output->stream, NULL, 0);
return;
}
spa_format_video_raw_parse(format, &output->video_format);
width = output->video_format.size.width;
height = output->video_format.size.height;
stride = SPA_ROUND_UP_N(width * bpp, 4);
size = height * stride;
pipewire_output_debug(output, "format = %dx%d", width, height);
params[0] = spa_pod_builder_add_object(&builder,
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
SPA_PARAM_BUFFERS_size, SPA_POD_Int(size),
SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride),
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(4, 2, 8),
SPA_PARAM_BUFFERS_align, SPA_POD_Int(16));
params[1] = spa_pod_builder_add_object(&builder,
SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
pw_stream_update_params(output->stream, params, 2);
}
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.state_changed = pipewire_output_stream_state_changed,
.param_changed = pipewire_output_stream_param_changed,
};
static struct weston_output *
pipewire_output_create(struct weston_compositor *c, char *name)
{
struct weston_pipewire *pipewire = weston_pipewire_get(c);
struct pipewire_output *output;
struct weston_head *head;
const struct weston_drm_virtual_output_api *api;
const char *make = "Weston";
const char *model = "Virtual Display";
const char *connector_name = "pipewire";
char *remoting_name;
if (!name || !strlen(name))
return NULL;
api = pipewire->virtual_output_api;
output = zalloc(sizeof *output);
if (!output)
return NULL;
head = zalloc(sizeof *head);
if (!head)
goto err;
output->stream = pw_stream_new(pipewire->core, name, NULL);
if (!output->stream) {
weston_log("Cannot initialize pipewire stream\n");
goto err;
}
pw_stream_add_listener(output->stream, &output->stream_listener,
&stream_events, output);
output->output = api->create_output(c, name, pipewire_output_destroy);
if (!output->output) {
weston_log("Cannot create virtual output\n");
goto err;
}
output->saved_enable = output->output->enable;
output->output->enable = pipewire_output_enable;
output->saved_disable = output->output->disable;
output->output->disable = pipewire_output_disable;
output->pipewire = pipewire;
wl_list_insert(pipewire->output_list.prev, &output->link);
str_printf(&remoting_name, "%s-%s", connector_name, name);
weston_head_init(head, remoting_name);
weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_NONE);
weston_head_set_monitor_strings(head, make, model, NULL);
head->compositor = c;
output->head = head;
weston_output_attach_head(output->output, head);
free(remoting_name);
pipewire_output_debug(output, "created");
return output->output;
err:
if (output->stream)
pw_stream_destroy(output->stream);
if (head)
free(head);
free(output);
return NULL;
}
static bool
pipewire_output_is_pipewire(struct weston_output *output)
{
return lookup_pipewire_output(output) != NULL;
}
static int
pipewire_output_set_mode(struct weston_output *base_output, const char *modeline)
{
struct pipewire_output *output = lookup_pipewire_output(base_output);
const struct weston_drm_virtual_output_api *api =
output->pipewire->virtual_output_api;
struct weston_mode *mode;
int n, width, height, refresh = 0;
if (output == NULL) {
weston_log("Output is not pipewire.\n");
return -1;
}
if (!modeline)
return -1;
n = sscanf(modeline, "%dx%d@%d", &width, &height, &refresh);
if (n != 2 && n != 3)
return -1;
if (pw_stream_get_state(output->stream, NULL) !=
PW_STREAM_STATE_UNCONNECTED) {
return -1;
}
mode = zalloc(sizeof *mode);
if (!mode)
return -1;
pipewire_output_debug(output, "mode = %dx%d@%d", width, height, refresh);
mode->flags = WL_OUTPUT_MODE_CURRENT;
mode->width = width;
mode->height = height;
mode->refresh = (refresh ? refresh : 60) * 1000LL;
wl_list_insert(base_output->mode_list.prev, &mode->link);
base_output->current_mode = mode;
api->set_gbm_format(base_output, "XRGB8888");
return 0;
}
static void
pipewire_output_set_seat(struct weston_output *output, const char *seat)
{
}
static void
weston_pipewire_destroy(struct wl_listener *l, void *data)
{
struct weston_pipewire *pipewire =
wl_container_of(l, pipewire, destroy_listener);
struct pipewire_output *p_output, *p_output_next;
weston_log_scope_destroy(pipewire->debug);
pipewire->debug = NULL;
wl_list_for_each_safe(p_output, p_output_next, &pipewire->output_list, link)
pipewire_output_destroy(p_output->output);
wl_event_source_remove(pipewire->loop_source);
pw_loop_leave(pipewire->loop);
pw_loop_destroy(pipewire->loop);
free(pipewire);
}
static struct weston_pipewire *
weston_pipewire_get(struct weston_compositor *compositor)
{
struct wl_listener *listener;
struct weston_pipewire *pipewire;
listener = wl_signal_get(&compositor->destroy_signal,
weston_pipewire_destroy);
if (!listener)
return NULL;
pipewire = wl_container_of(listener, pipewire, destroy_listener);
return pipewire;
}
static int
weston_pipewire_loop_handler(int fd, uint32_t mask, void *data)
{
struct weston_pipewire *pipewire = data;
int ret;
ret = pw_loop_iterate(pipewire->loop, 0);
if (ret < 0)
weston_log("pipewire_loop_iterate failed: %s",
spa_strerror(ret));
return 0;
}
static void
weston_pipewire_error(void *data, uint32_t id, int seq, int res,
const char *error)
{
weston_log("pipewire remote error: %s\n", error);
}
static const struct pw_core_events core_events = {
PW_VERSION_CORE_EVENTS,
.error = weston_pipewire_error,
};
static int
weston_pipewire_init(struct weston_pipewire *pipewire)
{
struct wl_event_loop *loop;
pw_init(NULL, NULL);
pipewire->loop = pw_loop_new(NULL);
if (!pipewire->loop)
return -1;
pw_loop_enter(pipewire->loop);
pipewire->context = pw_context_new(pipewire->loop, NULL, 0);
pipewire->core = pw_context_connect(pipewire->context, NULL, 0);
pw_core_add_listener(pipewire->core,
&pipewire->core_listener,
&core_events, pipewire);
loop = wl_display_get_event_loop(pipewire->compositor->wl_display);
pipewire->loop_source =
wl_event_loop_add_fd(loop, pw_loop_get_fd(pipewire->loop),
WL_EVENT_READABLE,
weston_pipewire_loop_handler,
pipewire);
return 0;
}
static const struct weston_pipewire_api pipewire_api = {
pipewire_output_create,
pipewire_output_is_pipewire,
pipewire_output_set_mode,
pipewire_output_set_seat,
};
WL_EXPORT int
weston_module_init(struct weston_compositor *compositor)
{
int ret;
struct weston_pipewire *pipewire;
const struct weston_drm_virtual_output_api *api =
weston_drm_virtual_output_get_api(compositor);
if (!api)
return -1;
pipewire = zalloc(sizeof *pipewire);
if (!pipewire)
return -1;
if (!weston_compositor_add_destroy_listener_once(compositor,
&pipewire->destroy_listener,
weston_pipewire_destroy)) {
free(pipewire);
return 0;
}
pipewire->virtual_output_api = api;
pipewire->compositor = compositor;
wl_list_init(&pipewire->output_list);
ret = weston_plugin_api_register(compositor, WESTON_PIPEWIRE_API_NAME,
&pipewire_api, sizeof(pipewire_api));
if (ret < 0) {
weston_log("Failed to register pipewire API.\n");
goto failed;
}
ret = weston_pipewire_init(pipewire);
if (ret < 0) {
weston_log("Failed to initialize pipewire.\n");
goto failed;
}
pipewire->debug =
weston_compositor_add_log_scope(compositor, "pipewire",
"Debug messages from pipewire plugin\n",
NULL, NULL, NULL);
return 0;
failed:
wl_list_remove(&pipewire->destroy_listener.link);
free(pipewire);
return -1;
}