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 */