From d5d5aa91a274117428b6054fc98c06bcd3a0cb99 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 23 Apr 2019 12:34:05 +0200 Subject: [PATCH] Add pipewire plugin It is quite similar to the remoting plugin. It just exports the frames via pipewire instead of the builtin GStreamer pipeline. It implements the same virtual output API. Virtual outputs can be created by adding 'pipewire-output' sections to weston.ini. The generated frames can be accessed with any pipewire client. e.g. with GStreamer: gst-launch-1.0 pipewiresrc ! video/x-raw,format=BGRx ! ... Signed-off-by: Michael Olbrich --- .gitlab-ci/debian-install.sh | 2 +- compositor/main.c | 128 ++++++ meson.build | 1 + meson_options.txt | 7 + pipewire/meson.build | 30 ++ pipewire/pipewire-plugin.c | 831 +++++++++++++++++++++++++++++++++++ pipewire/pipewire-plugin.h | 62 +++ 7 files changed, 1060 insertions(+), 1 deletion(-) create mode 100644 pipewire/meson.build create mode 100644 pipewire/pipewire-plugin.c create mode 100644 pipewire/pipewire-plugin.h diff --git a/.gitlab-ci/debian-install.sh b/.gitlab-ci/debian-install.sh index 2ebb0260..06b3900b 100644 --- a/.gitlab-ci/debian-install.sh +++ b/.gitlab-ci/debian-install.sh @@ -4,7 +4,7 @@ set -o xtrace echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list apt-get update -apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libevdev-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev freerdp2-dev curl python3-pip python3-setuptools doxygen ninja-build libdbus-1-dev +apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libevdev-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev freerdp2-dev curl python3-pip python3-setuptools doxygen ninja-build libdbus-1-dev libpipewire-0.2-dev pip3 install --user git+https://github.com/mesonbuild/meson.git@0.49 # for documentation diff --git a/compositor/main.c b/compositor/main.c index 0fc53664..c91da16f 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -64,6 +64,7 @@ #include #include #include "../remoting/remoting-plugin.h" +#include "../pipewire/pipewire-plugin.h" #define WINDOW_TITLE "Weston Compositor" /* flight recorder size (in bytes) */ @@ -2332,6 +2333,130 @@ load_remoting(struct weston_compositor *c, struct weston_config *wc) } } +static int +drm_backend_pipewire_output_configure(struct weston_output *output, + struct weston_config_section *section, + char *modeline, + const struct weston_pipewire_api *api) +{ + char *seat = NULL; + int ret; + + ret = api->set_mode(output, modeline); + if (ret < 0) { + weston_log("Cannot configure an output \"%s\" using " + "weston_pipewire_api. Invalid mode\n", + output->name); + return -1; + } + + wet_output_set_scale(output, section, 1, 0); + wet_output_set_transform(output, section, WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX); + + weston_config_section_get_string(section, "seat", &seat, ""); + + api->set_seat(output, seat); + free(seat); + + return 0; +} + +static void +pipewire_output_init(struct weston_compositor *c, + struct weston_config_section *section, + const struct weston_pipewire_api *api) +{ + struct weston_output *output = NULL; + char *output_name, *modeline = NULL; + int ret; + + weston_config_section_get_string(section, "name", &output_name, + NULL); + if (!output_name) + return; + + weston_config_section_get_string(section, "mode", &modeline, "off"); + if (strcmp(modeline, "off") == 0) + goto err; + + output = api->create_output(c, output_name); + if (!output) { + weston_log("Cannot create pipewire output \"%s\".\n", + output_name); + goto err; + } + + ret = drm_backend_pipewire_output_configure(output, section, modeline, + api); + if (ret < 0) { + weston_log("Cannot configure pipewire output \"%s\".\n", + output_name); + goto err; + } + + if (weston_output_enable(output) < 0) { + weston_log("Enabling pipewire output \"%s\" failed.\n", + output_name); + goto err; + } + + free(modeline); + free(output_name); + weston_log("pipewire output '%s' enabled\n", output->name); + return; + +err: + free(modeline); + free(output_name); + if (output) + weston_output_destroy(output); +} + +static void +load_pipewire(struct weston_compositor *c, struct weston_config *wc) +{ + const struct weston_pipewire_api *api = NULL; + int (*module_init)(struct weston_compositor *ec); + struct weston_config_section *section = NULL; + const char *section_name; + + /* read pipewire-output section in weston.ini */ + while (weston_config_next_section(wc, §ion, §ion_name)) { + if (strcmp(section_name, "pipewire-output")) + continue; + + if (!api) { + char *module_name; + struct weston_config_section *core_section = + weston_config_get_section(wc, "core", NULL, + NULL); + + weston_config_section_get_string(core_section, + "pipewire", + &module_name, + "pipewire-plugin.so"); + module_init = weston_load_module(module_name, + "weston_module_init"); + free(module_name); + if (!module_init) { + weston_log("Can't load pipewire-plugin\n"); + return; + } + if (module_init(c) < 0) { + weston_log("Pipewire-plugin init failed\n"); + return; + } + + api = weston_pipewire_get_api(c); + if (!api) + return; + } + + pipewire_output_init(c, section, api); + } +} + static int load_drm_backend(struct weston_compositor *c, int *argc, char **argv, struct weston_config *wc) @@ -2387,6 +2512,9 @@ load_drm_backend(struct weston_compositor *c, /* remoting */ load_remoting(c, wc); + /* pipewire */ + load_pipewire(c, wc); + free(config.gbm_format); free(config.seat_id); diff --git a/meson.build b/meson.build index 4acdf972..e8bfd360 100644 --- a/meson.build +++ b/meson.build @@ -153,6 +153,7 @@ subdir('desktop-shell') subdir('fullscreen-shell') subdir('ivi-shell') subdir('remoting') +subdir('pipewire') subdir('clients') subdir('wcap') subdir('tests') diff --git a/meson_options.txt b/meson_options.txt index 62762c8e..d5bf1d54 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -99,6 +99,13 @@ option( description: 'Virtual remote output with GStreamer on DRM backend' ) +option( + 'pipewire', + type: 'boolean', + value: true, + description: 'Virtual remote output with Pipewire on DRM backend' +) + option( 'shell-desktop', type: 'boolean', diff --git a/pipewire/meson.build b/pipewire/meson.build new file mode 100644 index 00000000..aada8693 --- /dev/null +++ b/pipewire/meson.build @@ -0,0 +1,30 @@ +if get_option('pipewire') + user_hint = 'If you rather not build this, set "pipewire=false".' + + if not get_option('backend-drm') + error('Attempting to build the pipewire plugin without the required DRM backend. ' + user_hint) + endif + + depnames = [ + 'libpipewire-0.2', 'libspa-0.1' + ] + deps_pipewire = [ dep_libweston ] + foreach depname : depnames + dep = dependency(depname, required: false) + if not dep.found() + error('Pipewire plugin requires @0@ which was not found. '.format(depname) + user_hint) + endif + deps_pipewire += dep + endforeach + + plugin_pipewire = shared_library( + 'pipewire-plugin', + 'pipewire-plugin.c', + include_directories: include_directories('..', '../shared'), + dependencies: deps_pipewire, + name_prefix: '', + install: true, + install_dir: dir_module_libweston + ) + env_modmap += 'pipewire-plugin.so=@0@;'.format(plugin_pipewire.full_path()) +endif diff --git a/pipewire/pipewire-plugin.c b/pipewire/pipewire-plugin.c new file mode 100644 index 00000000..bd873caa --- /dev/null +++ b/pipewire/pipewire-plugin.c @@ -0,0 +1,831 @@ +/* + * Copyright © 2019 Pengutronix, Michael Olbrich + * + * 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 "pipewire-plugin.h" +#include "backend.h" +#include "libweston-internal.h" +#include "shared/timespec-util.h" +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#define PROP_RANGE(min, max) 2, (min), (max) + +struct type { + struct spa_type_media_type media_type; + struct spa_type_media_subtype media_subtype; + struct spa_type_format_video format_video; + struct spa_type_video_format video_format; +}; + +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_core *core; + struct pw_type *t; + struct type type; + + struct pw_remote *remote; + struct spa_hook remote_listener; +}; + +struct pipewire_output { + struct weston_output *output; + void (*saved_destroy)(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; +}; + +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 inline void init_type(struct type *type, struct spa_type_map *map) +{ + spa_type_media_type_map(map, &type->media_type); + spa_type_media_subtype_map(map, &type->media_subtype); + spa_type_format_video_map(map, &type->format_video); + spa_type_video_format_map(map, &type->video_format); +} + +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_debug(struct weston_pipewire *pipewire, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + pipewire_debug_impl(pipewire, NULL, fmt, ap); + va_end(ap); +} + +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; + + 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_type *t = output->pipewire->t; + 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(spa_buffer, t->meta.Header))) { + h->pts = -1; + 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); + } + + pipewire_output_timer_update(output); + 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; + + wl_list_for_each_safe(mode, next, &base_output->mode_list, link) { + wl_list_remove(&mode->link); + free(mode); + } + + output->saved_destroy(base_output); + + pw_stream_destroy(output->stream); + + wl_list_remove(&output->link); + weston_head_release(output->head); + 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 int +pipewire_output_connect(struct pipewire_output *output) +{ + struct weston_pipewire *pipewire = output->pipewire; + struct type *type = &pipewire->type; + uint8_t buffer[1024]; + struct spa_pod_builder builder = + SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + const struct spa_pod *params[1]; + struct pw_type *t = pipewire->t; + 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_object(&builder, + t->param.idEnumFormat, t->spa_format, + "I", type->media_type.video, + "I", type->media_subtype.raw, + ":", type->format_video.format, + "I", type->video_format.BGRx, + ":", type->format_video.size, + "R", &SPA_RECTANGLE(width, height), + ":", type->format_video.framerate, + "F", &SPA_FRACTION(0, 1), + ":", type->format_video.max_framerate, + "Fru", &SPA_FRACTION(frame_rate, 1), + PROP_RANGE(&SPA_FRACTION(1, 1), + &SPA_FRACTION(frame_rate, 1))); + + ret = pw_stream_connect(output->stream, PW_DIRECTION_OUTPUT, NULL, + (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; + + 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); + + 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_format_changed(void *data, const struct spa_pod *format) +{ + struct pipewire_output *output = data; + struct weston_pipewire *pipewire = output->pipewire; + uint8_t buffer[1024]; + struct spa_pod_builder builder = + SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + const struct spa_pod *params[2]; + struct pw_type *t = pipewire->t; + int32_t width, height, stride, size; + const int bpp = 4; + + if (!format) { + pipewire_output_debug(output, "format = None"); + pw_stream_finish_format(output->stream, 0, NULL, 0); + return; + } + + spa_format_video_raw_parse(format, &output->video_format, + &pipewire->type.format_video); + + 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_object(&builder, + t->param.idBuffers, t->param_buffers.Buffers, + ":", t->param_buffers.size, + "i", size, + ":", t->param_buffers.stride, + "i", stride, + ":", t->param_buffers.buffers, + "iru", 4, PROP_RANGE(2, 8), + ":", t->param_buffers.align, + "i", 16); + + params[1] = spa_pod_builder_object(&builder, + t->param.idMeta, t->param_meta.Meta, + ":", t->param_meta.type, "I", t->meta.Header, + ":", t->param_meta.size, "i", sizeof(struct spa_meta_header)); + + pw_stream_finish_format(output->stream, 0, params, 2); +} + +static const struct pw_stream_events stream_events = { + PW_VERSION_STREAM_EVENTS, + .state_changed = pipewire_output_stream_state_changed, + .format_changed = pipewire_output_stream_format_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 *serial_number = "unknown"; + const char *connector_name = "pipewire"; + + 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->remote, 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); + if (!output->output) { + weston_log("Cannot create virtual output\n"); + goto err; + } + + output->saved_destroy = output->output->destroy; + output->output->destroy = pipewire_output_destroy; + 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); + + weston_head_init(head, connector_name); + weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_NONE); + weston_head_set_monitor_strings(head, make, model, serial_number); + head->compositor = c; + output->head = head; + + weston_output_attach_head(output->output, head); + + 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); + + weston_compositor_log_scope_destroy(pipewire->debug); + pipewire->debug = NULL; + + wl_event_source_remove(pipewire->loop_source); + pw_loop_leave(pipewire->loop); + pw_loop_destroy(pipewire->loop); +} + +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_state_changed(void *data, enum pw_remote_state old, + enum pw_remote_state state, const char *error) +{ + struct weston_pipewire *pipewire = data; + + pipewire_debug(pipewire, "[remote] state changed %s -> %s", + pw_remote_state_as_string(old), + pw_remote_state_as_string(state)); + + switch (state) { + case PW_REMOTE_STATE_ERROR: + weston_log("pipewire remote error: %s\n", error); + break; + case PW_REMOTE_STATE_CONNECTED: + weston_log("connected to pipewire daemon\n"); + break; + default: + break; + } +} + + +static const struct pw_remote_events remote_events = { + PW_VERSION_REMOTE_EVENTS, + .state_changed = weston_pipewire_state_changed, +}; + +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->core = pw_core_new(pipewire->loop, NULL); + pipewire->t = pw_core_get_type(pipewire->core); + init_type(&pipewire->type, pipewire->t->map); + + pipewire->remote = pw_remote_new(pipewire->core, NULL, 0); + pw_remote_add_listener(pipewire->remote, + &pipewire->remote_listener, + &remote_events, pipewire); + + pw_remote_connect(pipewire->remote); + + while (true) { + enum pw_remote_state state; + const char *error = NULL; + int ret; + + state = pw_remote_get_state(pipewire->remote, &error); + if (state == PW_REMOTE_STATE_CONNECTED) + break; + + if (state == PW_REMOTE_STATE_ERROR) { + weston_log("pipewire error: %s\n", error); + goto err; + } + + ret = pw_loop_iterate(pipewire->loop, -1); + if (ret < 0) { + weston_log("pipewire_loop_iterate failed: %s", + spa_strerror(ret)); + goto err; + } + } + + 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; +err: + if (pipewire->remote) + pw_remote_destroy(pipewire->remote); + pw_loop_leave(pipewire->loop); + pw_loop_destroy(pipewire->loop); + return -1; +} + +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; + + 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->weston_log_ctx, + "pipewire", + "Debug messages from pipewire plugin\n", + NULL, NULL); + + pipewire->destroy_listener.notify = weston_pipewire_destroy; + wl_signal_add(&compositor->destroy_signal, &pipewire->destroy_listener); + return 0; + +failed: + free(pipewire); + return -1; +} diff --git a/pipewire/pipewire-plugin.h b/pipewire/pipewire-plugin.h new file mode 100644 index 00000000..88f728a0 --- /dev/null +++ b/pipewire/pipewire-plugin.h @@ -0,0 +1,62 @@ +/* + * Copyright © 2019 Pengutronix, Michael Olbrich + * + * 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. + */ + +#ifndef PIPEWIRE_PLUGIN_H +#define PIPEWIRE_PLUGIN_H + +#include +#include + +#define WESTON_PIPEWIRE_API_NAME "weston_pipewire_api_v1" + +struct weston_pipewire_api { + /** Create pipewire outputs + * + * Returns 0 on success, -1 on failure. + */ + struct weston_output *(*create_output)(struct weston_compositor *c, + char *name); + + /** Check if output is pipewire */ + bool (*is_pipewire_output)(struct weston_output *output); + + /** Set mode */ + int (*set_mode)(struct weston_output *output, const char *modeline); + + /** Set seat */ + void (*set_seat)(struct weston_output *output, const char *seat); +}; + +static inline const struct weston_pipewire_api * +weston_pipewire_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, WESTON_PIPEWIRE_API_NAME, + sizeof(struct weston_pipewire_api)); + + return (const struct weston_pipewire_api *)api; +} + +#endif /* PIPEWIRE_PLUGIN_H */