aboutsummaryrefslogtreecommitdiffstats
path: root/meta-pipewire/recipes-multimedia/pipewire
diff options
context:
space:
mode:
authorGeorge Kiagiadakis <george.kiagiadakis@collabora.com>2019-09-26 17:55:46 +0300
committerJan-Simon Moeller <jsmoeller@linuxfoundation.org>2019-09-29 12:45:29 +0000
commit34e620431e485932dab84735253eb412497a09ae (patch)
treef17cd7a0c7fdd0f67b8207b5c35a6f2db8507d6d /meta-pipewire/recipes-multimedia/pipewire
parent0308bce9409477d557761b4a8e8b3634b7f4b0a3 (diff)
pipewire: update pipewire & wireplumber to the latest development version
This refreshes all the patches, removing all those that made it upstream, redoing the endpoint extension (now called session-manager extension), and adding some more last moment fixes. In addition, the configuration files for wireplumber & pipewire are being updated to load the new modules, as the module set has changed in both daemons. Finally, the pipewire recipe is adding PACKAGECONFIG options for jack and vulkan, so that we can actually disable them. Pipewire upstream builds them by default and we don't want that. Bug-AGL: SPEC-2837 Change-Id: Id42119c027558466f0a0aa71813ff15f33dfcb56 Signed-off-by: George Kiagiadakis <george.kiagiadakis@collabora.com>
Diffstat (limited to 'meta-pipewire/recipes-multimedia/pipewire')
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in14
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire.inc4
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-arm-build-with-mno-unaligned-access.patch (renamed from meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-arm-build-with-mno-unaligned-access.patch)6
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-spa-include-install-missing-headers.patch41
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-extensions-implement-Endpoint-ClientEndpoint-interfa.patch1563
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-logger-print-timestamps-on-logged-messages.patch (renamed from meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-logger-print-timestamps-on-logged-messages.patch)12
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch (renamed from meta-pipewire/recipes-multimedia/pipewire/pipewire/0007-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch)45
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch (renamed from meta-pipewire/recipes-multimedia/pipewire/pipewire/0010-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch)4
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-pipewire-cli-add-command-to-modify-endpoint-control-.patch124
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch (renamed from meta-pipewire/recipes-multimedia/pipewire/pipewire/0012-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch)6
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch (renamed from meta-pipewire/recipes-multimedia/pipewire/pipewire/0013-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch)8
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0007-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch (renamed from meta-pipewire/recipes-multimedia/pipewire/pipewire/0014-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch)4
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0008-audio-dsp-allow-mode-to-be-set-with-a-property.patch45
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0008-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch (renamed from meta-pipewire/recipes-multimedia/pipewire/pipewire/0021-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch)14
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0009-alsa-do-not-expose-non-interleaved-formats-since-the.patch37
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0009-audioconvert-do-setup-internal-links-and-buffers-als.patch55
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0010-bluez-monitor-fix-usage-of-pw_properties_setf-withou.patch27
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0011-gst-pwaudioringbuffer-make-the-buffer-size-sensitive.patch60
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0011-meson-revert-version-check-to-require-meson-0.47-not.patch30
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0012-extensions-implement-new-session-manager-extension.patch5715
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0013-pipewire-cli-add-support-for-printing-endpoint-info-.patch (renamed from meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-pipewire-cli-add-support-for-printing-endpoint-info-.patch)80
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0014-daemon-config-remote-load-module-session-manager-by-.patch37
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0015-audioconvert-fmtconvert-assume-F32-on-the-other-port.patch44
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-a2dpsink-fix-infinite-loop-when-buffer-could-not-be-.patch32
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-bluez5-add-sco-sink-and-sco-src-nodes.patch2620
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-device-add-name-field-in-spa_device_object_info.patch39
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0019-bluez-add-transport-name-and-use-it-when-emitting-no.patch90
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0020-a2dp-sink-check-if-transport-is-valid-before-releasi.patch36
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb37
29 files changed, 5968 insertions, 4861 deletions
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in
index ad9ab7e6..24646593 100644
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in
@@ -1,11 +1,17 @@
# daemon config file for PipeWire version "0.2.9"
# distributed by Automotive Grade Linux
+
+add-spa-lib audio.convert* audioconvert/libspa-audioconvert
+add-spa-lib api.alsa.* alsa/libspa-alsa
+add-spa-lib api.v4l2.* v4l2/libspa-v4l2
+add-spa-lib api.bluez5.* bluez5/libspa-bluez5
+
load-module libpipewire-module-protocol-native
-load-module libpipewire-module-spa-monitor alsa/libspa-alsa alsa-monitor alsa
-#IF_BLUEZ5 load-module libpipewire-module-spa-monitor bluez5/libspa-bluez5 bluez5-monitor bluez5
+load-module libpipewire-module-spa-node-factory
load-module libpipewire-module-client-node
+load-module libpipewire-module-client-device
load-module libpipewire-module-access
-load-module libpipewire-module-audio-dsp
+load-module libpipewire-module-adapter
load-module libpipewire-module-link-factory
-load-module libpipewire-module-endpoint
+load-module libpipewire-module-session-manager
exec /usr/bin/wireplumber
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc b/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc
index e9046e8e..de6d9b86 100644
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc
@@ -31,11 +31,13 @@ PACKAGECONFIG[systemd] = "-Dsystemd=true,-Dsystemd=false,systemd"
# SPA plugins
PACKAGECONFIG[alsa] = "-Dalsa=true,-Dalsa=false,udev alsa-lib"
-PACKAGECONFIG[audioconvert] = "-Daudioconvert=true,-Daudioconvert=false,speexdsp"
+PACKAGECONFIG[audioconvert] = "-Daudioconvert=true,-Daudioconvert=false,"
PACKAGECONFIG[audiotestsrc] = "-Daudiotestsrc=true,-Daudiotestsrc=false, "
PACKAGECONFIG[bluez5] = "-Dbluez5=true,-Dbluez5=false,bluez5 sbc"
+PACKAGECONFIG[jack] = "-Djack=true,-Djack=false,jack"
PACKAGECONFIG[v4l2] = "-Dv4l2=true,-Dv4l2=false,udev v4l-utils"
PACKAGECONFIG[videotestsrc] = "-Dvideotestsrc=true,-Dvideotestsrc=false, "
+PACKAGECONFIG[vulkan] = "-Dvulkan=true,-Dvulkan=false,vulkan"
# alsa plugin to redirect audio to pipewire
PACKAGECONFIG[pipewire-alsa] = "-Dpipewire-alsa=true,-Dpipewire-alsa=false,alsa-lib"
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-arm-build-with-mno-unaligned-access.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-arm-build-with-mno-unaligned-access.patch
index a670e7ff..3309836a 100644
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-arm-build-with-mno-unaligned-access.patch
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-arm-build-with-mno-unaligned-access.patch
@@ -1,4 +1,4 @@
-From 2016605938f02835c75928648e99b25f7248aa5b Mon Sep 17 00:00:00 2001
+From e1d8927b7963a9bf2c09e50cd95943c3139313cf Mon Sep 17 00:00:00 2001
From: George Kiagiadakis <george.kiagiadakis@collabora.com>
Date: Mon, 24 Jun 2019 12:19:20 +0300
Subject: [PATCH] arm: build with -mno-unaligned-access
@@ -10,7 +10,7 @@ See also https://github.com/PipeWire/pipewire/issues/161
1 file changed, 5 insertions(+)
diff --git a/meson.build b/meson.build
-index 81303d27..f3cc6030 100644
+index aa60db35..5cd61fde 100644
--- a/meson.build
+++ b/meson.build
@@ -50,6 +50,11 @@ if cc.get_id() == 'gcc'
@@ -26,5 +26,5 @@ index 81303d27..f3cc6030 100644
sse2_args = '-msse2'
ssse3_args = '-mssse3'
--
-2.20.1
+2.23.0
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-spa-include-install-missing-headers.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-spa-include-install-missing-headers.patch
deleted file mode 100644
index 5b928117..00000000
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-spa-include-install-missing-headers.patch
+++ /dev/null
@@ -1,41 +0,0 @@
-From dbb6e10df8c2ba9b874eb9350d4cb93c62dba5a9 Mon Sep 17 00:00:00 2001
-From: George Kiagiadakis <george.kiagiadakis@collabora.com>
-Date: Wed, 29 May 2019 12:09:13 +0300
-Subject: [PATCH] spa/include: install missing headers
-
-Upstream-Status: Accepted
----
- spa/include/spa/meson.build | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/spa/include/spa/meson.build b/spa/include/spa/meson.build
-index c9d07659..c079a1a2 100644
---- a/spa/include/spa/meson.build
-+++ b/spa/include/spa/meson.build
-@@ -39,6 +39,7 @@ spa_monitor_headers = [
- 'monitor/device.h',
- 'monitor/monitor.h',
- 'monitor/type-info.h',
-+ 'monitor/utils.h',
- ]
-
- install_headers(spa_monitor_headers,
-@@ -50,6 +51,7 @@ spa_node_headers = [
- 'node/io.h',
- 'node/node.h',
- 'node/type-info.h',
-+ 'node/utils.h',
- ]
-
- install_headers(spa_node_headers,
-@@ -97,6 +99,7 @@ spa_utils_headers = [
- 'utils/dict.h',
- 'utils/hook.h',
- 'utils/list.h',
-+ 'utils/result.h',
- 'utils/ringbuffer.h',
- 'utils/type.h',
- 'utils/type-info.h',
---
-2.20.1
-
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-extensions-implement-Endpoint-ClientEndpoint-interfa.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-extensions-implement-Endpoint-ClientEndpoint-interfa.patch
deleted file mode 100644
index e49edf49..00000000
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-extensions-implement-Endpoint-ClientEndpoint-interfa.patch
+++ /dev/null
@@ -1,1563 +0,0 @@
-From 5afe82a430642c2f7e116941709a624b8fd73011 Mon Sep 17 00:00:00 2001
-From: George Kiagiadakis <george.kiagiadakis@collabora.com>
-Date: Thu, 23 May 2019 18:59:05 +0300
-Subject: [PATCH] extensions: implement Endpoint & ClientEndpoint interfaces
-
-The ClientEndpoint interface allows session managers to register
-endpoint objects on pipewire.
-The Endpoint interface allows other clients to interact with
-endpoints provided by the session manager.
-
-Upstream-Status: Pending
----
- src/extensions/client-endpoint.h | 106 ++++
- src/extensions/endpoint.h | 237 +++++++++
- src/extensions/meson.build | 2 +
- src/modules/meson.build | 12 +
- src/modules/module-endpoint.c | 137 +++++
- src/modules/module-endpoint/endpoint-impl.c | 428 ++++++++++++++++
- src/modules/module-endpoint/endpoint-impl.h | 52 ++
- src/modules/module-endpoint/protocol-native.c | 472 ++++++++++++++++++
- src/pipewire/pipewire.c | 2 +
- src/pipewire/type.h | 3 +-
- 10 files changed, 1450 insertions(+), 1 deletion(-)
- create mode 100644 src/extensions/client-endpoint.h
- create mode 100644 src/extensions/endpoint.h
- create mode 100644 src/modules/module-endpoint.c
- create mode 100644 src/modules/module-endpoint/endpoint-impl.c
- create mode 100644 src/modules/module-endpoint/endpoint-impl.h
- create mode 100644 src/modules/module-endpoint/protocol-native.c
-
-diff --git a/src/extensions/client-endpoint.h b/src/extensions/client-endpoint.h
-new file mode 100644
-index 00000000..0389893c
---- /dev/null
-+++ b/src/extensions/client-endpoint.h
-@@ -0,0 +1,106 @@
-+/* PipeWire
-+ *
-+ * Copyright © 2019 Collabora Ltd.
-+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
-+ *
-+ * 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_EXT_CLIENT_ENDPOINT_H
-+#define PIPEWIRE_EXT_CLIENT_ENDPOINT_H
-+
-+#include "endpoint.h"
-+
-+#ifdef __cplusplus
-+extern "C" {
-+#endif
-+
-+struct pw_client_endpoint_proxy;
-+
-+#define PW_VERSION_CLIENT_ENDPOINT 0
-+#define PW_EXTENSION_MODULE_CLIENT_ENDPOINT PIPEWIRE_MODULE_PREFIX "module-endpoint"
-+
-+#define PW_CLIENT_ENDPOINT_PROXY_METHOD_UPDATE 0
-+#define PW_CLIENT_ENDPOINT_PROXY_METHOD_NUM 1
-+
-+struct pw_client_endpoint_proxy_methods {
-+#define PW_VERSION_CLIENT_ENDPOINT_PROXY_METHODS 0
-+ uint32_t version;
-+
-+ /**
-+ * Update endpoint info
-+ */
-+ int (*update) (void *object,
-+#define PW_CLIENT_ENDPOINT_UPDATE_PARAMS (1 << 0)
-+#define PW_CLIENT_ENDPOINT_UPDATE_PARAMS_INCREMENTAL (1 << 1)
-+#define PW_CLIENT_ENDPOINT_UPDATE_INFO (1 << 2)
-+ uint32_t change_mask,
-+ uint32_t n_params,
-+ const struct spa_pod **params,
-+ const struct pw_endpoint_info *info);
-+};
-+
-+static inline int
-+pw_client_endpoint_proxy_update(struct pw_client_endpoint_proxy *p,
-+ uint32_t change_mask,
-+ uint32_t n_params,
-+ const struct spa_pod **params,
-+ struct pw_endpoint_info *info)
-+{
-+ return pw_proxy_do((struct pw_proxy*)p,
-+ struct pw_client_endpoint_proxy_methods, update,
-+ change_mask, n_params, params, info);
-+}
-+
-+#define PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_PARAM 0
-+#define PW_CLIENT_ENDPOINT_PROXY_EVENT_NUM 1
-+
-+struct pw_client_endpoint_proxy_events {
-+#define PW_VERSION_CLIENT_ENDPOINT_PROXY_EVENTS 0
-+ uint32_t version;
-+
-+ /**
-+ * Set a parameter on the endpoint
-+ *
-+ * \param id the parameter id to set
-+ * \param flags extra parameter flags
-+ * \param param the parameter to set
-+ */
-+ void (*set_param) (void *object, uint32_t id, uint32_t flags,
-+ const struct spa_pod *param);
-+};
-+
-+static inline void
-+pw_client_endpoint_proxy_add_listener(struct pw_client_endpoint_proxy *p,
-+ struct spa_hook *listener,
-+ const struct pw_client_endpoint_proxy_events *events,
-+ void *data)
-+{
-+ pw_proxy_add_proxy_listener((struct pw_proxy*)p, listener, events, data);
-+}
-+
-+#define pw_client_endpoint_resource_set_param(r,...) \
-+ pw_resource_notify(r,struct pw_client_endpoint_proxy_events,set_param,__VA_ARGS__)
-+
-+#ifdef __cplusplus
-+} /* extern "C" */
-+#endif
-+
-+#endif /* PIPEWIRE_EXT_CLIENT_ENDPOINT_H */
-diff --git a/src/extensions/endpoint.h b/src/extensions/endpoint.h
-new file mode 100644
-index 00000000..211c0895
---- /dev/null
-+++ b/src/extensions/endpoint.h
-@@ -0,0 +1,237 @@
-+/* PipeWire
-+ *
-+ * Copyright © 2019 Collabora Ltd.
-+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
-+ *
-+ * 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_EXT_ENDPOINT_H
-+#define PIPEWIRE_EXT_ENDPOINT_H
-+
-+#include <spa/utils/defs.h>
-+#include <spa/utils/type-info.h>
-+#include <pipewire/proxy.h>
-+
-+#ifdef __cplusplus
-+extern "C" {
-+#endif
-+
-+struct pw_endpoint_proxy;
-+
-+#define PW_VERSION_ENDPOINT 0
-+#define PW_EXTENSION_MODULE_ENDPOINT PIPEWIRE_MODULE_PREFIX "module-endpoint"
-+
-+/* extending enum spa_param_type */
-+enum endpoint_param_type {
-+ PW_ENDPOINT_PARAM_EnumControl = 0x1000,
-+ PW_ENDPOINT_PARAM_Control,
-+ PW_ENDPOINT_PARAM_EnumStream,
-+};
-+
-+enum endpoint_param_object_type {
-+ PW_ENDPOINT_OBJECT_ParamControl = PW_TYPE_FIRST + SPA_TYPE_OBJECT_START + 0x1001,
-+ PW_ENDPOINT_OBJECT_ParamStream,
-+};
-+
-+/** properties for PW_ENDPOINT_OBJECT_ParamControl */
-+enum endpoint_param_control {
-+ PW_ENDPOINT_PARAM_CONTROL_START, /**< object id, one of enum endpoint_param_type */
-+ PW_ENDPOINT_PARAM_CONTROL_id, /**< control id (Int) */
-+ PW_ENDPOINT_PARAM_CONTROL_stream_id, /**< stream id (Int) */
-+ PW_ENDPOINT_PARAM_CONTROL_name, /**< control name (String) */
-+ PW_ENDPOINT_PARAM_CONTROL_type, /**< control type (Range) */
-+ PW_ENDPOINT_PARAM_CONTROL_value, /**< control value */
-+};
-+
-+/** properties for PW_ENDPOINT_OBJECT_ParamStream */
-+enum endpoint_param_stream {
-+ PW_ENDPOINT_PARAM_STREAM_START, /**< object id, one of enum endpoint_param_type */
-+ PW_ENDPOINT_PARAM_STREAM_id, /**< stream id (Int) */
-+ PW_ENDPOINT_PARAM_STREAM_name, /**< stream name (String) */
-+};
-+
-+static const struct spa_type_info endpoint_param_type_info[] = {
-+ { PW_ENDPOINT_PARAM_EnumControl, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ID_BASE "EnumControl", NULL },
-+ { PW_ENDPOINT_PARAM_Control, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ID_BASE "Control", NULL },
-+ { PW_ENDPOINT_PARAM_EnumStream, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ID_BASE "EnumStream", NULL },
-+ { 0, 0, NULL, NULL },
-+};
-+
-+#define PW_ENDPOINT_TYPE_INFO_ParamControl SPA_TYPE_INFO_PARAM_BASE "ParamControl"
-+#define PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE PW_ENDPOINT_TYPE_INFO_ParamControl ":"
-+
-+static const struct spa_type_info endpoint_param_control_info[] = {
-+ { PW_ENDPOINT_PARAM_CONTROL_START, SPA_TYPE_Id, PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE, spa_type_param, },
-+ { PW_ENDPOINT_PARAM_CONTROL_id, SPA_TYPE_Int, PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE "id", NULL },
-+ { PW_ENDPOINT_PARAM_CONTROL_stream_id, SPA_TYPE_Int, PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE "streamId", NULL },
-+ { PW_ENDPOINT_PARAM_CONTROL_name, SPA_TYPE_String, PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE "name", NULL },
-+ { PW_ENDPOINT_PARAM_CONTROL_type, SPA_TYPE_Pod, PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE "type", NULL },
-+ { PW_ENDPOINT_PARAM_CONTROL_value, SPA_TYPE_Struct, PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE "value", NULL },
-+ { 0, 0, NULL, NULL },
-+};
-+
-+#define PW_ENDPOINT_TYPE_INFO_ParamStream SPA_TYPE_INFO_PARAM_BASE "ParamStream"
-+#define PW_ENDPOINT_TYPE_INFO_PARAM_STREAM_BASE PW_ENDPOINT_TYPE_INFO_ParamStream ":"
-+
-+static const struct spa_type_info endpoint_param_stream_info[] = {
-+ { PW_ENDPOINT_PARAM_STREAM_START, SPA_TYPE_Id, PW_ENDPOINT_TYPE_INFO_PARAM_STREAM_BASE, spa_type_param, },
-+ { PW_ENDPOINT_PARAM_STREAM_id, SPA_TYPE_Int, PW_ENDPOINT_TYPE_INFO_PARAM_STREAM_BASE "id", NULL },
-+ { PW_ENDPOINT_PARAM_STREAM_name, SPA_TYPE_String, PW_ENDPOINT_TYPE_INFO_PARAM_STREAM_BASE "name", NULL },
-+ { 0, 0, NULL, NULL },
-+};
-+
-+static const struct spa_type_info endpoint_param_object_type_info[] = {
-+ { PW_ENDPOINT_OBJECT_ParamControl, SPA_TYPE_Object, SPA_TYPE_INFO_OBJECT_BASE "ParamControl", endpoint_param_control_info, },
-+ { PW_ENDPOINT_OBJECT_ParamStream, SPA_TYPE_Object, SPA_TYPE_INFO_OBJECT_BASE "ParamStream", endpoint_param_stream_info },
-+ { 0, 0, NULL, NULL },
-+};
-+
-+struct pw_endpoint_info {
-+ uint32_t id; /**< id of the global */
-+#define PW_ENDPOINT_CHANGE_MASK_PARAMS (1 << 0)
-+#define PW_ENDPOINT_CHANGE_MASK_PROPS (1 << 1)
-+ uint32_t change_mask; /**< bitfield of changed fields since last call */
-+ uint32_t n_params; /**< number of items in \a params */
-+ struct spa_param_info *params; /**< parameters */
-+ struct spa_dict *props; /**< extra properties */
-+};
-+
-+#define PW_ENDPOINT_PROXY_METHOD_SUBSCRIBE_PARAMS 0
-+#define PW_ENDPOINT_PROXY_METHOD_ENUM_PARAMS 1
-+#define PW_ENDPOINT_PROXY_METHOD_SET_PARAM 2
-+#define PW_ENDPOINT_PROXY_METHOD_NUM 3
-+
-+struct pw_endpoint_proxy_methods {
-+#define PW_VERSION_ENDPOINT_PROXY_METHODS 0
-+ uint32_t version;
-+
-+ /**
-+ * Subscribe to parameter changes
-+ *
-+ * Automatically emit param events for the given ids when
-+ * they are changed.
-+ *
-+ * \param ids an array of param ids
-+ * \param n_ids the number of ids in \a ids
-+ */
-+ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
-+
-+ /**
-+ * Enumerate endpoint parameters
-+ *
-+ * Start enumeration of endpoint parameters. For each param, a
-+ * param event will be emited.
-+ *
-+ * \param seq a sequence number to place in the reply
-+ * \param id the parameter id to enum or SPA_ID_INVALID for all
-+ * \param start the start index or 0 for the first param
-+ * \param num the maximum number of params to retrieve
-+ * \param filter a param filter or NULL
-+ */
-+ int (*enum_params) (void *object, int seq,
-+ uint32_t id, uint32_t start, uint32_t num,
-+ const struct spa_pod *filter);
-+
-+ /**
-+ * Set a parameter on the endpoint
-+ *
-+ * \param id the parameter id to set
-+ * \param flags extra parameter flags
-+ * \param param the parameter to set
-+ */
-+ int (*set_param) (void *object, uint32_t id, uint32_t flags,
-+ const struct spa_pod *param);
-+};
-+
-+static inline int
-+pw_endpoint_proxy_subscribe_params(struct pw_endpoint_proxy *p, uint32_t *ids, uint32_t n_ids)
-+{
-+ return pw_proxy_do((struct pw_proxy*)p, struct pw_endpoint_proxy_methods,
-+ subscribe_params, ids, n_ids);
-+}
-+
-+static inline int
-+pw_endpoint_proxy_enum_params(struct pw_endpoint_proxy *p, int seq,
-+ uint32_t id, uint32_t start, uint32_t num,
-+ const struct spa_pod *filter)
-+{
-+ return pw_proxy_do((struct pw_proxy*)p, struct pw_endpoint_proxy_methods,
-+ enum_params, seq, id, start, num, filter);
-+}
-+
-+static inline int
-+pw_endpoint_proxy_set_param(struct pw_endpoint_proxy *p, uint32_t id,
-+ uint32_t flags, const struct spa_pod *param)
-+{
-+ return pw_proxy_do((struct pw_proxy*)p, struct pw_endpoint_proxy_methods,
-+ set_param, id, flags, param);
-+}
-+
-+#define PW_ENDPOINT_PROXY_EVENT_INFO 0
-+#define PW_ENDPOINT_PROXY_EVENT_PARAM 1
-+#define PW_ENDPOINT_PROXY_EVENT_NUM 2
-+
-+struct pw_endpoint_proxy_events {
-+#define PW_VERSION_ENDPOINT_PROXY_EVENTS 0
-+ uint32_t version;
-+
-+ /**
-+ * Notify endpoint info
-+ *
-+ * \param info info about the endpoint
-+ */
-+ void (*info) (void *object, const struct pw_endpoint_info * info);
-+
-+ /**
-+ * Notify an endpoint param
-+ *
-+ * Event emited as a result of the enum_params method.
-+ *
-+ * \param seq the sequence number of the request
-+ * \param id the param id
-+ * \param index the param index
-+ * \param next the param index of the next param
-+ * \param param the parameter
-+ */
-+ void (*param) (void *object, int seq, uint32_t id,
-+ uint32_t index, uint32_t next,
-+ const struct spa_pod *param);
-+};
-+
-+static inline void
-+pw_endpoint_proxy_add_listener(struct pw_endpoint_proxy *p,
-+ struct spa_hook *listener,
-+ const struct pw_endpoint_proxy_events *events,
-+ void *data)
-+{
-+ pw_proxy_add_proxy_listener((struct pw_proxy*)p, listener, events, data);
-+}
-+
-+#define pw_endpoint_resource_info(r,...) \
-+ pw_resource_notify(r,struct pw_endpoint_proxy_events,info,__VA_ARGS__)
-+#define pw_endpoint_resource_param(r,...) \
-+ pw_resource_notify(r,struct pw_endpoint_proxy_events,param,__VA_ARGS__)
-+
-+#ifdef __cplusplus
-+} /* extern "C" */
-+#endif
-+
-+#endif /* PIPEWIRE_EXT_ENDPOINT_H */
-diff --git a/src/extensions/meson.build b/src/extensions/meson.build
-index a7f5d3cb..9f690caf 100644
---- a/src/extensions/meson.build
-+++ b/src/extensions/meson.build
-@@ -1,5 +1,7 @@
- pipewire_ext_headers = [
-+ 'client-endpoint.h',
- 'client-node.h',
-+ 'endpoint.h',
- 'protocol-native.h',
- ]
-
-diff --git a/src/modules/meson.build b/src/modules/meson.build
-index 98bc3864..572f1b6b 100644
---- a/src/modules/meson.build
-+++ b/src/modules/meson.build
-@@ -37,6 +37,18 @@ pipewire_module_client_node = shared_library('pipewire-module-client-node',
- dependencies : [mathlib, dl_lib, pipewire_dep],
- )
-
-+pipewire_module_endpoint = shared_library('pipewire-module-endpoint',
-+ [ 'module-endpoint.c',
-+ 'module-endpoint/endpoint-impl.c',
-+ 'module-endpoint/protocol-native.c',
-+ ],
-+ c_args : pipewire_module_c_args,
-+ include_directories : [configinc, spa_inc],
-+ install : true,
-+ install_dir : modules_install_dir,
-+ dependencies : [mathlib, dl_lib, pipewire_dep],
-+)
-+
- pipewire_module_link_factory = shared_library('pipewire-module-link-factory',
- [ 'module-link-factory.c' ],
- c_args : pipewire_module_c_args,
-diff --git a/src/modules/module-endpoint.c b/src/modules/module-endpoint.c
-new file mode 100644
-index 00000000..d830de1b
---- /dev/null
-+++ b/src/modules/module-endpoint.c
-@@ -0,0 +1,137 @@
-+/* PipeWire
-+ *
-+ * Copyright © 2019 Collabora Ltd.
-+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
-+ *
-+ * 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 "module-endpoint/endpoint-impl.h"
-+
-+struct pw_protocol *pw_protocol_native_ext_endpoint_init(struct pw_core *core);
-+
-+static const struct spa_dict_item module_props[] = {
-+ { PW_MODULE_PROP_AUTHOR, "George Kiagiadakis <george.kiagiadakis@collabora.com>" },
-+ { PW_MODULE_PROP_DESCRIPTION, "Allows clients to interract with session manager endpoints" },
-+ { PW_MODULE_PROP_VERSION, PACKAGE_VERSION },
-+};
-+
-+struct factory_data {
-+ struct pw_factory *this;
-+ struct pw_properties *properties;
-+
-+ struct pw_module *module;
-+ struct spa_hook module_listener;
-+};
-+
-+static void *create_object(void *_data,
-+ struct pw_resource *resource,
-+ uint32_t type,
-+ uint32_t version,
-+ struct pw_properties *properties,
-+ uint32_t new_id)
-+{
-+ void *result;
-+ struct pw_resource *endpoint_resource;
-+ struct pw_global *parent;
-+ struct pw_client *client = pw_resource_get_client(resource);
-+
-+ endpoint_resource = pw_resource_new(client, new_id, PW_PERM_RWX, type, version, 0);
-+ if (endpoint_resource == NULL)
-+ goto no_mem;
-+
-+ parent = pw_client_get_global(client);
-+
-+ result = pw_client_endpoint_new(endpoint_resource, parent, properties);
-+ if (result == NULL)
-+ goto no_mem;
-+
-+ return result;
-+
-+ no_mem:
-+ pw_log_error("can't create endpoint");
-+ pw_resource_error(resource, -ENOMEM, "can't create endpoint: no memory");
-+ if (properties)
-+ pw_properties_free(properties);
-+ return NULL;
-+}
-+
-+static const struct pw_factory_implementation impl_factory = {
-+ PW_VERSION_FACTORY_IMPLEMENTATION,
-+ .create_object = create_object,
-+};
-+
-+static void module_destroy(void *data)
-+{
-+ struct factory_data *d = data;
-+
-+ spa_hook_remove(&d->module_listener);
-+
-+ if (d->properties)
-+ pw_properties_free(d->properties);
-+
-+ pw_factory_destroy(d->this);
-+}
-+
-+static const struct pw_module_events module_events = {
-+ PW_VERSION_MODULE_EVENTS,
-+ .destroy = module_destroy,
-+};
-+
-+static int module_init(struct pw_module *module, struct pw_properties *properties)
-+{
-+ struct pw_core *core = pw_module_get_core(module);
-+ struct pw_factory *factory;
-+ struct factory_data *data;
-+
-+ factory = pw_factory_new(core,
-+ "client-endpoint",
-+ PW_TYPE_INTERFACE_ClientEndpoint,
-+ PW_VERSION_CLIENT_ENDPOINT,
-+ NULL,
-+ sizeof(*data));
-+ if (factory == NULL)
-+ return -ENOMEM;
-+
-+ data = pw_factory_get_user_data(factory);
-+ data->this = factory;
-+ data->module = module;
-+ data->properties = properties;
-+
-+ pw_log_debug("module-endpoint %p: new", module);
-+
-+ pw_factory_set_implementation(factory, &impl_factory, data);
-+ pw_factory_register(factory, NULL, pw_module_get_global(module), NULL);
-+
-+ pw_protocol_native_ext_endpoint_init(core);
-+
-+ pw_module_add_listener(module, &data->module_listener, &module_events, data);
-+ pw_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
-+
-+ return 0;
-+}
-+
-+SPA_EXPORT
-+int pipewire__module_init(struct pw_module *module, const char *args)
-+{
-+ return module_init(module, NULL);
-+}
-diff --git a/src/modules/module-endpoint/endpoint-impl.c b/src/modules/module-endpoint/endpoint-impl.c
-new file mode 100644
-index 00000000..252eeca1
---- /dev/null
-+++ b/src/modules/module-endpoint/endpoint-impl.c
-@@ -0,0 +1,428 @@
-+/* PipeWire
-+ *
-+ * Copyright © 2019 Collabora Ltd.
-+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
-+ *
-+ * 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 "endpoint-impl.h"
-+#include <pipewire/private.h>
-+#include <spa/pod/filter.h>
-+#include <spa/pod/compare.h>
-+
-+struct pw_endpoint {
-+ struct pw_core *core;
-+ struct pw_global *global;
-+ struct pw_global *parent;
-+
-+ struct pw_client_endpoint *client_ep;
-+
-+ uint32_t n_params;
-+ struct spa_pod **params;
-+
-+ struct pw_endpoint_info info;
-+ struct pw_properties *props;
-+};
-+
-+struct pw_client_endpoint {
-+ struct pw_resource *owner_resource;
-+ struct spa_hook owner_resource_listener;
-+
-+ struct pw_endpoint endpoint;
-+};
-+
-+struct resource_data {
-+ struct pw_endpoint *endpoint;
-+ struct pw_client_endpoint *client_ep;
-+
-+ struct spa_hook resource_listener;
-+
-+ uint32_t n_subscribe_ids;
-+ uint32_t subscribe_ids[32];
-+};
-+
-+static int
-+endpoint_enum_params (void *object, int seq,
-+ uint32_t id, uint32_t start, uint32_t num,
-+ const struct spa_pod *filter)
-+{
-+ struct pw_resource *resource = object;
-+ struct resource_data *data = pw_resource_get_user_data(resource);
-+ struct pw_endpoint *this = data->endpoint;
-+ struct spa_pod *result;
-+ struct spa_pod *param;
-+ uint8_t buffer[1024];
-+ struct spa_pod_builder b = { 0 };
-+ uint32_t index;
-+ uint32_t next = start;
-+ uint32_t count = 0;
-+
-+ while (true) {
-+ index = next++;
-+ if (index >= this->n_params)
-+ break;
-+
-+ param = this->params[index];
-+
-+ if (param == NULL || !spa_pod_is_object_id(param, id))
-+ continue;
-+
-+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
-+ if (spa_pod_filter(&b, &result, param, filter) != 0)
-+ continue;
-+
-+ pw_log_debug("endpoint %p: %d param %u", this, seq, index);
-+
-+ pw_endpoint_resource_param(resource, seq, id, index, next, result);
-+
-+ if (++count == num)
-+ break;
-+ }
-+ return 0;
-+}
-+
-+static int
-+endpoint_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids)
-+{
-+ struct pw_resource *resource = object;
-+ struct resource_data *data = pw_resource_get_user_data(resource);
-+ uint32_t i;
-+
-+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
-+ data->n_subscribe_ids = n_ids;
-+
-+ for (i = 0; i < n_ids; i++) {
-+ data->subscribe_ids[i] = ids[i];
-+ pw_log_debug("endpoint %p: resource %d subscribe param %u",
-+ data->endpoint, resource->id, ids[i]);
-+ endpoint_enum_params(resource, 1, ids[i], 0, UINT32_MAX, NULL);
-+ }
-+ return 0;
-+}
-+
-+static int
-+endpoint_set_param (void *object, uint32_t id, uint32_t flags,
-+ const struct spa_pod *param)
-+{
-+ struct pw_resource *resource = object;
-+ struct resource_data *data = pw_resource_get_user_data(resource);
-+ struct pw_client_endpoint *client_ep = data->client_ep;
-+
-+ pw_client_endpoint_resource_set_param(client_ep->owner_resource,
-+ id, flags, param);
-+
-+ return 0;
-+}
-+
-+static const struct pw_endpoint_proxy_methods endpoint_methods = {
-+ PW_VERSION_ENDPOINT_PROXY_METHODS,
-+ .subscribe_params = endpoint_subscribe_params,
-+ .enum_params = endpoint_enum_params,
-+ .set_param = endpoint_set_param,
-+};
-+
-+static void
-+endpoint_unbind(void *data)
-+{
-+ struct pw_resource *resource = data;
-+ spa_list_remove(&resource->link);
-+}
-+
-+static const struct pw_resource_events resource_events = {
-+ PW_VERSION_RESOURCE_EVENTS,
-+ .destroy = endpoint_unbind,
-+};
-+
-+static int
-+endpoint_bind(void *_data, struct pw_client *client, uint32_t permissions,
-+ uint32_t version, uint32_t id)
-+{
-+ struct pw_endpoint *this = _data;
-+ struct pw_global *global = this->global;
-+ struct pw_resource *resource;
-+ struct resource_data *data;
-+
-+ resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data));
-+ if (resource == NULL)
-+ goto no_mem;
-+
-+ data = pw_resource_get_user_data(resource);
-+ data->endpoint = this;
-+ data->client_ep = this->client_ep;
-+ pw_resource_add_listener(resource, &data->resource_listener, &resource_events, resource);
-+
-+ pw_resource_set_implementation(resource, &endpoint_methods, resource);
-+
-+ pw_log_debug("endpoint %p: bound to %d", this, resource->id);
-+
-+ spa_list_append(&global->resource_list, &resource->link);
-+
-+ this->info.change_mask = PW_ENDPOINT_CHANGE_MASK_PARAMS |
-+ PW_ENDPOINT_CHANGE_MASK_PROPS;
-+ pw_endpoint_resource_info(resource, &this->info);
-+ this->info.change_mask = 0;
-+
-+ return 0;
-+
-+ no_mem:
-+ pw_log_error("can't create node resource");
-+ return -ENOMEM;
-+}
-+
-+static int
-+pw_endpoint_init(struct pw_endpoint *this,
-+ struct pw_core *core,
-+ struct pw_client *owner,
-+ struct pw_global *parent,
-+ struct pw_properties *properties)
-+{
-+ struct pw_properties *props = NULL;
-+
-+ pw_log_debug("endpoint %p: new", this);
-+
-+ this->core = core;
-+ this->parent = parent;
-+
-+ props = properties ? properties : pw_properties_new(NULL, NULL);
-+ if (!props)
-+ goto no_mem;
-+
-+ this->props = pw_properties_copy (props);
-+ if (!this->props)
-+ goto no_mem;
-+
-+ this->global = pw_global_new (core,
-+ PW_TYPE_INTERFACE_Endpoint,
-+ PW_VERSION_ENDPOINT,
-+ props, endpoint_bind, this);
-+ if (!this->global)
-+ goto no_mem;
-+
-+ this->info.id = this->global->id;
-+ this->info.props = &this->props->dict;
-+
-+ return pw_global_register(this->global, owner, parent);
-+
-+ no_mem:
-+ pw_log_error("can't create endpoint - out of memory");
-+ if (props && !properties)
-+ pw_properties_free(props);
-+ if (this->props)
-+ pw_properties_free(this->props);
-+ return -ENOMEM;
-+}
-+
-+static void
-+pw_endpoint_clear(struct pw_endpoint *this)
-+{
-+ uint32_t i;
-+
-+ pw_log_debug("endpoint %p: destroy", this);
-+
-+ pw_global_destroy(this->global);
-+
-+ for (i = 0; i < this->n_params; i++)
-+ free(this->params[i]);
-+ free(this->params);
-+
-+ free(this->info.params);
-+
-+ if (this->props)
-+ pw_properties_free(this->props);
-+}
-+
-+static void
-+endpoint_notify_subscribed(struct pw_endpoint *this,
-+ uint32_t index, uint32_t next)
-+{
-+ struct pw_global *global = this->global;
-+ struct pw_resource *resource;
-+ struct resource_data *data;
-+ struct spa_pod *param = this->params[index];
-+ uint32_t id;
-+ uint32_t i;
-+
-+ if (!param || !spa_pod_is_object (param))
-+ return;
-+
-+ id = SPA_POD_OBJECT_ID (param);
-+
-+ spa_list_for_each(resource, &global->resource_list, link) {
-+ data = pw_resource_get_user_data(resource);
-+ for (i = 0; i < data->n_subscribe_ids; i++) {
-+ if (data->subscribe_ids[i] == id) {
-+ pw_endpoint_resource_param(resource, 1, id,
-+ index, next, param);
-+ }
-+ }
-+ }
-+}
-+
-+static int
-+client_endpoint_update(void *object,
-+ uint32_t change_mask,
-+ uint32_t n_params,
-+ const struct spa_pod **params,
-+ const struct pw_endpoint_info *info)
-+{
-+ struct pw_client_endpoint *cliep = object;
-+ struct pw_endpoint *this = &cliep->endpoint;
-+
-+ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_PARAMS) {
-+ uint32_t i;
-+
-+ pw_log_debug("endpoint %p: update %d params", this, n_params);
-+
-+ for (i = 0; i < this->n_params; i++)
-+ free(this->params[i]);
-+ this->n_params = n_params;
-+ this->params = realloc(this->params, this->n_params * sizeof(struct spa_pod *));
-+
-+ for (i = 0; i < this->n_params; i++) {
-+ this->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL;
-+ endpoint_notify_subscribed(this, i, i+1);
-+ }
-+ }
-+ else if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_PARAMS_INCREMENTAL) {
-+ uint32_t i, j;
-+ const struct spa_pod_prop *pold, *pnew;
-+
-+ pw_log_debug("endpoint %p: update %d params incremental", this, n_params);
-+
-+ for (i = 0; i < this->n_params; i++) {
-+ /* we only support incremental updates for controls */
-+ if (!spa_pod_is_object_id (this->params[i], PW_ENDPOINT_PARAM_Control))
-+ continue;
-+
-+ for (j = 0; j < n_params; j++) {
-+ if (!spa_pod_is_object_id (params[j], PW_ENDPOINT_PARAM_Control)) {
-+ pw_log_warn ("endpoint %p: ignoring incremental update "
-+ "on non-control param", this);
-+ continue;
-+ }
-+
-+ pold = spa_pod_object_find_prop (
-+ (const struct spa_pod_object *) this->params[i],
-+ NULL, PW_ENDPOINT_PARAM_CONTROL_id);
-+ pnew = spa_pod_object_find_prop (
-+ (const struct spa_pod_object *) params[j],
-+ NULL, PW_ENDPOINT_PARAM_CONTROL_id);
-+
-+ if (pold && pnew && spa_pod_compare (&pold->value, &pnew->value) == 0) {
-+ free (this->params[i]);
-+ this->params[i] = spa_pod_copy (params[j]);
-+ endpoint_notify_subscribed(this, i, UINT32_MAX);
-+ }
-+ }
-+ }
-+ }
-+
-+ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_INFO) {
-+ struct pw_global *global = this->global;
-+ struct pw_resource *resource;
-+
-+ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) {
-+ size_t size = info->n_params * sizeof(struct spa_param_info);
-+ free(this->info.params);
-+ this->info.params = malloc(size);
-+ this->info.n_params = info->n_params;
-+ memcpy(this->info.params, info->params, size);
-+ }
-+
-+ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) {
-+ pw_properties_update(this->props, info->props);
-+ }
-+
-+ this->info.change_mask = info->change_mask;
-+ spa_list_for_each(resource, &global->resource_list, link) {
-+ pw_endpoint_resource_info(resource, &this->info);
-+ }
-+ this->info.change_mask = 0;
-+ }
-+
-+ return 0;
-+}
-+
-+static struct pw_client_endpoint_proxy_methods client_endpoint_methods = {
-+ PW_VERSION_CLIENT_ENDPOINT_PROXY_METHODS,
-+ .update = client_endpoint_update,
-+};
-+
-+static void
-+client_endpoint_resource_destroy(void *data)
-+{
-+ struct pw_client_endpoint *this = data;
-+
-+ pw_log_debug("client-endpoint %p: destroy", this);
-+
-+ pw_endpoint_clear(&this->endpoint);
-+
-+ this->owner_resource = NULL;
-+ spa_hook_remove(&this->owner_resource_listener);
-+ free(this);
-+}
-+
-+static const struct pw_resource_events owner_resource_events = {
-+ PW_VERSION_RESOURCE_EVENTS,
-+ .destroy = client_endpoint_resource_destroy,
-+};
-+
-+struct pw_client_endpoint *
-+pw_client_endpoint_new(struct pw_resource *owner_resource,
-+ struct pw_global *parent,
-+ struct pw_properties *properties)
-+{
-+ struct pw_client_endpoint *this;
-+ struct pw_client *owner = pw_resource_get_client(owner_resource);
-+ struct pw_core *core = pw_client_get_core(owner);
-+
-+ this = calloc(1, sizeof(struct pw_client_endpoint));
-+ if (this == NULL)
-+ return NULL;
-+
-+ pw_log_debug("client-endpoint %p: new", this);
-+
-+ if (pw_endpoint_init(&this->endpoint, core, owner, parent, properties) < 0)
-+ goto error_no_endpoint;
-+ this->endpoint.client_ep = this;
-+
-+ this->owner_resource = owner_resource;
-+ pw_resource_add_listener(this->owner_resource,
-+ &this->owner_resource_listener,
-+ &owner_resource_events,
-+ this);
-+ pw_resource_set_implementation(this->owner_resource,
-+ &client_endpoint_methods,
-+ this);
-+
-+ return this;
-+
-+ error_no_endpoint:
-+ pw_resource_destroy(owner_resource);
-+ free(this);
-+ return NULL;
-+}
-+
-+void
-+pw_client_endpoint_destroy(struct pw_client_endpoint *this)
-+{
-+ pw_resource_destroy(this->owner_resource);
-+}
-diff --git a/src/modules/module-endpoint/endpoint-impl.h b/src/modules/module-endpoint/endpoint-impl.h
-new file mode 100644
-index 00000000..059aa904
---- /dev/null
-+++ b/src/modules/module-endpoint/endpoint-impl.h
-@@ -0,0 +1,52 @@
-+/* PipeWire
-+ *
-+ * Copyright © 2019 Collabora Ltd.
-+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
-+ *
-+ * 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_ENDPOINT_IMPL_H
-+#define PIPEWIRE_ENDPOINT_IMPL_H
-+
-+#include <pipewire/pipewire.h>
-+#include <extensions/endpoint.h>
-+#include <extensions/client-endpoint.h>
-+
-+#ifdef __cplusplus
-+extern "C" {
-+#endif
-+
-+struct pw_endpoint;
-+struct pw_client_endpoint;
-+
-+struct pw_client_endpoint *
-+pw_client_endpoint_new(struct pw_resource *resource,
-+ struct pw_global *parent,
-+ struct pw_properties *properties);
-+
-+void
-+pw_client_endpoint_destroy(struct pw_client_endpoint *endpoint);
-+
-+#ifdef __cplusplus
-+}
-+#endif
-+
-+#endif /* PIPEWIRE_ENDPOINT_IMPL_H */
-diff --git a/src/modules/module-endpoint/protocol-native.c b/src/modules/module-endpoint/protocol-native.c
-new file mode 100644
-index 00000000..a41d3119
---- /dev/null
-+++ b/src/modules/module-endpoint/protocol-native.c
-@@ -0,0 +1,472 @@
-+/* PipeWire
-+ *
-+ * Copyright © 2019 Collabora Ltd.
-+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
-+ *
-+ * 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/pipewire.h>
-+#include <spa/pod/parser.h>
-+
-+#include <extensions/client-endpoint.h>
-+#include <extensions/endpoint.h>
-+#include <extensions/protocol-native.h>
-+
-+static void
-+serialize_pw_endpoint_info(struct spa_pod_builder *b,
-+ const struct pw_endpoint_info *info)
-+{
-+ struct spa_pod_frame f;
-+ uint32_t i, n_props;
-+
-+ n_props = info->props ? info->props->n_items : 0;
-+
-+ spa_pod_builder_push_struct(b, &f);
-+ spa_pod_builder_add(b,
-+ SPA_POD_Id(info->id),
-+ SPA_POD_Int(info->change_mask),
-+ SPA_POD_Int(info->n_params),
-+ SPA_POD_Int(n_props),
-+ NULL);
-+
-+ for (i = 0; i < info->n_params; i++) {
-+ spa_pod_builder_add(b,
-+ SPA_POD_Id(info->params[i].id),
-+ SPA_POD_Int(info->params[i].flags), NULL);
-+ }
-+
-+ for (i = 0; i < n_props; i++) {
-+ spa_pod_builder_add(b,
-+ SPA_POD_String(info->props->items[i].key),
-+ SPA_POD_String(info->props->items[i].value),
-+ NULL);
-+ }
-+
-+ spa_pod_builder_pop(b, &f);
-+}
-+
-+/* macro because of alloca() */
-+#define deserialize_pw_endpoint_info(p, f, info) \
-+do { \
-+ if (spa_pod_parser_push_struct(p, f) < 0 || \
-+ spa_pod_parser_get(p, \
-+ SPA_POD_Id(&(info)->id), \
-+ SPA_POD_Int(&(info)->change_mask), \
-+ SPA_POD_Int(&(info)->n_params), \
-+ SPA_POD_Int(&(info)->props->n_items), \
-+ NULL) < 0) \
-+ return -EINVAL; \
-+ \
-+ if ((info)->n_params > 0) \
-+ (info)->params = alloca((info)->n_params * sizeof(struct spa_param_info)); \
-+ if ((info)->props->n_items > 0) \
-+ (info)->props->items = alloca((info)->props->n_items * sizeof(struct spa_dict_item)); \
-+ \
-+ for (i = 0; i < (info)->n_params; i++) { \
-+ if (spa_pod_parser_get(p, \
-+ SPA_POD_Id(&(info)->params[i].id), \
-+ SPA_POD_Int(&(info)->params[i].flags), \
-+ NULL) < 0) \
-+ return -EINVAL; \
-+ } \
-+ \
-+ for (i = 0; i < (info)->props->n_items; i++) { \
-+ if (spa_pod_parser_get(p, \
-+ SPA_POD_String(&(info)->props->items[i].key), \
-+ SPA_POD_String(&(info)->props->items[i].value), \
-+ NULL) < 0) \
-+ return -EINVAL; \
-+ } \
-+ \
-+ spa_pod_parser_pop(p, f); \
-+} while(0)
-+
-+static int
-+endpoint_marshal_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids)
-+{
-+ struct pw_proxy *proxy = object;
-+ struct spa_pod_builder *b;
-+
-+ b = pw_protocol_native_begin_proxy(proxy,
-+ PW_ENDPOINT_PROXY_METHOD_SUBSCRIBE_PARAMS, NULL);
-+
-+ spa_pod_builder_add_struct(b,
-+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
-+
-+ return pw_protocol_native_end_proxy(proxy, b);
-+}
-+
-+static int
-+endpoint_demarshal_subscribe_params(void *object, const struct pw_protocol_native_message *msg)
-+{
-+ struct pw_resource *resource = object;
-+ struct spa_pod_parser prs;
-+ uint32_t csize, ctype, n_ids;
-+ uint32_t *ids;
-+
-+ spa_pod_parser_init(&prs, msg->data, msg->size);
-+ if (spa_pod_parser_get_struct(&prs,
-+ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
-+ return -EINVAL;
-+
-+ if (ctype != SPA_TYPE_Id)
-+ return -EINVAL;
-+
-+ return pw_resource_do(resource, struct pw_endpoint_proxy_methods,
-+ subscribe_params, 0, ids, n_ids);
-+}
-+
-+static int
-+endpoint_marshal_enum_params(void *object, int seq, uint32_t id,
-+ uint32_t index, uint32_t num, const struct spa_pod *filter)
-+{
-+ struct pw_proxy *proxy = object;
-+ struct spa_pod_builder *b;
-+
-+ b = pw_protocol_native_begin_proxy(proxy,
-+ PW_ENDPOINT_PROXY_METHOD_ENUM_PARAMS, NULL);
-+
-+ spa_pod_builder_add_struct(b,
-+ SPA_POD_Int(seq),
-+ SPA_POD_Id(id),
-+ SPA_POD_Int(index),
-+ SPA_POD_Int(num),
-+ SPA_POD_Pod(filter));
-+
-+ return pw_protocol_native_end_proxy(proxy, b);
-+}
-+
-+static int
-+endpoint_demarshal_enum_params(void *object, const struct pw_protocol_native_message *msg)
-+{
-+ struct pw_resource *resource = object;
-+ struct spa_pod_parser prs;
-+ uint32_t id, index, num;
-+ int seq;
-+ struct spa_pod *filter;
-+
-+ spa_pod_parser_init(&prs, msg->data, msg->size);
-+ if (spa_pod_parser_get_struct(&prs,
-+ SPA_POD_Int(&seq),
-+ SPA_POD_Id(&id),
-+ SPA_POD_Int(&index),
-+ SPA_POD_Int(&num),
-+ SPA_POD_Pod(&filter)) < 0)
-+ return -EINVAL;
-+
-+ return pw_resource_do(resource, struct pw_endpoint_proxy_methods,
-+ enum_params, 0, seq, id, index, num, filter);
-+}
-+
-+static int
-+endpoint_marshal_set_param(void *object, uint32_t id, uint32_t flags,
-+ const struct spa_pod *param)
-+{
-+ struct pw_proxy *proxy = object;
-+ struct spa_pod_builder *b;
-+
-+ b = pw_protocol_native_begin_proxy(proxy,
-+ PW_ENDPOINT_PROXY_METHOD_SET_PARAM, NULL);
-+
-+ spa_pod_builder_add_struct(b,
-+ SPA_POD_Id(id),
-+ SPA_POD_Int(flags),
-+ SPA_POD_Pod(param));
-+ return pw_protocol_native_end_proxy(proxy, b);
-+}
-+
-+static int
-+endpoint_demarshal_set_param(void *object, const struct pw_protocol_native_message *msg)
-+{
-+ struct pw_resource *resource = object;
-+ struct spa_pod_parser prs;
-+ uint32_t id, flags;
-+ struct spa_pod *param;
-+
-+ spa_pod_parser_init(&prs, msg->data, msg->size);
-+ if (spa_pod_parser_get_struct(&prs,
-+ SPA_POD_Id(&id),
-+ SPA_POD_Int(&flags),
-+ SPA_POD_Pod(&param)) < 0)
-+ return -EINVAL;
-+
-+ return pw_resource_do(resource, struct pw_endpoint_proxy_methods,
-+ set_param, 0, id, flags, param);
-+}
-+
-+static void
-+endpoint_marshal_info(void *object, const struct pw_endpoint_info *info)
-+{
-+ struct pw_resource *resource = object;
-+ struct spa_pod_builder *b;
-+
-+ b = pw_protocol_native_begin_resource(resource,
-+ PW_ENDPOINT_PROXY_EVENT_INFO, NULL);
-+ serialize_pw_endpoint_info (b, info);
-+ pw_protocol_native_end_resource(resource, b);
-+}
-+
-+static int
-+endpoint_demarshal_info(void *object, const struct pw_protocol_native_message *msg)
-+{
-+ struct pw_proxy *proxy = object;
-+ struct spa_pod_parser prs;
-+ struct spa_pod_frame f;
-+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
-+ struct pw_endpoint_info info = { .props = &props };
-+ uint32_t i;
-+
-+ spa_pod_parser_init(&prs, msg->data, msg->size);
-+
-+ deserialize_pw_endpoint_info(&prs, &f, &info);
-+
-+ return pw_proxy_notify(proxy, struct pw_endpoint_proxy_events,
-+ info, 0, &info);
-+}
-+
-+static void
-+endpoint_marshal_param(void *object, int seq, uint32_t id,
-+ uint32_t index, uint32_t next, const struct spa_pod *param)
-+{
-+ struct pw_resource *resource = object;
-+ struct spa_pod_builder *b;
-+
-+ b = pw_protocol_native_begin_resource(resource,
-+ PW_ENDPOINT_PROXY_EVENT_PARAM, NULL);
-+
-+ spa_pod_builder_add_struct(b,
-+ SPA_POD_Int(seq),
-+ SPA_POD_Id(id),
-+ SPA_POD_Int(index),
-+ SPA_POD_Int(next),
-+ SPA_POD_Pod(param));
-+
-+ pw_protocol_native_end_resource(resource, b);
-+}
-+
-+static int
-+endpoint_demarshal_param(void *object, const struct pw_protocol_native_message *msg)
-+{
-+ struct pw_proxy *proxy = object;
-+ struct spa_pod_parser prs;
-+ uint32_t id, index, next;
-+ int seq;
-+ struct spa_pod *param;
-+
-+ spa_pod_parser_init(&prs, msg->data, msg->size);
-+ if (spa_pod_parser_get_struct(&prs,
-+ SPA_POD_Int(&seq),
-+ SPA_POD_Id(&id),
-+ SPA_POD_Int(&index),
-+ SPA_POD_Int(&next),
-+ SPA_POD_Pod(&param)) < 0)
-+ return -EINVAL;
-+
-+ return pw_proxy_notify(proxy, struct pw_endpoint_proxy_events, param, 0,
-+ seq, id, index, next, param);
-+}
-+
-+static const struct pw_endpoint_proxy_methods pw_protocol_native_endpoint_method_marshal = {
-+ PW_VERSION_ENDPOINT_PROXY_METHODS,
-+ &endpoint_marshal_subscribe_params,
-+ &endpoint_marshal_enum_params,
-+ &endpoint_marshal_set_param,
-+};
-+
-+static const struct pw_protocol_native_demarshal pw_protocol_native_endpoint_method_demarshal[] = {
-+ { &endpoint_demarshal_subscribe_params, 0 },
-+ { &endpoint_demarshal_enum_params, 0 },
-+ { &endpoint_demarshal_set_param, 0 }
-+};
-+
-+static const struct pw_endpoint_proxy_events pw_protocol_native_endpoint_event_marshal = {
-+ PW_VERSION_ENDPOINT_PROXY_EVENTS,
-+ &endpoint_marshal_info,
-+ &endpoint_marshal_param,
-+};
-+
-+static const struct pw_protocol_native_demarshal pw_protocol_native_endpoint_event_demarshal[] = {
-+ { &endpoint_demarshal_info, 0 },
-+ { &endpoint_demarshal_param, 0 }
-+};
-+
-+static const struct pw_protocol_marshal pw_protocol_native_endpoint_marshal = {
-+ PW_TYPE_INTERFACE_Endpoint,
-+ PW_VERSION_ENDPOINT,
-+ PW_ENDPOINT_PROXY_METHOD_NUM,
-+ PW_ENDPOINT_PROXY_EVENT_NUM,
-+ &pw_protocol_native_endpoint_method_marshal,
-+ &pw_protocol_native_endpoint_method_demarshal,
-+ &pw_protocol_native_endpoint_event_marshal,
-+ &pw_protocol_native_endpoint_event_demarshal,
-+};
-+
-+
-+static int
-+client_endpoint_marshal_update(
-+ void *object,
-+ uint32_t change_mask,
-+ uint32_t n_params,
-+ const struct spa_pod **params,
-+ const struct pw_endpoint_info *info)
-+{
-+ struct pw_proxy *proxy = object;
-+ struct spa_pod_builder *b;
-+ struct spa_pod_frame f;
-+ uint32_t i;
-+
-+ b = pw_protocol_native_begin_proxy(proxy,
-+ PW_CLIENT_ENDPOINT_PROXY_METHOD_UPDATE, NULL);
-+
-+ spa_pod_builder_push_struct(b, &f);
-+ spa_pod_builder_add(b,
-+ SPA_POD_Int(change_mask),
-+ SPA_POD_Int(n_params), NULL);
-+
-+ for (i = 0; i < n_params; i++)
-+ spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL);
-+
-+ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_INFO)
-+ serialize_pw_endpoint_info(b, info);
-+
-+ spa_pod_builder_pop(b, &f);
-+
-+ return pw_protocol_native_end_proxy(proxy, b);
-+}
-+
-+static int
-+client_endpoint_demarshal_update(void *object,
-+ const struct pw_protocol_native_message *msg)
-+{
-+ struct pw_resource *resource = object;
-+ struct spa_pod_parser prs;
-+ struct spa_pod_frame f[2];
-+ uint32_t change_mask, n_params;
-+ const struct spa_pod **params = NULL;
-+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
-+ struct pw_endpoint_info info = { .props = &props };
-+ uint32_t i;
-+
-+ spa_pod_parser_init(&prs, msg->data, msg->size);
-+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
-+ spa_pod_parser_get(&prs,
-+ SPA_POD_Int(&change_mask),
-+ SPA_POD_Int(&n_params), NULL) < 0)
-+ return -EINVAL;
-+
-+ if (n_params > 0)
-+ params = alloca(n_params * sizeof(struct spa_pod *));
-+ for (i = 0; i < n_params; i++)
-+ if (spa_pod_parser_get(&prs,
-+ SPA_POD_PodObject(&params[i]), NULL) < 0)
-+ return -EINVAL;
-+
-+ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_INFO)
-+ deserialize_pw_endpoint_info(&prs, &f[1], &info);
-+
-+ pw_resource_do(resource, struct pw_client_endpoint_proxy_methods,
-+ update, 0, change_mask, n_params, params, &info);
-+ return 0;
-+}
-+
-+static void
-+client_endpoint_marshal_set_param (void *object,
-+ uint32_t id, uint32_t flags,
-+ const struct spa_pod *param)
-+{
-+ struct pw_resource *resource = object;
-+ struct spa_pod_builder *b;
-+
-+ b = pw_protocol_native_begin_resource(resource,
-+ PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_PARAM, NULL);
-+
-+ spa_pod_builder_add_struct(b,
-+ SPA_POD_Id(id),
-+ SPA_POD_Int(flags),
-+ SPA_POD_Pod(param));
-+
-+ pw_protocol_native_end_resource(resource, b);
-+}
-+
-+static int
-+client_endpoint_demarshal_set_param(void *object,
-+ const struct pw_protocol_native_message *msg)
-+{
-+ struct pw_proxy *proxy = object;
-+ struct spa_pod_parser prs;
-+ uint32_t id, flags;
-+ const struct spa_pod *param = NULL;
-+
-+ spa_pod_parser_init(&prs, msg->data, msg->size);
-+ if (spa_pod_parser_get_struct(&prs,
-+ SPA_POD_Id(&id),
-+ SPA_POD_Int(&flags),
-+ SPA_POD_PodObject(&param)) < 0)
-+ return -EINVAL;
-+
-+ pw_proxy_notify(proxy, struct pw_client_endpoint_proxy_events,
-+ set_param, 0, id, flags, param);
-+ return 0;
-+}
-+
-+static const struct pw_client_endpoint_proxy_methods pw_protocol_native_client_endpoint_method_marshal = {
-+ PW_VERSION_CLIENT_ENDPOINT_PROXY_METHODS,
-+ &client_endpoint_marshal_update,
-+};
-+
-+static const struct pw_protocol_native_demarshal pw_protocol_native_client_endpoint_method_demarshal[] = {
-+ { &client_endpoint_demarshal_update, 0 }
-+};
-+
-+static const struct pw_client_endpoint_proxy_events pw_protocol_native_client_endpoint_event_marshal = {
-+ PW_VERSION_CLIENT_ENDPOINT_PROXY_EVENTS,
-+ &client_endpoint_marshal_set_param,
-+};
-+
-+static const struct pw_protocol_native_demarshal pw_protocol_native_client_endpoint_event_demarshal[] = {
-+ { &client_endpoint_demarshal_set_param, 0 }
-+};
-+
-+static const struct pw_protocol_marshal pw_protocol_native_client_endpoint_marshal = {
-+ PW_TYPE_INTERFACE_ClientEndpoint,
-+ PW_VERSION_CLIENT_ENDPOINT,
-+ PW_CLIENT_ENDPOINT_PROXY_METHOD_NUM,
-+ PW_CLIENT_ENDPOINT_PROXY_EVENT_NUM,
-+ &pw_protocol_native_client_endpoint_method_marshal,
-+ &pw_protocol_native_client_endpoint_method_demarshal,
-+ &pw_protocol_native_client_endpoint_event_marshal,
-+ &pw_protocol_native_client_endpoint_event_demarshal,
-+};
-+
-+struct pw_protocol *pw_protocol_native_ext_endpoint_init(struct pw_core *core)
-+{
-+ struct pw_protocol *protocol;
-+
-+ protocol = pw_core_find_protocol(core, PW_TYPE_INFO_PROTOCOL_Native);
-+
-+ if (protocol == NULL)
-+ return NULL;
-+
-+ pw_protocol_add_marshal(protocol, &pw_protocol_native_client_endpoint_marshal);
-+ pw_protocol_add_marshal(protocol, &pw_protocol_native_endpoint_marshal);
-+
-+ return protocol;
-+}
-diff --git a/src/pipewire/pipewire.c b/src/pipewire/pipewire.c
-index a8752438..bbbf9420 100644
---- a/src/pipewire/pipewire.c
-+++ b/src/pipewire/pipewire.c
-@@ -647,6 +647,8 @@ static const struct spa_type_info type_info[] = {
- { PW_TYPE_INTERFACE_Module, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "Module", NULL },
- { PW_TYPE_INTERFACE_ClientNode, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "ClientNode", NULL },
- { PW_TYPE_INTERFACE_Device, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "Device", NULL },
-+ { PW_TYPE_INTERFACE_Endpoint, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "Endpoint", NULL},
-+ { PW_TYPE_INTERFACE_ClientEndpoint, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "ClientEndpoint", NULL},
- { SPA_ID_INVALID, SPA_ID_INVALID, "spa_types", spa_types },
- { 0, 0, NULL, NULL },
- };
-diff --git a/src/pipewire/type.h b/src/pipewire/type.h
-index a1b205f7..39544913 100644
---- a/src/pipewire/type.h
-+++ b/src/pipewire/type.h
-@@ -48,7 +48,8 @@ enum {
- /* extensions */
- PW_TYPE_INTERFACE_EXTENSIONS = PW_TYPE_INTERFACE_START + 0x1000,
- PW_TYPE_INTERFACE_ClientNode,
--
-+ PW_TYPE_INTERFACE_Endpoint,
-+ PW_TYPE_INTERFACE_ClientEndpoint,
- };
-
- #define PW_TYPE_INFO_BASE "PipeWire:"
---
-2.20.1
-
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-logger-print-timestamps-on-logged-messages.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-logger-print-timestamps-on-logged-messages.patch
index 4d9117f0..6012aa56 100644
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-logger-print-timestamps-on-logged-messages.patch
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-logger-print-timestamps-on-logged-messages.patch
@@ -1,4 +1,4 @@
-From 352c58357e5922b21d664c1f5a0b89a74f864f41 Mon Sep 17 00:00:00 2001
+From 289f58b815badd54a32f2409bae7abd7e5474327 Mon Sep 17 00:00:00 2001
From: George Kiagiadakis <george.kiagiadakis@collabora.com>
Date: Wed, 3 Jul 2019 17:47:46 +0300
Subject: [PATCH] logger: print timestamps on logged messages
@@ -14,7 +14,7 @@ Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/164]
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/spa/plugins/support/logger.c b/spa/plugins/support/logger.c
-index 87ba3c21..2976601c 100644
+index 9ed2896b..4100102c 100644
--- a/spa/plugins/support/logger.c
+++ b/spa/plugins/support/logger.c
@@ -27,6 +27,7 @@
@@ -22,10 +22,10 @@ index 87ba3c21..2976601c 100644
#include <errno.h>
#include <stdio.h>
+#include <time.h>
- #include <sys/eventfd.h>
#include <spa/support/log.h>
-@@ -70,6 +71,9 @@ impl_log_logv(struct spa_log *log,
+ #include <spa/support/loop.h>
+@@ -72,6 +73,9 @@ impl_log_logv(void *object,
const char *prefix = "", *suffix = "";
int size;
bool do_trace;
@@ -35,7 +35,7 @@ index 87ba3c21..2976601c 100644
if ((do_trace = (level == SPA_LOG_LEVEL_TRACE && impl->have_source)))
level++;
-@@ -86,8 +90,9 @@ impl_log_logv(struct spa_log *log,
+@@ -88,8 +92,9 @@ impl_log_logv(void *object,
}
vsnprintf(text, sizeof(text), fmt, args);
@@ -48,5 +48,5 @@ index 87ba3c21..2976601c 100644
if (SPA_UNLIKELY(do_trace)) {
uint32_t index;
--
-2.20.1
+2.23.0
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0007-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch
index 86495014..17280da0 100644
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0007-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch
@@ -1,4 +1,4 @@
-From 05a3a926df4906cdf60f7978843c637bbf37714a Mon Sep 17 00:00:00 2001
+From 0c232229f3dc6b0cdf02ef44ae212b256c1828a3 Mon Sep 17 00:00:00 2001
From: George Kiagiadakis <george.kiagiadakis@collabora.com>
Date: Tue, 9 Jul 2019 18:06:18 +0300
Subject: [PATCH] alsa: make corrections on the timeout based on how fast ALSA
@@ -28,17 +28,17 @@ Fixes #163
Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/166]
---
- spa/plugins/alsa/alsa-utils.c | 31 ++++++++++++++++++++++++++-----
+ spa/plugins/alsa/alsa-utils.c | 29 +++++++++++++++++++++++++----
spa/plugins/alsa/alsa-utils.h | 2 ++
- 2 files changed, 28 insertions(+), 5 deletions(-)
+ 2 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/spa/plugins/alsa/alsa-utils.c b/spa/plugins/alsa/alsa-utils.c
-index 87582c1c..872969bf 100644
+index 7cf1d0d2..e8548345 100644
--- a/spa/plugins/alsa/alsa-utils.c
+++ b/spa/plugins/alsa/alsa-utils.c
-@@ -593,7 +593,21 @@ static int get_status(struct state *state, snd_pcm_sframes_t *delay)
-
- static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t delay, bool slave)
+@@ -624,7 +624,21 @@ static int get_status(struct state *state, snd_pcm_uframes_t *delay, snd_pcm_ufr
+ static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t delay,
+ snd_pcm_sframes_t target, bool slave)
{
- double err, corr;
+ double err, corr, drift;
@@ -58,9 +58,9 @@ index 87582c1c..872969bf 100644
+ }
if (state->stream == SND_PCM_STREAM_PLAYBACK)
- err = delay - state->last_threshold;
-@@ -650,11 +664,11 @@ static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
- state->clock->rate_diff = corr;
+ err = delay - target;
+@@ -677,11 +691,11 @@ static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
+ state->clock->next_nsec = state->next_time;
}
- spa_log_trace_fp(state->log, "slave:%d %"PRIu64" %f %ld %f %f %d", slave, nsec,
@@ -74,7 +74,7 @@ index 87582c1c..872969bf 100644
state->last_threshold = state->threshold;
return 0;
-@@ -783,6 +797,10 @@ again:
+@@ -812,6 +826,10 @@ again:
goto again;
state->sample_count += total_written;
@@ -85,17 +85,8 @@ index 87582c1c..872969bf 100644
if (!state->alsa_started && total_written > 0) {
spa_log_trace(state->log, "snd_pcm_start %lu", written);
-@@ -935,7 +953,7 @@ static int handle_play(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
- {
- int res;
-
-- if (delay >= state->last_threshold * 2) {
-+ if (delay > state->last_threshold * 2) {
- spa_log_trace(state->log, "early wakeup %ld %d", delay, state->threshold);
- state->next_time = nsec + (delay - state->last_threshold) * SPA_NSEC_PER_SEC / state->rate;
- return -EAGAIN;
-@@ -944,6 +962,8 @@ static int handle_play(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
- if ((res = update_time(state, nsec, delay, false)) < 0)
+@@ -981,6 +999,8 @@ static int handle_play(struct state *state, uint64_t nsec,
+ if ((res = update_time(state, nsec, delay, target, false)) < 0)
return res;
+ state->fill_level = delay;
@@ -103,19 +94,19 @@ index 87582c1c..872969bf 100644
if (spa_list_is_empty(&state->ready)) {
struct spa_io_buffers *io = state->io;
-@@ -1072,6 +1092,7 @@ int spa_alsa_start(struct state *state)
+@@ -1120,6 +1140,7 @@ int spa_alsa_start(struct state *state)
- state->slaved = is_slaved(state);
+ state->threshold = (state->duration * state->rate + state->rate_denom-1) / state->rate_denom;
state->last_threshold = state->threshold;
+ state->fill_level = 0;
init_loop(state);
state->safety = 0.0;
diff --git a/spa/plugins/alsa/alsa-utils.h b/spa/plugins/alsa/alsa-utils.h
-index a862873f..b53890b5 100644
+index 110d4204..bab0f67b 100644
--- a/spa/plugins/alsa/alsa-utils.h
+++ b/spa/plugins/alsa/alsa-utils.h
-@@ -134,7 +134,9 @@ struct state {
+@@ -141,7 +141,9 @@ struct state {
int64_t sample_time;
uint64_t next_time;
uint64_t base_time;
@@ -126,5 +117,5 @@ index a862873f..b53890b5 100644
double safety;
--
-2.20.1
+2.23.0
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0010-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch
index 6b1a6441..84b35e64 100644
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0010-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch
@@ -1,4 +1,4 @@
-From bbc875ec4268a88bf2465244e089b119011e7479 Mon Sep 17 00:00:00 2001
+From 2570b2f404ce094098e2244833ab7dddf62d02a0 Mon Sep 17 00:00:00 2001
From: George Kiagiadakis <george.kiagiadakis@collabora.com>
Date: Tue, 19 Feb 2019 18:23:19 +0200
Subject: [PATCH] gst: Implement new pwaudio{src,sink} elements, based on
@@ -1245,5 +1245,5 @@ index ad0e0801..0e922347 100644
pipewire_gst_c_args = [
--
-2.20.1
+2.23.0
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-pipewire-cli-add-command-to-modify-endpoint-control-.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-pipewire-cli-add-command-to-modify-endpoint-control-.patch
deleted file mode 100644
index 4394d60d..00000000
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-pipewire-cli-add-command-to-modify-endpoint-control-.patch
+++ /dev/null
@@ -1,124 +0,0 @@
-From 824c8abf88e9ee82567c177145798b619298ab91 Mon Sep 17 00:00:00 2001
-From: George Kiagiadakis <george.kiagiadakis@collabora.com>
-Date: Wed, 5 Jun 2019 14:57:37 +0300
-Subject: [PATCH] pipewire-cli: add command to modify endpoint control values
-
-Upstream-Status: Pending
----
- src/tools/pipewire-cli.c | 86 ++++++++++++++++++++++++++++++++++++++++
- 1 file changed, 86 insertions(+)
-
-diff --git a/src/tools/pipewire-cli.c b/src/tools/pipewire-cli.c
-index 9511db82..b52ab100 100644
---- a/src/tools/pipewire-cli.c
-+++ b/src/tools/pipewire-cli.c
-@@ -210,6 +210,7 @@ static bool do_export_node(struct data *data, const char *cmd, char *args, char
- static bool do_enum_params(struct data *data, const char *cmd, char *args, char **error);
- static bool do_permissions(struct data *data, const char *cmd, char *args, char **error);
- static bool do_get_permissions(struct data *data, const char *cmd, char *args, char **error);
-+static bool do_endpoint_control(struct data *data, const char *cmd, char *args, char **error);
-
- static struct command command_list[] = {
- { "help", "Show this help", do_help },
-@@ -228,6 +229,7 @@ static struct command command_list[] = {
- { "enum-params", "Enumerate params of an object <object-id> [<param-id-name>]", do_enum_params },
- { "permissions", "Set permissions for a client <client-id> <permissions>", do_permissions },
- { "get-permissions", "Get permissions of a client <client-id>", do_get_permissions },
-+ { "endpoint-control", "Set control value on an endpoint <object-id> <control-id> <type: b|i|l|d> <value>", do_endpoint_control },
- };
-
- static bool do_help(struct data *data, const char *cmd, char *args, char **error)
-@@ -1357,6 +1359,90 @@ static bool do_get_permissions(struct data *data, const char *cmd, char *args, c
- return true;
- }
-
-+static bool do_endpoint_control(struct data *data, const char *cmd, char *args, char **error)
-+{
-+ struct remote_data *rd = data->current;
-+ int n;
-+ char *a[4];
-+ uint32_t id, control_id;
-+ struct global *global;
-+ char buffer[1024];
-+ struct spa_pod_builder b;
-+ struct spa_pod_frame f;
-+ struct spa_pod *param;
-+
-+ n = pw_split_ip(args, WHITESPACE, 4, a);
-+ if (n < 4) {
-+ asprintf(error, "%s <object-id> <control-id> <type: b|i|l|d> <value>", cmd);
-+ return false;
-+ }
-+
-+ id = atoi(a[0]);
-+ global = pw_map_lookup(&rd->globals, id);
-+ if (global == NULL) {
-+ asprintf(error, "%s: unknown global %d", cmd, id);
-+ return false;
-+ }
-+ if (global->type != PW_TYPE_INTERFACE_Endpoint) {
-+ asprintf(error, "object %d is not an endpoint", atoi(a[0]));
-+ return false;
-+ }
-+ if (global->proxy == NULL) {
-+ if (!bind_global(rd, global, error))
-+ return false;
-+ }
-+
-+ control_id = atoi(a[1]);
-+
-+ spa_pod_builder_init(&b, buffer, 1024);
-+ spa_pod_builder_push_object (&b, &f,
-+ PW_ENDPOINT_OBJECT_ParamControl, PW_ENDPOINT_PARAM_Control);
-+ spa_pod_builder_add(&b,
-+ PW_ENDPOINT_PARAM_CONTROL_id, SPA_POD_Int(control_id),
-+ NULL);
-+
-+ switch (*a[2]) {
-+ case 'b': {
-+ bool val = atoi(a[3]);
-+ spa_pod_builder_add(&b,
-+ PW_ENDPOINT_PARAM_CONTROL_value, SPA_POD_Bool(val),
-+ NULL);
-+ break;
-+ }
-+ case 'i': {
-+ int val = atoi(a[3]);
-+ spa_pod_builder_add(&b,
-+ PW_ENDPOINT_PARAM_CONTROL_value, SPA_POD_Int(val),
-+ NULL);
-+ break;
-+ }
-+ case 'l': {
-+ int64_t val = strtoll(a[3], NULL, 10);
-+ spa_pod_builder_add(&b,
-+ PW_ENDPOINT_PARAM_CONTROL_value, SPA_POD_Long(val),
-+ NULL);
-+ break;
-+ }
-+ case 'd': {
-+ double val = strtod(a[3], NULL);
-+ spa_pod_builder_add(&b,
-+ PW_ENDPOINT_PARAM_CONTROL_value, SPA_POD_Double(val),
-+ NULL);
-+ break;
-+ }
-+ default:
-+ asprintf(error, "%s: unknown value type %s", cmd, a[2]);
-+ return false;
-+ }
-+
-+ param = spa_pod_builder_pop(&b, &f);
-+
-+ pw_endpoint_proxy_set_param((struct pw_endpoint_proxy *) global->proxy,
-+ PW_ENDPOINT_PARAM_Control, 0, param);
-+
-+ return true;
-+}
-+
- static bool parse(struct data *data, char *buf, size_t size, char **error)
- {
- char *a[2];
---
-2.20.1
-
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0012-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch
index 3680cc35..7d0bc5a2 100644
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0012-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch
@@ -1,4 +1,4 @@
-From 1eb1e3a839f97ad4aa43c289f702c587a068a333 Mon Sep 17 00:00:00 2001
+From 9a01115acc2316908afaed004e2f042125b7f5e3 Mon Sep 17 00:00:00 2001
From: George Kiagiadakis <george.kiagiadakis@collabora.com>
Date: Thu, 11 Jul 2019 16:21:17 +0300
Subject: [PATCH] gst/pwaudioringbuffer: request pause/play on the appropriate
@@ -20,7 +20,7 @@ Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/140]
1 file changed, 23 insertions(+), 4 deletions(-)
diff --git a/src/gst/gstpwaudioringbuffer.c b/src/gst/gstpwaudioringbuffer.c
-index 181304e8..04259927 100644
+index 989b2cd7..97350f38 100644
--- a/src/gst/gstpwaudioringbuffer.c
+++ b/src/gst/gstpwaudioringbuffer.c
@@ -202,11 +202,16 @@ on_stream_state_changed (void *data, enum pw_stream_state old,
@@ -72,5 +72,5 @@ index 181304e8..04259927 100644
}
pw_thread_loop_signal (self->main_loop, FALSE);
--
-2.20.1
+2.23.0
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0013-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch
index 539e3a5e..124a3804 100644
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0013-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch
@@ -1,4 +1,4 @@
-From 1b2bf0f435f2912c32fbd7a6118ed9bfb41f031c Mon Sep 17 00:00:00 2001
+From f4903fe9c356b58737eb33fcfa389e006d18f801 Mon Sep 17 00:00:00 2001
From: George Kiagiadakis <george.kiagiadakis@collabora.com>
Date: Thu, 11 Jul 2019 16:34:35 +0300
Subject: [PATCH] gst/pwaudioringbuffer: wait only for STREAM_STATE_CONFIGURE
@@ -15,10 +15,10 @@ Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/140]
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/gst/gstpwaudioringbuffer.c b/src/gst/gstpwaudioringbuffer.c
-index 04259927..b92b5feb 100644
+index 97350f38..3efec6ec 100644
--- a/src/gst/gstpwaudioringbuffer.c
+++ b/src/gst/gstpwaudioringbuffer.c
-@@ -444,9 +444,9 @@ gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
+@@ -442,9 +442,9 @@ gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
params, 1) < 0)
goto start_error;
@@ -31,5 +31,5 @@ index 04259927..b92b5feb 100644
pw_thread_loop_unlock (self->main_loop);
--
-2.20.1
+2.23.0
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0014-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0007-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch
index 6f15b7f7..9ae8eab3 100644
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0014-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0007-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch
@@ -1,4 +1,4 @@
-From 460ce06c9cc6fd7b0106e0ce8a265bbeff4ae406 Mon Sep 17 00:00:00 2001
+From 4d8e0de16717f250d22b24f335df8f27c67f2c52 Mon Sep 17 00:00:00 2001
From: George Kiagiadakis <george.kiagiadakis@collabora.com>
Date: Thu, 11 Jul 2019 17:07:15 +0300
Subject: [PATCH] gst/pwaudiosink: set the default latency time (buffer size)
@@ -33,5 +33,5 @@ index 6cb71385..069996c3 100644
static void
--
-2.20.1
+2.23.0
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0008-audio-dsp-allow-mode-to-be-set-with-a-property.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0008-audio-dsp-allow-mode-to-be-set-with-a-property.patch
deleted file mode 100644
index 681bae69..00000000
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0008-audio-dsp-allow-mode-to-be-set-with-a-property.patch
+++ /dev/null
@@ -1,45 +0,0 @@
-From 0c85e1110e32720785f861c7a85cf0636394eee4 Mon Sep 17 00:00:00 2001
-From: Julian Bouzas <julian.bouzas@collabora.com>
-Date: Thu, 27 Jun 2019 12:44:39 -0400
-Subject: [PATCH] audio-dsp: allow mode to be set with a property
-
-Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/167]
----
- src/modules/module-audio-dsp/audio-dsp.c | 7 +++++--
- 1 file changed, 5 insertions(+), 2 deletions(-)
-
-diff --git a/src/modules/module-audio-dsp/audio-dsp.c b/src/modules/module-audio-dsp/audio-dsp.c
-index be8a7592..68249307 100644
---- a/src/modules/module-audio-dsp/audio-dsp.c
-+++ b/src/modules/module-audio-dsp/audio-dsp.c
-@@ -264,7 +264,7 @@ struct pw_node *pw_audio_dsp_new(struct pw_core *core,
- {
- struct pw_node *node;
- struct node *n;
-- const char *api, *alias, *str, *factory;
-+ const char *api, *alias, *str, *factory, *mode;
- char node_name[128];
- struct pw_properties *pr;
- int i;
-@@ -279,6 +279,7 @@ struct pw_node *pw_audio_dsp_new(struct pw_core *core,
- pw_log_error("missing audio-dsp.name property");
- goto error;
- }
-+ mode = pw_properties_get(pr, "audio-dsp.mode");
-
- snprintf(node_name, sizeof(node_name), "system_%s", alias);
- for (i = 0; node_name[i]; i++) {
-@@ -296,7 +297,9 @@ struct pw_node *pw_audio_dsp_new(struct pw_core *core,
- if ((str = pw_properties_get(pr, "node.id")) != NULL)
- pw_properties_set(pr, "node.session", str);
-
-- if (direction == PW_DIRECTION_OUTPUT) {
-+ if (mode) {
-+ factory = mode;
-+ } else if (direction == PW_DIRECTION_OUTPUT) {
- pw_properties_set(pr, "merger.monitor", "1");
- factory = "merge";
- } else {
---
-2.20.1
-
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0021-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0008-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch
index 9b51a297..a6d7724f 100644
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0021-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0008-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch
@@ -1,4 +1,4 @@
-From 851738e3c5970a699d2313dec1cfeedb9d051d83 Mon Sep 17 00:00:00 2001
+From a9fb1fa9ce662ee3f06afda5fd9eb2182520ea4d Mon Sep 17 00:00:00 2001
From: George Kiagiadakis <george.kiagiadakis@collabora.com>
Date: Tue, 20 Aug 2019 18:33:35 +0300
Subject: [PATCH] gst: pwaudioringbuffer: set node.latency to get scheduled
@@ -10,10 +10,10 @@ Upstream-Status: Pending
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/gst/gstpwaudioringbuffer.c b/src/gst/gstpwaudioringbuffer.c
-index b92b5feb..2314dd77 100644
+index 3efec6ec..8136b815 100644
--- a/src/gst/gstpwaudioringbuffer.c
+++ b/src/gst/gstpwaudioringbuffer.c
-@@ -403,11 +403,9 @@ gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
+@@ -402,11 +402,9 @@ gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
/* construct param & props objects */
@@ -26,16 +26,16 @@ index b92b5feb..2314dd77 100644
}
spa_pod_builder_init (&b, buffer, sizeof (buffer));
-@@ -425,6 +423,9 @@ gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
- self->channels = GST_AUDIO_INFO_CHANNELS (&spec->info);
+@@ -423,6 +421,9 @@ gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
+ self->rate = GST_AUDIO_INFO_RATE (&spec->info);
self->segoffset = 0;
-+ pw_properties_setf(props, "node.latency", "%u/%u",
++ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u",
+ self->segsize / self->bpf, self->rate);
+
/* connect stream */
pw_thread_loop_lock (self->main_loop);
--
-2.23.0.rc1
+2.23.0
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0009-alsa-do-not-expose-non-interleaved-formats-since-the.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0009-alsa-do-not-expose-non-interleaved-formats-since-the.patch
new file mode 100644
index 00000000..8d8d4d11
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0009-alsa-do-not-expose-non-interleaved-formats-since-the.patch
@@ -0,0 +1,37 @@
+From b80645ad1b348a99b2cbdc170e122dc06d367ea9 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Wed, 18 Sep 2019 12:31:36 +0300
+Subject: [PATCH] alsa: do not expose non-interleaved formats, since they won't
+ work
+
+This bug appeared when testing bluez-alsa nodes with pipewire.
+bluez-alsa exposes also non-interleaved formats and if such a format
+is picked, then nothing works because the converters are not working
+
+Upstream-Status: Inappropriate [workaround]
+---
+ spa/plugins/alsa/alsa-utils.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/spa/plugins/alsa/alsa-utils.c b/spa/plugins/alsa/alsa-utils.c
+index e8548345..76c81c7c 100644
+--- a/spa/plugins/alsa/alsa-utils.c
++++ b/spa/plugins/alsa/alsa-utils.c
+@@ -289,12 +289,14 @@ spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num,
+ spa_pod_builder_id(&b, fi->spa_format);
+ spa_pod_builder_id(&b, fi->spa_format);
+ }
++ /*
+ if (snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED) &&
+ fi->spa_pformat != SPA_AUDIO_FORMAT_UNKNOWN) {
+ if (j++ == 0)
+ spa_pod_builder_id(&b, fi->spa_pformat);
+ spa_pod_builder_id(&b, fi->spa_pformat);
+ }
++ */
+ }
+ }
+ if (j > 1)
+--
+2.23.0
+
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0009-audioconvert-do-setup-internal-links-and-buffers-als.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0009-audioconvert-do-setup-internal-links-and-buffers-als.patch
deleted file mode 100644
index 87141e91..00000000
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0009-audioconvert-do-setup-internal-links-and-buffers-als.patch
+++ /dev/null
@@ -1,55 +0,0 @@
-From ddcda9fa6ec49168c5ddd9fbeda748c5fad18fce Mon Sep 17 00:00:00 2001
-From: George Kiagiadakis <george.kiagiadakis@collabora.com>
-Date: Wed, 10 Jul 2019 14:53:15 +0300
-Subject: [PATCH] audioconvert: do setup internal links and buffers also in
- convert mode
-
-Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/167]
----
- spa/plugins/audioconvert/audioconvert.c | 10 ++++++----
- 1 file changed, 6 insertions(+), 4 deletions(-)
-
-diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c
-index fa8dec97..0af0732d 100644
---- a/spa/plugins/audioconvert/audioconvert.c
-+++ b/spa/plugins/audioconvert/audioconvert.c
-@@ -79,6 +79,8 @@ struct impl {
- #define MODE_MERGE 1
- #define MODE_CONVERT 2
- int mode;
-+ bool fmt_is_set[2];
-+ bool buffers_set[2];
- bool started;
-
- struct spa_handle *hnd_fmt[2];
-@@ -791,11 +793,11 @@ impl_node_port_set_param(struct spa_node *node,
- if (id == SPA_PARAM_Format) {
- if (param == NULL)
- clean_convert(this);
-- else if ((direction == SPA_DIRECTION_OUTPUT && this->mode == MODE_MERGE) ||
-- (direction == SPA_DIRECTION_INPUT && this->mode == MODE_SPLIT)) {
-+ else if (this->fmt_is_set[SPA_DIRECTION_REVERSE(direction)]) {
- if ((res = setup_convert(this)) < 0)
- return res;
- }
-+ this->fmt_is_set[direction] = (param != NULL);
- }
- return res;
- }
-@@ -824,11 +826,11 @@ impl_node_port_use_buffers(struct spa_node *node,
- direction, port_id, buffers, n_buffers)) < 0)
- return res;
-
-- if ((direction == SPA_DIRECTION_OUTPUT && this->mode == MODE_MERGE) ||
-- (direction == SPA_DIRECTION_INPUT && this->mode == MODE_SPLIT)) {
-+ if (buffers && this->buffers_set[SPA_DIRECTION_REVERSE(direction)]) {
- if ((res = setup_buffers(this, SPA_DIRECTION_INPUT)) < 0)
- return res;
- }
-+ this->buffers_set[direction] = (buffers != NULL);
- return res;
- }
-
---
-2.20.1
-
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0010-bluez-monitor-fix-usage-of-pw_properties_setf-withou.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0010-bluez-monitor-fix-usage-of-pw_properties_setf-withou.patch
new file mode 100644
index 00000000..284d01d0
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0010-bluez-monitor-fix-usage-of-pw_properties_setf-withou.patch
@@ -0,0 +1,27 @@
+From 6f3335cfc06053e3ea5598058e0a2581e7ffa4af Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Sun, 22 Sep 2019 17:37:49 +0300
+Subject: [PATCH] bluez-monitor: fix usage of pw_properties_setf without a
+ format string
+
+Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/189]
+---
+ src/examples/bluez-monitor.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/examples/bluez-monitor.c b/src/examples/bluez-monitor.c
+index 1ae5ffe0..4bdfeb0d 100644
+--- a/src/examples/bluez-monitor.c
++++ b/src/examples/bluez-monitor.c
+@@ -123,7 +123,7 @@ static struct bluez5_node *bluez5_create_node(struct bluez5_object *obj, uint32_
+
+ pw_properties_setf(node->props, PW_KEY_NODE_NAME, "%s.%s", info->factory_name, str);
+ pw_properties_set(node->props, PW_KEY_NODE_DESCRIPTION, str);
+- pw_properties_setf(node->props, "factory.name", info->factory_name);
++ pw_properties_set(node->props, "factory.name", info->factory_name);
+
+ node->monitor = monitor;
+ node->object = obj;
+--
+2.23.0
+
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0011-gst-pwaudioringbuffer-make-the-buffer-size-sensitive.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0011-gst-pwaudioringbuffer-make-the-buffer-size-sensitive.patch
deleted file mode 100644
index 5ffabb6d..00000000
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0011-gst-pwaudioringbuffer-make-the-buffer-size-sensitive.patch
+++ /dev/null
@@ -1,60 +0,0 @@
-From 6e289d0058d71bc433d1918a8bbf3305f3e4f517 Mon Sep 17 00:00:00 2001
-From: Julian Bouzas <julian.bouzas@collabora.com>
-Date: Tue, 7 May 2019 10:36:35 -0400
-Subject: [PATCH] gst/pwaudioringbuffer: make the buffer size sensitive to the
- number of channels
-
-Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/140]
----
- src/gst/gstpwaudioringbuffer.c | 6 ++++--
- src/gst/gstpwaudioringbuffer.h | 1 +
- 2 files changed, 5 insertions(+), 2 deletions(-)
-
-diff --git a/src/gst/gstpwaudioringbuffer.c b/src/gst/gstpwaudioringbuffer.c
-index 989b2cd7..181304e8 100644
---- a/src/gst/gstpwaudioringbuffer.c
-+++ b/src/gst/gstpwaudioringbuffer.c
-@@ -246,17 +246,18 @@ on_stream_format_changed (void *data, const struct spa_pod *format)
- const struct spa_pod *params[1];
- struct spa_pod_builder b = { NULL };
- uint8_t buffer[512];
-+ const gint b_size = self->segsize * self->channels;
-
- spa_pod_builder_init (&b, buffer, sizeof (buffer));
- params[0] = spa_pod_builder_add_object (&b,
- SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
- SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 1, INT32_MAX),
- SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
-- SPA_PARAM_BUFFERS_size, SPA_POD_Int(self->segsize),
-+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(b_size),
- SPA_PARAM_BUFFERS_stride, SPA_POD_Int(self->bpf),
- SPA_PARAM_BUFFERS_align, SPA_POD_Int(16));
-
-- GST_DEBUG_OBJECT (self->elem, "doing finish format, buffer size:%d", self->segsize);
-+ GST_DEBUG_OBJECT (self->elem, "doing finish format, buffer size:%d", b_size);
- pw_stream_finish_format (self->stream, 0, params, 1);
- }
-
-@@ -402,6 +403,7 @@ gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
- self->segsize = spec->segsize;
- self->bpf = GST_AUDIO_INFO_BPF (&spec->info);
- self->rate = GST_AUDIO_INFO_RATE (&spec->info);
-+ self->channels = GST_AUDIO_INFO_CHANNELS (&spec->info);
- self->segoffset = 0;
-
- /* connect stream */
-diff --git a/src/gst/gstpwaudioringbuffer.h b/src/gst/gstpwaudioringbuffer.h
-index f47f668a..f600f012 100644
---- a/src/gst/gstpwaudioringbuffer.h
-+++ b/src/gst/gstpwaudioringbuffer.h
-@@ -64,6 +64,7 @@ struct _GstPwAudioRingBuffer
- gint segsize;
- gint bpf;
- gint rate;
-+ gint channels;
-
- /* on_stream_process() state */
- gint segoffset;
---
-2.20.1
-
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0011-meson-revert-version-check-to-require-meson-0.47-not.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0011-meson-revert-version-check-to-require-meson-0.47-not.patch
new file mode 100644
index 00000000..8471276b
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0011-meson-revert-version-check-to-require-meson-0.47-not.patch
@@ -0,0 +1,30 @@
+From 544adb27b33843c0bb970ddb07485f57c2dad7a9 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Sun, 22 Sep 2019 17:59:19 +0300
+Subject: [PATCH] meson: revert version check to require meson 0.47, not 0.50
+
+meson 0.50 is not really needed, but there are some strange warnings
+if you require an older version; in any case, AGL does not have 0.50
+yet, so let's not fail compilation because of that...
+
+Upstream-Status: Inappropriate [workaround]
+---
+ meson.build | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/meson.build b/meson.build
+index 5cd61fde..51a839fa 100644
+--- a/meson.build
++++ b/meson.build
+@@ -1,7 +1,7 @@
+ project('pipewire', ['c' ],
+ version : '0.2.9',
+ license : 'MIT',
+- meson_version : '>= 0.50.0',
++ meson_version : '>= 0.47.0',
+ default_options : [ 'warning_level=1',
+ 'c_std=gnu99',
+ 'buildtype=debugoptimized' ])
+--
+2.23.0
+
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0012-extensions-implement-new-session-manager-extension.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0012-extensions-implement-new-session-manager-extension.patch
new file mode 100644
index 00000000..cdb030e1
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0012-extensions-implement-new-session-manager-extension.patch
@@ -0,0 +1,5715 @@
+From acbce75de9587917cfa659ebc0e3404b6b1d4c29 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Thu, 23 May 2019 18:59:05 +0300
+Subject: [PATCH] extensions: implement new session manager extension
+
+This extension, implemented in module-session-manager, implements
+a set of objects that are useful for session managers.
+
+Upstream-Status: Pending
+---
+ src/extensions/meson.build | 9 +
+ src/extensions/session-manager.h | 34 +
+ .../session-manager/impl-interfaces.h | 329 +++
+ src/extensions/session-manager/interfaces.h | 465 ++++
+ src/extensions/session-manager/introspect.h | 131 +
+ src/extensions/session-manager/keys.h | 40 +
+ src/modules/meson.build | 17 +
+ src/modules/module-session-manager.c | 56 +
+ .../module-session-manager/client-endpoint.c | 270 +++
+ .../module-session-manager/client-endpoint.h | 60 +
+ .../module-session-manager/client-session.c | 270 +++
+ .../module-session-manager/client-session.h | 62 +
+ .../module-session-manager/endpoint-link.c | 359 +++
+ .../module-session-manager/endpoint-link.h | 64 +
+ .../module-session-manager/endpoint-stream.c | 329 +++
+ .../module-session-manager/endpoint-stream.h | 64 +
+ src/modules/module-session-manager/endpoint.c | 343 +++
+ src/modules/module-session-manager/endpoint.h | 61 +
+ .../module-session-manager/protocol-native.c | 2125 +++++++++++++++++
+ src/modules/module-session-manager/session.c | 341 +++
+ src/modules/module-session-manager/session.h | 61 +
+ src/pipewire/pipewire.c | 6 +
+ src/pipewire/type.h | 7 +-
+ 23 files changed, 5502 insertions(+), 1 deletion(-)
+ create mode 100644 src/extensions/session-manager.h
+ create mode 100644 src/extensions/session-manager/impl-interfaces.h
+ create mode 100644 src/extensions/session-manager/interfaces.h
+ create mode 100644 src/extensions/session-manager/introspect.h
+ create mode 100644 src/extensions/session-manager/keys.h
+ create mode 100644 src/modules/module-session-manager.c
+ create mode 100644 src/modules/module-session-manager/client-endpoint.c
+ create mode 100644 src/modules/module-session-manager/client-endpoint.h
+ create mode 100644 src/modules/module-session-manager/client-session.c
+ create mode 100644 src/modules/module-session-manager/client-session.h
+ create mode 100644 src/modules/module-session-manager/endpoint-link.c
+ create mode 100644 src/modules/module-session-manager/endpoint-link.h
+ create mode 100644 src/modules/module-session-manager/endpoint-stream.c
+ create mode 100644 src/modules/module-session-manager/endpoint-stream.h
+ create mode 100644 src/modules/module-session-manager/endpoint.c
+ create mode 100644 src/modules/module-session-manager/endpoint.h
+ create mode 100644 src/modules/module-session-manager/protocol-native.c
+ create mode 100644 src/modules/module-session-manager/session.c
+ create mode 100644 src/modules/module-session-manager/session.h
+
+diff --git a/src/extensions/meson.build b/src/extensions/meson.build
+index a7f5d3cb..95377faa 100644
+--- a/src/extensions/meson.build
++++ b/src/extensions/meson.build
+@@ -1,6 +1,15 @@
++pipewire_ext_sm_headers = [
++ 'session-manager/impl-interfaces.h',
++ 'session-manager/interfaces.h',
++ 'session-manager/introspect.h',
++ 'session-manager/keys.h',
++]
++
+ pipewire_ext_headers = [
+ 'client-node.h',
+ 'protocol-native.h',
++ 'session-manager.h',
+ ]
+
++install_headers(pipewire_ext_sm_headers, subdir : 'pipewire/extensions/session-manager')
+ install_headers(pipewire_ext_headers, subdir : 'pipewire/extensions')
+diff --git a/src/extensions/session-manager.h b/src/extensions/session-manager.h
+new file mode 100644
+index 00000000..95e759b0
+--- /dev/null
++++ b/src/extensions/session-manager.h
+@@ -0,0 +1,34 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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_EXT_SESSION_MANAGER_H
++#define PIPEWIRE_EXT_SESSION_MANAGER_H
++
++#include "session-manager/introspect.h"
++#include "session-manager/interfaces.h"
++#include "session-manager/impl-interfaces.h"
++#include "session-manager/keys.h"
++
++#endif /* PIPEWIRE_EXT_SESSION_MANAGER_H */
+diff --git a/src/extensions/session-manager/impl-interfaces.h b/src/extensions/session-manager/impl-interfaces.h
+new file mode 100644
+index 00000000..66daa0b9
+--- /dev/null
++++ b/src/extensions/session-manager/impl-interfaces.h
+@@ -0,0 +1,329 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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_EXT_SESSION_MANAGER_IMPL_INTERFACES_H
++#define PIPEWIRE_EXT_SESSION_MANAGER_IMPL_INTERFACES_H
++
++#include <spa/utils/defs.h>
++#include <spa/utils/hook.h>
++#include <errno.h>
++
++#include "introspect.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#define PW_VERSION_CLIENT_ENDPOINT_PROXY 0
++struct pw_client_endpoint_proxy { struct spa_interface iface; };
++
++#define PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_ID 0
++#define PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_SESSION_ID 1
++#define PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_PARAM 2
++#define PW_CLIENT_ENDPOINT_PROXY_EVENT_STREAM_SET_PARAM 3
++#define PW_CLIENT_ENDPOINT_PROXY_EVENT_NUM 4
++
++struct pw_client_endpoint_proxy_events {
++#define PW_VERSION_CLIENT_ENDPOINT_PROXY_EVENTS 0
++ uint32_t version; /**< version of this structure */
++
++ /**
++ * Sets the id of the \a endpoint.
++ *
++ * On endpoint implementations, this is called by the server to notify
++ * the implementation of the assigned global id of the endpoint. The
++ * implementation is obliged to set this id in the
++ * #struct pw_endpoint_info \a id field. The implementation should also
++ * not emit the info() event before this method is called.
++ *
++ * \param endpoint a #pw_endpoint
++ * \param id the global id assigned to this endpoint
++ *
++ * \return 0 on success
++ * -EINVAL when the id has already been set
++ * -ENOTSUP on the server-side endpoint implementation
++ */
++ int (*set_id) (void *endpoint, uint32_t id);
++
++ /**
++ * Sets the session id of the \a endpoint.
++ *
++ * On endpoints that are not session masters, this method notifies
++ * the implementation that it has been associated with a session.
++ * The implementation is obliged to set this id in the
++ * #struct pw_endpoint_info \a session_id field.
++ *
++ * \param endpoint a #pw_endpoint
++ * \param id the session id associated with this endpoint
++ *
++ * \return 0 on success
++ * -EINVAL when the session id has already been set
++ * -ENOTSUP when the endpoint is a session master
++ */
++ int (*set_session_id) (void *endpoint, uint32_t session_id);
++
++ /**
++ * Set the configurable parameter in \a endpoint.
++ *
++ * Usually, \a param will be obtained from enum_params and then
++ * modified but it is also possible to set another spa_pod
++ * as long as its keys and types match a supported object.
++ *
++ * Objects with property keys that are not known are ignored.
++ *
++ * This function must be called from the main thread.
++ *
++ * \param endpoint a #struct pw_endpoint
++ * \param id the parameter id to configure
++ * \param flags additional flags
++ * \param param the parameter to configure
++ *
++ * \return 0 on success
++ * -EINVAL when \a endpoint is NULL
++ * -ENOTSUP when there are no parameters implemented on \a endpoint
++ * -ENOENT the parameter is unknown
++ */
++ int (*set_param) (void *endpoint,
++ uint32_t id, uint32_t flags,
++ const struct spa_pod *param);
++
++ /**
++ * Set a parameter on \a stream_id of \a endpoint.
++ *
++ * When \a param is NULL, the parameter will be unset.
++ *
++ * This function must be called from the main thread.
++ *
++ * \param endpoint a #struct pw_endpoint
++ * \param stream_id the stream to configure
++ * \param id the parameter id to set
++ * \param flags optional flags
++ * \param param a #struct spa_pod with the parameter to set
++ * \return 0 on success
++ * 1 on success, the value of \a param might have been
++ * changed depending on \a flags and the final value can
++ * be found by doing stream_enum_params.
++ * -EINVAL when \a endpoint is NULL or invalid arguments are given
++ * -ESRCH when the type or size of a property is not correct.
++ * -ENOENT when the param id is not found
++ */
++ int (*stream_set_param) (void *endpoint, uint32_t stream_id,
++ uint32_t id, uint32_t flags,
++ const struct spa_pod *param);
++};
++
++#define PW_CLIENT_ENDPOINT_PROXY_METHOD_ADD_LISTENER 0
++#define PW_CLIENT_ENDPOINT_PROXY_METHOD_UPDATE 1
++#define PW_CLIENT_ENDPOINT_PROXY_METHOD_STREAM_UPDATE 2
++#define PW_CLIENT_ENDPOINT_PROXY_METHOD_NUM 3
++
++struct pw_client_endpoint_proxy_methods {
++#define PW_VERSION_CLIENT_ENDPOINT_PROXY_METHODS 0
++ uint32_t version; /**< version of this structure */
++
++ int (*add_listener) (void *object,
++ struct spa_hook *listener,
++ const struct pw_client_endpoint_proxy_events *events,
++ void *data);
++
++ /** Update endpoint information */
++ int (*update) (void *object,
++#define PW_CLIENT_ENDPOINT_UPDATE_PARAMS (1 << 0)
++#define PW_CLIENT_ENDPOINT_UPDATE_INFO (1 << 1)
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_info *info);
++
++ /** Update stream information */
++ int (*stream_update) (void *object,
++ uint32_t stream_id,
++#define PW_CLIENT_ENDPOINT_STREAM_UPDATE_PARAMS (1 << 0)
++#define PW_CLIENT_ENDPOINT_STREAM_UPDATE_INFO (1 << 1)
++#define PW_CLIENT_ENDPOINT_STREAM_UPDATE_DESTROYED (1 << 2)
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_stream_info *info);
++};
++
++#define pw_client_endpoint_proxy_method(o,method,version,...) \
++({ \
++ int _res = -ENOTSUP; \
++ struct pw_client_endpoint_proxy *_p = o; \
++ spa_interface_call_res(&_p->iface, \
++ struct pw_client_endpoint_proxy_methods, _res, \
++ method, version, ##__VA_ARGS__); \
++ _res; \
++})
++
++#define pw_client_endpoint_proxy_add_listener(o,...) pw_client_endpoint_proxy_method(o,add_listener,0,__VA_ARGS__)
++#define pw_client_endpoint_proxy_update(o,...) pw_client_endpoint_proxy_method(o,update,0,__VA_ARGS__)
++#define pw_client_endpoint_proxy_stream_update(o,...) pw_client_endpoint_proxy_method(o,stream_update,0,__VA_ARGS__)
++
++
++#define PW_VERSION_CLIENT_SESSION_PROXY 0
++struct pw_client_session_proxy { struct spa_interface iface; };
++
++#define PW_CLIENT_SESSION_PROXY_EVENT_SET_ID 0
++#define PW_CLIENT_SESSION_PROXY_EVENT_SET_PARAM 1
++#define PW_CLIENT_SESSION_PROXY_EVENT_LINK_SET_PARAM 2
++#define PW_CLIENT_SESSION_PROXY_EVENT_CREATE_LINK 3
++#define PW_CLIENT_SESSION_PROXY_EVENT_DESTROY_LINK 4
++#define PW_CLIENT_SESSION_PROXY_EVENT_LINK_REQUEST_STATE 5
++#define PW_CLIENT_SESSION_PROXY_EVENT_NUM 6
++
++struct pw_client_session_proxy_events {
++#define PW_VERSION_CLIENT_SESSION_PROXY_EVENTS 0
++ uint32_t version; /**< version of this structure */
++
++ /**
++ * Sets the id of the \a session.
++ *
++ * On session implementations, this is called by the server to notify
++ * the implementation of the assigned global id of the session. The
++ * implementation is obliged to set this id in the
++ * #struct pw_session_info \a id field. The implementation should also
++ * not emit the info() event before this method is called.
++ *
++ * \param session a #pw_session
++ * \param id the global id assigned to this session
++ *
++ * \return 0 on success
++ * -EINVAL when the id has already been set
++ * -ENOTSUP on the server-side session implementation
++ */
++ int (*set_id) (void *session, uint32_t id);
++
++ /**
++ * Set the configurable parameter in \a session.
++ *
++ * Usually, \a param will be obtained from enum_params and then
++ * modified but it is also possible to set another spa_pod
++ * as long as its keys and types match a supported object.
++ *
++ * Objects with property keys that are not known are ignored.
++ *
++ * This function must be called from the main thread.
++ *
++ * \param session a #struct pw_session
++ * \param id the parameter id to configure
++ * \param flags additional flags
++ * \param param the parameter to configure
++ *
++ * \return 0 on success
++ * -EINVAL when \a session is NULL
++ * -ENOTSUP when there are no parameters implemented on \a session
++ * -ENOENT the parameter is unknown
++ */
++ int (*set_param) (void *session,
++ uint32_t id, uint32_t flags,
++ const struct spa_pod *param);
++
++ /**
++ * Set a parameter on \a link_id of \a session.
++ *
++ * When \a param is NULL, the parameter will be unset.
++ *
++ * This function must be called from the main thread.
++ *
++ * \param session a #struct pw_session
++ * \param link_id the link to configure
++ * \param id the parameter id to set
++ * \param flags optional flags
++ * \param param a #struct spa_pod with the parameter to set
++ * \return 0 on success
++ * 1 on success, the value of \a param might have been
++ * changed depending on \a flags and the final value can
++ * be found by doing link_enum_params.
++ * -EINVAL when \a session is NULL or invalid arguments are given
++ * -ESRCH when the type or size of a property is not correct.
++ * -ENOENT when the param id is not found
++ */
++ int (*link_set_param) (void *session, uint32_t link_id,
++ uint32_t id, uint32_t flags,
++ const struct spa_pod *param);
++
++ int (*create_link) (void *session, const struct spa_dict *props);
++
++ int (*destroy_link) (void *session, uint32_t link_id);
++
++ int (*link_request_state) (void *session, uint32_t link_id, uint32_t state);
++};
++
++#define PW_CLIENT_SESSION_PROXY_METHOD_ADD_LISTENER 0
++#define PW_CLIENT_SESSION_PROXY_METHOD_UPDATE 1
++#define PW_CLIENT_SESSION_PROXY_METHOD_LINK_UPDATE 2
++#define PW_CLIENT_SESSION_PROXY_METHOD_NUM 3
++
++struct pw_client_session_proxy_methods {
++#define PW_VERSION_CLIENT_SESSION_PROXY_METHODS 0
++ uint32_t version; /**< version of this structure */
++
++ int (*add_listener) (void *object,
++ struct spa_hook *listener,
++ const struct pw_client_session_proxy_events *events,
++ void *data);
++
++ /** Update session information */
++ int (*update) (void *object,
++#define PW_CLIENT_SESSION_UPDATE_PARAMS (1 << 0)
++#define PW_CLIENT_SESSION_UPDATE_INFO (1 << 1)
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_session_info *info);
++
++ /** Update link information */
++ int (*link_update) (void *object,
++ uint32_t link_id,
++#define PW_CLIENT_SESSION_LINK_UPDATE_PARAMS (1 << 0)
++#define PW_CLIENT_SESSION_LINK_UPDATE_INFO (1 << 1)
++#define PW_CLIENT_SESSION_LINK_UPDATE_DESTROYED (1 << 2)
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_link_info *info);
++};
++
++#define pw_client_session_proxy_method(o,method,version,...) \
++({ \
++ int _res = -ENOTSUP; \
++ struct pw_client_session_proxy *_p = o; \
++ spa_interface_call_res(&_p->iface, \
++ struct pw_client_session_proxy_methods, _res, \
++ method, version, ##__VA_ARGS__); \
++ _res; \
++})
++
++#define pw_client_session_proxy_add_listener(o,...) pw_client_session_proxy_method(o,add_listener,0,__VA_ARGS__)
++#define pw_client_session_proxy_update(o,...) pw_client_session_proxy_method(o,update,0,__VA_ARGS__)
++#define pw_client_session_proxy_link_update(o,...) pw_client_session_proxy_method(o,link_update,0,__VA_ARGS__)
++
++#ifdef __cplusplus
++} /* extern "C" */
++#endif
++
++#endif /* PIPEWIRE_EXT_SESSION_MANAGER_IMPL_INTERFACES_H */
+diff --git a/src/extensions/session-manager/interfaces.h b/src/extensions/session-manager/interfaces.h
+new file mode 100644
+index 00000000..0651e8bf
+--- /dev/null
++++ b/src/extensions/session-manager/interfaces.h
+@@ -0,0 +1,465 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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_EXT_SESSION_MANAGER_INTERFACES_H
++#define PIPEWIRE_EXT_SESSION_MANAGER_INTERFACES_H
++
++#include <spa/utils/defs.h>
++#include <spa/utils/hook.h>
++
++#include "introspect.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#define PW_VERSION_SESSION_PROXY 0
++struct pw_session_proxy { struct spa_interface iface; };
++#define PW_VERSION_ENDPOINT_PROXY 0
++struct pw_endpoint_proxy { struct spa_interface iface; };
++#define PW_VERSION_ENDPOINT_STREAM_PROXY 0
++struct pw_endpoint_stream_proxy { struct spa_interface iface; };
++#define PW_VERSION_ENDPOINT_LINK_PROXY 0
++struct pw_endpoint_link_proxy { struct spa_interface iface; };
++
++/* Session */
++
++#define PW_SESSION_PROXY_EVENT_INFO 0
++#define PW_SESSION_PROXY_EVENT_PARAM 1
++#define PW_SESSION_PROXY_EVENT_NUM 2
++
++struct pw_session_proxy_events {
++#define PW_VERSION_SESSION_PROXY_EVENTS 0
++ uint32_t version; /**< version of this structure */
++
++ /**
++ * Notify session info
++ *
++ * \param info info about the session
++ */
++ void (*info) (void *object, const struct pw_session_info *info);
++
++ /**
++ * Notify a session param
++ *
++ * Event emited as a result of the enum_params method.
++ *
++ * \param seq the sequence number of the request
++ * \param id the param id
++ * \param index the param index
++ * \param next the param index of the next param
++ * \param param the parameter
++ */
++ void (*param) (void *object, int seq,
++ uint32_t id, uint32_t index, uint32_t next,
++ const struct spa_pod *param);
++};
++
++#define PW_SESSION_PROXY_METHOD_ADD_LISTENER 0
++#define PW_SESSION_PROXY_METHOD_SUBSCRIBE_PARAMS 1
++#define PW_SESSION_PROXY_METHOD_ENUM_PARAMS 2
++#define PW_SESSION_PROXY_METHOD_SET_PARAM 3
++#define PW_SESSION_PROXY_METHOD_CREATE_LINK 4
++#define PW_SESSION_PROXY_METHOD_NUM 5
++
++struct pw_session_proxy_methods {
++#define PW_VERSION_SESSION_PROXY_METHODS 0
++ uint32_t version; /**< version of this structure */
++
++ int (*add_listener) (void *object,
++ struct spa_hook *listener,
++ const struct pw_session_proxy_events *events,
++ void *data);
++
++ /**
++ * Subscribe to parameter changes
++ *
++ * Automatically emit param events for the given ids when
++ * they are changed.
++ *
++ * \param ids an array of param ids
++ * \param n_ids the number of ids in \a ids
++ */
++ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
++
++ /**
++ * Enumerate session parameters
++ *
++ * Start enumeration of session parameters. For each param, a
++ * param event will be emited.
++ *
++ * \param seq a sequence number returned in the reply
++ * \param id the parameter id to enumerate
++ * \param start the start index or 0 for the first param
++ * \param num the maximum number of params to retrieve
++ * \param filter a param filter or NULL
++ */
++ int (*enum_params) (void *object, int seq,
++ uint32_t id, uint32_t start, uint32_t num,
++ const struct spa_pod *filter);
++
++ /**
++ * Set a parameter on the session
++ *
++ * \param id the parameter id to set
++ * \param flags extra parameter flags
++ * \param param the parameter to set
++ */
++ int (*set_param) (void *object, uint32_t id, uint32_t flags,
++ const struct spa_pod *param);
++
++ int (*create_link) (void *object, const struct spa_dict *props);
++};
++
++#define pw_session_proxy_method(o,method,version,...) \
++({ \
++ int _res = -ENOTSUP; \
++ struct pw_session_proxy *_p = o; \
++ spa_interface_call_res(&_p->iface, \
++ struct pw_session_proxy_methods, _res, \
++ method, version, ##__VA_ARGS__); \
++ _res; \
++})
++
++#define pw_session_proxy_add_listener(c,...) pw_session_proxy_method(c,add_listener,0,__VA_ARGS__)
++#define pw_session_proxy_subscribe_params(c,...) pw_session_proxy_method(c,subscribe_params,0,__VA_ARGS__)
++#define pw_session_proxy_enum_params(c,...) pw_session_proxy_method(c,enum_params,0,__VA_ARGS__)
++#define pw_session_proxy_create_link(c,...) pw_session_proxy_method(c,create_link,0,__VA_ARGS__)
++
++/* Endpoint */
++
++#define PW_ENDPOINT_PROXY_EVENT_INFO 0
++#define PW_ENDPOINT_PROXY_EVENT_PARAM 1
++#define PW_ENDPOINT_PROXY_EVENT_NUM 2
++
++struct pw_endpoint_proxy_events {
++#define PW_VERSION_ENDPOINT_PROXY_EVENTS 0
++ uint32_t version; /**< version of this structure */
++
++ /**
++ * Notify endpoint info
++ *
++ * \param info info about the endpoint
++ */
++ void (*info) (void *object, const struct pw_endpoint_info *info);
++
++ /**
++ * Notify a endpoint param
++ *
++ * Event emited as a result of the enum_params method.
++ *
++ * \param seq the sequence number of the request
++ * \param id the param id
++ * \param index the param index
++ * \param next the param index of the next param
++ * \param param the parameter
++ */
++ void (*param) (void *object, int seq,
++ uint32_t id, uint32_t index, uint32_t next,
++ const struct spa_pod *param);
++};
++
++#define PW_ENDPOINT_PROXY_METHOD_ADD_LISTENER 0
++#define PW_ENDPOINT_PROXY_METHOD_SUBSCRIBE_PARAMS 1
++#define PW_ENDPOINT_PROXY_METHOD_ENUM_PARAMS 2
++#define PW_ENDPOINT_PROXY_METHOD_SET_PARAM 3
++#define PW_ENDPOINT_PROXY_METHOD_NUM 4
++
++struct pw_endpoint_proxy_methods {
++#define PW_VERSION_ENDPOINT_PROXY_METHODS 0
++ uint32_t version; /**< version of this structure */
++
++ int (*add_listener) (void *object,
++ struct spa_hook *listener,
++ const struct pw_endpoint_proxy_events *events,
++ void *data);
++
++ /**
++ * Subscribe to parameter changes
++ *
++ * Automatically emit param events for the given ids when
++ * they are changed.
++ *
++ * \param ids an array of param ids
++ * \param n_ids the number of ids in \a ids
++ */
++ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
++
++ /**
++ * Enumerate endpoint parameters
++ *
++ * Start enumeration of endpoint parameters. For each param, a
++ * param event will be emited.
++ *
++ * \param seq a sequence number returned in the reply
++ * \param id the parameter id to enumerate
++ * \param start the start index or 0 for the first param
++ * \param num the maximum number of params to retrieve
++ * \param filter a param filter or NULL
++ */
++ int (*enum_params) (void *object, int seq,
++ uint32_t id, uint32_t start, uint32_t num,
++ const struct spa_pod *filter);
++
++ /**
++ * Set a parameter on the endpoint
++ *
++ * \param id the parameter id to set
++ * \param flags extra parameter flags
++ * \param param the parameter to set
++ */
++ int (*set_param) (void *object, uint32_t id, uint32_t flags,
++ const struct spa_pod *param);
++};
++
++#define pw_endpoint_proxy_method(o,method,version,...) \
++({ \
++ int _res = -ENOTSUP; \
++ struct pw_endpoint_proxy *_p = o; \
++ spa_interface_call_res(&_p->iface, \
++ struct pw_endpoint_proxy_methods, _res, \
++ method, version, ##__VA_ARGS__); \
++ _res; \
++})
++
++#define pw_endpoint_proxy_add_listener(c,...) pw_endpoint_proxy_method(c,add_listener,0,__VA_ARGS__)
++#define pw_endpoint_proxy_subscribe_params(c,...) pw_endpoint_proxy_method(c,subscribe_params,0,__VA_ARGS__)
++#define pw_endpoint_proxy_enum_params(c,...) pw_endpoint_proxy_method(c,enum_params,0,__VA_ARGS__)
++
++/* Endpoint Stream */
++
++#define PW_ENDPOINT_STREAM_PROXY_EVENT_INFO 0
++#define PW_ENDPOINT_STREAM_PROXY_EVENT_PARAM 1
++#define PW_ENDPOINT_STREAM_PROXY_EVENT_NUM 2
++
++struct pw_endpoint_stream_proxy_events {
++#define PW_VERSION_ENDPOINT_STREAM_PROXY_EVENTS 0
++ uint32_t version; /**< version of this structure */
++
++ /**
++ * Notify endpoint stream info
++ *
++ * \param info info about the endpoint stream
++ */
++ void (*info) (void *object, const struct pw_endpoint_stream_info *info);
++
++ /**
++ * Notify a endpoint stream param
++ *
++ * Event emited as a result of the enum_params method.
++ *
++ * \param seq the sequence number of the request
++ * \param id the param id
++ * \param index the param index
++ * \param next the param index of the next param
++ * \param param the parameter
++ */
++ void (*param) (void *object, int seq,
++ uint32_t id, uint32_t index, uint32_t next,
++ const struct spa_pod *param);
++};
++
++#define PW_ENDPOINT_STREAM_PROXY_METHOD_ADD_LISTENER 0
++#define PW_ENDPOINT_STREAM_PROXY_METHOD_SUBSCRIBE_PARAMS 1
++#define PW_ENDPOINT_STREAM_PROXY_METHOD_ENUM_PARAMS 2
++#define PW_ENDPOINT_STREAM_PROXY_METHOD_SET_PARAM 3
++#define PW_ENDPOINT_STREAM_PROXY_METHOD_NUM 4
++
++struct pw_endpoint_stream_proxy_methods {
++#define PW_VERSION_ENDPOINT_STREAM_PROXY_METHODS 0
++ uint32_t version; /**< version of this structure */
++
++ int (*add_listener) (void *object,
++ struct spa_hook *listener,
++ const struct pw_endpoint_stream_proxy_events *events,
++ void *data);
++
++ /**
++ * Subscribe to parameter changes
++ *
++ * Automatically emit param events for the given ids when
++ * they are changed.
++ *
++ * \param ids an array of param ids
++ * \param n_ids the number of ids in \a ids
++ */
++ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
++
++ /**
++ * Enumerate stream parameters
++ *
++ * Start enumeration of stream parameters. For each param, a
++ * param event will be emited.
++ *
++ * \param seq a sequence number returned in the reply
++ * \param id the parameter id to enumerate
++ * \param start the start index or 0 for the first param
++ * \param num the maximum number of params to retrieve
++ * \param filter a param filter or NULL
++ */
++ int (*enum_params) (void *object, int seq,
++ uint32_t id, uint32_t start, uint32_t num,
++ const struct spa_pod *filter);
++
++ /**
++ * Set a parameter on the stream
++ *
++ * \param id the parameter id to set
++ * \param flags extra parameter flags
++ * \param param the parameter to set
++ */
++ int (*set_param) (void *object, uint32_t id, uint32_t flags,
++ const struct spa_pod *param);
++};
++
++#define pw_endpoint_stream_proxy_method(o,method,version,...) \
++({ \
++ int _res = -ENOTSUP; \
++ struct pw_endpoint_stream_proxy *_p = o; \
++ spa_interface_call_res(&_p->iface, \
++ struct pw_endpoint_stream_proxy_methods, _res, \
++ method, version, ##__VA_ARGS__); \
++ _res; \
++})
++
++#define pw_endpoint_stream_proxy_add_listener(c,...) pw_endpoint_stream_proxy_method(c,add_listener,0,__VA_ARGS__)
++#define pw_endpoint_stream_proxy_subscribe_params(c,...) pw_endpoint_stream_proxy_method(c,subscribe_params,0,__VA_ARGS__)
++#define pw_endpoint_stream_proxy_enum_params(c,...) pw_endpoint_stream_proxy_method(c,enum_params,0,__VA_ARGS__)
++
++/* Endpoint Link */
++
++#define PW_ENDPOINT_LINK_PROXY_EVENT_INFO 0
++#define PW_ENDPOINT_LINK_PROXY_EVENT_PARAM 1
++#define PW_ENDPOINT_LINK_PROXY_EVENT_NUM 2
++
++struct pw_endpoint_link_proxy_events {
++#define PW_VERSION_ENDPOINT_LINK_PROXY_EVENTS 0
++ uint32_t version; /**< version of this structure */
++
++ /**
++ * Notify endpoint link info
++ *
++ * \param info info about the endpoint link
++ */
++ void (*info) (void *object, const struct pw_endpoint_link_info *info);
++
++ /**
++ * Notify a endpoint link param
++ *
++ * Event emited as a result of the enum_params method.
++ *
++ * \param seq the sequence number of the request
++ * \param id the param id
++ * \param index the param index
++ * \param next the param index of the next param
++ * \param param the parameter
++ */
++ void (*param) (void *object, int seq,
++ uint32_t id, uint32_t index, uint32_t next,
++ const struct spa_pod *param);
++};
++
++#define PW_ENDPOINT_LINK_PROXY_METHOD_ADD_LISTENER 0
++#define PW_ENDPOINT_LINK_PROXY_METHOD_SUBSCRIBE_PARAMS 1
++#define PW_ENDPOINT_LINK_PROXY_METHOD_ENUM_PARAMS 2
++#define PW_ENDPOINT_LINK_PROXY_METHOD_SET_PARAM 3
++#define PW_ENDPOINT_LINK_PROXY_METHOD_REQUEST_STATE 4
++#define PW_ENDPOINT_LINK_PROXY_METHOD_DESTROY 5
++#define PW_ENDPOINT_LINK_PROXY_METHOD_NUM 6
++
++struct pw_endpoint_link_proxy_methods {
++#define PW_VERSION_ENDPOINT_LINK_PROXY_METHODS 0
++ uint32_t version; /**< version of this structure */
++
++ int (*add_listener) (void *object,
++ struct spa_hook *listener,
++ const struct pw_endpoint_link_proxy_events *events,
++ void *data);
++
++ /**
++ * Subscribe to parameter changes
++ *
++ * Automatically emit param events for the given ids when
++ * they are changed.
++ *
++ * \param ids an array of param ids
++ * \param n_ids the number of ids in \a ids
++ */
++ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
++
++ /**
++ * Enumerate link parameters
++ *
++ * Start enumeration of link parameters. For each param, a
++ * param event will be emited.
++ *
++ * \param seq a sequence number returned in the reply
++ * \param id the parameter id to enumerate
++ * \param start the start index or 0 for the first param
++ * \param num the maximum number of params to retrieve
++ * \param filter a param filter or NULL
++ */
++ int (*enum_params) (void *object, int seq,
++ uint32_t id, uint32_t start, uint32_t num,
++ const struct spa_pod *filter);
++
++ /**
++ * Set a parameter on the link
++ *
++ * \param id the parameter id to set
++ * \param flags extra parameter flags
++ * \param param the parameter to set
++ */
++ int (*set_param) (void *object, uint32_t id, uint32_t flags,
++ const struct spa_pod *param);
++
++ int (*request_state) (void *object, enum pw_endpoint_link_state state);
++
++ int (*destroy) (void *object);
++
++};
++
++#define pw_endpoint_link_proxy_method(o,method,version,...) \
++({ \
++ int _res = -ENOTSUP; \
++ struct pw_endpoint_link_proxy *_p = o; \
++ spa_interface_call_res(&_p->iface, \
++ struct pw_endpoint_link_proxy_methods, _res, \
++ method, version, ##__VA_ARGS__); \
++ _res; \
++})
++
++#define pw_endpoint_link_proxy_add_listener(c,...) pw_endpoint_link_proxy_method(c,add_listener,0,__VA_ARGS__)
++#define pw_endpoint_link_proxy_subscribe_params(c,...) pw_endpoint_link_proxy_method(c,subscribe_params,0,__VA_ARGS__)
++#define pw_endpoint_link_proxy_enum_params(c,...) pw_endpoint_link_proxy_method(c,enum_params,0,__VA_ARGS__)
++#define pw_endpoint_link_proxy_request_state(c,...) pw_endpoint_link_proxy_method(c,request_state,0,__VA_ARGS__)
++#define pw_endpoint_link_proxy_destroy(c,...) pw_endpoint_link_proxy_method(c,destroy,0,__VA_ARGS__)
++
++
++#ifdef __cplusplus
++} /* extern "C" */
++#endif
++
++#endif /* PIPEWIRE_EXT_SESSION_MANAGER_INTERFACES_H */
+diff --git a/src/extensions/session-manager/introspect.h b/src/extensions/session-manager/introspect.h
+new file mode 100644
+index 00000000..3b0e4113
+--- /dev/null
++++ b/src/extensions/session-manager/introspect.h
+@@ -0,0 +1,131 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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_EXT_SESSION_MANAGER_INTROSPECT_H
++#define PIPEWIRE_EXT_SESSION_MANAGER_INTROSPECT_H
++
++#include <spa/utils/defs.h>
++#include <spa/utils/dict.h>
++#include <spa/param/param.h>
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#define PW_KEY_ENDPOINT_ID "endpoint.id"
++#define PW_KEY_SESSION_ID "session.id"
++
++enum pw_endpoint_direction {
++ PW_ENDPOINT_DIRECTION_SINK_INPUT = SPA_DIRECTION_INPUT,
++ PW_ENDPOINT_DIRECTION_SOURCE_OUTPUT = SPA_DIRECTION_OUTPUT,
++ PW_ENDPOINT_DIRECTION_SOURCE,
++ PW_ENDPOINT_DIRECTION_SINK,
++};
++
++enum pw_endpoint_link_state {
++ PW_ENDPOINT_LINK_STATE_ERROR = -1,
++ PW_ENDPOINT_LINK_STATE_PREPARING,
++ PW_ENDPOINT_LINK_STATE_INACTIVE,
++ PW_ENDPOINT_LINK_STATE_ACTIVE,
++};
++
++struct pw_session_info {
++#define PW_VERSION_SESSION_INFO 0
++ uint32_t version; /**< version of this structure */
++ uint32_t id; /**< the session id (global) */
++#define PW_SESSION_CHANGE_MASK_PROPS (1 << 0)
++#define PW_SESSION_CHANGE_MASK_PARAMS (1 << 1)
++#define PW_SESSION_CHANGE_MASK_ALL ((1 << 2)-1)
++ uint32_t change_mask; /**< bitfield of changed fields since last call */
++ struct spa_dict *props; /**< extra properties */
++ struct spa_param_info *params; /**< parameters */
++ uint32_t n_params; /**< number of items in \a params */
++};
++
++struct pw_endpoint_info {
++#define PW_VERSION_ENDPOINT_INFO 0
++ uint32_t version; /**< version of this structure */
++ uint32_t id; /**< the endpoint id (global) */
++ char *name; /**< name of the endpoint */
++ char *media_class; /**< media class of the endpoint */
++ enum pw_endpoint_direction direction; /**< direction of the endpoint */
++#define PW_ENDPOINT_FLAG_PROVIDES_SESSION (1 << 0)
++ uint32_t flags; /**< additional flags */
++#define PW_ENDPOINT_CHANGE_MASK_STREAMS (1 << 0)
++#define PW_ENDPOINT_CHANGE_MASK_SESSION (1 << 1)
++#define PW_ENDPOINT_CHANGE_MASK_PROPS (1 << 2)
++#define PW_ENDPOINT_CHANGE_MASK_PARAMS (1 << 3)
++#define PW_ENDPOINT_CHANGE_MASK_ALL ((1 << 4)-1)
++ uint32_t change_mask; /**< bitfield of changed fields since last call */
++ uint32_t n_streams; /**< number of streams available */
++ uint32_t session_id; /**< the id of the controlling session */
++ struct spa_dict *props; /**< extra properties */
++ struct spa_param_info *params; /**< parameters */
++ uint32_t n_params; /**< number of items in \a params */
++};
++
++struct pw_endpoint_stream_info {
++#define PW_VERSION_ENDPOINT_STREAM_INFO 0
++ uint32_t version; /**< version of this structure */
++ uint32_t id; /**< the stream id (local or global) */
++ uint32_t endpoint_id; /**< the endpoint id (global) */
++ char *name; /**< name of the stream */
++#define PW_ENDPOINT_STREAM_CHANGE_MASK_LINK_PARAMS (1 << 0)
++#define PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS (1 << 1)
++#define PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS (1 << 2)
++#define PW_ENDPOINT_STREAM_CHANGE_MASK_ALL ((1 << 3)-1)
++ uint32_t change_mask; /**< bitfield of changed fields since last call */
++ struct spa_pod *link_params; /**< information for linking this stream */
++ struct spa_dict *props; /**< extra properties */
++ struct spa_param_info *params; /**< parameters */
++ uint32_t n_params; /**< number of items in \a params */
++};
++
++struct pw_endpoint_link_info {
++#define PW_VERSION_ENDPOINT_LINK_INFO 0
++ uint32_t version; /**< version of this structure */
++ uint32_t id; /**< the link id (global) */
++ uint32_t session_id; /**< the session id (global) */
++ uint32_t output_endpoint_id; /**< the output endpoint id (global) */
++ uint32_t output_stream_id; /**< the output stream id (local or global) */
++ uint32_t input_endpoint_id; /**< the input endpoint id (global) */
++ uint32_t input_stream_id; /**< the input stream id (local or global) */
++#define PW_ENDPOINT_LINK_CHANGE_MASK_STATE (1 << 0)
++#define PW_ENDPOINT_LINK_CHANGE_MASK_PROPS (1 << 1)
++#define PW_ENDPOINT_LINK_CHANGE_MASK_PARAMS (1 << 2)
++#define PW_ENDPOINT_LINK_CHANGE_MASK_ALL ((1 << 3)-1)
++ uint32_t change_mask; /**< bitfield of changed fields since last call */
++ enum pw_endpoint_link_state state; /**< the state of the link */
++ char *error; /**< error string if state == ERROR */
++ struct spa_dict *props; /**< extra properties */
++ struct spa_param_info *params; /**< parameters */
++ uint32_t n_params; /**< number of items in \a params */
++};
++
++#ifdef __cplusplus
++} /* extern "C" */
++#endif
++
++#endif /* PIPEWIRE_EXT_SESSION_MANAGER_INTROSPECT_H */
+diff --git a/src/extensions/session-manager/keys.h b/src/extensions/session-manager/keys.h
+new file mode 100644
+index 00000000..a7167510
+--- /dev/null
++++ b/src/extensions/session-manager/keys.h
+@@ -0,0 +1,40 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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_EXT_SESSION_MANAGER_KEYS_H
++#define PIPEWIRE_EXT_SESSION_MANAGER_KEYS_H
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#define PW_KEY_ENDPOINT_ID "endpoint.id"
++#define PW_KEY_SESSION_ID "session.id"
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif /* PIPEWIRE_EXT_SESSION_MANAGER_KEYS_H */
+diff --git a/src/modules/meson.build b/src/modules/meson.build
+index 9e1e94bd..da2684b6 100644
+--- a/src/modules/meson.build
++++ b/src/modules/meson.build
+@@ -100,3 +100,20 @@ pipewire_module_adapter = shared_library('pipewire-module-adapter',
+ install_dir : modules_install_dir,
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep],
+ )
++
++pipewire_module_session_manager = shared_library('pipewire-module-session-manager',
++ [ 'module-session-manager.c',
++ 'module-session-manager/client-endpoint.c',
++ 'module-session-manager/client-session.c',
++ 'module-session-manager/endpoint-link.c',
++ 'module-session-manager/endpoint-stream.c',
++ 'module-session-manager/endpoint.c',
++ 'module-session-manager/session.c',
++ 'module-session-manager/protocol-native.c',
++ ],
++ c_args : pipewire_module_c_args,
++ include_directories : [configinc, spa_inc],
++ install : true,
++ install_dir : modules_install_dir,
++ dependencies : [mathlib, dl_lib, pipewire_dep],
++)
+diff --git a/src/modules/module-session-manager.c b/src/modules/module-session-manager.c
+new file mode 100644
+index 00000000..dbea3357
+--- /dev/null
++++ b/src/modules/module-session-manager.c
+@@ -0,0 +1,56 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 <pipewire/pipewire.h>
++
++/* client-endpoint.c */
++int client_endpoint_factory_init(struct pw_module *module);
++/* client-session.c */
++int client_session_factory_init(struct pw_module *module);
++/* protocol-native.c */
++struct pw_protocol *pw_protocol_native_ext_session_manager_init(struct pw_core *core);
++
++static const struct spa_dict_item module_props[] = {
++ { PW_KEY_MODULE_AUTHOR, "George Kiagiadakis <george.kiagiadakis@collabora.com>" },
++ { PW_KEY_MODULE_DESCRIPTION, "Implements objects for session management" },
++ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
++};
++
++SPA_EXPORT
++int pipewire__module_init(struct pw_module *module, const char *args)
++{
++ struct pw_core *core = pw_module_get_core(module);
++
++ client_endpoint_factory_init(module);
++ client_session_factory_init(module);
++
++ pw_protocol_native_ext_session_manager_init(core);
++
++ pw_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
++
++ return 0;
++}
+diff --git a/src/modules/module-session-manager/client-endpoint.c b/src/modules/module-session-manager/client-endpoint.c
+new file mode 100644
+index 00000000..0e501c9f
+--- /dev/null
++++ b/src/modules/module-session-manager/client-endpoint.c
+@@ -0,0 +1,270 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 <stdbool.h>
++#include <string.h>
++
++#include <pipewire/pipewire.h>
++#include <extensions/session-manager.h>
++
++#include "client-endpoint.h"
++#include "endpoint.h"
++#include "endpoint-stream.h"
++
++#include <pipewire/private.h>
++
++#define NAME "client-endpoint"
++
++struct factory_data {
++ struct pw_factory *factory;
++ struct pw_module *module;
++ struct spa_hook module_listener;
++};
++
++static struct endpoint_stream *find_stream(struct client_endpoint *this, uint32_t id)
++{
++ struct endpoint_stream *s;
++ spa_list_for_each(s, &this->streams, link) {
++ if (s->id == id)
++ return s;
++ }
++ return NULL;
++}
++
++static int client_endpoint_update(void *object,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_info *info)
++{
++ struct client_endpoint *this = object;
++ struct endpoint *endpoint = &this->endpoint;
++
++ return endpoint_update(endpoint, change_mask, n_params, params, info);
++}
++
++static int client_endpoint_stream_update(void *object,
++ uint32_t stream_id,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_stream_info *info)
++{
++ struct client_endpoint *this = object;
++ struct endpoint *endpoint = &this->endpoint;
++ struct endpoint_stream *stream = find_stream(this, stream_id);
++ struct pw_properties *props = NULL;
++
++ if (!stream) {
++ struct pw_core *core = pw_global_get_core(endpoint->global);
++ const char *keys[] = {
++ PW_KEY_FACTORY_ID,
++ PW_KEY_CLIENT_ID,
++ PW_KEY_ENDPOINT_ID,
++ NULL
++ };
++
++ stream = calloc(1, sizeof(struct endpoint_stream));
++ if (!stream)
++ goto no_mem;
++
++ props = pw_properties_new(NULL, NULL);
++ if (!props)
++ goto no_mem;
++ pw_properties_copy_keys (endpoint->props, props, keys);
++
++ if (endpoint_stream_init(stream, stream_id, endpoint->info.id,
++ this, core, props) < 0)
++ goto no_mem;
++
++ spa_list_append(&this->streams, &stream->link);
++ }
++ else if (change_mask & PW_CLIENT_ENDPOINT_STREAM_UPDATE_DESTROYED) {
++ endpoint_stream_clear(stream);
++ spa_list_remove(&stream->link);
++ free(stream);
++ stream = NULL;
++ }
++
++ return stream ?
++ endpoint_stream_update(stream, change_mask, n_params, params, info)
++ : 0;
++
++ no_mem:
++ if (props)
++ pw_properties_free(props);
++ free(stream);
++ pw_log_error(NAME" %p: cannot update stream: no memory", this);
++ pw_resource_error(this->resource, -ENOMEM,
++ NAME" %p: cannot update stream: no memory", this);
++ return -ENOMEM;
++}
++
++static struct pw_client_endpoint_proxy_methods methods = {
++ PW_VERSION_CLIENT_ENDPOINT_PROXY_METHODS,
++ .update = client_endpoint_update,
++ .stream_update = client_endpoint_stream_update,
++};
++
++static void client_endpoint_destroy(void *data)
++{
++ struct client_endpoint *this = data;
++ struct endpoint_stream *s;
++
++ pw_log_debug(NAME" %p: destroy", this);
++
++ spa_list_consume(s, &this->streams, link) {
++ endpoint_stream_clear(s);
++ spa_list_remove(&s->link);
++ free(s);
++ }
++ endpoint_clear(&this->endpoint);
++ spa_hook_remove(&this->resource_listener);
++
++ free(this);
++}
++
++static const struct pw_resource_events resource_events = {
++ PW_VERSION_RESOURCE_EVENTS,
++ .destroy = client_endpoint_destroy,
++};
++
++static void *create_object(void *data,
++ struct pw_resource *owner_resource,
++ uint32_t type,
++ uint32_t version,
++ struct pw_properties *properties,
++ uint32_t new_id)
++{
++ struct factory_data *d = data;
++ struct pw_factory *factory = d->factory;
++ struct client_endpoint *this;
++ struct pw_client *owner = pw_resource_get_client(owner_resource);
++ struct pw_core *core = pw_client_get_core(owner);
++
++ this = calloc(1, sizeof(struct client_endpoint));
++ if (this == NULL)
++ goto no_mem;
++
++ spa_list_init(&this->streams);
++
++ pw_log_debug(NAME" %p: new", this);
++
++ if (!properties)
++ properties = pw_properties_new(NULL, NULL);
++ if (!properties)
++ goto no_mem;
++
++ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", owner->global->id);
++ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", factory->global->id);
++
++ this->resource = pw_resource_new(owner, new_id, PW_PERM_RWX, type, version, 0);
++ if (this->resource == NULL)
++ goto no_mem;
++
++ if (endpoint_init(&this->endpoint, this, core, properties) < 0)
++ goto no_mem;
++
++ pw_resource_add_listener(this->resource, &this->resource_listener,
++ &resource_events, this);
++ pw_resource_add_object_listener(this->resource, &this->object_listener,
++ &methods, this);
++
++ return this;
++
++ no_mem:
++ if (properties)
++ pw_properties_free(properties);
++ if (this && this->resource)
++ pw_resource_destroy(this->resource);
++ free(this);
++ pw_log_error("can't create client endpoint: no memory");
++ pw_resource_error(owner_resource, -ENOMEM,
++ "can't create client endpoint: no memory");
++ return NULL;
++}
++
++static const struct pw_factory_implementation impl_factory = {
++ PW_VERSION_FACTORY_IMPLEMENTATION,
++ .create_object = create_object,
++};
++
++static void module_destroy(void *data)
++{
++ struct factory_data *d = data;
++
++ spa_hook_remove(&d->module_listener);
++ pw_factory_destroy(d->factory);
++}
++
++static void module_registered(void *data)
++{
++ struct factory_data *d = data;
++ struct pw_module *module = d->module;
++ struct pw_factory *factory = d->factory;
++ struct spa_dict_item items[1];
++ char id[16];
++ int res;
++
++ snprintf(id, sizeof(id), "%d", module->global->id);
++ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
++ pw_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
++
++ if ((res = pw_factory_register(factory, NULL)) < 0) {
++ pw_log_error(NAME" %p: can't register factory: %s", factory, spa_strerror(res));
++ }
++}
++
++static const struct pw_module_events module_events = {
++ PW_VERSION_MODULE_EVENTS,
++ .destroy = module_destroy,
++ .registered = module_registered,
++};
++
++int client_endpoint_factory_init(struct pw_module *module)
++{
++ struct pw_core *core = pw_module_get_core(module);
++ struct pw_factory *factory;
++ struct factory_data *data;
++
++ factory = pw_factory_new(core,
++ "client-endpoint",
++ PW_TYPE_INTERFACE_ClientEndpoint,
++ PW_VERSION_CLIENT_ENDPOINT_PROXY,
++ NULL,
++ sizeof(*data));
++ if (factory == NULL)
++ return -ENOMEM;
++
++ data = pw_factory_get_user_data(factory);
++ data->factory = factory;
++ data->module = module;
++
++ pw_factory_set_implementation(factory, &impl_factory, data);
++
++ pw_module_add_listener(module, &data->module_listener, &module_events, data);
++
++ return 0;
++}
+diff --git a/src/modules/module-session-manager/client-endpoint.h b/src/modules/module-session-manager/client-endpoint.h
+new file mode 100644
+index 00000000..394e9fa8
+--- /dev/null
++++ b/src/modules/module-session-manager/client-endpoint.h
+@@ -0,0 +1,60 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 MODULE_SESSION_MANAGER_CLIENT_ENDPOINT_H
++#define MODULE_SESSION_MANAGER_CLIENT_ENDPOINT_H
++
++#include "endpoint.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++struct client_endpoint {
++ struct pw_resource *resource;
++ struct spa_hook resource_listener;
++ struct spa_hook object_listener;
++ struct endpoint endpoint;
++ struct spa_list streams;
++};
++
++#define pw_client_endpoint_resource(r,m,v,...) \
++ pw_resource_call_res(r,struct pw_client_endpoint_proxy_events,m,v,__VA_ARGS__)
++#define pw_client_endpoint_resource_set_id(r,...) \
++ pw_client_endpoint_resource(r,set_id,0,__VA_ARGS__)
++#define pw_client_endpoint_resource_set_session_id(r,...) \
++ pw_client_endpoint_resource(r,set_session_id,0,__VA_ARGS__)
++#define pw_client_endpoint_resource_set_param(r,...) \
++ pw_client_endpoint_resource(r,set_param,0,__VA_ARGS__)
++#define pw_client_endpoint_resource_stream_set_param(r,...) \
++ pw_client_endpoint_resource(r,stream_set_param,0,__VA_ARGS__)
++
++int client_endpoint_factory_init(struct pw_module *module);
++
++#ifdef __cplusplus
++} /* extern "C" */
++#endif
++
++#endif /* MODULE_SESSION_MANAGER_CLIENT_ENDPOINT_H */
+diff --git a/src/modules/module-session-manager/client-session.c b/src/modules/module-session-manager/client-session.c
+new file mode 100644
+index 00000000..9b20d833
+--- /dev/null
++++ b/src/modules/module-session-manager/client-session.c
+@@ -0,0 +1,270 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 <stdbool.h>
++#include <string.h>
++
++#include <pipewire/pipewire.h>
++#include <extensions/session-manager.h>
++
++#include "client-session.h"
++#include "session.h"
++#include "endpoint-link.h"
++
++#include <pipewire/private.h>
++
++#define NAME "client-session"
++
++struct factory_data {
++ struct pw_factory *factory;
++ struct pw_module *module;
++ struct spa_hook module_listener;
++};
++
++static struct endpoint_link *find_link(struct client_session *this, uint32_t id)
++{
++ struct endpoint_link *l;
++ spa_list_for_each(l, &this->links, link) {
++ if (l->id == id)
++ return l;
++ }
++ return NULL;
++}
++
++static int client_session_update(void *object,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_session_info *info)
++{
++ struct client_session *this = object;
++ struct session *session = &this->session;
++
++ return session_update(session, change_mask, n_params, params, info);
++}
++
++static int client_session_link_update(void *object,
++ uint32_t link_id,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_link_info *info)
++{
++ struct client_session *this = object;
++ struct session *session = &this->session;
++ struct endpoint_link *link = find_link(this, link_id);
++ struct pw_properties *props = NULL;
++
++ if (!link) {
++ struct pw_core *core = pw_global_get_core(session->global);
++ const char *keys[] = {
++ PW_KEY_FACTORY_ID,
++ PW_KEY_CLIENT_ID,
++ PW_KEY_SESSION_ID,
++ NULL
++ };
++
++ link = calloc(1, sizeof(struct endpoint_link));
++ if (!link)
++ goto no_mem;
++
++ props = pw_properties_new(NULL, NULL);
++ if (!props)
++ goto no_mem;
++ pw_properties_copy_keys (session->props, props, keys);
++
++ if (endpoint_link_init(link, link_id, session->info.id,
++ this, core, props) < 0)
++ goto no_mem;
++
++ spa_list_append(&this->links, &link->link);
++ }
++ else if (change_mask & PW_CLIENT_SESSION_LINK_UPDATE_DESTROYED) {
++ endpoint_link_clear(link);
++ spa_list_remove(&link->link);
++ free(link);
++ link = NULL;
++ }
++
++ return link ?
++ endpoint_link_update(link, change_mask, n_params, params, info)
++ : 0;
++
++ no_mem:
++ if (props)
++ pw_properties_free(props);
++ free(link);
++ pw_log_error(NAME" %p: cannot update link: no memory", this);
++ pw_resource_error(this->resource, -ENOMEM,
++ NAME" %p: cannot update link: no memory", this);
++ return -ENOMEM;
++}
++
++static struct pw_client_session_proxy_methods methods = {
++ PW_VERSION_CLIENT_SESSION_PROXY_METHODS,
++ .update = client_session_update,
++ .link_update = client_session_link_update,
++};
++
++static void client_session_destroy(void *data)
++{
++ struct client_session *this = data;
++ struct endpoint_link *l;
++
++ pw_log_debug(NAME" %p: destroy", this);
++
++ spa_list_consume(l, &this->links, link) {
++ endpoint_link_clear(l);
++ spa_list_remove(&l->link);
++ free(l);
++ }
++ session_clear(&this->session);
++ spa_hook_remove(&this->resource_listener);
++
++ free(this);
++}
++
++static const struct pw_resource_events resource_events = {
++ PW_VERSION_RESOURCE_EVENTS,
++ .destroy = client_session_destroy,
++};
++
++static void *create_object(void *data,
++ struct pw_resource *owner_resource,
++ uint32_t type,
++ uint32_t version,
++ struct pw_properties *properties,
++ uint32_t new_id)
++{
++ struct factory_data *d = data;
++ struct pw_factory *factory = d->factory;
++ struct client_session *this;
++ struct pw_client *owner = pw_resource_get_client(owner_resource);
++ struct pw_core *core = pw_client_get_core(owner);
++
++ this = calloc(1, sizeof(struct client_session));
++ if (this == NULL)
++ goto no_mem;
++
++ spa_list_init(&this->links);
++
++ pw_log_debug(NAME" %p: new", this);
++
++ if (!properties)
++ properties = pw_properties_new(NULL, NULL);
++ if (!properties)
++ goto no_mem;
++
++ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", owner->global->id);
++ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", factory->global->id);
++
++ this->resource = pw_resource_new(owner, new_id, PW_PERM_RWX, type, version, 0);
++ if (this->resource == NULL)
++ goto no_mem;
++
++ if (session_init(&this->session, this, core, properties) < 0)
++ goto no_mem;
++
++ pw_resource_add_listener(this->resource, &this->resource_listener,
++ &resource_events, this);
++ pw_resource_add_object_listener(this->resource, &this->object_listener,
++ &methods, this);
++
++ return this;
++
++ no_mem:
++ if (properties)
++ pw_properties_free(properties);
++ if (this && this->resource)
++ pw_resource_destroy(this->resource);
++ free(this);
++ pw_log_error("can't create client session: no memory");
++ pw_resource_error(owner_resource, -ENOMEM,
++ "can't create client session: no memory");
++ return NULL;
++}
++
++static const struct pw_factory_implementation impl_factory = {
++ PW_VERSION_FACTORY_IMPLEMENTATION,
++ .create_object = create_object,
++};
++
++static void module_destroy(void *data)
++{
++ struct factory_data *d = data;
++
++ spa_hook_remove(&d->module_listener);
++ pw_factory_destroy(d->factory);
++}
++
++static void module_registered(void *data)
++{
++ struct factory_data *d = data;
++ struct pw_module *module = d->module;
++ struct pw_factory *factory = d->factory;
++ struct spa_dict_item items[1];
++ char id[16];
++ int res;
++
++ snprintf(id, sizeof(id), "%d", module->global->id);
++ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
++ pw_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
++
++ if ((res = pw_factory_register(factory, NULL)) < 0) {
++ pw_log_error(NAME" %p: can't register factory: %s", factory, spa_strerror(res));
++ }
++}
++
++static const struct pw_module_events module_events = {
++ PW_VERSION_MODULE_EVENTS,
++ .destroy = module_destroy,
++ .registered = module_registered,
++};
++
++int client_session_factory_init(struct pw_module *module)
++{
++ struct pw_core *core = pw_module_get_core(module);
++ struct pw_factory *factory;
++ struct factory_data *data;
++
++ factory = pw_factory_new(core,
++ "client-session",
++ PW_TYPE_INTERFACE_ClientSession,
++ PW_VERSION_CLIENT_SESSION_PROXY,
++ NULL,
++ sizeof(*data));
++ if (factory == NULL)
++ return -ENOMEM;
++
++ data = pw_factory_get_user_data(factory);
++ data->factory = factory;
++ data->module = module;
++
++ pw_factory_set_implementation(factory, &impl_factory, data);
++
++ pw_module_add_listener(module, &data->module_listener, &module_events, data);
++
++ return 0;
++}
+diff --git a/src/modules/module-session-manager/client-session.h b/src/modules/module-session-manager/client-session.h
+new file mode 100644
+index 00000000..c764564d
+--- /dev/null
++++ b/src/modules/module-session-manager/client-session.h
+@@ -0,0 +1,62 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 MODULE_SESSION_MANAGER_CLIENT_SESSION_H
++#define MODULE_SESSION_MANAGER_CLIENT_SESSION_H
++
++#include "session.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++struct client_session {
++ struct pw_resource *resource;
++ struct spa_hook resource_listener;
++ struct spa_hook object_listener;
++ struct session session;
++ struct spa_list links;
++};
++
++#define pw_client_session_resource(r,m,v,...) \
++ pw_resource_call_res(r,struct pw_client_session_proxy_events,m,v,__VA_ARGS__)
++#define pw_client_session_resource_set_id(r,...) \
++ pw_client_session_resource(r,set_id,0,__VA_ARGS__)
++#define pw_client_session_resource_set_param(r,...) \
++ pw_client_session_resource(r,set_param,0,__VA_ARGS__)
++#define pw_client_session_resource_link_set_param(r,...) \
++ pw_client_session_resource(r,link_set_param,0,__VA_ARGS__)
++#define pw_client_session_resource_create_link(r,...) \
++ pw_client_session_resource(r,create_link,0,__VA_ARGS__)
++#define pw_client_session_resource_destroy_link(r,...) \
++ pw_client_session_resource(r,destroy_link,0,__VA_ARGS__)
++#define pw_client_session_resource_link_request_state(r,...) \
++ pw_client_session_resource(r,link_request_state,0,__VA_ARGS__)
++
++#ifdef __cplusplus
++} /* extern "C" */
++#endif
++
++#endif /* MODULE_SESSION_MANAGER_CLIENT_SESSION_H */
+diff --git a/src/modules/module-session-manager/endpoint-link.c b/src/modules/module-session-manager/endpoint-link.c
+new file mode 100644
+index 00000000..bce06598
+--- /dev/null
++++ b/src/modules/module-session-manager/endpoint-link.c
+@@ -0,0 +1,359 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 <stdbool.h>
++#include <string.h>
++
++#include <pipewire/pipewire.h>
++#include <extensions/session-manager.h>
++
++#include <spa/pod/filter.h>
++
++#include "endpoint-link.h"
++#include "client-session.h"
++
++#include <pipewire/private.h>
++
++#define NAME "endpoint-link"
++
++struct resource_data {
++ struct endpoint_link *link;
++ struct spa_hook resource_listener;
++ struct spa_hook object_listener;
++ uint32_t n_subscribe_ids;
++ uint32_t subscribe_ids[32];
++};
++
++#define pw_endpoint_link_resource(r,m,v,...) \
++ pw_resource_call(r,struct pw_endpoint_link_proxy_events,m,v,__VA_ARGS__)
++#define pw_endpoint_link_resource_info(r,...) \
++ pw_endpoint_link_resource(r,info,0,__VA_ARGS__)
++#define pw_endpoint_link_resource_param(r,...) \
++ pw_endpoint_link_resource(r,param,0,__VA_ARGS__)
++
++static int endpoint_link_enum_params (void *object, int seq,
++ uint32_t id, uint32_t start, uint32_t num,
++ const struct spa_pod *filter)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct endpoint_link *this = data->link;
++ struct spa_pod *result;
++ struct spa_pod *param;
++ uint8_t buffer[1024];
++ struct spa_pod_builder b = { 0 };
++ uint32_t index;
++ uint32_t next = start;
++ uint32_t count = 0;
++
++ while (true) {
++ index = next++;
++ if (index >= this->n_params)
++ break;
++
++ param = this->params[index];
++
++ if (param == NULL || !spa_pod_is_object_id(param, id))
++ continue;
++
++ spa_pod_builder_init(&b, buffer, sizeof(buffer));
++ if (spa_pod_filter(&b, &result, param, filter) != 0)
++ continue;
++
++ pw_log_debug(NAME" %p: %d param %u", this, seq, index);
++
++ pw_endpoint_link_resource_param(resource, seq, id, index, next, result);
++
++ if (++count == num)
++ break;
++ }
++ return 0;
++}
++
++static int endpoint_link_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ uint32_t i;
++
++ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
++ data->n_subscribe_ids = n_ids;
++
++ for (i = 0; i < n_ids; i++) {
++ data->subscribe_ids[i] = ids[i];
++ pw_log_debug(NAME" %p: resource %d subscribe param %u",
++ data->link, resource->id, ids[i]);
++ endpoint_link_enum_params(resource, 1, ids[i], 0, UINT32_MAX, NULL);
++ }
++ return 0;
++}
++
++static int endpoint_link_set_param (void *object, uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct endpoint_link *this = data->link;
++
++ pw_client_session_resource_set_param(this->client_sess->resource,
++ id, flags, param);
++
++ return 0;
++}
++
++static int endpoint_link_request_state(void *object, enum pw_endpoint_link_state state)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct endpoint_link *this = data->link;
++
++ pw_client_session_resource_link_request_state(this->client_sess->resource,
++ this->id, state);
++
++ return 0;
++}
++
++static int endpoint_link_destroy(void *object)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct endpoint_link *this = data->link;
++
++ pw_client_session_resource_destroy_link(this->client_sess->resource,
++ this->id);
++
++ return 0;
++}
++
++static const struct pw_endpoint_link_proxy_methods methods = {
++ PW_VERSION_ENDPOINT_LINK_PROXY_METHODS,
++ .subscribe_params = endpoint_link_subscribe_params,
++ .enum_params = endpoint_link_enum_params,
++ .set_param = endpoint_link_set_param,
++ .request_state = endpoint_link_request_state,
++ .destroy = endpoint_link_destroy,
++};
++
++static void endpoint_link_notify_subscribed(struct endpoint_link *this,
++ uint32_t index, uint32_t next)
++{
++ struct pw_global *global = this->global;
++ struct pw_resource *resource;
++ struct resource_data *data;
++ struct spa_pod *param = this->params[index];
++ uint32_t id;
++ uint32_t i;
++
++ if (!param || !spa_pod_is_object (param))
++ return;
++
++ id = SPA_POD_OBJECT_ID (param);
++
++ spa_list_for_each(resource, &global->resource_list, link) {
++ data = pw_resource_get_user_data(resource);
++ for (i = 0; i < data->n_subscribe_ids; i++) {
++ if (data->subscribe_ids[i] == id) {
++ pw_endpoint_link_resource_param(resource, 1,
++ id, index, next, param);
++ }
++ }
++ }
++}
++
++int endpoint_link_update(struct endpoint_link *this,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_link_info *info)
++{
++ if (change_mask & PW_CLIENT_SESSION_UPDATE_PARAMS) {
++ uint32_t i;
++ size_t size = n_params * sizeof(struct spa_pod *);
++
++ pw_log_debug(NAME" %p: update %d params", this, n_params);
++
++ for (i = 0; i < this->n_params; i++)
++ free(this->params[i]);
++ this->params = realloc(this->params, size);
++ if (size > 0 && !this->params) {
++ this->n_params = 0;
++ goto no_mem;
++ }
++ this->n_params = n_params;
++
++ for (i = 0; i < this->n_params; i++) {
++ this->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL;
++ endpoint_link_notify_subscribed(this, i, i+1);
++ }
++ }
++
++ if (change_mask & PW_CLIENT_SESSION_UPDATE_INFO) {
++ struct pw_resource *resource;
++
++ if (info->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_STATE) {
++ this->info.state = info->state;
++ free(this->info.error);
++ this->info.error = info->error ? strdup(info->error) : NULL;
++ }
++
++ if (info->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_PROPS)
++ pw_properties_update(this->props, info->props);
++
++ if (info->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_PARAMS) {
++ size_t size = info->n_params * sizeof(struct spa_param_info);
++
++ this->info.params = realloc(this->info.params, size);
++ if (size > 0 && !this->info.params) {
++ this->info.n_params = 0;
++ goto no_mem;
++ }
++ this->info.n_params = info->n_params;
++
++ memcpy(this->info.params, info->params, size);
++ }
++
++ if (!this->info.output_endpoint_id) {
++ this->info.output_endpoint_id = info->output_endpoint_id;
++ this->info.output_stream_id = info->output_stream_id;
++ this->info.input_endpoint_id = info->input_endpoint_id;
++ this->info.input_stream_id = info->input_stream_id;
++ }
++
++ this->info.change_mask = info->change_mask;
++ spa_list_for_each(resource, &this->global->resource_list, link) {
++ pw_endpoint_link_resource_info(resource, &this->info);
++ }
++ this->info.change_mask = 0;
++ }
++
++ return 0;
++
++ no_mem:
++ pw_log_error(NAME" %p: can't update: no memory", this);
++ pw_resource_error(this->client_sess->resource, -ENOMEM,
++ NAME" %p: can't update: no memory", this);
++ return -ENOMEM;
++}
++
++static void endpoint_link_unbind(void *data)
++{
++ struct pw_resource *resource = data;
++ spa_list_remove(&resource->link);
++}
++
++static const struct pw_resource_events resource_events = {
++ PW_VERSION_RESOURCE_EVENTS,
++ .destroy = endpoint_link_unbind,
++};
++
++static int endpoint_link_bind(void *_data, struct pw_client *client,
++ uint32_t permissions, uint32_t version, uint32_t id)
++{
++ struct endpoint_link *this = _data;
++ struct pw_global *global = this->global;
++ struct pw_resource *resource;
++ struct resource_data *data;
++
++ resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data));
++ if (resource == NULL)
++ goto no_mem;
++
++ data = pw_resource_get_user_data(resource);
++ data->link = this;
++ pw_resource_add_listener(resource, &data->resource_listener,
++ &resource_events, resource);
++ pw_resource_add_object_listener(resource, &data->object_listener,
++ &methods, resource);
++
++ pw_log_debug(NAME" %p: bound to %d", this, resource->id);
++
++ spa_list_append(&global->resource_list, &resource->link);
++
++ this->info.change_mask = PW_ENDPOINT_LINK_CHANGE_MASK_ALL;
++ pw_endpoint_link_resource_info(resource, &this->info);
++ this->info.change_mask = 0;
++
++ return 0;
++
++ no_mem:
++ pw_log_error(NAME" %p: can't create resource: no memory", this);
++ pw_resource_error(this->client_sess->resource, -ENOMEM,
++ NAME" %p: can't create resource: no memory", this);
++ return -ENOMEM;
++}
++
++int endpoint_link_init(struct endpoint_link *this,
++ uint32_t id, uint32_t session_id,
++ struct client_session *client_sess,
++ struct pw_core *core,
++ struct pw_properties *properties)
++{
++ pw_log_debug(NAME" %p: new", this);
++
++ this->client_sess = client_sess;
++ this->id = id;
++ this->props = properties;
++
++ properties = pw_properties_copy(properties);
++ if (!properties)
++ goto no_mem;
++
++ this->global = pw_global_new (core,
++ PW_TYPE_INTERFACE_EndpointLink,
++ PW_VERSION_ENDPOINT_LINK_PROXY,
++ properties, endpoint_link_bind, this);
++ if (!this->global)
++ goto no_mem;
++
++ this->info.version = PW_VERSION_ENDPOINT_LINK_INFO;
++ this->info.id = this->global->id;
++ this->info.session_id = session_id;
++ this->info.props = &this->props->dict;
++
++ return pw_global_register(this->global);
++
++ no_mem:
++ pw_log_error(NAME" - can't create - out of memory");
++ return -ENOMEM;
++}
++
++void endpoint_link_clear(struct endpoint_link *this)
++{
++ uint32_t i;
++
++ pw_log_debug(NAME" %p: destroy", this);
++
++ pw_global_destroy(this->global);
++
++ for (i = 0; i < this->n_params; i++)
++ free(this->params[i]);
++ free(this->params);
++
++ free(this->info.error);
++ free(this->info.params);
++
++ if (this->props)
++ pw_properties_free(this->props);
++}
+diff --git a/src/modules/module-session-manager/endpoint-link.h b/src/modules/module-session-manager/endpoint-link.h
+new file mode 100644
+index 00000000..a9c18d32
+--- /dev/null
++++ b/src/modules/module-session-manager/endpoint-link.h
+@@ -0,0 +1,64 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 MODULE_SESSION_MANAGER_ENDPOINT_LINK_H
++#define MODULE_SESSION_MANAGER_ENDPOINT_LINK_H
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++struct client_session;
++
++struct endpoint_link {
++ struct spa_list link;
++ struct client_session *client_sess;
++ struct pw_global *global;
++ uint32_t id; /* session-local link id */
++ uint32_t n_params;
++ struct spa_pod **params;
++ struct pw_endpoint_link_info info;
++ struct pw_properties *props; /* wrapper of info.props */
++};
++
++int endpoint_link_init(struct endpoint_link *this,
++ uint32_t id, uint32_t session_id,
++ struct client_session *client_sess,
++ struct pw_core *core,
++ struct pw_properties *properties);
++
++void endpoint_link_clear(struct endpoint_link *this);
++
++int endpoint_link_update(struct endpoint_link *this,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_link_info *info);
++
++#ifdef __cplusplus
++} /* extern "C" */
++#endif
++
++#endif /* MODULE_SESSION_MANAGER_ENDPOINT_LINK_H */
+diff --git a/src/modules/module-session-manager/endpoint-stream.c b/src/modules/module-session-manager/endpoint-stream.c
+new file mode 100644
+index 00000000..47d2a4ea
+--- /dev/null
++++ b/src/modules/module-session-manager/endpoint-stream.c
+@@ -0,0 +1,329 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 <stdbool.h>
++#include <string.h>
++
++#include <pipewire/pipewire.h>
++#include <extensions/session-manager.h>
++
++#include <spa/pod/filter.h>
++
++#include "endpoint-stream.h"
++#include "client-endpoint.h"
++
++#include <pipewire/private.h>
++
++#define NAME "endpoint-stream"
++
++struct resource_data {
++ struct endpoint_stream *stream;
++ struct spa_hook resource_listener;
++ struct spa_hook object_listener;
++ uint32_t n_subscribe_ids;
++ uint32_t subscribe_ids[32];
++};
++
++#define pw_endpoint_stream_resource(r,m,v,...) \
++ pw_resource_call(r,struct pw_endpoint_stream_proxy_events,m,v,__VA_ARGS__)
++#define pw_endpoint_stream_resource_info(r,...) \
++ pw_endpoint_stream_resource(r,info,0,__VA_ARGS__)
++#define pw_endpoint_stream_resource_param(r,...) \
++ pw_endpoint_stream_resource(r,param,0,__VA_ARGS__)
++
++static int endpoint_stream_enum_params (void *object, int seq,
++ uint32_t id, uint32_t start, uint32_t num,
++ const struct spa_pod *filter)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct endpoint_stream *this = data->stream;
++ struct spa_pod *result;
++ struct spa_pod *param;
++ uint8_t buffer[1024];
++ struct spa_pod_builder b = { 0 };
++ uint32_t index;
++ uint32_t next = start;
++ uint32_t count = 0;
++
++ while (true) {
++ index = next++;
++ if (index >= this->n_params)
++ break;
++
++ param = this->params[index];
++
++ if (param == NULL || !spa_pod_is_object_id(param, id))
++ continue;
++
++ spa_pod_builder_init(&b, buffer, sizeof(buffer));
++ if (spa_pod_filter(&b, &result, param, filter) != 0)
++ continue;
++
++ pw_log_debug(NAME" %p: %d param %u", this, seq, index);
++
++ pw_endpoint_stream_resource_param(resource, seq, id, index, next, result);
++
++ if (++count == num)
++ break;
++ }
++ return 0;
++}
++
++static int endpoint_stream_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ uint32_t i;
++
++ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
++ data->n_subscribe_ids = n_ids;
++
++ for (i = 0; i < n_ids; i++) {
++ data->subscribe_ids[i] = ids[i];
++ pw_log_debug(NAME" %p: resource %d subscribe param %u",
++ data->stream, resource->id, ids[i]);
++ endpoint_stream_enum_params(resource, 1, ids[i], 0, UINT32_MAX, NULL);
++ }
++ return 0;
++}
++
++static int endpoint_stream_set_param (void *object, uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct endpoint_stream *this = data->stream;
++
++ pw_client_endpoint_resource_set_param(this->client_ep->resource,
++ id, flags, param);
++
++ return 0;
++}
++
++static const struct pw_endpoint_stream_proxy_methods methods = {
++ PW_VERSION_ENDPOINT_STREAM_PROXY_METHODS,
++ .subscribe_params = endpoint_stream_subscribe_params,
++ .enum_params = endpoint_stream_enum_params,
++ .set_param = endpoint_stream_set_param,
++};
++
++static void endpoint_stream_notify_subscribed(struct endpoint_stream *this,
++ uint32_t index, uint32_t next)
++{
++ struct pw_global *global = this->global;
++ struct pw_resource *resource;
++ struct resource_data *data;
++ struct spa_pod *param = this->params[index];
++ uint32_t id;
++ uint32_t i;
++
++ if (!param || !spa_pod_is_object (param))
++ return;
++
++ id = SPA_POD_OBJECT_ID (param);
++
++ spa_list_for_each(resource, &global->resource_list, link) {
++ data = pw_resource_get_user_data(resource);
++ for (i = 0; i < data->n_subscribe_ids; i++) {
++ if (data->subscribe_ids[i] == id) {
++ pw_endpoint_stream_resource_param(resource, 1,
++ id, index, next, param);
++ }
++ }
++ }
++}
++
++int endpoint_stream_update(struct endpoint_stream *this,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_stream_info *info)
++{
++ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_PARAMS) {
++ uint32_t i;
++ size_t size = n_params * sizeof(struct spa_pod *);
++
++ pw_log_debug(NAME" %p: update %d params", this, n_params);
++
++ for (i = 0; i < this->n_params; i++)
++ free(this->params[i]);
++ this->params = realloc(this->params, size);
++ if (size > 0 && !this->params) {
++ this->n_params = 0;
++ goto no_mem;
++ }
++ this->n_params = n_params;
++
++ for (i = 0; i < this->n_params; i++) {
++ this->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL;
++ endpoint_stream_notify_subscribed(this, i, i+1);
++ }
++ }
++
++ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_INFO) {
++ struct pw_resource *resource;
++
++ if (info->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_LINK_PARAMS) {
++ free(this->info.link_params);
++ this->info.link_params = spa_pod_copy(info->link_params);
++ }
++
++ if (info->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS)
++ pw_properties_update(this->props, info->props);
++
++ if (info->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS) {
++ size_t size = info->n_params * sizeof(struct spa_param_info);
++
++ this->info.params = realloc(this->info.params, size);
++ if (size > 0 && !this->info.params) {
++ this->info.n_params = 0;
++ goto no_mem;
++ }
++ this->info.n_params = info->n_params;
++
++ memcpy(this->info.params, info->params, size);
++ }
++
++ if (!this->info.name)
++ this->info.name = strdup(info->name);
++
++ this->info.change_mask = info->change_mask;
++ spa_list_for_each(resource, &this->global->resource_list, link) {
++ pw_endpoint_stream_resource_info(resource, &this->info);
++ }
++ this->info.change_mask = 0;
++ }
++
++ return 0;
++
++ no_mem:
++ pw_log_error(NAME" can't update: no memory");
++ pw_resource_error(this->client_ep->resource, -ENOMEM,
++ NAME" can't update: no memory");
++ return -ENOMEM;
++}
++
++static void endpoint_stream_unbind(void *data)
++{
++ struct pw_resource *resource = data;
++ spa_list_remove(&resource->link);
++}
++
++static const struct pw_resource_events resource_events = {
++ PW_VERSION_RESOURCE_EVENTS,
++ .destroy = endpoint_stream_unbind,
++};
++
++static int endpoint_stream_bind(void *_data, struct pw_client *client,
++ uint32_t permissions, uint32_t version, uint32_t id)
++{
++ struct endpoint_stream *this = _data;
++ struct pw_global *global = this->global;
++ struct pw_resource *resource;
++ struct resource_data *data;
++
++ resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data));
++ if (resource == NULL)
++ goto no_mem;
++
++ data = pw_resource_get_user_data(resource);
++ data->stream = this;
++ pw_resource_add_listener(resource, &data->resource_listener,
++ &resource_events, resource);
++ pw_resource_add_object_listener(resource, &data->object_listener,
++ &methods, resource);
++
++ pw_log_debug(NAME" %p: bound to %d", this, resource->id);
++
++ spa_list_append(&global->resource_list, &resource->link);
++
++ this->info.change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_ALL;
++ pw_endpoint_stream_resource_info(resource, &this->info);
++ this->info.change_mask = 0;
++
++ return 0;
++
++ no_mem:
++ pw_log_error(NAME" can't create resource: no memory");
++ pw_resource_error(this->client_ep->resource, -ENOMEM,
++ NAME" can't create resource: no memory");
++ return -ENOMEM;
++}
++
++int endpoint_stream_init(struct endpoint_stream *this,
++ uint32_t id, uint32_t endpoint_id,
++ struct client_endpoint *client_ep,
++ struct pw_core *core,
++ struct pw_properties *properties)
++{
++ pw_log_debug(NAME" %p: new", this);
++
++ this->client_ep = client_ep;
++ this->id = id;
++ this->props = properties;
++
++ properties = pw_properties_copy(properties);
++ if (!properties)
++ goto no_mem;
++
++ this->global = pw_global_new (core,
++ PW_TYPE_INTERFACE_EndpointStream,
++ PW_VERSION_ENDPOINT_STREAM_PROXY,
++ properties, endpoint_stream_bind, this);
++ if (!this->global)
++ goto no_mem;
++
++ this->info.version = PW_VERSION_ENDPOINT_STREAM_INFO;
++ this->info.id = this->global->id;
++ this->info.endpoint_id = endpoint_id;
++ this->info.props = &this->props->dict;
++
++ return pw_global_register(this->global);
++
++ no_mem:
++ pw_log_error(NAME" - can't create - out of memory");
++ return -ENOMEM;
++}
++
++void endpoint_stream_clear(struct endpoint_stream *this)
++{
++ uint32_t i;
++
++ pw_log_debug(NAME" %p: destroy", this);
++
++ pw_global_destroy(this->global);
++
++ for (i = 0; i < this->n_params; i++)
++ free(this->params[i]);
++ free(this->params);
++
++ free(this->info.name);
++ free(this->info.link_params);
++ free(this->info.params);
++
++ if (this->props)
++ pw_properties_free(this->props);
++}
+diff --git a/src/modules/module-session-manager/endpoint-stream.h b/src/modules/module-session-manager/endpoint-stream.h
+new file mode 100644
+index 00000000..99bd4836
+--- /dev/null
++++ b/src/modules/module-session-manager/endpoint-stream.h
+@@ -0,0 +1,64 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 MODULE_SESSION_MANAGER_ENDPOINT_STREAM_H
++#define MODULE_SESSION_MANAGER_ENDPOINT_STREAM_H
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++struct client_endpoint;
++
++struct endpoint_stream {
++ struct spa_list link;
++ struct client_endpoint *client_ep;
++ struct pw_global *global;
++ uint32_t id; /* endpoint-local stream id */
++ uint32_t n_params;
++ struct spa_pod **params;
++ struct pw_endpoint_stream_info info;
++ struct pw_properties *props; /* wrapper of info.props */
++};
++
++int endpoint_stream_init(struct endpoint_stream *this,
++ uint32_t id, uint32_t endpoint_id,
++ struct client_endpoint *client_ep,
++ struct pw_core *core,
++ struct pw_properties *properties);
++
++void endpoint_stream_clear(struct endpoint_stream *this);
++
++int endpoint_stream_update(struct endpoint_stream *this,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_stream_info *info);
++
++#ifdef __cplusplus
++} /* extern "C" */
++#endif
++
++#endif /* MODULE_SESSION_MANAGER_ENDPOINT_STREAM_H */
+diff --git a/src/modules/module-session-manager/endpoint.c b/src/modules/module-session-manager/endpoint.c
+new file mode 100644
+index 00000000..0866e71d
+--- /dev/null
++++ b/src/modules/module-session-manager/endpoint.c
+@@ -0,0 +1,343 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 <stdbool.h>
++#include <string.h>
++
++#include <pipewire/pipewire.h>
++#include <extensions/session-manager.h>
++
++#include <spa/pod/filter.h>
++
++#include "endpoint.h"
++#include "client-endpoint.h"
++
++#include <pipewire/private.h>
++
++#define NAME "endpoint"
++
++struct resource_data {
++ struct endpoint *endpoint;
++ struct spa_hook resource_listener;
++ struct spa_hook object_listener;
++ uint32_t n_subscribe_ids;
++ uint32_t subscribe_ids[32];
++};
++
++#define pw_endpoint_resource(r,m,v,...) \
++ pw_resource_call(r,struct pw_endpoint_proxy_events,m,v,__VA_ARGS__)
++#define pw_endpoint_resource_info(r,...) \
++ pw_endpoint_resource(r,info,0,__VA_ARGS__)
++#define pw_endpoint_resource_param(r,...) \
++ pw_endpoint_resource(r,param,0,__VA_ARGS__)
++
++static int endpoint_enum_params (void *object, int seq,
++ uint32_t id, uint32_t start, uint32_t num,
++ const struct spa_pod *filter)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct endpoint *this = data->endpoint;
++ struct spa_pod *result;
++ struct spa_pod *param;
++ uint8_t buffer[1024];
++ struct spa_pod_builder b = { 0 };
++ uint32_t index;
++ uint32_t next = start;
++ uint32_t count = 0;
++
++ while (true) {
++ index = next++;
++ if (index >= this->n_params)
++ break;
++
++ param = this->params[index];
++
++ if (param == NULL || !spa_pod_is_object_id(param, id))
++ continue;
++
++ spa_pod_builder_init(&b, buffer, sizeof(buffer));
++ if (spa_pod_filter(&b, &result, param, filter) != 0)
++ continue;
++
++ pw_log_debug(NAME" %p: %d param %u", this, seq, index);
++
++ pw_endpoint_resource_param(resource, seq, id, index, next, result);
++
++ if (++count == num)
++ break;
++ }
++ return 0;
++}
++
++static int endpoint_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ uint32_t i;
++
++ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
++ data->n_subscribe_ids = n_ids;
++
++ for (i = 0; i < n_ids; i++) {
++ data->subscribe_ids[i] = ids[i];
++ pw_log_debug(NAME" %p: resource %d subscribe param %u",
++ data->endpoint, resource->id, ids[i]);
++ endpoint_enum_params(resource, 1, ids[i], 0, UINT32_MAX, NULL);
++ }
++ return 0;
++}
++
++static int endpoint_set_param (void *object, uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct endpoint *this = data->endpoint;
++
++ pw_client_endpoint_resource_set_param(this->client_ep->resource,
++ id, flags, param);
++
++ return 0;
++}
++
++static const struct pw_endpoint_proxy_methods methods = {
++ PW_VERSION_ENDPOINT_PROXY_METHODS,
++ .subscribe_params = endpoint_subscribe_params,
++ .enum_params = endpoint_enum_params,
++ .set_param = endpoint_set_param,
++};
++
++static void endpoint_notify_subscribed(struct endpoint *this,
++ uint32_t index, uint32_t next)
++{
++ struct pw_global *global = this->global;
++ struct pw_resource *resource;
++ struct resource_data *data;
++ struct spa_pod *param = this->params[index];
++ uint32_t id;
++ uint32_t i;
++
++ if (!param || !spa_pod_is_object (param))
++ return;
++
++ id = SPA_POD_OBJECT_ID (param);
++
++ spa_list_for_each(resource, &global->resource_list, link) {
++ data = pw_resource_get_user_data(resource);
++ for (i = 0; i < data->n_subscribe_ids; i++) {
++ if (data->subscribe_ids[i] == id) {
++ pw_endpoint_resource_param(resource, 1, id,
++ index, next, param);
++ }
++ }
++ }
++}
++
++int endpoint_update(struct endpoint *this,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_info *info)
++{
++ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_PARAMS) {
++ uint32_t i;
++ size_t size = n_params * sizeof(struct spa_pod *);
++
++ pw_log_debug(NAME" %p: update %d params", this, n_params);
++
++ for (i = 0; i < this->n_params; i++)
++ free(this->params[i]);
++ this->params = realloc(this->params, size);
++ if (size > 0 && !this->params) {
++ this->n_params = 0;
++ goto no_mem;
++ }
++ this->n_params = n_params;
++
++ for (i = 0; i < this->n_params; i++) {
++ this->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL;
++ endpoint_notify_subscribed(this, i, i+1);
++ }
++ }
++
++ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_INFO) {
++ struct pw_resource *resource;
++
++ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_STREAMS)
++ this->info.n_streams = info->n_streams;
++
++ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_SESSION)
++ this->info.session_id = info->session_id;
++
++ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS)
++ pw_properties_update(this->props, info->props);
++
++ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) {
++ size_t size = info->n_params * sizeof(struct spa_param_info);
++
++ this->info.params = realloc(this->info.params, size);
++ if (size > 0 && !this->info.params) {
++ this->info.n_params = 0;
++ goto no_mem;
++ }
++ this->info.n_params = info->n_params;
++
++ memcpy(this->info.params, info->params, size);
++ }
++
++ if (!this->info.name) {
++ this->info.name = strdup(info->name);
++ this->info.media_class = strdup(info->media_class);
++ this->info.direction = info->direction;
++ this->info.flags = info->flags;
++ }
++
++ this->info.change_mask = info->change_mask;
++ spa_list_for_each(resource, &this->global->resource_list, link) {
++ pw_endpoint_resource_info(resource, &this->info);
++ }
++ this->info.change_mask = 0;
++ }
++
++ return 0;
++
++ no_mem:
++ pw_log_error(NAME" can't update: no memory");
++ pw_resource_error(this->client_ep->resource, -ENOMEM,
++ NAME" can't update: no memory");
++ return -ENOMEM;
++}
++
++static void endpoint_unbind(void *data)
++{
++ struct pw_resource *resource = data;
++ spa_list_remove(&resource->link);
++}
++
++static const struct pw_resource_events resource_events = {
++ PW_VERSION_RESOURCE_EVENTS,
++ .destroy = endpoint_unbind,
++};
++
++static int endpoint_bind(void *_data, struct pw_client *client,
++ uint32_t permissions, uint32_t version, uint32_t id)
++{
++ struct endpoint *this = _data;
++ struct pw_global *global = this->global;
++ struct pw_resource *resource;
++ struct resource_data *data;
++
++ resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data));
++ if (resource == NULL)
++ goto no_mem;
++
++ data = pw_resource_get_user_data(resource);
++ data->endpoint = this;
++ pw_resource_add_listener(resource, &data->resource_listener,
++ &resource_events, resource);
++ pw_resource_add_object_listener(resource, &data->object_listener,
++ &methods, resource);
++
++ pw_log_debug(NAME" %p: bound to %d", this, resource->id);
++
++ spa_list_append(&global->resource_list, &resource->link);
++
++ this->info.change_mask = PW_ENDPOINT_CHANGE_MASK_ALL;
++ pw_endpoint_resource_info(resource, &this->info);
++ this->info.change_mask = 0;
++
++ return 0;
++
++ no_mem:
++ pw_log_error(NAME" can't create resource: no memory");
++ pw_resource_error(this->client_ep->resource, -ENOMEM,
++ NAME" can't create resource: no memory");
++ return -ENOMEM;
++}
++
++int endpoint_init(struct endpoint *this,
++ struct client_endpoint *client_ep,
++ struct pw_core *core,
++ struct pw_properties *properties)
++{
++ const char *keys[] = {
++ PW_KEY_FACTORY_ID,
++ PW_KEY_CLIENT_ID,
++ NULL
++ };
++
++ pw_log_debug(NAME" %p: new", this);
++
++ this->client_ep = client_ep;
++ this->props = properties;
++
++ properties = pw_properties_new(NULL, NULL);
++ if (!properties)
++ goto no_mem;
++
++ pw_properties_copy_keys(this->props, properties, keys);
++
++ this->global = pw_global_new (core,
++ PW_TYPE_INTERFACE_Endpoint,
++ PW_VERSION_ENDPOINT_PROXY,
++ properties, endpoint_bind, this);
++ if (!this->global)
++ goto no_mem;
++
++ pw_properties_setf(this->props, PW_KEY_ENDPOINT_ID, "%u", this->global->id);
++
++ this->info.version = PW_VERSION_ENDPOINT_INFO;
++ this->info.id = this->global->id;
++ this->info.props = &this->props->dict;
++
++ pw_client_endpoint_resource_set_id(client_ep->resource, this->global->id);
++
++ return pw_global_register(this->global);
++
++ no_mem:
++ pw_log_error(NAME" - can't create - out of memory");
++ return -ENOMEM;
++}
++
++void endpoint_clear(struct endpoint *this)
++{
++ uint32_t i;
++
++ pw_log_debug(NAME" %p: destroy", this);
++
++ pw_global_destroy(this->global);
++
++ for (i = 0; i < this->n_params; i++)
++ free(this->params[i]);
++ free(this->params);
++
++ free(this->info.name);
++ free(this->info.media_class);
++ free(this->info.params);
++
++ if (this->props)
++ pw_properties_free(this->props);
++}
+diff --git a/src/modules/module-session-manager/endpoint.h b/src/modules/module-session-manager/endpoint.h
+new file mode 100644
+index 00000000..89d26028
+--- /dev/null
++++ b/src/modules/module-session-manager/endpoint.h
+@@ -0,0 +1,61 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 MODULE_SESSION_MANAGER_ENDPOINT_H
++#define MODULE_SESSION_MANAGER_ENDPOINT_H
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++struct client_endpoint;
++
++struct endpoint {
++ struct client_endpoint *client_ep;
++ struct pw_global *global;
++ uint32_t n_params;
++ struct spa_pod **params;
++ struct pw_endpoint_info info;
++ struct pw_properties *props; /* wrapper of info.props */
++};
++
++int endpoint_init(struct endpoint *this,
++ struct client_endpoint *client_ep,
++ struct pw_core *core,
++ struct pw_properties *properties);
++
++void endpoint_clear(struct endpoint *this);
++
++int endpoint_update(struct endpoint *this,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_info *info);
++
++#ifdef __cplusplus
++} /* extern "C" */
++#endif
++
++#endif /* MODULE_SESSION_MANAGER_ENDPOINT_H */
+diff --git a/src/modules/module-session-manager/protocol-native.c b/src/modules/module-session-manager/protocol-native.c
+new file mode 100644
+index 00000000..2c791ffc
+--- /dev/null
++++ b/src/modules/module-session-manager/protocol-native.c
+@@ -0,0 +1,2125 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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/pipewire.h>
++#include <spa/pod/parser.h>
++
++#include <extensions/session-manager.h>
++#include <extensions/protocol-native.h>
++
++static void push_dict(struct spa_pod_builder *b, const struct spa_dict *dict)
++{
++ struct spa_pod_frame f;
++ uint32_t n_items;
++ uint32_t i;
++
++ n_items = dict ? dict->n_items : 0;
++
++ spa_pod_builder_push_struct(b, &f);
++ spa_pod_builder_add(b, SPA_POD_Int(n_items), NULL);
++ for (i = 0; i < n_items; i++) {
++ spa_pod_builder_add(b,
++ SPA_POD_String(dict->items[i].key),
++ SPA_POD_String(dict->items[i].value),
++ NULL);
++ }
++ spa_pod_builder_pop(b, &f);
++}
++
++/* macro because of alloca() */
++#define parse_dict(p, f, dict) \
++do { \
++ uint32_t i; \
++ \
++ if (spa_pod_parser_push_struct(p, f) < 0 || \
++ spa_pod_parser_get(p, SPA_POD_Int(&(dict)->n_items), NULL) < 0) \
++ return -EINVAL; \
++ \
++ if ((dict)->n_items > 0) { \
++ (dict)->items = alloca((dict)->n_items * sizeof(struct spa_dict_item)); \
++ for (i = 0; i < (dict)->n_items; i++) { \
++ if (spa_pod_parser_get(p, \
++ SPA_POD_String(&(dict)->items[i].key), \
++ SPA_POD_String(&(dict)->items[i].value), \
++ NULL) < 0) \
++ return -EINVAL; \
++ } \
++ } \
++ spa_pod_parser_pop(p, f); \
++} while(0)
++
++static void push_param_infos(struct spa_pod_builder *b, uint32_t n_params,
++ const struct spa_param_info *params)
++{
++ struct spa_pod_frame f;
++ uint32_t i;
++
++ spa_pod_builder_push_struct(b, &f);
++ spa_pod_builder_add(b, SPA_POD_Int(n_params), NULL);
++ for (i = 0; i < n_params; i++) {
++ spa_pod_builder_add(b,
++ SPA_POD_Id(params[i].id),
++ SPA_POD_Int(params[i].flags),
++ NULL);
++ }
++ spa_pod_builder_pop(b, &f);
++}
++
++/* macro because of alloca() */
++#define parse_param_infos(p, f, n_params_p, params_p) \
++do { \
++ uint32_t i; \
++ \
++ if (spa_pod_parser_push_struct(p, f) < 0 || \
++ spa_pod_parser_get(p, SPA_POD_Int(n_params_p), NULL) < 0) \
++ return -EINVAL; \
++ \
++ if (*(n_params_p) > 0) { \
++ *(params_p) = alloca(*(n_params_p) * sizeof(struct spa_param_info)); \
++ for (i = 0; i < *(n_params_p); i++) { \
++ if (spa_pod_parser_get(p, \
++ SPA_POD_Id(&(*(params_p))[i].id), \
++ SPA_POD_Int(&(*(params_p))[i].flags), \
++ NULL) < 0) \
++ return -EINVAL; \
++ } \
++ } \
++ spa_pod_parser_pop(p, f); \
++} while(0)
++
++/***********************************************
++ * INFO STRUCTURES
++ ***********************************************/
++
++static void
++marshal_pw_session_info(struct spa_pod_builder *b,
++ const struct pw_session_info *info)
++{
++ struct spa_pod_frame f;
++
++ spa_pod_builder_push_struct(b, &f);
++ spa_pod_builder_add(b,
++ SPA_POD_Int(info->version),
++ SPA_POD_Int(info->id),
++ SPA_POD_Int(info->change_mask),
++ NULL);
++ push_dict(b, info->props);
++ push_param_infos(b, info->n_params, info->params);
++ spa_pod_builder_pop(b, &f);
++}
++
++/* macro because of alloca() */
++#define demarshal_pw_session_info(p, f, info) \
++do { \
++ struct spa_pod_frame sub_f; \
++ uint32_t version; \
++ \
++ if (spa_pod_parser_push_struct(p, f) < 0 || \
++ spa_pod_parser_get(p, \
++ SPA_POD_Int(&version), \
++ SPA_POD_Int(&(info)->id), \
++ SPA_POD_Int(&(info)->change_mask), \
++ SPA_POD_Int(&(info)->n_params), \
++ SPA_POD_Int(&(info)->props->n_items), \
++ NULL) < 0) \
++ return -EINVAL; \
++ \
++ (info)->change_mask &= PW_SESSION_CHANGE_MASK_ALL; \
++ \
++ parse_dict(p, &sub_f, (info)->props); \
++ parse_param_infos(p, &sub_f, &(info)->n_params, &(info)->params); \
++ \
++ spa_pod_parser_pop(p, f); \
++} while(0)
++
++static void
++marshal_pw_endpoint_info(struct spa_pod_builder *b,
++ const struct pw_endpoint_info *info)
++{
++ struct spa_pod_frame f;
++
++ spa_pod_builder_push_struct(b, &f);
++ spa_pod_builder_add(b,
++ SPA_POD_Int(info->version),
++ SPA_POD_Int(info->id),
++ SPA_POD_String(info->name),
++ SPA_POD_String(info->media_class),
++ SPA_POD_Int(info->direction),
++ SPA_POD_Int(info->flags),
++ SPA_POD_Int(info->change_mask),
++ NULL);
++ push_dict(b, info->props);
++ push_param_infos(b, info->n_params, info->params);
++ spa_pod_builder_pop(b, &f);
++}
++
++/* macro because of alloca() */
++#define demarshal_pw_endpoint_info(p, f, info) \
++do { \
++ struct spa_pod_frame sub_f; \
++ uint32_t version; \
++ \
++ if (spa_pod_parser_push_struct(p, f) < 0 || \
++ spa_pod_parser_get(p, \
++ SPA_POD_Int(&version), \
++ SPA_POD_Int(&(info)->id), \
++ SPA_POD_String(&(info)->name), \
++ SPA_POD_String(&(info)->media_class), \
++ SPA_POD_Int(&(info)->direction), \
++ SPA_POD_Int(&(info)->flags), \
++ SPA_POD_Int(&(info)->change_mask), \
++ NULL) < 0) \
++ return -EINVAL; \
++ \
++ (info)->change_mask &= PW_ENDPOINT_CHANGE_MASK_ALL; \
++ \
++ parse_dict(p, &sub_f, (info)->props); \
++ parse_param_infos(p, &sub_f, &(info)->n_params, &(info)->params); \
++ \
++ spa_pod_parser_pop(p, f); \
++} while(0)
++
++static void
++marshal_pw_endpoint_stream_info(struct spa_pod_builder *b,
++ const struct pw_endpoint_stream_info *info)
++{
++ struct spa_pod_frame f;
++
++ spa_pod_builder_push_struct(b, &f);
++ spa_pod_builder_add(b,
++ SPA_POD_Int(info->version),
++ SPA_POD_Int(info->id),
++ SPA_POD_Int(info->endpoint_id),
++ SPA_POD_String(info->name),
++ SPA_POD_Int(info->change_mask),
++ SPA_POD_Pod(info->link_params),
++ NULL);
++ push_dict(b, info->props);
++ push_param_infos(b, info->n_params, info->params);
++ spa_pod_builder_pop(b, &f);
++}
++
++/* macro because of alloca() */
++#define demarshal_pw_endpoint_stream_info(p, f, info) \
++do { \
++ struct spa_pod_frame sub_f; \
++ uint32_t version; \
++ \
++ if (spa_pod_parser_push_struct(p, f) < 0 || \
++ spa_pod_parser_get(p, \
++ SPA_POD_Int(&version), \
++ SPA_POD_Int(&(info)->id), \
++ SPA_POD_Int(&(info)->endpoint_id), \
++ SPA_POD_String(&(info)->name), \
++ SPA_POD_Int(&(info)->change_mask), \
++ SPA_POD_Pod(&(info)->link_params), \
++ NULL) < 0) \
++ return -EINVAL; \
++ \
++ (info)->change_mask &= PW_ENDPOINT_STREAM_CHANGE_MASK_ALL; \
++ \
++ parse_dict(p, &sub_f, (info)->props); \
++ parse_param_infos(p, &sub_f, &(info)->n_params, &(info)->params); \
++ \
++ spa_pod_parser_pop(p, f); \
++} while(0)
++
++static void
++marshal_pw_endpoint_link_info(struct spa_pod_builder *b,
++ const struct pw_endpoint_link_info *info)
++{
++ struct spa_pod_frame f;
++
++ spa_pod_builder_push_struct(b, &f);
++ spa_pod_builder_add(b,
++ SPA_POD_Int(info->version),
++ SPA_POD_Int(info->id),
++ SPA_POD_Int(info->session_id),
++ SPA_POD_Int(info->output_endpoint_id),
++ SPA_POD_Int(info->output_stream_id),
++ SPA_POD_Int(info->input_endpoint_id),
++ SPA_POD_Int(info->input_stream_id),
++ SPA_POD_Int(info->change_mask),
++ SPA_POD_Int(info->state),
++ SPA_POD_String(info->error),
++ NULL);
++ push_dict(b, info->props);
++ push_param_infos(b, info->n_params, info->params);
++ spa_pod_builder_pop(b, &f);
++}
++
++/* macro because of alloca() */
++#define demarshal_pw_endpoint_link_info(p, f, info) \
++do { \
++ struct spa_pod_frame sub_f; \
++ uint32_t version; \
++ \
++ if (spa_pod_parser_push_struct(p, f) < 0 || \
++ spa_pod_parser_get(p, \
++ SPA_POD_Int(&version), \
++ SPA_POD_Int(&(info)->id), \
++ SPA_POD_Int(&(info)->session_id), \
++ SPA_POD_Int(&(info)->output_endpoint_id), \
++ SPA_POD_Int(&(info)->output_stream_id), \
++ SPA_POD_Int(&(info)->input_endpoint_id), \
++ SPA_POD_Int(&(info)->input_stream_id), \
++ SPA_POD_Int(&(info)->change_mask), \
++ SPA_POD_Int(&(info)->state), \
++ SPA_POD_String(&(info)->error), \
++ NULL) < 0) \
++ return -EINVAL; \
++ \
++ (info)->change_mask &= PW_ENDPOINT_LINK_CHANGE_MASK_ALL; \
++ \
++ parse_dict(p, &sub_f, (info)->props); \
++ parse_param_infos(p, &sub_f, &(info)->n_params, &(info)->params); \
++ \
++ spa_pod_parser_pop(p, f); \
++} while(0)
++
++/***********************************************
++ * CLIENT ENDPOINT
++ ***********************************************/
++
++static int client_endpoint_marshal_set_id (void *object, uint32_t id)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_ID, NULL);
++
++ spa_pod_builder_add (b, SPA_POD_Int(id), NULL);
++
++ return pw_protocol_native_end_resource(resource, b);
++}
++
++static int client_endpoint_marshal_set_session_id (void *object, uint32_t id)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_SESSION_ID, NULL);
++
++ spa_pod_builder_add (b, SPA_POD_Int(id), NULL);
++
++ return pw_protocol_native_end_resource(resource, b);
++}
++
++static int client_endpoint_marshal_set_param (void *object,
++ uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Id(id),
++ SPA_POD_Int(flags),
++ SPA_POD_Pod(param));
++
++ return pw_protocol_native_end_resource(resource, b);
++}
++
++static int client_endpoint_marshal_stream_set_param (void *object,
++ uint32_t stream_id, uint32_t id,
++ uint32_t flags, const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_CLIENT_ENDPOINT_PROXY_EVENT_STREAM_SET_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(stream_id),
++ SPA_POD_Id(id),
++ SPA_POD_Int(flags),
++ SPA_POD_Pod(param));
++
++ return pw_protocol_native_end_resource(resource, b);
++}
++
++static int client_endpoint_marshal_add_listener(void *object,
++ struct spa_hook *listener,
++ const struct pw_client_endpoint_proxy_events *events,
++ void *data)
++{
++ struct pw_proxy *proxy = object;
++ pw_proxy_add_object_listener(proxy, listener, events, data);
++ return 0;
++}
++
++static int client_endpoint_marshal_update(void *object,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_info *info)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++ struct spa_pod_frame f;
++ uint32_t i;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_CLIENT_ENDPOINT_PROXY_METHOD_UPDATE, NULL);
++
++ spa_pod_builder_push_struct(b, &f);
++ spa_pod_builder_add(b,
++ SPA_POD_Int(change_mask),
++ SPA_POD_Int(n_params),
++ NULL);
++
++ for (i = 0; i < n_params; i++)
++ spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL);
++
++ if (info)
++ marshal_pw_endpoint_info(b, info);
++ else
++ spa_pod_builder_add(b, SPA_POD_Pod(NULL), NULL);
++
++ spa_pod_builder_pop(b, &f);
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int client_endpoint_marshal_stream_update(void *object,
++ uint32_t stream_id,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_stream_info *info)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++ struct spa_pod_frame f;
++ uint32_t i;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_CLIENT_ENDPOINT_PROXY_METHOD_STREAM_UPDATE, NULL);
++
++ spa_pod_builder_push_struct(b, &f);
++ spa_pod_builder_add(b,
++ SPA_POD_Int(stream_id),
++ SPA_POD_Int(change_mask),
++ SPA_POD_Int(n_params),
++ NULL);
++
++ for (i = 0; i < n_params; i++)
++ spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL);
++
++ if (info)
++ marshal_pw_endpoint_stream_info(b, info);
++ else
++ spa_pod_builder_add(b, SPA_POD_Pod(NULL), NULL);
++
++ spa_pod_builder_pop(b, &f);
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int client_endpoint_demarshal_set_id(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t id;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&id)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_client_endpoint_proxy_events,
++ set_id, 0, id);
++}
++
++static int client_endpoint_demarshal_set_session_id(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t id;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&id)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_client_endpoint_proxy_events,
++ set_session_id, 0, id);
++}
++
++static int client_endpoint_demarshal_set_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t id, flags;
++ const struct spa_pod *param = NULL;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&id),
++ SPA_POD_Int(&flags),
++ SPA_POD_PodObject(&param)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_client_endpoint_proxy_events,
++ set_param, 0, id, flags, param);
++}
++
++static int client_endpoint_demarshal_stream_set_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t stream_id, id, flags;
++ const struct spa_pod *param = NULL;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&stream_id),
++ SPA_POD_Int(&id),
++ SPA_POD_Int(&flags),
++ SPA_POD_PodObject(&param)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_client_endpoint_proxy_events,
++ stream_set_param, 0, stream_id, id, flags, param);
++}
++
++static int client_endpoint_demarshal_update(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs[2];
++ struct spa_pod_frame f[2];
++ uint32_t change_mask, n_params;
++ const struct spa_pod **params = NULL;
++ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
++ struct pw_endpoint_info info = { .props = &props }, *infop = NULL;
++ struct spa_pod *ipod;
++ uint32_t i;
++
++ spa_pod_parser_init(&prs[0], msg->data, msg->size);
++ if (spa_pod_parser_push_struct(&prs[0], &f[0]) < 0 ||
++ spa_pod_parser_get(&prs[0],
++ SPA_POD_Int(&change_mask),
++ SPA_POD_Int(&n_params), NULL) < 0)
++ return -EINVAL;
++
++ if (n_params > 0)
++ params = alloca(n_params * sizeof(struct spa_pod *));
++ for (i = 0; i < n_params; i++)
++ if (spa_pod_parser_get(&prs[0],
++ SPA_POD_PodObject(&params[i]), NULL) < 0)
++ return -EINVAL;
++
++ if (spa_pod_parser_get(&prs[0], SPA_POD_PodStruct(&ipod), NULL) < 0)
++ return -EINVAL;
++ if (ipod) {
++ infop = &info;
++ spa_pod_parser_pod(&prs[1], ipod);
++ demarshal_pw_endpoint_info(&prs[1], &f[1], infop);
++ }
++
++ return pw_resource_notify(resource, struct pw_client_endpoint_proxy_methods,
++ update, 0, change_mask, n_params, params, infop);
++}
++
++static int client_endpoint_demarshal_stream_update(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs[2];
++ struct spa_pod_frame f[2];
++ uint32_t stream_id, change_mask, n_params;
++ const struct spa_pod **params = NULL;
++ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
++ struct pw_endpoint_stream_info info = { .props = &props }, *infop = NULL;
++ struct spa_pod *ipod;
++ uint32_t i;
++
++ spa_pod_parser_init(&prs[0], msg->data, msg->size);
++ if (spa_pod_parser_push_struct(&prs[0], &f[0]) < 0 ||
++ spa_pod_parser_get(&prs[0],
++ SPA_POD_Int(&stream_id),
++ SPA_POD_Int(&change_mask),
++ SPA_POD_Int(&n_params), NULL) < 0)
++ return -EINVAL;
++
++ if (n_params > 0)
++ params = alloca(n_params * sizeof(struct spa_pod *));
++ for (i = 0; i < n_params; i++)
++ if (spa_pod_parser_get(&prs[0],
++ SPA_POD_PodObject(&params[i]), NULL) < 0)
++ return -EINVAL;
++
++ if (spa_pod_parser_get(&prs[0], SPA_POD_PodStruct(&ipod), NULL) < 0)
++ return -EINVAL;
++ if (ipod) {
++ infop = &info;
++ spa_pod_parser_pod(&prs[1], ipod);
++ demarshal_pw_endpoint_stream_info(&prs[1], &f[1], infop);
++ }
++
++ return pw_resource_notify(resource, struct pw_client_endpoint_proxy_methods,
++ stream_update, 0, stream_id, change_mask, n_params, params, infop);
++}
++
++static const struct pw_client_endpoint_proxy_events pw_protocol_native_client_endpoint_event_marshal = {
++ PW_VERSION_CLIENT_ENDPOINT_PROXY_EVENTS,
++ .set_id = client_endpoint_marshal_set_id,
++ .set_session_id = client_endpoint_marshal_set_session_id,
++ .set_param = client_endpoint_marshal_set_param,
++ .stream_set_param = client_endpoint_marshal_stream_set_param,
++};
++
++static const struct pw_protocol_native_demarshal
++pw_protocol_native_client_endpoint_event_demarshal[PW_CLIENT_ENDPOINT_PROXY_EVENT_NUM] =
++{
++ [PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_ID] = { client_endpoint_demarshal_set_id, 0 },
++ [PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_SESSION_ID] = { client_endpoint_demarshal_set_session_id, 0 },
++ [PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_PARAM] = { client_endpoint_demarshal_set_param, 0 },
++ [PW_CLIENT_ENDPOINT_PROXY_EVENT_STREAM_SET_PARAM] = { client_endpoint_demarshal_stream_set_param, 0 },
++};
++
++static const struct pw_client_endpoint_proxy_methods pw_protocol_native_client_endpoint_method_marshal = {
++ PW_VERSION_CLIENT_ENDPOINT_PROXY_METHODS,
++ .add_listener = client_endpoint_marshal_add_listener,
++ .update = client_endpoint_marshal_update,
++ .stream_update = client_endpoint_marshal_stream_update,
++};
++
++static const struct pw_protocol_native_demarshal
++pw_protocol_native_client_endpoint_method_demarshal[PW_CLIENT_ENDPOINT_PROXY_METHOD_NUM] =
++{
++ [PW_CLIENT_ENDPOINT_PROXY_METHOD_ADD_LISTENER] = { NULL, 0 },
++ [PW_CLIENT_ENDPOINT_PROXY_METHOD_UPDATE] = { client_endpoint_demarshal_update, 0 },
++ [PW_CLIENT_ENDPOINT_PROXY_METHOD_STREAM_UPDATE] = { client_endpoint_demarshal_stream_update, 0 },
++};
++
++static const struct pw_protocol_marshal pw_protocol_native_client_endpoint_marshal = {
++ PW_TYPE_INTERFACE_ClientEndpoint,
++ PW_VERSION_CLIENT_ENDPOINT_PROXY,
++ PW_CLIENT_ENDPOINT_PROXY_METHOD_NUM,
++ PW_CLIENT_ENDPOINT_PROXY_EVENT_NUM,
++ &pw_protocol_native_client_endpoint_method_marshal,
++ &pw_protocol_native_client_endpoint_method_demarshal,
++ &pw_protocol_native_client_endpoint_event_marshal,
++ &pw_protocol_native_client_endpoint_event_demarshal,
++};
++
++/***********************************************
++ * CLIENT SESSION
++ ***********************************************/
++
++static int client_session_marshal_set_id (void *object, uint32_t id)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_CLIENT_SESSION_PROXY_EVENT_SET_ID, NULL);
++
++ spa_pod_builder_add (b, SPA_POD_Int(id), NULL);
++
++ return pw_protocol_native_end_resource(resource, b);
++}
++
++static int client_session_marshal_set_param (void *object,
++ uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_CLIENT_SESSION_PROXY_EVENT_SET_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(id),
++ SPA_POD_Int(flags),
++ SPA_POD_Pod(param));
++
++ return pw_protocol_native_end_resource(resource, b);
++}
++
++static int client_session_marshal_link_set_param (void *object,
++ uint32_t link_id, uint32_t id,
++ uint32_t flags, const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_CLIENT_SESSION_PROXY_EVENT_LINK_SET_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(link_id),
++ SPA_POD_Id(id),
++ SPA_POD_Int(flags),
++ SPA_POD_Pod(param));
++
++ return pw_protocol_native_end_resource(resource, b);
++}
++
++static int client_session_marshal_create_link(void *object,
++ const struct spa_dict *props)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ spa_return_val_if_fail(props, -EINVAL);
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_CLIENT_SESSION_PROXY_EVENT_CREATE_LINK, NULL);
++
++ push_dict(b, props);
++
++ return pw_protocol_native_end_resource(resource, b);
++}
++
++static int client_session_marshal_destroy_link (void *object, uint32_t link_id)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_CLIENT_SESSION_PROXY_EVENT_DESTROY_LINK, NULL);
++
++ spa_pod_builder_add(b, SPA_POD_Int(link_id), NULL);
++
++ return pw_protocol_native_end_resource(resource, b);
++}
++
++static int client_session_marshal_link_request_state (void *object,
++ uint32_t link_id, uint32_t state)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_CLIENT_SESSION_PROXY_EVENT_LINK_REQUEST_STATE, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(link_id),
++ SPA_POD_Int(state));
++
++ return pw_protocol_native_end_resource(resource, b);
++}
++
++static int client_session_marshal_add_listener(void *object,
++ struct spa_hook *listener,
++ const struct pw_client_session_proxy_events *events,
++ void *data)
++{
++ struct pw_proxy *proxy = object;
++ pw_proxy_add_object_listener(proxy, listener, events, data);
++ return 0;
++}
++
++static int client_session_marshal_update(void *object,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_session_info *info)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++ struct spa_pod_frame f;
++ uint32_t i;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_CLIENT_SESSION_PROXY_METHOD_UPDATE, NULL);
++
++ spa_pod_builder_push_struct(b, &f);
++ spa_pod_builder_add(b,
++ SPA_POD_Int(change_mask),
++ SPA_POD_Int(n_params),
++ NULL);
++
++ for (i = 0; i < n_params; i++)
++ spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL);
++
++ if (info)
++ marshal_pw_session_info(b, info);
++ else
++ spa_pod_builder_add(b, SPA_POD_Pod(NULL), NULL);
++
++ spa_pod_builder_pop(b, &f);
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int client_session_marshal_link_update(void *object,
++ uint32_t link_id,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_link_info *info)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++ struct spa_pod_frame f;
++ uint32_t i;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_CLIENT_SESSION_PROXY_METHOD_LINK_UPDATE, NULL);
++
++ spa_pod_builder_push_struct(b, &f);
++ spa_pod_builder_add(b,
++ SPA_POD_Int(link_id),
++ SPA_POD_Int(change_mask),
++ SPA_POD_Int(n_params),
++ NULL);
++
++ for (i = 0; i < n_params; i++)
++ spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL);
++
++ if (info)
++ marshal_pw_endpoint_link_info(b, info);
++ else
++ spa_pod_builder_add(b, SPA_POD_Pod(NULL), NULL);
++
++ spa_pod_builder_pop(b, &f);
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int client_session_demarshal_set_id(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t id;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&id)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_client_session_proxy_events,
++ set_id, 0, id);
++}
++
++static int client_session_demarshal_set_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t id, flags;
++ const struct spa_pod *param = NULL;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&id),
++ SPA_POD_Int(&flags),
++ SPA_POD_PodObject(&param)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_client_session_proxy_events,
++ set_param, 0, id, flags, param);
++}
++
++static int client_session_demarshal_link_set_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t link_id, id, flags;
++ const struct spa_pod *param = NULL;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&link_id),
++ SPA_POD_Int(&id),
++ SPA_POD_Int(&flags),
++ SPA_POD_PodObject(&param)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_client_session_proxy_events,
++ link_set_param, 0, link_id, id, flags, param);
++}
++
++static int client_session_demarshal_create_link(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ struct spa_pod_frame f;
++ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ parse_dict(&prs, &f, &props);
++
++ return pw_proxy_notify(proxy, struct pw_client_session_proxy_events,
++ create_link, 0, &props);
++}
++
++static int client_session_demarshal_destroy_link(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t link_id;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&link_id)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_client_session_proxy_events,
++ destroy_link, 0, link_id);
++}
++
++static int client_session_demarshal_link_request_state(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t link_id, state;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&link_id),
++ SPA_POD_Int(&state)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_client_session_proxy_events,
++ link_request_state, 0, link_id, state);
++}
++
++static int client_session_demarshal_update(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs[2];
++ struct spa_pod_frame f[2];
++ uint32_t change_mask, n_params;
++ const struct spa_pod **params = NULL;
++ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
++ struct pw_session_info info = { .props = &props }, *infop = NULL;
++ struct spa_pod *ipod;
++ uint32_t i;
++
++ spa_pod_parser_init(&prs[0], msg->data, msg->size);
++ if (spa_pod_parser_push_struct(&prs[0], &f[0]) < 0 ||
++ spa_pod_parser_get(&prs[0],
++ SPA_POD_Int(&change_mask),
++ SPA_POD_Int(&n_params), NULL) < 0)
++ return -EINVAL;
++
++ if (n_params > 0)
++ params = alloca(n_params * sizeof(struct spa_pod *));
++ for (i = 0; i < n_params; i++)
++ if (spa_pod_parser_get(&prs[0],
++ SPA_POD_PodObject(&params[i]), NULL) < 0)
++ return -EINVAL;
++
++ if (spa_pod_parser_get(&prs[0], SPA_POD_PodStruct(&ipod), NULL) < 0)
++ return -EINVAL;
++ if (ipod) {
++ infop = &info;
++ spa_pod_parser_pod(&prs[1], ipod);
++ demarshal_pw_session_info(&prs[1], &f[1], infop);
++ }
++
++ return pw_resource_notify(resource, struct pw_client_session_proxy_methods,
++ update, 0, change_mask, n_params, params, infop);
++}
++
++static int client_session_demarshal_link_update(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs[2];
++ struct spa_pod_frame f[2];
++ uint32_t link_id, change_mask, n_params;
++ const struct spa_pod **params = NULL;
++ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
++ struct pw_endpoint_link_info info = { .props = &props }, *infop = NULL;
++ struct spa_pod *ipod;
++ uint32_t i;
++
++ spa_pod_parser_init(&prs[0], msg->data, msg->size);
++ if (spa_pod_parser_push_struct(&prs[0], &f[0]) < 0 ||
++ spa_pod_parser_get(&prs[0],
++ SPA_POD_Int(&link_id),
++ SPA_POD_Int(&change_mask),
++ SPA_POD_Int(&n_params), NULL) < 0)
++ return -EINVAL;
++
++ if (n_params > 0)
++ params = alloca(n_params * sizeof(struct spa_pod *));
++ for (i = 0; i < n_params; i++)
++ if (spa_pod_parser_get(&prs[0],
++ SPA_POD_PodObject(&params[i]), NULL) < 0)
++ return -EINVAL;
++
++ if (spa_pod_parser_get(&prs[0], SPA_POD_PodStruct(&ipod), NULL) < 0)
++ return -EINVAL;
++ if (ipod) {
++ infop = &info;
++ spa_pod_parser_pod(&prs[1], ipod);
++ demarshal_pw_endpoint_link_info(&prs[1], &f[1], infop);
++ }
++
++ return pw_resource_notify(resource, struct pw_client_session_proxy_methods,
++ link_update, 0, link_id, change_mask, n_params, params, infop);
++}
++
++static const struct pw_client_session_proxy_events pw_protocol_native_client_session_event_marshal = {
++ PW_VERSION_CLIENT_SESSION_PROXY_EVENTS,
++ .set_id = client_session_marshal_set_id,
++ .set_param = client_session_marshal_set_param,
++ .link_set_param = client_session_marshal_link_set_param,
++ .create_link = client_session_marshal_create_link,
++ .destroy_link = client_session_marshal_destroy_link,
++ .link_request_state = client_session_marshal_link_request_state,
++};
++
++static const struct pw_protocol_native_demarshal
++pw_protocol_native_client_session_event_demarshal[PW_CLIENT_SESSION_PROXY_EVENT_NUM] =
++{
++ [PW_CLIENT_SESSION_PROXY_EVENT_SET_ID] = { client_session_demarshal_set_id, 0 },
++ [PW_CLIENT_SESSION_PROXY_EVENT_SET_PARAM] = { client_session_demarshal_set_param, 0 },
++ [PW_CLIENT_SESSION_PROXY_EVENT_LINK_SET_PARAM] = { client_session_demarshal_link_set_param, 0 },
++ [PW_CLIENT_SESSION_PROXY_EVENT_CREATE_LINK] = { client_session_demarshal_create_link, 0 },
++ [PW_CLIENT_SESSION_PROXY_EVENT_DESTROY_LINK] = { client_session_demarshal_destroy_link, 0 },
++ [PW_CLIENT_SESSION_PROXY_EVENT_LINK_REQUEST_STATE] = { client_session_demarshal_link_request_state, 0 },
++};
++
++static const struct pw_client_session_proxy_methods pw_protocol_native_client_session_method_marshal = {
++ PW_VERSION_CLIENT_SESSION_PROXY_METHODS,
++ .add_listener = client_session_marshal_add_listener,
++ .update = client_session_marshal_update,
++ .link_update = client_session_marshal_link_update,
++};
++
++static const struct pw_protocol_native_demarshal
++pw_protocol_native_client_session_method_demarshal[PW_CLIENT_SESSION_PROXY_METHOD_NUM] =
++{
++ [PW_CLIENT_SESSION_PROXY_METHOD_ADD_LISTENER] = { NULL, 0 },
++ [PW_CLIENT_SESSION_PROXY_METHOD_UPDATE] = { client_session_demarshal_update, 0 },
++ [PW_CLIENT_SESSION_PROXY_METHOD_LINK_UPDATE] = { client_session_demarshal_link_update, 0 },
++};
++
++static const struct pw_protocol_marshal pw_protocol_native_client_session_marshal = {
++ PW_TYPE_INTERFACE_ClientSession,
++ PW_VERSION_CLIENT_SESSION_PROXY,
++ PW_CLIENT_SESSION_PROXY_METHOD_NUM,
++ PW_CLIENT_SESSION_PROXY_EVENT_NUM,
++ &pw_protocol_native_client_session_method_marshal,
++ &pw_protocol_native_client_session_method_demarshal,
++ &pw_protocol_native_client_session_event_marshal,
++ &pw_protocol_native_client_session_event_demarshal,
++};
++
++/***********************************************
++ * ENDPOINT LINK
++ ***********************************************/
++
++static void endpoint_link_marshal_info (void *object,
++ const struct pw_endpoint_link_info *info)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_ENDPOINT_LINK_PROXY_EVENT_INFO, NULL);
++
++ marshal_pw_endpoint_link_info(b, info);
++
++ pw_protocol_native_end_resource(resource, b);
++}
++
++static void endpoint_link_marshal_param (void *object, int seq, uint32_t id,
++ uint32_t index, uint32_t next,
++ const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_ENDPOINT_LINK_PROXY_EVENT_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(seq),
++ SPA_POD_Id(id),
++ SPA_POD_Int(index),
++ SPA_POD_Int(next),
++ SPA_POD_Pod(param));
++
++ pw_protocol_native_end_resource(resource, b);
++}
++
++static int endpoint_link_marshal_add_listener(void *object,
++ struct spa_hook *listener,
++ const struct pw_endpoint_link_proxy_events *events,
++ void *data)
++{
++ struct pw_proxy *proxy = object;
++ pw_proxy_add_object_listener(proxy, listener, events, data);
++ return 0;
++}
++
++static int endpoint_link_marshal_subscribe_params(void *object,
++ uint32_t *ids, uint32_t n_ids)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_LINK_PROXY_METHOD_SUBSCRIBE_PARAMS, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int endpoint_link_marshal_enum_params(void *object,
++ int seq, uint32_t id,
++ uint32_t start, uint32_t num,
++ const struct spa_pod *filter)
++{
++ struct pw_protocol_native_message *msg;
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_LINK_PROXY_METHOD_ENUM_PARAMS, &msg);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
++ SPA_POD_Id(id),
++ SPA_POD_Int(index),
++ SPA_POD_Int(num),
++ SPA_POD_Pod(filter));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int endpoint_link_marshal_set_param(void *object,
++ uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_LINK_PROXY_METHOD_SET_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Id(id),
++ SPA_POD_Int(flags),
++ SPA_POD_Pod(param));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int endpoint_link_marshal_request_state(void *object,
++ enum pw_endpoint_link_state state)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_LINK_PROXY_METHOD_REQUEST_STATE, NULL);
++
++ spa_pod_builder_add_struct(b, SPA_POD_Int(state));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int endpoint_link_marshal_destroy(void *object)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_LINK_PROXY_METHOD_DESTROY, NULL);
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int endpoint_link_demarshal_info(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ struct spa_pod_frame f;
++ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
++ struct pw_endpoint_link_info info = { .props = &props };
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++
++ demarshal_pw_endpoint_link_info(&prs, &f, &info);
++
++ return pw_proxy_notify(proxy, struct pw_endpoint_link_proxy_events,
++ info, 0, &info);
++}
++
++static int endpoint_link_demarshal_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t id, index, next;
++ int seq;
++ struct spa_pod *param;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&seq),
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&index),
++ SPA_POD_Int(&next),
++ SPA_POD_Pod(&param)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_endpoint_link_proxy_events,
++ param, 0, seq, id, index, next, param);
++}
++
++static int endpoint_link_demarshal_subscribe_params(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t csize, ctype, n_ids;
++ uint32_t *ids;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
++ return -EINVAL;
++
++ if (ctype != SPA_TYPE_Id)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_endpoint_link_proxy_methods,
++ subscribe_params, 0, ids, n_ids);
++}
++
++static int endpoint_link_demarshal_enum_params(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t id, index, num;
++ int seq;
++ struct spa_pod *filter;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&seq),
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&index),
++ SPA_POD_Int(&num),
++ SPA_POD_Pod(&filter)) < 0)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_endpoint_link_proxy_methods,
++ enum_params, 0, seq, id, index, num, filter);
++}
++
++static int endpoint_link_demarshal_set_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t id, flags;
++ struct spa_pod *param;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&flags),
++ SPA_POD_Pod(&param)) < 0)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_endpoint_link_proxy_methods,
++ set_param, 0, id, flags, param);
++}
++
++static int endpoint_link_demarshal_request_state(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ enum pw_endpoint_link_state state;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&state)) < 0)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_endpoint_link_proxy_methods,
++ request_state, 0, state);
++}
++
++static int endpoint_link_demarshal_destroy(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++
++ return pw_resource_notify(resource, struct pw_endpoint_link_proxy_methods,
++ destroy, 0);
++}
++
++static const struct pw_endpoint_link_proxy_events pw_protocol_native_endpoint_link_event_marshal = {
++ PW_VERSION_ENDPOINT_LINK_PROXY_EVENTS,
++ .info = endpoint_link_marshal_info,
++ .param = endpoint_link_marshal_param,
++};
++
++static const struct pw_protocol_native_demarshal
++pw_protocol_native_endpoint_link_event_demarshal[PW_ENDPOINT_LINK_PROXY_EVENT_NUM] =
++{
++ [PW_ENDPOINT_LINK_PROXY_EVENT_INFO] = { endpoint_link_demarshal_info, 0 },
++ [PW_ENDPOINT_LINK_PROXY_EVENT_PARAM] = { endpoint_link_demarshal_param, 0 },
++};
++
++static const struct pw_endpoint_link_proxy_methods pw_protocol_native_endpoint_link_method_marshal = {
++ PW_VERSION_ENDPOINT_LINK_PROXY_METHODS,
++ .add_listener = endpoint_link_marshal_add_listener,
++ .subscribe_params = endpoint_link_marshal_subscribe_params,
++ .enum_params = endpoint_link_marshal_enum_params,
++ .set_param = endpoint_link_marshal_set_param,
++ .request_state = endpoint_link_marshal_request_state,
++ .destroy = endpoint_link_marshal_destroy,
++};
++
++static const struct pw_protocol_native_demarshal
++pw_protocol_native_endpoint_link_method_demarshal[PW_ENDPOINT_LINK_PROXY_METHOD_NUM] =
++{
++ [PW_ENDPOINT_LINK_PROXY_METHOD_ADD_LISTENER] = { NULL, 0 },
++ [PW_ENDPOINT_LINK_PROXY_METHOD_SUBSCRIBE_PARAMS] = { endpoint_link_demarshal_subscribe_params, 0 },
++ [PW_ENDPOINT_LINK_PROXY_METHOD_ENUM_PARAMS] = { endpoint_link_demarshal_enum_params, 0 },
++ [PW_ENDPOINT_LINK_PROXY_METHOD_SET_PARAM] = { endpoint_link_demarshal_set_param, PW_PERM_W },
++ [PW_ENDPOINT_LINK_PROXY_METHOD_REQUEST_STATE] = { endpoint_link_demarshal_request_state, PW_PERM_W },
++ [PW_ENDPOINT_LINK_PROXY_METHOD_DESTROY] = { endpoint_link_demarshal_destroy, PW_PERM_W },
++};
++
++static const struct pw_protocol_marshal pw_protocol_native_endpoint_link_marshal = {
++ PW_TYPE_INTERFACE_EndpointLink,
++ PW_VERSION_ENDPOINT_LINK_PROXY,
++ PW_ENDPOINT_LINK_PROXY_METHOD_NUM,
++ PW_ENDPOINT_LINK_PROXY_EVENT_NUM,
++ &pw_protocol_native_endpoint_link_method_marshal,
++ &pw_protocol_native_endpoint_link_method_demarshal,
++ &pw_protocol_native_endpoint_link_event_marshal,
++ &pw_protocol_native_endpoint_link_event_demarshal,
++};
++
++/***********************************************
++ * ENDPOINT STREAM
++ ***********************************************/
++
++static void endpoint_stream_marshal_info (void *object,
++ const struct pw_endpoint_stream_info *info)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_ENDPOINT_STREAM_PROXY_EVENT_INFO, NULL);
++
++ marshal_pw_endpoint_stream_info(b, info);
++
++ pw_protocol_native_end_resource(resource, b);
++}
++
++static void endpoint_stream_marshal_param (void *object, int seq, uint32_t id,
++ uint32_t index, uint32_t next,
++ const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_ENDPOINT_STREAM_PROXY_EVENT_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(seq),
++ SPA_POD_Id(id),
++ SPA_POD_Int(index),
++ SPA_POD_Int(next),
++ SPA_POD_Pod(param));
++
++ pw_protocol_native_end_resource(resource, b);
++}
++
++static int endpoint_stream_marshal_add_listener(void *object,
++ struct spa_hook *listener,
++ const struct pw_endpoint_stream_proxy_events *events,
++ void *data)
++{
++ struct pw_proxy *proxy = object;
++ pw_proxy_add_object_listener(proxy, listener, events, data);
++ return 0;
++}
++
++static int endpoint_stream_marshal_subscribe_params(void *object,
++ uint32_t *ids, uint32_t n_ids)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_STREAM_PROXY_METHOD_SUBSCRIBE_PARAMS, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int endpoint_stream_marshal_enum_params(void *object,
++ int seq, uint32_t id,
++ uint32_t start, uint32_t num,
++ const struct spa_pod *filter)
++{
++ struct pw_protocol_native_message *msg;
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_STREAM_PROXY_METHOD_ENUM_PARAMS, &msg);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
++ SPA_POD_Id(id),
++ SPA_POD_Int(index),
++ SPA_POD_Int(num),
++ SPA_POD_Pod(filter));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int endpoint_stream_marshal_set_param(void *object,
++ uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_STREAM_PROXY_METHOD_SET_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Id(id),
++ SPA_POD_Int(flags),
++ SPA_POD_Pod(param));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int endpoint_stream_demarshal_info(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ struct spa_pod_frame f;
++ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
++ struct pw_endpoint_stream_info info = { .props = &props };
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++
++ demarshal_pw_endpoint_stream_info(&prs, &f, &info);
++
++ return pw_proxy_notify(proxy, struct pw_endpoint_stream_proxy_events,
++ info, 0, &info);
++}
++
++static int endpoint_stream_demarshal_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t id, index, next;
++ int seq;
++ struct spa_pod *param;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&seq),
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&index),
++ SPA_POD_Int(&next),
++ SPA_POD_Pod(&param)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_endpoint_stream_proxy_events,
++ param, 0, seq, id, index, next, param);
++}
++
++static int endpoint_stream_demarshal_subscribe_params(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t csize, ctype, n_ids;
++ uint32_t *ids;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
++ return -EINVAL;
++
++ if (ctype != SPA_TYPE_Id)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_endpoint_stream_proxy_methods,
++ subscribe_params, 0, ids, n_ids);
++}
++
++static int endpoint_stream_demarshal_enum_params(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t id, index, num;
++ int seq;
++ struct spa_pod *filter;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&seq),
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&index),
++ SPA_POD_Int(&num),
++ SPA_POD_Pod(&filter)) < 0)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_endpoint_stream_proxy_methods,
++ enum_params, 0, seq, id, index, num, filter);
++}
++
++static int endpoint_stream_demarshal_set_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t id, flags;
++ struct spa_pod *param;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&flags),
++ SPA_POD_Pod(&param)) < 0)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_endpoint_stream_proxy_methods,
++ set_param, 0, id, flags, param);
++}
++
++static const struct pw_endpoint_stream_proxy_events pw_protocol_native_endpoint_stream_event_marshal = {
++ PW_VERSION_ENDPOINT_STREAM_PROXY_EVENTS,
++ .info = endpoint_stream_marshal_info,
++ .param = endpoint_stream_marshal_param,
++};
++
++static const struct pw_protocol_native_demarshal
++pw_protocol_native_endpoint_stream_event_demarshal[PW_ENDPOINT_STREAM_PROXY_EVENT_NUM] =
++{
++ [PW_ENDPOINT_STREAM_PROXY_EVENT_INFO] = { endpoint_stream_demarshal_info, 0 },
++ [PW_ENDPOINT_STREAM_PROXY_EVENT_PARAM] = { endpoint_stream_demarshal_param, 0 },
++};
++
++static const struct pw_endpoint_stream_proxy_methods pw_protocol_native_endpoint_stream_method_marshal = {
++ PW_VERSION_ENDPOINT_STREAM_PROXY_METHODS,
++ .add_listener = endpoint_stream_marshal_add_listener,
++ .subscribe_params = endpoint_stream_marshal_subscribe_params,
++ .enum_params = endpoint_stream_marshal_enum_params,
++ .set_param = endpoint_stream_marshal_set_param,
++};
++
++static const struct pw_protocol_native_demarshal
++pw_protocol_native_endpoint_stream_method_demarshal[PW_ENDPOINT_STREAM_PROXY_METHOD_NUM] =
++{
++ [PW_ENDPOINT_STREAM_PROXY_METHOD_ADD_LISTENER] = { NULL, 0 },
++ [PW_ENDPOINT_STREAM_PROXY_METHOD_SUBSCRIBE_PARAMS] = { endpoint_stream_demarshal_subscribe_params, 0 },
++ [PW_ENDPOINT_STREAM_PROXY_METHOD_ENUM_PARAMS] = { endpoint_stream_demarshal_enum_params, 0 },
++ [PW_ENDPOINT_STREAM_PROXY_METHOD_SET_PARAM] = { endpoint_stream_demarshal_set_param, PW_PERM_W },
++};
++
++static const struct pw_protocol_marshal pw_protocol_native_endpoint_stream_marshal = {
++ PW_TYPE_INTERFACE_EndpointStream,
++ PW_VERSION_ENDPOINT_STREAM_PROXY,
++ PW_ENDPOINT_STREAM_PROXY_METHOD_NUM,
++ PW_ENDPOINT_STREAM_PROXY_EVENT_NUM,
++ &pw_protocol_native_endpoint_stream_method_marshal,
++ &pw_protocol_native_endpoint_stream_method_demarshal,
++ &pw_protocol_native_endpoint_stream_event_marshal,
++ &pw_protocol_native_endpoint_stream_event_demarshal,
++};
++
++/***********************************************
++ * ENDPOINT
++ ***********************************************/
++
++static void endpoint_marshal_info (void *object,
++ const struct pw_endpoint_info *info)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_ENDPOINT_PROXY_EVENT_INFO, NULL);
++
++ marshal_pw_endpoint_info(b, info);
++
++ pw_protocol_native_end_resource(resource, b);
++}
++
++static void endpoint_marshal_param (void *object, int seq, uint32_t id,
++ uint32_t index, uint32_t next,
++ const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_ENDPOINT_PROXY_EVENT_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(seq),
++ SPA_POD_Id(id),
++ SPA_POD_Int(index),
++ SPA_POD_Int(next),
++ SPA_POD_Pod(param));
++
++ pw_protocol_native_end_resource(resource, b);
++}
++
++static int endpoint_marshal_add_listener(void *object,
++ struct spa_hook *listener,
++ const struct pw_endpoint_proxy_events *events,
++ void *data)
++{
++ struct pw_proxy *proxy = object;
++ pw_proxy_add_object_listener(proxy, listener, events, data);
++ return 0;
++}
++
++static int endpoint_marshal_subscribe_params(void *object,
++ uint32_t *ids, uint32_t n_ids)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_PROXY_METHOD_SUBSCRIBE_PARAMS, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int endpoint_marshal_enum_params(void *object,
++ int seq, uint32_t id,
++ uint32_t start, uint32_t num,
++ const struct spa_pod *filter)
++{
++ struct pw_protocol_native_message *msg;
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_PROXY_METHOD_ENUM_PARAMS, &msg);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
++ SPA_POD_Id(id),
++ SPA_POD_Int(index),
++ SPA_POD_Int(num),
++ SPA_POD_Pod(filter));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int endpoint_marshal_set_param(void *object,
++ uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_PROXY_METHOD_SET_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Id(id),
++ SPA_POD_Int(flags),
++ SPA_POD_Pod(param));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int endpoint_demarshal_info(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ struct spa_pod_frame f;
++ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
++ struct pw_endpoint_info info = { .props = &props };
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++
++ demarshal_pw_endpoint_info(&prs, &f, &info);
++
++ return pw_proxy_notify(proxy, struct pw_endpoint_proxy_events,
++ info, 0, &info);
++}
++
++static int endpoint_demarshal_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t id, index, next;
++ int seq;
++ struct spa_pod *param;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&seq),
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&index),
++ SPA_POD_Int(&next),
++ SPA_POD_Pod(&param)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_endpoint_proxy_events,
++ param, 0, seq, id, index, next, param);
++}
++
++static int endpoint_demarshal_subscribe_params(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t csize, ctype, n_ids;
++ uint32_t *ids;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
++ return -EINVAL;
++
++ if (ctype != SPA_TYPE_Id)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_endpoint_proxy_methods,
++ subscribe_params, 0, ids, n_ids);
++}
++
++static int endpoint_demarshal_enum_params(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t id, index, num;
++ int seq;
++ struct spa_pod *filter;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&seq),
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&index),
++ SPA_POD_Int(&num),
++ SPA_POD_Pod(&filter)) < 0)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_endpoint_proxy_methods,
++ enum_params, 0, seq, id, index, num, filter);
++}
++
++static int endpoint_demarshal_set_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t id, flags;
++ struct spa_pod *param;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&flags),
++ SPA_POD_Pod(&param)) < 0)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_endpoint_proxy_methods,
++ set_param, 0, id, flags, param);
++}
++
++static const struct pw_endpoint_proxy_events pw_protocol_native_endpoint_event_marshal = {
++ PW_VERSION_ENDPOINT_PROXY_EVENTS,
++ .info = endpoint_marshal_info,
++ .param = endpoint_marshal_param,
++};
++
++static const struct pw_protocol_native_demarshal
++pw_protocol_native_endpoint_event_demarshal[PW_ENDPOINT_PROXY_EVENT_NUM] =
++{
++ [PW_ENDPOINT_PROXY_EVENT_INFO] = { endpoint_demarshal_info, 0 },
++ [PW_ENDPOINT_PROXY_EVENT_PARAM] = { endpoint_demarshal_param, 0 },
++};
++
++static const struct pw_endpoint_proxy_methods pw_protocol_native_endpoint_method_marshal = {
++ PW_VERSION_ENDPOINT_PROXY_METHODS,
++ .add_listener = endpoint_marshal_add_listener,
++ .subscribe_params = endpoint_marshal_subscribe_params,
++ .enum_params = endpoint_marshal_enum_params,
++ .set_param = endpoint_marshal_set_param,
++};
++
++static const struct pw_protocol_native_demarshal
++pw_protocol_native_endpoint_method_demarshal[PW_ENDPOINT_PROXY_METHOD_NUM] =
++{
++ [PW_ENDPOINT_PROXY_METHOD_ADD_LISTENER] = { NULL, 0 },
++ [PW_ENDPOINT_PROXY_METHOD_SUBSCRIBE_PARAMS] = { endpoint_demarshal_subscribe_params, 0 },
++ [PW_ENDPOINT_PROXY_METHOD_ENUM_PARAMS] = { endpoint_demarshal_enum_params, 0 },
++ [PW_ENDPOINT_PROXY_METHOD_SET_PARAM] = { endpoint_demarshal_set_param, PW_PERM_W },
++};
++
++static const struct pw_protocol_marshal pw_protocol_native_endpoint_marshal = {
++ PW_TYPE_INTERFACE_Endpoint,
++ PW_VERSION_ENDPOINT_PROXY,
++ PW_ENDPOINT_PROXY_METHOD_NUM,
++ PW_ENDPOINT_PROXY_EVENT_NUM,
++ &pw_protocol_native_endpoint_method_marshal,
++ &pw_protocol_native_endpoint_method_demarshal,
++ &pw_protocol_native_endpoint_event_marshal,
++ &pw_protocol_native_endpoint_event_demarshal,
++};
++
++/***********************************************
++ * SESSION
++ ***********************************************/
++
++static void session_marshal_info (void *object,
++ const struct pw_session_info *info)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_SESSION_PROXY_EVENT_INFO, NULL);
++
++ marshal_pw_session_info(b, info);
++
++ pw_protocol_native_end_resource(resource, b);
++}
++
++static void session_marshal_param (void *object, int seq, uint32_t id,
++ uint32_t index, uint32_t next,
++ const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_SESSION_PROXY_EVENT_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(seq),
++ SPA_POD_Id(id),
++ SPA_POD_Int(index),
++ SPA_POD_Int(next),
++ SPA_POD_Pod(param));
++
++ pw_protocol_native_end_resource(resource, b);
++}
++
++static int session_marshal_add_listener(void *object,
++ struct spa_hook *listener,
++ const struct pw_session_proxy_events *events,
++ void *data)
++{
++ struct pw_proxy *proxy = object;
++ pw_proxy_add_object_listener(proxy, listener, events, data);
++ return 0;
++}
++
++static int session_marshal_subscribe_params(void *object,
++ uint32_t *ids, uint32_t n_ids)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_SESSION_PROXY_METHOD_SUBSCRIBE_PARAMS, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int session_marshal_enum_params(void *object,
++ int seq, uint32_t id,
++ uint32_t start, uint32_t num,
++ const struct spa_pod *filter)
++{
++ struct pw_protocol_native_message *msg;
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_SESSION_PROXY_METHOD_ENUM_PARAMS, &msg);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
++ SPA_POD_Id(id),
++ SPA_POD_Int(index),
++ SPA_POD_Int(num),
++ SPA_POD_Pod(filter));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int session_marshal_set_param(void *object,
++ uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_SESSION_PROXY_METHOD_SET_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Id(id),
++ SPA_POD_Int(flags),
++ SPA_POD_Pod(param));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int session_marshal_create_link(void *object,
++ const struct spa_dict *props)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_SESSION_PROXY_METHOD_CREATE_LINK, NULL);
++
++ push_dict(b, props);
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int session_demarshal_info(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ struct spa_pod_frame f;
++ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
++ struct pw_session_info info = { .props = &props };
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++
++ demarshal_pw_session_info(&prs, &f, &info);
++
++ return pw_proxy_notify(proxy, struct pw_session_proxy_events,
++ info, 0, &info);
++}
++
++static int session_demarshal_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t id, index, next;
++ int seq;
++ struct spa_pod *param;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&seq),
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&index),
++ SPA_POD_Int(&next),
++ SPA_POD_Pod(&param)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_session_proxy_events,
++ param, 0, seq, id, index, next, param);
++}
++
++static int session_demarshal_subscribe_params(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t csize, ctype, n_ids;
++ uint32_t *ids;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
++ return -EINVAL;
++
++ if (ctype != SPA_TYPE_Id)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_session_proxy_methods,
++ subscribe_params, 0, ids, n_ids);
++}
++
++static int session_demarshal_enum_params(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t id, index, num;
++ int seq;
++ struct spa_pod *filter;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&seq),
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&index),
++ SPA_POD_Int(&num),
++ SPA_POD_Pod(&filter)) < 0)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_session_proxy_methods,
++ enum_params, 0, seq, id, index, num, filter);
++}
++
++static int session_demarshal_set_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t id, flags;
++ struct spa_pod *param;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&flags),
++ SPA_POD_Pod(&param)) < 0)
++ return -EINVAL;
++
++ return pw_resource_notify(resource, struct pw_session_proxy_methods,
++ set_param, 0, id, flags, param);
++}
++
++static int session_demarshal_create_link(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ struct spa_pod_frame f;
++ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++
++ parse_dict(&prs, &f, &props);
++
++ return pw_resource_notify(resource, struct pw_session_proxy_methods,
++ create_link, 0, &props);
++}
++
++static const struct pw_session_proxy_events pw_protocol_native_session_event_marshal = {
++ PW_VERSION_SESSION_PROXY_EVENTS,
++ .info = session_marshal_info,
++ .param = session_marshal_param,
++};
++
++static const struct pw_protocol_native_demarshal
++pw_protocol_native_session_event_demarshal[PW_SESSION_PROXY_EVENT_NUM] =
++{
++ [PW_SESSION_PROXY_EVENT_INFO] = { session_demarshal_info, 0 },
++ [PW_SESSION_PROXY_EVENT_PARAM] = { session_demarshal_param, 0 },
++};
++
++static const struct pw_session_proxy_methods pw_protocol_native_session_method_marshal = {
++ PW_VERSION_SESSION_PROXY_METHODS,
++ .add_listener = session_marshal_add_listener,
++ .subscribe_params = session_marshal_subscribe_params,
++ .enum_params = session_marshal_enum_params,
++ .set_param = session_marshal_set_param,
++ .create_link = session_marshal_create_link,
++};
++
++static const struct pw_protocol_native_demarshal
++pw_protocol_native_session_method_demarshal[PW_SESSION_PROXY_METHOD_NUM] =
++{
++ [PW_SESSION_PROXY_METHOD_ADD_LISTENER] = { NULL, 0 },
++ [PW_SESSION_PROXY_METHOD_SUBSCRIBE_PARAMS] = { session_demarshal_subscribe_params, 0 },
++ [PW_SESSION_PROXY_METHOD_ENUM_PARAMS] = { session_demarshal_enum_params, 0 },
++ [PW_SESSION_PROXY_METHOD_SET_PARAM] = { session_demarshal_set_param, PW_PERM_W },
++ [PW_SESSION_PROXY_METHOD_CREATE_LINK] = { session_demarshal_create_link, PW_PERM_W },
++};
++
++static const struct pw_protocol_marshal pw_protocol_native_session_marshal = {
++ PW_TYPE_INTERFACE_Session,
++ PW_VERSION_SESSION_PROXY,
++ PW_SESSION_PROXY_METHOD_NUM,
++ PW_SESSION_PROXY_EVENT_NUM,
++ &pw_protocol_native_session_method_marshal,
++ &pw_protocol_native_session_method_demarshal,
++ &pw_protocol_native_session_event_marshal,
++ &pw_protocol_native_session_event_demarshal,
++};
++
++struct pw_protocol *pw_protocol_native_ext_session_manager_init(struct pw_core *core)
++{
++ struct pw_protocol *protocol;
++
++ protocol = pw_core_find_protocol(core, PW_TYPE_INFO_PROTOCOL_Native);
++
++ if (protocol == NULL)
++ return NULL;
++
++ pw_protocol_add_marshal(protocol, &pw_protocol_native_client_endpoint_marshal);
++ pw_protocol_add_marshal(protocol, &pw_protocol_native_client_session_marshal);
++ pw_protocol_add_marshal(protocol, &pw_protocol_native_endpoint_link_marshal);
++ pw_protocol_add_marshal(protocol, &pw_protocol_native_endpoint_stream_marshal);
++ pw_protocol_add_marshal(protocol, &pw_protocol_native_endpoint_marshal);
++ pw_protocol_add_marshal(protocol, &pw_protocol_native_session_marshal);
++
++ return protocol;
++}
+diff --git a/src/modules/module-session-manager/session.c b/src/modules/module-session-manager/session.c
+new file mode 100644
+index 00000000..226eba4e
+--- /dev/null
++++ b/src/modules/module-session-manager/session.c
+@@ -0,0 +1,341 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 <stdbool.h>
++#include <string.h>
++
++#include <pipewire/pipewire.h>
++#include <extensions/session-manager.h>
++
++#include <spa/pod/filter.h>
++
++#include "session.h"
++#include "client-session.h"
++
++#include <pipewire/private.h>
++
++#define NAME "session"
++
++struct resource_data {
++ struct session *session;
++ struct spa_hook resource_listener;
++ struct spa_hook object_listener;
++ uint32_t n_subscribe_ids;
++ uint32_t subscribe_ids[32];
++};
++
++#define pw_session_resource(r,m,v,...) \
++ pw_resource_call(r,struct pw_session_proxy_events,m,v,__VA_ARGS__)
++#define pw_session_resource_info(r,...) \
++ pw_session_resource(r,info,0,__VA_ARGS__)
++#define pw_session_resource_param(r,...) \
++ pw_session_resource(r,param,0,__VA_ARGS__)
++
++static int session_enum_params (void *object, int seq,
++ uint32_t id, uint32_t start, uint32_t num,
++ const struct spa_pod *filter)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct session *this = data->session;
++ struct spa_pod *result;
++ struct spa_pod *param;
++ uint8_t buffer[1024];
++ struct spa_pod_builder b = { 0 };
++ uint32_t index;
++ uint32_t next = start;
++ uint32_t count = 0;
++
++ while (true) {
++ index = next++;
++ if (index >= this->n_params)
++ break;
++
++ param = this->params[index];
++
++ if (param == NULL || !spa_pod_is_object_id(param, id))
++ continue;
++
++ spa_pod_builder_init(&b, buffer, sizeof(buffer));
++ if (spa_pod_filter(&b, &result, param, filter) != 0)
++ continue;
++
++ pw_log_debug(NAME" %p: %d param %u", this, seq, index);
++
++ pw_session_resource_param(resource, seq, id, index, next, result);
++
++ if (++count == num)
++ break;
++ }
++ return 0;
++}
++
++static int session_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ uint32_t i;
++
++ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
++ data->n_subscribe_ids = n_ids;
++
++ for (i = 0; i < n_ids; i++) {
++ data->subscribe_ids[i] = ids[i];
++ pw_log_debug(NAME" %p: resource %d subscribe param %u",
++ data->session, resource->id, ids[i]);
++ session_enum_params(resource, 1, ids[i], 0, UINT32_MAX, NULL);
++ }
++ return 0;
++}
++
++static int session_set_param (void *object, uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct session *this = data->session;
++
++ pw_client_session_resource_set_param(this->client_sess->resource,
++ id, flags, param);
++
++ return 0;
++}
++
++static int session_create_link(void *object, const struct spa_dict *props)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct session *this = data->session;
++
++ pw_client_session_resource_create_link(this->client_sess->resource,
++ props);
++
++ return 0;
++}
++
++static const struct pw_session_proxy_methods methods = {
++ PW_VERSION_SESSION_PROXY_METHODS,
++ .subscribe_params = session_subscribe_params,
++ .enum_params = session_enum_params,
++ .set_param = session_set_param,
++ .create_link = session_create_link,
++};
++
++static void session_notify_subscribed(struct session *this,
++ uint32_t index, uint32_t next)
++{
++ struct pw_global *global = this->global;
++ struct pw_resource *resource;
++ struct resource_data *data;
++ struct spa_pod *param = this->params[index];
++ uint32_t id;
++ uint32_t i;
++
++ if (!param || !spa_pod_is_object (param))
++ return;
++
++ id = SPA_POD_OBJECT_ID (param);
++
++ spa_list_for_each(resource, &global->resource_list, link) {
++ data = pw_resource_get_user_data(resource);
++ for (i = 0; i < data->n_subscribe_ids; i++) {
++ if (data->subscribe_ids[i] == id) {
++ pw_session_resource_param(resource, 1, id,
++ index, next, param);
++ }
++ }
++ }
++}
++
++int session_update(struct session *this,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_session_info *info)
++{
++ if (change_mask & PW_CLIENT_SESSION_UPDATE_PARAMS) {
++ uint32_t i;
++ size_t size = n_params * sizeof(struct spa_pod *);
++
++ pw_log_debug(NAME" %p: update %d params", this, n_params);
++
++ for (i = 0; i < this->n_params; i++)
++ free(this->params[i]);
++ this->params = realloc(this->params, size);
++ if (size > 0 && !this->params) {
++ this->n_params = 0;
++ goto no_mem;
++ }
++ this->n_params = n_params;
++
++ for (i = 0; i < this->n_params; i++) {
++ this->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL;
++ session_notify_subscribed(this, i, i+1);
++ }
++ }
++
++ if (change_mask & PW_CLIENT_SESSION_UPDATE_INFO) {
++ struct pw_resource *resource;
++
++ if (info->change_mask & PW_SESSION_CHANGE_MASK_PROPS)
++ pw_properties_update(this->props, info->props);
++
++ if (info->change_mask & PW_SESSION_CHANGE_MASK_PARAMS) {
++ size_t size = info->n_params * sizeof(struct spa_param_info);
++
++ this->info.params = realloc(this->info.params, size);
++ if (size > 0 && !this->info.params) {
++ this->info.n_params = 0;
++ goto no_mem;
++ }
++ this->info.n_params = info->n_params;
++
++ memcpy(this->info.params, info->params, size);
++ }
++
++ this->info.change_mask = info->change_mask;
++ spa_list_for_each(resource, &this->global->resource_list, link) {
++ pw_session_resource_info(resource, &this->info);
++ }
++ this->info.change_mask = 0;
++ }
++
++ return 0;
++
++ no_mem:
++ pw_log_error(NAME" can't update: no memory");
++ pw_resource_error(this->client_sess->resource, -ENOMEM,
++ NAME" can't update: no memory");
++ return -ENOMEM;
++}
++
++static void session_unbind(void *data)
++{
++ struct pw_resource *resource = data;
++ spa_list_remove(&resource->link);
++}
++
++static const struct pw_resource_events resource_events = {
++ PW_VERSION_RESOURCE_EVENTS,
++ .destroy = session_unbind,
++};
++
++static int session_bind(void *_data, struct pw_client *client,
++ uint32_t permissions, uint32_t version, uint32_t id)
++{
++ struct session *this = _data;
++ struct pw_global *global = this->global;
++ struct pw_resource *resource;
++ struct resource_data *data;
++
++ resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data));
++ if (resource == NULL)
++ goto no_mem;
++
++ data = pw_resource_get_user_data(resource);
++ data->session = this;
++ pw_resource_add_listener(resource, &data->resource_listener,
++ &resource_events, resource);
++ pw_resource_add_object_listener(resource, &data->object_listener,
++ &methods, resource);
++
++ pw_log_debug(NAME" %p: bound to %d", this, resource->id);
++
++ spa_list_append(&global->resource_list, &resource->link);
++
++ this->info.change_mask = PW_SESSION_CHANGE_MASK_ALL;
++ pw_session_resource_info(resource, &this->info);
++ this->info.change_mask = 0;
++
++ return 0;
++
++ no_mem:
++ pw_log_error(NAME" can't create resource: no memory");
++ pw_resource_error(this->client_sess->resource, -ENOMEM,
++ NAME" can't create resource: no memory");
++ return -ENOMEM;
++}
++
++int session_init(struct session *this,
++ struct client_session *client_sess,
++ struct pw_core *core,
++ struct pw_properties *properties)
++{
++ const char *keys[] = {
++ PW_KEY_FACTORY_ID,
++ PW_KEY_CLIENT_ID,
++ NULL
++ };
++
++ pw_log_debug(NAME" %p: new", this);
++
++ this->client_sess = client_sess;
++ this->props = properties;
++
++ properties = pw_properties_new(NULL, NULL);
++ if (!properties)
++ goto no_mem;
++
++ pw_properties_copy_keys(this->props, properties, keys);
++
++ this->global = pw_global_new (core,
++ PW_TYPE_INTERFACE_Session,
++ PW_VERSION_SESSION_PROXY,
++ properties, session_bind, this);
++ if (!this->global)
++ goto no_mem;
++
++ pw_properties_setf(this->props, PW_KEY_SESSION_ID, "%u", this->global->id);
++
++ this->info.version = PW_VERSION_SESSION_INFO;
++ this->info.id = this->global->id;
++ this->info.props = &this->props->dict;
++
++ pw_client_session_resource_set_id(client_sess->resource, this->global->id);
++
++ return pw_global_register(this->global);
++
++ no_mem:
++ pw_log_error(NAME" - can't create - out of memory");
++ return -ENOMEM;
++}
++
++void session_clear(struct session *this)
++{
++ uint32_t i;
++
++ pw_log_debug(NAME" %p: destroy", this);
++
++ pw_global_destroy(this->global);
++
++ for (i = 0; i < this->n_params; i++)
++ free(this->params[i]);
++ free(this->params);
++
++ free(this->info.params);
++
++ if (this->props)
++ pw_properties_free(this->props);
++}
+diff --git a/src/modules/module-session-manager/session.h b/src/modules/module-session-manager/session.h
+new file mode 100644
+index 00000000..ad0b9b1b
+--- /dev/null
++++ b/src/modules/module-session-manager/session.h
+@@ -0,0 +1,61 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * 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 MODULE_SESSION_MANAGER_SESSION_H
++#define MODULE_SESSION_MANAGER_SESSION_H
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++struct client_session;
++
++struct session {
++ struct client_session *client_sess;
++ struct pw_global *global;
++ uint32_t n_params;
++ struct spa_pod **params;
++ struct pw_session_info info;
++ struct pw_properties *props; /* wrapper of info.props */
++};
++
++int session_init(struct session *this,
++ struct client_session *client_sess,
++ struct pw_core *core,
++ struct pw_properties *properties);
++
++void session_clear(struct session *this);
++
++int session_update(struct session *this,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_session_info *info);
++
++#ifdef __cplusplus
++} /* extern "C" */
++#endif
++
++#endif /* MODULE_SESSION_MANAGER_SESSION_H */
+diff --git a/src/pipewire/pipewire.c b/src/pipewire/pipewire.c
+index a7012ab2..ec5f1f85 100644
+--- a/src/pipewire/pipewire.c
++++ b/src/pipewire/pipewire.c
+@@ -575,6 +575,12 @@ static const struct spa_type_info type_info[] = {
+ { PW_TYPE_INTERFACE_Module, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "Module", NULL },
+ { PW_TYPE_INTERFACE_ClientNode, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "ClientNode", NULL },
+ { PW_TYPE_INTERFACE_Device, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "Device", NULL },
++ { PW_TYPE_INTERFACE_ClientEndpoint, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "ClientEndpoint", NULL},
++ { PW_TYPE_INTERFACE_Endpoint, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "Endpoint", NULL},
++ { PW_TYPE_INTERFACE_EndpointStream, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "EndpointStream", NULL},
++ { PW_TYPE_INTERFACE_ClientSession, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "ClientSession", NULL},
++ { PW_TYPE_INTERFACE_Session, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "Session", NULL},
++ { PW_TYPE_INTERFACE_EndpointLink, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "EndpointLink", NULL},
+ { SPA_ID_INVALID, SPA_ID_INVALID, "spa_types", spa_types },
+ { 0, 0, NULL, NULL },
+ };
+diff --git a/src/pipewire/type.h b/src/pipewire/type.h
+index a1b205f7..6b1b8b50 100644
+--- a/src/pipewire/type.h
++++ b/src/pipewire/type.h
+@@ -48,7 +48,12 @@ enum {
+ /* extensions */
+ PW_TYPE_INTERFACE_EXTENSIONS = PW_TYPE_INTERFACE_START + 0x1000,
+ PW_TYPE_INTERFACE_ClientNode,
+-
++ PW_TYPE_INTERFACE_ClientEndpoint,
++ PW_TYPE_INTERFACE_Endpoint,
++ PW_TYPE_INTERFACE_EndpointStream,
++ PW_TYPE_INTERFACE_ClientSession,
++ PW_TYPE_INTERFACE_Session,
++ PW_TYPE_INTERFACE_EndpointLink,
+ };
+
+ #define PW_TYPE_INFO_BASE "PipeWire:"
+--
+2.23.0
+
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-pipewire-cli-add-support-for-printing-endpoint-info-.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0013-pipewire-cli-add-support-for-printing-endpoint-info-.patch
index a709abdf..8c71afb3 100644
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-pipewire-cli-add-support-for-printing-endpoint-info-.patch
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0013-pipewire-cli-add-support-for-printing-endpoint-info-.patch
@@ -1,37 +1,31 @@
-From 0e9fe3cfb19c751655f16ce4b8c6100269f23ff2 Mon Sep 17 00:00:00 2001
+From 1249a42d1380945fd8dc7924c1ac912570bef501 Mon Sep 17 00:00:00 2001
From: George Kiagiadakis <george.kiagiadakis@collabora.com>
Date: Tue, 28 May 2019 11:46:36 +0300
Subject: [PATCH] pipewire-cli: add support for printing endpoint info & params
-Note that you have to run:
- load-module libpipewire-module-endpoint
-before trying to query any endpoints
-
Upstream-Status: Pending
---
- src/tools/pipewire-cli.c | 78 +++++++++++++++++++++++++++++++++++++++-
- 1 file changed, 77 insertions(+), 1 deletion(-)
+ src/tools/pipewire-cli.c | 107 ++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 106 insertions(+), 1 deletion(-)
diff --git a/src/tools/pipewire-cli.c b/src/tools/pipewire-cli.c
-index 521739f6..9511db82 100644
+index 6110d170..0dbc8368 100644
--- a/src/tools/pipewire-cli.c
+++ b/src/tools/pipewire-cli.c
-@@ -38,6 +38,8 @@
+@@ -37,6 +37,8 @@
#include <pipewire/type.h>
#include <pipewire/permission.h>
-+#include <extensions/endpoint.h>
++#include <extensions/session-manager.h>
+
static const char WHITESPACE[] = " \t";
struct remote_data;
-@@ -176,8 +178,12 @@ static void print_params(struct spa_param_info *params, uint32_t n_params, char
+@@ -174,8 +176,10 @@ static void print_params(struct spa_param_info *params, uint32_t n_params, char
return;
}
for (i = 0; i < n_params; i++) {
+ const struct spa_type_info *type_info = spa_type_param;
-+ if (params[i].id >= PW_ENDPOINT_PARAM_EnumControl)
-+ type_info = endpoint_param_type_info;
+
fprintf(stdout, "%c\t %d (%s) %c%c\n", mark, params[i].id,
- spa_debug_type_find_name(spa_type_param, params[i].id),
@@ -39,39 +33,55 @@ index 521739f6..9511db82 100644
params[i].flags & SPA_PARAM_INFO_READ ? 'r' : '-',
params[i].flags & SPA_PARAM_INFO_WRITE ? 'w' : '-');
}
-@@ -641,6 +647,16 @@ static void info_device(struct proxy_data *pd)
+@@ -652,6 +656,40 @@ static void info_device(struct proxy_data *pd)
info->change_mask = 0;
}
+static void info_endpoint(struct proxy_data *pd)
+{
+ struct pw_endpoint_info *info = pd->info;
++ const char *direction;
+
+ info_global(pd);
-+ print_properties(info->props, MARK_CHANGE(1), true);
-+ print_params(info->params, info->n_params, MARK_CHANGE(0), true);
++ fprintf(stdout, "\tname: %s\n", info->name);
++ fprintf(stdout, "\tmedia-class: %s\n", info->media_class);
++ switch(info->direction) {
++ case PW_ENDPOINT_DIRECTION_SINK_INPUT:
++ direction = "sink-input";
++ break;
++ case PW_ENDPOINT_DIRECTION_SOURCE_OUTPUT:
++ direction = "source-output";
++ break;
++ case PW_ENDPOINT_DIRECTION_SOURCE:
++ direction = "source";
++ break;
++ case PW_ENDPOINT_DIRECTION_SINK:
++ direction = "sink";
++ break;
++ default:
++ direction = "invalid";
++ break;
++ }
++ fprintf(stdout, "\tdirection: %s\n", direction);
++ fprintf(stdout, "\tflags: 0x%x\n", info->flags);
++ fprintf(stdout, "%c\tstreams: %u\n", MARK_CHANGE(0), info->n_streams);
++ fprintf(stdout, "%c\tsession: %u\n", MARK_CHANGE(0), info->session_id);
++ print_properties(info->props, MARK_CHANGE(2), true);
++ print_params(info->params, info->n_params, MARK_CHANGE(3), true);
+ info->change_mask = 0;
+}
+
static void core_event_info(void *object, const struct pw_core_info *info)
{
struct proxy_data *pd = object;
-@@ -708,6 +724,9 @@ static void event_param(void *object, int seq, uint32_t id,
-
- if (spa_pod_is_object_type(param, SPA_TYPE_OBJECT_Format))
- spa_debug_format(2, NULL, param);
-+ else if (spa_pod_is_object_type(param, PW_ENDPOINT_OBJECT_ParamControl) ||
-+ spa_pod_is_object_type(param, PW_ENDPOINT_OBJECT_ParamStream))
-+ spa_debug_pod(2, endpoint_param_object_type_info, param);
- else
- spa_debug_pod(2, NULL, param);
- }
-@@ -842,6 +861,53 @@ static const struct pw_device_proxy_events device_events = {
+@@ -853,6 +891,63 @@ static const struct pw_device_proxy_events device_events = {
.param = event_param
};
+static void endpoint_info_free(struct pw_endpoint_info *info)
+{
++ free(info->name);
++ free(info->media_class);
+ free(info->params);
+ if (info->props)
+ pw_properties_free ((struct pw_properties *)info->props);
@@ -88,7 +98,15 @@ index 521739f6..9511db82 100644
+ if (!info) {
+ info = pd->info = calloc(1, sizeof(*info));
+ info->id = update->id;
++ info->name = strdup(update->name);
++ info->media_class = strdup(update->media_class);
++ info->direction = update->direction;
++ info->flags = update->flags;
+ }
++ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_STREAMS)
++ info->n_streams = update->n_streams;
++ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_SESSION)
++ info->session_id = update->session_id;
+ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) {
+ info->n_params = update->n_params;
+ free(info->params);
@@ -120,20 +138,20 @@ index 521739f6..9511db82 100644
static void
destroy_proxy (void *data)
{
-@@ -928,6 +994,12 @@ static bool bind_global(struct remote_data *rd, struct global *global, char **er
+@@ -939,6 +1034,12 @@ static bool bind_global(struct remote_data *rd, struct global *global, char **er
destroy = (pw_destroy_t) pw_link_info_free;
info_func = info_link;
break;
+ case PW_TYPE_INTERFACE_Endpoint:
+ events = &endpoint_events;
-+ client_version = PW_VERSION_ENDPOINT;
++ client_version = PW_VERSION_ENDPOINT_PROXY;
+ destroy = (pw_destroy_t) endpoint_info_free;
+ info_func = info_endpoint;
+ break;
default:
asprintf(error, "unsupported type %s", spa_debug_type_find_name(pw_type_info(), global->type));
return false;
-@@ -1201,6 +1273,10 @@ static bool do_enum_params(struct data *data, const char *cmd, char *args, char
+@@ -1213,6 +1314,10 @@ static bool do_enum_params(struct data *data, const char *cmd, char *args, char
pw_device_proxy_enum_params((struct pw_device_proxy*)global->proxy, 0,
param_id, 0, 0, NULL);
break;
@@ -145,5 +163,5 @@ index 521739f6..9511db82 100644
asprintf(error, "enum-params not implemented on object %d", atoi(a[0]));
return false;
--
-2.20.1
+2.23.0
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0014-daemon-config-remote-load-module-session-manager-by-.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0014-daemon-config-remote-load-module-session-manager-by-.patch
new file mode 100644
index 00000000..30066451
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0014-daemon-config-remote-load-module-session-manager-by-.patch
@@ -0,0 +1,37 @@
+From a7379880b3cb14ce81cbb3475b1b251df60c86f8 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Fri, 9 Aug 2019 13:26:15 +0300
+Subject: [PATCH] daemon config & remote: load module-session-manager by
+ default
+
+Upstream-Status: Pending
+---
+ src/daemon/pipewire.conf.in | 1 +
+ src/pipewire/remote.c | 1 +
+ 2 files changed, 2 insertions(+)
+
+diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in
+index 079ec730..19c898b9 100644
+--- a/src/daemon/pipewire.conf.in
++++ b/src/daemon/pipewire.conf.in
+@@ -21,4 +21,5 @@ load-module libpipewire-module-client-device
+ load-module libpipewire-module-access
+ load-module libpipewire-module-adapter
+ load-module libpipewire-module-link-factory
++load-module libpipewire-module-session-manager
+ exec build/src/examples/media-session
+diff --git a/src/pipewire/remote.c b/src/pipewire/remote.c
+index 2b545f79..62841e58 100644
+--- a/src/pipewire/remote.c
++++ b/src/pipewire/remote.c
+@@ -242,6 +242,7 @@ struct pw_remote *pw_remote_new(struct pw_core *core,
+ pw_module_load(core, "libpipewire-module-rtkit", NULL, NULL);
+ pw_module_load(core, "libpipewire-module-client-node", NULL, NULL);
+ pw_module_load(core, "libpipewire-module-adapter", NULL, NULL);
++ pw_module_load(core, "libpipewire-module-session-manager", NULL, NULL);
+
+ spa_list_append(&core->remote_list, &this->link);
+
+--
+2.23.0
+
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0015-audioconvert-fmtconvert-assume-F32-on-the-other-port.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0015-audioconvert-fmtconvert-assume-F32-on-the-other-port.patch
deleted file mode 100644
index ed3c1b06..00000000
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0015-audioconvert-fmtconvert-assume-F32-on-the-other-port.patch
+++ /dev/null
@@ -1,44 +0,0 @@
-From aa5de0cfc31df9cd8fb6d24367d2852dbbc8dcb9 Mon Sep 17 00:00:00 2001
-From: George Kiagiadakis <george.kiagiadakis@collabora.com>
-Date: Mon, 29 Jul 2019 16:12:45 +0300
-Subject: [PATCH] audioconvert/fmtconvert: assume F32 on the other port when
- listing formats
-
-This allows picking F32LE as the default format on links that have
-no restriction and it avoids failing negotiation when the restricted
-end cannot handle S16/F32/F32P
-
-For instance this pipeline would previously fail:
-
- audio-dsp mode=merge ! audio-dsp mode=convert ! alsa-sink
-old negotiation: S16LE S24_32LE
-new negotiation: F32LE S24_32LE
-
-The link between the audio-dsp nodes has no restriction, so previously
-it would negotiate S16LE, which would then fail to negotiate with alsa-sink
-because fmtconvert does not know how to convert S16LE to S24_32LE directly.
-
-With this change, the middle link negotiates to F32LE, which can be
-converted to anything.
-
-Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/169]
----
- spa/plugins/audioconvert/fmtconvert.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/spa/plugins/audioconvert/fmtconvert.c b/spa/plugins/audioconvert/fmtconvert.c
-index df4ffb6b..a330f68f 100644
---- a/spa/plugins/audioconvert/fmtconvert.c
-+++ b/spa/plugins/audioconvert/fmtconvert.c
-@@ -338,7 +338,7 @@ static int port_enum_formats(struct spa_node *node,
- if (other->have_format)
- info = other->format;
- else
-- info.info.raw.format = SPA_AUDIO_FORMAT_S16;
-+ info.info.raw.format = SPA_AUDIO_FORMAT_F32;
-
- if (info.info.raw.format == SPA_AUDIO_FORMAT_F32P ||
- info.info.raw.format == SPA_AUDIO_FORMAT_F32) {
---
-2.20.1
-
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-a2dpsink-fix-infinite-loop-when-buffer-could-not-be-.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-a2dpsink-fix-infinite-loop-when-buffer-could-not-be-.patch
deleted file mode 100644
index d747a7a9..00000000
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-a2dpsink-fix-infinite-loop-when-buffer-could-not-be-.patch
+++ /dev/null
@@ -1,32 +0,0 @@
-From c186e40905f78f41cbc015da0e204735a0398450 Mon Sep 17 00:00:00 2001
-From: Julian Bouzas <julian.bouzas@collabora.com>
-Date: Fri, 19 Jul 2019 08:38:21 -0400
-Subject: [PATCH] a2dpsink: fix infinite loop when buffer could not be encoded
-
-Upstream-Status: Backport [4b202b965665bbcb55194b4ab827984e5804e3e0]
----
- spa/plugins/bluez5/a2dp-sink.c | 7 ++++++-
- 1 file changed, 6 insertions(+), 1 deletion(-)
-
-diff --git a/spa/plugins/bluez5/a2dp-sink.c b/spa/plugins/bluez5/a2dp-sink.c
-index 731577e5..d6d9e7d6 100644
---- a/spa/plugins/bluez5/a2dp-sink.c
-+++ b/spa/plugins/bluez5/a2dp-sink.c
-@@ -558,8 +558,13 @@ static int flush_data(struct impl *this, uint64_t now_time)
- n_bytes = add_data(this, src + offs, l0);
- if (n_bytes > 0 && l1 > 0)
- n_bytes += add_data(this, src, l1);
-- if (n_bytes <= 0)
-+ if (n_bytes <= 0) {
-+ spa_list_remove(&b->link);
-+ b->outstanding = true;
-+ spa_node_call_reuse_buffer(&this->callbacks, 0, b->id);
-+ port->ready_offset = 0;
- break;
-+ }
-
- n_frames = n_bytes / port->frame_size;
-
---
-2.23.0.rc1
-
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-bluez5-add-sco-sink-and-sco-src-nodes.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-bluez5-add-sco-sink-and-sco-src-nodes.patch
deleted file mode 100644
index 389ebbda..00000000
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-bluez5-add-sco-sink-and-sco-src-nodes.patch
+++ /dev/null
@@ -1,2620 +0,0 @@
-From c1d18d46b48d056b6458ec6ac1075cb74411145a Mon Sep 17 00:00:00 2001
-From: Julian Bouzas <julian.bouzas@collabora.com>
-Date: Tue, 23 Jul 2019 12:52:46 -0400
-Subject: [PATCH] bluez5: add sco-sink and sco-src nodes
-
-Upstream-Status: Backport [de031b42b1d5e89dfb23d39317955c4b56f17c1b]
----
- spa/plugins/bluez5/bluez5-device.c | 140 +---
- spa/plugins/bluez5/bluez5-monitor.c | 24 +-
- spa/plugins/bluez5/meson.build | 2 +
- spa/plugins/bluez5/sco-sink.c | 1200 +++++++++++++++++++++++++++
- spa/plugins/bluez5/sco-source.c | 1136 +++++++++++++++++++++++++
- 5 files changed, 2396 insertions(+), 106 deletions(-)
- create mode 100644 spa/plugins/bluez5/sco-sink.c
- create mode 100644 spa/plugins/bluez5/sco-source.c
-
-diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c
-index d6ea467e..40a340c9 100644
---- a/spa/plugins/bluez5/bluez5-device.c
-+++ b/spa/plugins/bluez5/bluez5-device.c
-@@ -44,6 +44,8 @@
-
- extern const struct spa_handle_factory spa_a2dp_source_factory;
- extern const struct spa_handle_factory spa_a2dp_sink_factory;
-+extern const struct spa_handle_factory spa_sco_sink_factory;
-+extern const struct spa_handle_factory spa_sco_source_factory;
-
- static const char default_device[] = "";
-
-@@ -68,117 +70,61 @@ struct impl {
- struct props props;
-
- struct spa_bt_device *bt_dev;
-+
-+ uint32_t next_id;
- };
-
--static int emit_source_node(struct impl *this)
-+static void emit_node (struct impl *this, struct spa_bt_transport *t, const struct spa_handle_factory *factory)
- {
-- struct spa_dict_item items[1];
-- struct spa_bt_transport *t;
-- struct spa_bt_device *device = this->bt_dev;
-- enum spa_bt_profile profile = SPA_BT_PROFILE_NULL;
--
-- if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) {
-- spa_log_info(this->log, "A2DP (source) profile found");
-- profile = SPA_BT_PROFILE_A2DP_SOURCE;
-- } else if (device->connected_profiles & SPA_BT_PROFILE_HSP_HS) {
-- spa_log_info(this->log, "HSP (source) profile found (Not implemented yet)");
-- profile = SPA_BT_PROFILE_HSP_HS;
-- return -ENODEV;
-- } else if (device->connected_profiles & SPA_BT_PROFILE_HFP_HF) {
-- spa_log_info(this->log, "HFP (source) profile found (Not implemented yet)");
-- profile = SPA_BT_PROFILE_HFP_HF;
-- return -ENODEV;
-- }
--
-- /* Return if no profiles are connected */
-- if (profile == SPA_BT_PROFILE_NULL)
-- return -ENODEV;
--
-- spa_list_for_each(t, &device->transport_list, device_link) {
-- if (t->profile == profile) {
-- struct spa_device_object_info info;
-- char transport[16];
--
-- snprintf(transport, 16, "%p", t);
-- items[0] = SPA_DICT_ITEM_INIT("bluez5.transport", transport);
--
-- spa_bt_transport_acquire(t, true);
--
-- info = SPA_DEVICE_OBJECT_INFO_INIT();
-- info.type = SPA_TYPE_INTERFACE_Node;
-- info.factory = &spa_a2dp_source_factory;
-- info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
-- info.props = &SPA_DICT_INIT_ARRAY(items);
--
-- spa_device_emit_object_info(&this->hooks, 0, &info);
-- break;
-- }
-- }
--
-- spa_log_info (this->log, "bluez5 source nodes emitted");
-- return 0;
-+ struct spa_device_object_info info;
-+ struct spa_dict_item items[1];
-+ char transport[16];
-+
-+ /* Set the info */
-+ info = SPA_DEVICE_OBJECT_INFO_INIT();
-+ info.type = SPA_TYPE_INTERFACE_Node;
-+ info.factory = factory;
-+ info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
-+
-+ /* Pass the transport pointer as a property */
-+ snprintf(transport, 16, "%p", t);
-+ items[0] = SPA_DICT_ITEM_INIT("bluez5.transport", transport);
-+ info.props = &SPA_DICT_INIT_ARRAY(items);
-+
-+ /* Emit the node */
-+ spa_device_emit_object_info(&this->hooks, this->next_id++, &info);
- }
-
--static int emit_sink_node(struct impl *this)
-+static int emit_nodes(struct impl *this)
- {
-- struct spa_dict_item items[1];
-- struct spa_bt_transport *t;
- struct spa_bt_device *device = this->bt_dev;
-- enum spa_bt_profile profile = SPA_BT_PROFILE_NULL;
--
-- if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) {
-- spa_log_info(this->log, "A2DP (sink) profile found");
-- profile = SPA_BT_PROFILE_A2DP_SINK;
-- } else if (device->connected_profiles & SPA_BT_PROFILE_HSP_AG) {
-- spa_log_info(this->log, "HSP (sink) profile found (Not implemented yet)");
-- profile = SPA_BT_PROFILE_HSP_AG;
-- return -ENODEV;
-- } else if (device->connected_profiles & SPA_BT_PROFILE_HFP_AG) {
-- spa_log_info(this->log, "HFP (sink) profile found (Not implemented yet)");
-- profile = SPA_BT_PROFILE_HFP_AG;
-- return -ENODEV;
-- }
--
-- /* Return if no profiles are connected */
-- if (profile == SPA_BT_PROFILE_NULL)
-- return -ENODEV;
-+ struct spa_bt_transport *t;
-
- spa_list_for_each(t, &device->transport_list, device_link) {
-- if (t->profile == profile) {
-- struct spa_device_object_info info;
-- char transport[16];
--
-- snprintf(transport, 16, "%p", t);
-- items[0] = SPA_DICT_ITEM_INIT("bluez5.transport", transport);
--
-- info = SPA_DEVICE_OBJECT_INFO_INIT();
-- info.type = SPA_TYPE_INTERFACE_Node;
-- info.factory = &spa_a2dp_sink_factory;
-- info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
-- info.props = &SPA_DICT_INIT_ARRAY(items);
--
-- spa_device_emit_object_info(&this->hooks, 0, &info);
-- break;
-+ if (t->profile & device->connected_profiles) {
-+ switch (t->profile) {
-+ case SPA_BT_PROFILE_A2DP_SOURCE:
-+ emit_node (this, t, &spa_a2dp_source_factory);
-+ break;
-+ case SPA_BT_PROFILE_A2DP_SINK:
-+ emit_node (this, t, &spa_a2dp_sink_factory);
-+ break;
-+ case SPA_BT_PROFILE_HSP_HS:
-+ case SPA_BT_PROFILE_HSP_AG:
-+ case SPA_BT_PROFILE_HFP_HF:
-+ case SPA_BT_PROFILE_HFP_AG:
-+ emit_node (this, t, &spa_sco_source_factory);
-+ emit_node (this, t, &spa_sco_sink_factory);
-+ break;
-+ default:
-+ return -EINVAL;
-+ }
- }
- }
-
-- spa_log_info(this->log, "bluez5 sink nodes emitted");
- return 0;
- }
-
--static int emit_nodes(struct impl *this)
--{
-- int sink, src;
--
-- sink = emit_sink_node(this);
-- src = emit_source_node(this);
--
-- if (sink == -ENODEV && src == -ENODEV)
-- spa_log_warn(this->log, "no profile available");
--
-- return SPA_MAX(sink, src);
--}
--
- static const struct spa_dict_item info_items[] = {
- { "media.class", "Audio/Device" },
- };
-@@ -314,6 +260,8 @@ impl_init(const struct spa_handle_factory *factory,
-
- reset_props(&this->props);
-
-+ this->next_id = 0;
-+
- return 0;
- }
-
-diff --git a/spa/plugins/bluez5/bluez5-monitor.c b/spa/plugins/bluez5/bluez5-monitor.c
-index 5b8ff495..2a243715 100644
---- a/spa/plugins/bluez5/bluez5-monitor.c
-+++ b/spa/plugins/bluez5/bluez5-monitor.c
-@@ -1392,7 +1392,10 @@ static void rfcomm_event(struct spa_source *source)
- {
- struct spa_bt_transport *t = source->data;
- struct spa_bt_monitor *monitor = t->monitor;
-+ char buf[512];
-+ ssize_t len;
-
-+ /* Check for errors */
- if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) {
- spa_log_info(monitor->log, "lost RFCOMM connection.");
- if (source->loop)
-@@ -1400,20 +1403,19 @@ static void rfcomm_event(struct spa_source *source)
- goto fail;
- }
-
-+ /* Read the command */
-+ len = read(source->fd, buf, 511);
-+ if (len < 0) {
-+ spa_log_error(monitor->log, "RFCOMM read error: %s", strerror(errno));
-+ goto fail;
-+ }
-+ buf[len] = 0;
-+ printf ("RFCOMM AT COMMAND: %s\n", buf);
-+
- if (source->rmask & SPA_IO_IN) {
-- char buf[512];
-- ssize_t len;
- int gain, dummy;
- bool do_reply = false;
-
-- len = read(source->fd, buf, 511);
-- if (len < 0) {
-- spa_log_error(monitor->log, "RFCOMM read error: %s", strerror(errno));
-- goto fail;
-- }
-- buf[len] = 0;
-- spa_log_debug(monitor->log, "RFCOMM << %s", buf);
--
- /* There are only four HSP AT commands:
- * AT+VGS=value: value between 0 and 15, sent by the HS to AG to set the speaker gain.
- * +VGS=value is sent by AG to HS as a response to an AT+VGS command or when the gain
-@@ -1669,12 +1671,14 @@ static int sco_destroy_cb(void *data)
- spa_loop_remove_source(td->sco.loop, &td->sco);
- shutdown(td->sco.fd, SHUT_RDWR);
- close (td->sco.fd);
-+ td->sco.fd = -1;
- }
- if (td->rfcomm.data) {
- if (td->rfcomm.loop)
- spa_loop_remove_source(td->rfcomm.loop, &td->rfcomm);
- shutdown(td->rfcomm.fd, SHUT_RDWR);
- close (td->rfcomm.fd);
-+ td->rfcomm.fd = -1;
- }
- return 0;
- }
-diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build
-index 5fb285ec..ddcc74fa 100644
---- a/spa/plugins/bluez5/meson.build
-+++ b/spa/plugins/bluez5/meson.build
-@@ -3,6 +3,8 @@ bluez5_sources = ['plugin.c',
- 'a2dp-codecs.c',
- 'a2dp-sink.c',
- 'a2dp-source.c',
-+ 'sco-sink.c',
-+ 'sco-source.c',
- 'bluez5-device.c',
- 'bluez5-monitor.c']
-
-diff --git a/spa/plugins/bluez5/sco-sink.c b/spa/plugins/bluez5/sco-sink.c
-new file mode 100644
-index 00000000..52307e10
---- /dev/null
-+++ b/spa/plugins/bluez5/sco-sink.c
-@@ -0,0 +1,1200 @@
-+/* Spa SCO Sink
-+ *
-+ * Copyright © 2018 Wim Taymans
-+ * Copyright © 2019 Collabora Ltd.
-+ *
-+ * 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 <unistd.h>
-+#include <stddef.h>
-+#include <stdio.h>
-+#include <sys/timerfd.h>
-+#include <arpa/inet.h>
-+#include <sys/ioctl.h>
-+
-+#include <spa/support/loop.h>
-+#include <spa/support/log.h>
-+#include <spa/utils/list.h>
-+
-+#include <spa/node/node.h>
-+#include <spa/node/io.h>
-+#include <spa/param/param.h>
-+#include <spa/param/audio/format.h>
-+#include <spa/param/audio/format-utils.h>
-+#include <spa/pod/filter.h>
-+
-+#include <sbc/sbc.h>
-+
-+#include "defs.h"
-+
-+struct props {
-+ uint32_t min_latency;
-+ uint32_t max_latency;
-+};
-+
-+#define MAX_BUFFERS 32
-+
-+struct buffer {
-+ uint32_t id;
-+ unsigned int outstanding:1;
-+ struct spa_buffer *buf;
-+ struct spa_meta_header *h;
-+ struct spa_list link;
-+};
-+
-+struct port {
-+ struct spa_audio_info current_format;
-+ int frame_size;
-+ unsigned int have_format:1;
-+
-+ uint64_t info_all;
-+ struct spa_port_info info;
-+ struct spa_io_buffers *io;
-+ struct spa_param_info params[8];
-+
-+ struct buffer buffers[MAX_BUFFERS];
-+ uint32_t n_buffers;
-+
-+ struct spa_list free;
-+ struct spa_list ready;
-+};
-+
-+struct impl {
-+ struct spa_handle handle;
-+ struct spa_node node;
-+
-+ /* Support */
-+ struct spa_log *log;
-+ struct spa_loop *main_loop;
-+ struct spa_loop *data_loop;
-+
-+ /* Hooks and callbacks */
-+ struct spa_hook_list hooks;
-+ struct spa_callbacks callbacks;
-+
-+ /* Info */
-+ uint64_t info_all;
-+ struct spa_node_info info;
-+ struct spa_param_info params[8];
-+ struct props props;
-+
-+ /* Transport */
-+ struct spa_bt_transport *transport;
-+ struct spa_hook transport_listener;
-+ int sock_fd;
-+
-+ /* Port */
-+ struct port port;
-+
-+ /* Flags */
-+ unsigned int started:1;
-+ unsigned int slaved:1;
-+
-+ /* Sources */
-+ struct spa_source source;
-+ struct spa_source flush_source;
-+
-+ /* Timer */
-+ int timerfd;
-+ struct timespec now;
-+ struct spa_io_clock *clock;
-+ struct spa_io_position *position;
-+ int threshold;
-+
-+ /* Times */
-+ uint64_t start_time;
-+
-+ /* Counts */
-+ uint64_t sample_count;
-+};
-+
-+#define NAME "sco-sink"
-+
-+#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0)
-+
-+static const uint32_t default_min_latency = 128;
-+static const uint32_t default_max_latency = 1024;
-+
-+static void reset_props(struct props *props)
-+{
-+ props->min_latency = default_min_latency;
-+ props->max_latency = default_max_latency;
-+}
-+
-+static int impl_node_enum_params(struct spa_node *node, int seq,
-+ uint32_t id, uint32_t start, uint32_t num,
-+ const struct spa_pod *filter)
-+{
-+ struct impl *this;
-+ struct spa_pod *param;
-+ struct spa_pod_builder b = { 0 };
-+ uint8_t buffer[1024];
-+ struct spa_result_node_params result;
-+ uint32_t count = 0;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+ spa_return_val_if_fail(num != 0, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ result.id = id;
-+ result.next = start;
-+ next:
-+ result.index = result.next++;
-+
-+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
-+
-+ switch (id) {
-+ case SPA_PARAM_PropInfo:
-+ {
-+ struct props *p = &this->props;
-+
-+ switch (result.index) {
-+ case 0:
-+ param = spa_pod_builder_add_object(&b,
-+ SPA_TYPE_OBJECT_PropInfo, id,
-+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_minLatency),
-+ SPA_PROP_INFO_name, SPA_POD_String("The minimum latency"),
-+ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->min_latency, 1, INT32_MAX));
-+ break;
-+ case 1:
-+ param = spa_pod_builder_add_object(&b,
-+ SPA_TYPE_OBJECT_PropInfo, id,
-+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_maxLatency),
-+ SPA_PROP_INFO_name, SPA_POD_String("The maximum latency"),
-+ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->max_latency, 1, INT32_MAX));
-+ break;
-+ default:
-+ return 0;
-+ }
-+ break;
-+ }
-+ case SPA_PARAM_Props:
-+ {
-+ struct props *p = &this->props;
-+
-+ switch (result.index) {
-+ case 0:
-+ param = spa_pod_builder_add_object(&b,
-+ SPA_TYPE_OBJECT_Props, id,
-+ SPA_PROP_minLatency, SPA_POD_Int(p->min_latency),
-+ SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency));
-+ break;
-+ default:
-+ return 0;
-+ }
-+ break;
-+ }
-+ default:
-+ return -ENOENT;
-+ }
-+
-+ if (spa_pod_filter(&b, &result.param, param, filter) < 0)
-+ goto next;
-+
-+ spa_node_emit_result(&this->hooks, seq, 0, &result);
-+
-+ if (++count != num)
-+ goto next;
-+
-+ return 0;
-+}
-+
-+static void set_timeout(struct impl *this, time_t sec, long nsec)
-+{
-+ struct itimerspec ts;
-+
-+ ts.it_value.tv_sec = sec;
-+ ts.it_value.tv_nsec = nsec;
-+ ts.it_interval.tv_sec = 0;
-+ ts.it_interval.tv_nsec = 0;
-+
-+ timerfd_settime(this->timerfd, TFD_TIMER_ABSTIME, &ts, NULL);
-+ this->source.mask = SPA_IO_IN;
-+ spa_loop_update_source(this->data_loop, &this->source);
-+}
-+
-+static void reset_timeout(struct impl *this)
-+{
-+ set_timeout(this, 0, this->slaved ? 0 : 1);
-+}
-+
-+
-+static void set_next_timeout(struct impl *this, uint64_t now_time)
-+{
-+ struct port *port = &this->port;
-+
-+ /* Set the next timeout if not slaved, otherwise reset values */
-+ if (!this->slaved) {
-+ /* Get the elapsed time */
-+ const uint64_t elapsed_time = now_time - this->start_time;
-+
-+ /* Get the elapsed samples */
-+ const uint64_t elapsed_samples = elapsed_time * port->current_format.info.raw.rate / SPA_NSEC_PER_SEC;
-+
-+ /* Get the queued samples (processed - elapsed) */
-+ const uint64_t queued_samples = this->sample_count - elapsed_samples;
-+
-+ /* Get the queued time */
-+ const uint64_t queued_time = (queued_samples * SPA_NSEC_PER_SEC) / port->current_format.info.raw.rate;
-+
-+ /* Get the next time */
-+ const uint64_t next_time = now_time + queued_time;
-+
-+ /* Set the next timeout */
-+ set_timeout (this, next_time / SPA_NSEC_PER_SEC, next_time % SPA_NSEC_PER_SEC);
-+ } else {
-+ this->start_time = now_time;
-+ this->sample_count = 0;
-+ }
-+}
-+
-+static int do_reslave(struct spa_loop *loop,
-+ bool async,
-+ uint32_t seq,
-+ const void *data,
-+ size_t size,
-+ void *user_data)
-+{
-+ struct impl *this = user_data;
-+ reset_timeout(this);
-+ return 0;
-+}
-+
-+static inline bool is_slaved(struct impl *this)
-+{
-+ return this->position && this->clock && this->position->clock.id != this->clock->id;
-+}
-+
-+static int impl_node_set_io(struct spa_node *node, uint32_t id, void *data, size_t size)
-+{
-+ struct impl *this;
-+ bool slaved;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ switch (id) {
-+ case SPA_IO_Clock:
-+ this->clock = data;
-+ break;
-+ case SPA_IO_Position:
-+ this->position = data;
-+ break;
-+ default:
-+ return -ENOENT;
-+ }
-+
-+ slaved = is_slaved(this);
-+ if (this->started && slaved != this->slaved) {
-+ spa_log_debug(this->log, "sco-sink %p: reslave %d->%d", this, this->slaved, slaved);
-+ this->slaved = slaved;
-+ spa_loop_invoke(this->data_loop, do_reslave, 0, NULL, 0, true, this);
-+ }
-+ return 0;
-+}
-+
-+static int impl_node_set_param(struct spa_node *node, uint32_t id, uint32_t flags,
-+ const struct spa_pod *param)
-+{
-+ struct impl *this;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ switch (id) {
-+ case SPA_PARAM_Props:
-+ {
-+ struct props *p = &this->props;
-+
-+ if (param == NULL) {
-+ reset_props(p);
-+ return 0;
-+ }
-+ spa_pod_parse_object(param,
-+ SPA_TYPE_OBJECT_Props, NULL,
-+ SPA_PROP_minLatency, SPA_POD_OPT_Int(&p->min_latency),
-+ SPA_PROP_maxLatency, SPA_POD_OPT_Int(&p->max_latency));
-+ break;
-+ }
-+ default:
-+ return -ENOENT;
-+ }
-+
-+ return 0;
-+}
-+
-+static bool write_data(struct impl *this, uint8_t *data, uint32_t size, uint32_t *total_written)
-+{
-+ uint32_t local_total_written = 0;
-+ const uint32_t mtu_size = this->transport->write_mtu;
-+
-+ /* TODO: For now we assume the size is always a mutliple of mtu_size */
-+ while (local_total_written < (size - mtu_size)) {
-+ const int bytes_written = write(this->sock_fd, data, mtu_size);
-+ if (bytes_written < 0) {
-+ spa_log_warn(this->log, "error writting data: %s", strerror(errno));
-+ return false;
-+ }
-+
-+ data += bytes_written;
-+ local_total_written += bytes_written;
-+ }
-+
-+ if (total_written)
-+ *total_written = local_total_written;
-+ return true;
-+}
-+
-+static int render_buffers(struct impl *this, uint64_t now_time)
-+{
-+ struct port *port = &this->port;
-+
-+ /* Render the buffer */
-+ while (!spa_list_is_empty(&port->ready)) {
-+ uint8_t *src;
-+ struct buffer *b;
-+ struct spa_data *d;
-+ uint32_t offset, size;
-+ uint32_t total_written = 0;
-+
-+ /* Get the buffer and datas */
-+ b = spa_list_first(&port->ready, struct buffer, link);
-+ d = b->buf->datas;
-+
-+ /* Get the data, offset and size */
-+ src = d[0].data;
-+ offset = d[0].chunk->offset;
-+ size = d[0].chunk->size;
-+
-+ /* Write data */
-+ write_data(this, src + offset, size, &total_written);
-+
-+ /* Update the cample count */
-+ this->sample_count += total_written / port->frame_size;
-+
-+ /* Remove the buffer and mark it as reusable */
-+ spa_list_remove(&b->link);
-+ b->outstanding = true;
-+ spa_node_call_reuse_buffer(&this->callbacks, 0, b->id);
-+ }
-+
-+ /* Set next timeout */
-+ set_next_timeout(this, now_time);
-+
-+ return 0;
-+}
-+
-+static void sco_on_flush(struct spa_source *source)
-+{
-+ struct impl *this = source->data;
-+ uint64_t now_time;
-+
-+ if ((source->rmask & SPA_IO_OUT) == 0) {
-+ spa_log_warn(this->log, "error %d", source->rmask);
-+ if (this->flush_source.loop)
-+ spa_loop_remove_source(this->data_loop, &this->flush_source);
-+ this->source.mask = 0;
-+ spa_loop_update_source(this->data_loop, &this->source);
-+ return;
-+ }
-+
-+ /* Get the current time */
-+ clock_gettime(CLOCK_MONOTONIC, &this->now);
-+ now_time = SPA_TIMESPEC_TO_NSEC(&this->now);
-+
-+ /* Render buffers */
-+ render_buffers(this, now_time);
-+}
-+
-+static void sco_on_timeout(struct spa_source *source)
-+{
-+ struct impl *this = source->data;
-+ struct port *port = &this->port;
-+ uint64_t exp, now_time;
-+ struct spa_io_buffers *io = port->io;
-+
-+ /* Read the timerfd */
-+ if (this->started && read(this->timerfd, &exp, sizeof(uint64_t)) != sizeof(uint64_t))
-+ spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno));
-+
-+ /* Get the current time */
-+ clock_gettime(CLOCK_MONOTONIC, &this->now);
-+ now_time = SPA_TIMESPEC_TO_NSEC(&this->now);
-+
-+ /* Set the start time to the current time */
-+ if (this->start_time == 0)
-+ this->start_time = now_time;
-+
-+ /* Notify we need a new buffer if we have processed all of them */
-+ if (spa_list_is_empty(&port->ready)) {
-+ io->status = SPA_STATUS_NEED_BUFFER;
-+ spa_node_call_ready(&this->callbacks, SPA_STATUS_NEED_BUFFER);
-+ }
-+
-+ /* Render the buffers */
-+ render_buffers(this, now_time);
-+}
-+
-+static int do_start(struct impl *this)
-+{
-+ int val;
-+ bool do_accept;
-+
-+ /* Dont do anything if the node has already started */
-+ if (this->started)
-+ return 0;
-+
-+ /* Make sure the transport is valid */
-+ spa_return_val_if_fail (this->transport != NULL, -EIO);
-+
-+ /* Set the slaved flag */
-+ this->slaved = is_slaved(this);
-+
-+ /* Do accept if Gateway; otherwise do connect for Head Unit */
-+ do_accept = this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY;
-+
-+ /* acquire the socked fd (false -> connect | true -> accept) */
-+ this->sock_fd = spa_bt_transport_acquire(this->transport, do_accept);
-+ if (this->sock_fd < 0)
-+ return -1;
-+
-+ /* Set the write MTU */
-+ val = this->transport->write_mtu;
-+ if (setsockopt(this->sock_fd, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) < 0)
-+ spa_log_warn(this->log, "sco-sink %p: SO_SNDBUF %m", this);
-+
-+ /* Set the read MTU */
-+ val = this->transport->read_mtu;
-+ if (setsockopt(this->sock_fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) < 0)
-+ spa_log_warn(this->log, "sco-sink %p: SO_RCVBUF %m", this);
-+
-+ /* Set the priority */
-+ val = 6;
-+ if (setsockopt(this->sock_fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0)
-+ spa_log_warn(this->log, "SO_PRIORITY failed: %m");
-+
-+ /* Add the timeout callback */
-+ this->source.data = this;
-+ this->source.fd = this->timerfd;
-+ this->source.func = sco_on_timeout;
-+ this->source.mask = SPA_IO_IN;
-+ this->source.rmask = 0;
-+ spa_loop_add_source(this->data_loop, &this->source);
-+
-+ /* Add the flush callback */
-+ this->flush_source.data = this;
-+ this->flush_source.fd = this->sock_fd;
-+ this->flush_source.func = sco_on_flush;
-+ this->flush_source.mask = 0;
-+ this->flush_source.rmask = 0;
-+ spa_loop_add_source(this->data_loop, &this->flush_source);
-+
-+ /* Reset timeout to start processing */
-+ reset_timeout(this);
-+
-+ /* Set the started flag */
-+ this->started = true;
-+
-+ return 0;
-+}
-+
-+static int do_remove_source(struct spa_loop *loop,
-+ bool async,
-+ uint32_t seq,
-+ const void *data,
-+ size_t size,
-+ void *user_data)
-+{
-+ struct impl *this = user_data;
-+ struct itimerspec ts;
-+
-+ if (this->source.loop)
-+ spa_loop_remove_source(this->data_loop, &this->source);
-+ ts.it_value.tv_sec = 0;
-+ ts.it_value.tv_nsec = 0;
-+ ts.it_interval.tv_sec = 0;
-+ ts.it_interval.tv_nsec = 0;
-+ timerfd_settime(this->timerfd, 0, &ts, NULL);
-+ if (this->flush_source.loop)
-+ spa_loop_remove_source(this->data_loop, &this->flush_source);
-+
-+ return 0;
-+}
-+
-+static int do_stop(struct impl *this)
-+{
-+ int res = 0;
-+
-+ if (!this->started)
-+ return 0;
-+
-+ spa_log_trace(this->log, "sco-sink %p: stop", this);
-+
-+ spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this);
-+
-+ this->started = false;
-+
-+ if (this->transport) {
-+ /* Release the transport */
-+ res = spa_bt_transport_release(this->transport);
-+
-+ /* Shutdown and close the socket */
-+ shutdown(this->sock_fd, SHUT_RDWR);
-+ close(this->sock_fd);
-+ this->sock_fd = -1;
-+ }
-+
-+ return res;
-+}
-+
-+static int impl_node_send_command(struct spa_node *node, const struct spa_command *command)
-+{
-+ struct impl *this;
-+ struct port *port;
-+ int res;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+ spa_return_val_if_fail(command != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+ port = &this->port;
-+
-+ switch (SPA_NODE_COMMAND_ID(command)) {
-+ case SPA_NODE_COMMAND_Start:
-+ if (!port->have_format)
-+ return -EIO;
-+ if (port->n_buffers == 0)
-+ return -EIO;
-+ if ((res = do_start(this)) < 0)
-+ return res;
-+ break;
-+ case SPA_NODE_COMMAND_Pause:
-+ if ((res = do_stop(this)) < 0)
-+ return res;
-+ break;
-+ default:
-+ return -ENOTSUP;
-+ }
-+ return 0;
-+}
-+
-+static const struct spa_dict_item node_info_items[] = {
-+ { "media.class", "Audio/Sink" },
-+ { "node.driver", "true" },
-+};
-+
-+static void emit_node_info(struct impl *this, bool full)
-+{
-+ if (full)
-+ this->info.change_mask = this->info_all;
-+ if (this->info.change_mask) {
-+ this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items);
-+ spa_node_emit_info(&this->hooks, &this->info);
-+ this->info.change_mask = 0;
-+ }
-+}
-+
-+static void emit_port_info(struct impl *this, struct port *port, bool full)
-+{
-+ if (full)
-+ port->info.change_mask = port->info_all;
-+ if (port->info.change_mask) {
-+ spa_node_emit_port_info(&this->hooks,
-+ SPA_DIRECTION_INPUT, 0, &port->info);
-+ port->info.change_mask = 0;
-+ }
-+}
-+
-+static int
-+impl_node_add_listener(struct spa_node *node,
-+ struct spa_hook *listener,
-+ const struct spa_node_events *events,
-+ void *data)
-+{
-+ struct impl *this;
-+ struct spa_hook_list save;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+ spa_hook_list_isolate(&this->hooks, &save, listener, events, data);
-+
-+ emit_node_info(this, true);
-+ emit_port_info(this, &this->port, true);
-+
-+ spa_hook_list_join(&this->hooks, &save);
-+
-+ return 0;
-+}
-+
-+static int
-+impl_node_set_callbacks(struct spa_node *node,
-+ const struct spa_node_callbacks *callbacks,
-+ void *data)
-+{
-+ struct impl *this;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ this->callbacks = SPA_CALLBACKS_INIT(callbacks, data);
-+
-+ return 0;
-+}
-+
-+static int impl_node_add_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id,
-+ const struct spa_dict *props)
-+{
-+ return -ENOTSUP;
-+}
-+
-+static int impl_node_remove_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id)
-+{
-+ return -ENOTSUP;
-+}
-+
-+static int
-+impl_node_port_enum_params(struct spa_node *node, int seq,
-+ enum spa_direction direction, uint32_t port_id,
-+ uint32_t id, uint32_t start, uint32_t num,
-+ const struct spa_pod *filter)
-+{
-+
-+ struct impl *this;
-+ struct port *port;
-+ struct spa_pod *param;
-+ struct spa_pod_builder b = { 0 };
-+ uint8_t buffer[1024];
-+ struct spa_result_node_params result;
-+ uint32_t count = 0;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+ spa_return_val_if_fail(num != 0, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
-+ port = &this->port;
-+
-+ result.id = id;
-+ result.next = start;
-+ next:
-+ result.index = result.next++;
-+
-+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
-+
-+ switch (id) {
-+ case SPA_PARAM_EnumFormat:
-+ if (result.index > 0)
-+ return 0;
-+
-+ /* set the info structure */
-+ struct spa_audio_info_raw info = { 0, };
-+ info.format = SPA_AUDIO_FORMAT_S16;
-+ info.channels = 1;
-+ info.position[0] = SPA_AUDIO_CHANNEL_MONO;
-+
-+ /* TODO: For now we only handle HSP profiles which has always CVSD format,
-+ * but we eventually need to support HFP that can have both CVSD and MSBC formats */
-+
-+ /* CVSD format has a rate of 8kHz
-+ * MSBC format has a rate of 16kHz */
-+ info.rate = 8000;
-+
-+ /* build the param */
-+ param = spa_format_audio_raw_build(&b, id, &info);
-+
-+ break;
-+
-+ case SPA_PARAM_Format:
-+ if (!port->have_format)
-+ return -EIO;
-+ if (result.index > 0)
-+ return 0;
-+
-+ param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw);
-+ break;
-+
-+ case SPA_PARAM_Buffers:
-+ if (!port->have_format)
-+ return -EIO;
-+ if (result.index > 0)
-+ return 0;
-+
-+ param = spa_pod_builder_add_object(&b,
-+ SPA_TYPE_OBJECT_ParamBuffers, id,
-+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 2, MAX_BUFFERS),
-+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
-+ SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(
-+ this->props.min_latency * port->frame_size,
-+ this->props.min_latency * port->frame_size,
-+ INT32_MAX),
-+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->frame_size),
-+ SPA_PARAM_BUFFERS_align, SPA_POD_Int(16));
-+ break;
-+
-+ case SPA_PARAM_Meta:
-+ switch (result.index) {
-+ case 0:
-+ param = spa_pod_builder_add_object(&b,
-+ SPA_TYPE_OBJECT_ParamMeta, id,
-+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
-+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
-+ break;
-+ default:
-+ return 0;
-+ }
-+ break;
-+
-+ default:
-+ return -ENOENT;
-+ }
-+
-+ if (spa_pod_filter(&b, &result.param, param, filter) < 0)
-+ goto next;
-+
-+ spa_node_emit_result(&this->hooks, seq, 0, &result);
-+
-+ if (++count != num)
-+ goto next;
-+
-+ return 0;
-+}
-+
-+static int clear_buffers(struct impl *this, struct port *port)
-+{
-+ do_stop(this);
-+ if (port->n_buffers > 0) {
-+ spa_list_init(&port->ready);
-+ port->n_buffers = 0;
-+ }
-+ return 0;
-+}
-+
-+static int port_set_format(struct impl *this, struct port *port,
-+ uint32_t flags,
-+ const struct spa_pod *format)
-+{
-+ int err;
-+
-+ if (format == NULL) {
-+ spa_log_info(this->log, "clear format");
-+ clear_buffers(this, port);
-+ port->have_format = false;
-+ } else {
-+ struct spa_audio_info info = { 0 };
-+
-+ if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0)
-+ return err;
-+
-+ if (info.media_type != SPA_MEDIA_TYPE_audio ||
-+ info.media_subtype != SPA_MEDIA_SUBTYPE_raw)
-+ return -EINVAL;
-+
-+ if (spa_format_audio_raw_parse(format, &info.info.raw) < 0)
-+ return -EINVAL;
-+
-+ port->frame_size = info.info.raw.channels * 2;
-+ port->current_format = info;
-+ port->have_format = true;
-+ this->threshold = this->props.min_latency;
-+ }
-+
-+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
-+ if (port->have_format) {
-+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS;
-+ port->info.flags = SPA_PORT_FLAG_CAN_USE_BUFFERS | SPA_PORT_FLAG_LIVE;
-+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE;
-+ port->info.rate = SPA_FRACTION(1, port->current_format.info.raw.rate);
-+ port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE);
-+ port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ);
-+ } else {
-+ port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
-+ port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
-+ }
-+ emit_port_info(this, port, false);
-+
-+ return 0;
-+}
-+
-+static int
-+impl_node_port_set_param(struct spa_node *node,
-+ enum spa_direction direction, uint32_t port_id,
-+ uint32_t id, uint32_t flags,
-+ const struct spa_pod *param)
-+{
-+ struct impl *this;
-+ struct port *port;
-+ int res;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL);
-+ port = &this->port;
-+
-+ switch (id) {
-+ case SPA_PARAM_Format:
-+ res = port_set_format(this, port, flags, param);
-+ break;
-+ default:
-+ res = -ENOENT;
-+ break;
-+ }
-+ return res;
-+}
-+
-+static int
-+impl_node_port_use_buffers(struct spa_node *node,
-+ enum spa_direction direction,
-+ uint32_t port_id, struct spa_buffer **buffers, uint32_t n_buffers)
-+{
-+ struct impl *this;
-+ struct port *port;
-+ uint32_t i;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
-+ port = &this->port;
-+
-+ spa_log_info(this->log, "use buffers %d", n_buffers);
-+
-+ if (!port->have_format)
-+ return -EIO;
-+
-+ clear_buffers(this, port);
-+
-+ for (i = 0; i < n_buffers; i++) {
-+ struct buffer *b = &port->buffers[i];
-+ uint32_t type;
-+
-+ b->buf = buffers[i];
-+ b->id = i;
-+ b->outstanding = true;
-+
-+ b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h));
-+
-+ type = buffers[i]->datas[0].type;
-+ if ((type == SPA_DATA_MemFd ||
-+ type == SPA_DATA_DmaBuf ||
-+ type == SPA_DATA_MemPtr) && buffers[i]->datas[0].data == NULL) {
-+ spa_log_error(this->log, NAME " %p: need mapped memory", this);
-+ return -EINVAL;
-+ }
-+ this->threshold = buffers[i]->datas[0].maxsize / port->frame_size;
-+ }
-+ port->n_buffers = n_buffers;
-+
-+ return 0;
-+}
-+
-+static int
-+impl_node_port_alloc_buffers(struct spa_node *node,
-+ enum spa_direction direction,
-+ uint32_t port_id,
-+ struct spa_pod **params,
-+ uint32_t n_params,
-+ struct spa_buffer **buffers,
-+ uint32_t *n_buffers)
-+{
-+ struct impl *this;
-+ struct port *port;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+ spa_return_val_if_fail(buffers != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
-+ port = &this->port;
-+
-+ if (!port->have_format)
-+ return -EIO;
-+
-+ return -ENOTSUP;
-+}
-+
-+static int
-+impl_node_port_set_io(struct spa_node *node,
-+ enum spa_direction direction,
-+ uint32_t port_id,
-+ uint32_t id,
-+ void *data, size_t size)
-+{
-+ struct impl *this;
-+ struct port *port;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
-+ port = &this->port;
-+
-+ switch (id) {
-+ case SPA_IO_Buffers:
-+ port->io = data;
-+ break;
-+ default:
-+ return -ENOENT;
-+ }
-+ return 0;
-+}
-+
-+static int impl_node_port_reuse_buffer(struct spa_node *node, uint32_t port_id, uint32_t buffer_id)
-+{
-+ return -ENOTSUP;
-+}
-+
-+static int impl_node_process(struct spa_node *node)
-+{
-+ struct impl *this;
-+ struct port *port;
-+ struct spa_io_buffers *io;
-+ uint64_t now_time;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+ port = &this->port;
-+ io = port->io;
-+ spa_return_val_if_fail(io != NULL, -EIO);
-+
-+ /* Get the current time */
-+ clock_gettime(CLOCK_MONOTONIC, &this->now);
-+ now_time = SPA_TIMESPEC_TO_NSEC(&this->now);
-+
-+ /* Make sure we process all the previous buffers */
-+ if (!spa_list_is_empty(&port->ready))
-+ render_buffers(this, now_time);
-+
-+ /* Process the new buffers */
-+ if (io->status == SPA_STATUS_HAVE_BUFFER && io->buffer_id < port->n_buffers) {
-+ struct buffer *b = &port->buffers[io->buffer_id];
-+
-+ if (!b->outstanding) {
-+ spa_log_warn(this->log, NAME " %p: buffer %u in use", this, io->buffer_id);
-+ io->status = -EINVAL;
-+ return -EINVAL;
-+ }
-+
-+ spa_log_trace(this->log, NAME " %p: queue buffer %u", this, io->buffer_id);
-+
-+ spa_list_append(&port->ready, &b->link);
-+ b->outstanding = false;
-+
-+ this->threshold = SPA_MIN(b->buf->datas[0].chunk->size / port->frame_size,
-+ this->props.max_latency);
-+
-+ render_buffers(this, now_time);
-+
-+ io->status = SPA_STATUS_OK;
-+ }
-+
-+ return SPA_STATUS_HAVE_BUFFER;
-+}
-+
-+static const struct spa_node impl_node = {
-+ SPA_VERSION_NODE,
-+ .add_listener = impl_node_add_listener,
-+ .set_callbacks = impl_node_set_callbacks,
-+ .enum_params = impl_node_enum_params,
-+ .set_param = impl_node_set_param,
-+ .set_io = impl_node_set_io,
-+ .send_command = impl_node_send_command,
-+ .add_port = impl_node_add_port,
-+ .remove_port = impl_node_remove_port,
-+ .port_enum_params = impl_node_port_enum_params,
-+ .port_set_param = impl_node_port_set_param,
-+ .port_use_buffers = impl_node_port_use_buffers,
-+ .port_alloc_buffers = impl_node_port_alloc_buffers,
-+ .port_set_io = impl_node_port_set_io,
-+ .port_reuse_buffer = impl_node_port_reuse_buffer,
-+ .process = impl_node_process,
-+};
-+
-+static void transport_destroy(void *data)
-+{
-+ struct impl *this = data;
-+ spa_log_debug(this->log, "transport %p destroy", this->transport);
-+ this->transport = NULL;
-+}
-+
-+static const struct spa_bt_transport_events transport_events = {
-+ SPA_VERSION_BT_TRANSPORT_EVENTS,
-+ .destroy = transport_destroy,
-+};
-+
-+static int impl_get_interface(struct spa_handle *handle, uint32_t type, void **interface)
-+{
-+ struct impl *this;
-+
-+ spa_return_val_if_fail(handle != NULL, -EINVAL);
-+ spa_return_val_if_fail(interface != NULL, -EINVAL);
-+
-+ this = (struct impl *) handle;
-+
-+ if (type == SPA_TYPE_INTERFACE_Node)
-+ *interface = &this->node;
-+ else
-+ return -ENOENT;
-+
-+ return 0;
-+}
-+
-+static int impl_clear(struct spa_handle *handle)
-+{
-+ return 0;
-+}
-+
-+static size_t
-+impl_get_size(const struct spa_handle_factory *factory,
-+ const struct spa_dict *params)
-+{
-+ return sizeof(struct impl);
-+}
-+
-+static int
-+impl_init(const struct spa_handle_factory *factory,
-+ struct spa_handle *handle,
-+ const struct spa_dict *info,
-+ const struct spa_support *support,
-+ uint32_t n_support)
-+{
-+ struct impl *this;
-+ struct port *port;
-+ uint32_t i;
-+
-+ spa_return_val_if_fail(factory != NULL, -EINVAL);
-+ spa_return_val_if_fail(handle != NULL, -EINVAL);
-+
-+ handle->get_interface = impl_get_interface;
-+ handle->clear = impl_clear;
-+
-+ this = (struct impl *) handle;
-+
-+ for (i = 0; i < n_support; i++) {
-+ if (support[i].type == SPA_TYPE_INTERFACE_Log)
-+ this->log = support[i].data;
-+ else if (support[i].type == SPA_TYPE_INTERFACE_DataLoop)
-+ this->data_loop = support[i].data;
-+ else if (support[i].type == SPA_TYPE_INTERFACE_MainLoop)
-+ this->main_loop = support[i].data;
-+ }
-+ if (this->data_loop == NULL) {
-+ spa_log_error(this->log, "a data loop is needed");
-+ return -EINVAL;
-+ }
-+ if (this->main_loop == NULL) {
-+ spa_log_error(this->log, "a main loop is needed");
-+ return -EINVAL;
-+ }
-+
-+ this->node = impl_node;
-+ spa_hook_list_init(&this->hooks);
-+
-+ reset_props(&this->props);
-+
-+ this->info_all = SPA_NODE_CHANGE_MASK_FLAGS |
-+ SPA_NODE_CHANGE_MASK_PARAMS |
-+ SPA_NODE_CHANGE_MASK_PROPS;
-+ this->info = SPA_NODE_INFO_INIT();
-+ this->info.flags = SPA_NODE_FLAG_RT;
-+ this->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ);
-+ this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
-+ this->info.params = this->params;
-+ this->info.n_params = 2;
-+
-+ port = &this->port;
-+ port->info_all = SPA_PORT_CHANGE_MASK_FLAGS |
-+ SPA_PORT_CHANGE_MASK_PARAMS;
-+ port->info = SPA_PORT_INFO_INIT();
-+ port->info.flags = SPA_PORT_FLAG_CAN_USE_BUFFERS;
-+ port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
-+ port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ);
-+ port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ);
-+ port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
-+ port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
-+ port->info.params = port->params;
-+ port->info.n_params = 5;
-+ spa_list_init(&port->ready);
-+
-+ for (i = 0; info && i < info->n_items; i++) {
-+ if (strcmp(info->items[i].key, "bluez5.transport") == 0)
-+ sscanf(info->items[i].value, "%p", &this->transport);
-+ }
-+ if (this->transport == NULL) {
-+ spa_log_error(this->log, "a transport is needed");
-+ return -EINVAL;
-+ }
-+ spa_bt_transport_add_listener(this->transport,
-+ &this->transport_listener, &transport_events, this);
-+ this->sock_fd = -1;
-+
-+ this->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
-+
-+ return 0;
-+}
-+
-+static const struct spa_interface_info impl_interfaces[] = {
-+ {SPA_TYPE_INTERFACE_Node,},
-+};
-+
-+static int
-+impl_enum_interface_info(const struct spa_handle_factory *factory,
-+ const struct spa_interface_info **info, uint32_t *index)
-+{
-+ spa_return_val_if_fail(factory != NULL, -EINVAL);
-+ spa_return_val_if_fail(info != NULL, -EINVAL);
-+ spa_return_val_if_fail(index != NULL, -EINVAL);
-+
-+ switch (*index) {
-+ case 0:
-+ *info = &impl_interfaces[*index];
-+ break;
-+ default:
-+ return 0;
-+ }
-+ (*index)++;
-+ return 1;
-+}
-+
-+static const struct spa_dict_item info_items[] = {
-+ { "factory.author", "Wim Taymans <wim.taymans@gmail.com>" },
-+ { "factory.description", "Play audio with the sco (hsp/hfp)" },
-+};
-+
-+static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items);
-+
-+struct spa_handle_factory spa_sco_sink_factory = {
-+ SPA_VERSION_HANDLE_FACTORY,
-+ NAME,
-+ &info,
-+ impl_get_size,
-+ impl_init,
-+ impl_enum_interface_info,
-+};
-diff --git a/spa/plugins/bluez5/sco-source.c b/spa/plugins/bluez5/sco-source.c
-new file mode 100644
-index 00000000..ce83ee24
---- /dev/null
-+++ b/spa/plugins/bluez5/sco-source.c
-@@ -0,0 +1,1136 @@
-+/* Spa SCO Source
-+ *
-+ * Copyright © 2018 Wim Taymans
-+ * Copyright © 2019 Collabora Ltd.
-+ *
-+ * 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 <unistd.h>
-+#include <stddef.h>
-+#include <stdio.h>
-+#include <time.h>
-+#include <fcntl.h>
-+#include <sys/types.h>
-+#include <sys/socket.h>
-+
-+#include <spa/support/loop.h>
-+#include <spa/support/log.h>
-+#include <spa/utils/list.h>
-+
-+#include <spa/node/node.h>
-+#include <spa/node/io.h>
-+#include <spa/param/param.h>
-+#include <spa/param/audio/format.h>
-+#include <spa/param/audio/format-utils.h>
-+#include <spa/pod/filter.h>
-+
-+#include "defs.h"
-+
-+struct props {
-+ uint32_t min_latency;
-+ uint32_t max_latency;
-+};
-+
-+#define MAX_BUFFERS 32
-+
-+struct buffer {
-+ uint32_t id;
-+ unsigned int outstanding:1;
-+ struct spa_buffer *buf;
-+ struct spa_meta_header *h;
-+ struct spa_list link;
-+};
-+
-+struct port {
-+ struct spa_audio_info current_format;
-+ int frame_size;
-+ unsigned int have_format:1;
-+
-+ uint64_t info_all;
-+ struct spa_port_info info;
-+ struct spa_io_buffers *io;
-+ struct spa_param_info params[8];
-+
-+ struct buffer buffers[MAX_BUFFERS];
-+ uint32_t n_buffers;
-+
-+ struct spa_list free;
-+ struct spa_list ready;
-+
-+ size_t ready_offset;
-+};
-+
-+struct impl {
-+ struct spa_handle handle;
-+ struct spa_node node;
-+
-+ struct spa_log *log;
-+ struct spa_loop *main_loop;
-+ struct spa_loop *data_loop;
-+
-+ struct spa_hook_list hooks;
-+ struct spa_callbacks callbacks;
-+
-+ uint64_t info_all;
-+ struct spa_node_info info;
-+ struct spa_param_info params[8];
-+ struct props props;
-+
-+ struct spa_bt_transport *transport;
-+ struct spa_hook transport_listener;
-+ int sock_fd;
-+
-+ struct port port;
-+
-+ unsigned int started:1;
-+ unsigned int slaved:1;
-+
-+ struct spa_source source;
-+
-+ struct spa_io_clock *clock;
-+ struct spa_io_position *position;
-+
-+ struct timespec now;
-+ uint32_t sample_count;
-+};
-+
-+#define NAME "sco-source"
-+
-+#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0)
-+
-+static const uint32_t default_min_latency = 64;
-+static const uint32_t default_max_latency = 256;
-+
-+static void reset_props(struct props *props)
-+{
-+ props->min_latency = default_min_latency;
-+ props->max_latency = default_max_latency;
-+}
-+
-+static int impl_node_enum_params(struct spa_node *node, int seq,
-+ uint32_t id, uint32_t start, uint32_t num,
-+ const struct spa_pod *filter)
-+{
-+ struct impl *this;
-+ struct spa_pod *param;
-+ struct spa_pod_builder b = { 0 };
-+ uint8_t buffer[1024];
-+ struct spa_result_node_params result;
-+ uint32_t count = 0;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+ spa_return_val_if_fail(num != 0, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ result.id = id;
-+ result.next = start;
-+ next:
-+ result.index = result.next++;
-+
-+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
-+
-+ switch (id) {
-+ case SPA_PARAM_PropInfo:
-+ {
-+ struct props *p = &this->props;
-+
-+ switch (result.index) {
-+ case 0:
-+ param = spa_pod_builder_add_object(&b,
-+ SPA_TYPE_OBJECT_PropInfo, id,
-+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_minLatency),
-+ SPA_PROP_INFO_name, SPA_POD_String("The minimum latency"),
-+ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->min_latency, 1, INT32_MAX));
-+ break;
-+ case 1:
-+ param = spa_pod_builder_add_object(&b,
-+ SPA_TYPE_OBJECT_PropInfo, id,
-+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_maxLatency),
-+ SPA_PROP_INFO_name, SPA_POD_String("The maximum latency"),
-+ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->max_latency, 1, INT32_MAX));
-+ break;
-+ default:
-+ return 0;
-+ }
-+ break;
-+ }
-+ case SPA_PARAM_Props:
-+ {
-+ struct props *p = &this->props;
-+
-+ switch (result.index) {
-+ case 0:
-+ param = spa_pod_builder_add_object(&b,
-+ SPA_TYPE_OBJECT_Props, id,
-+ SPA_PROP_minLatency, SPA_POD_Int(p->min_latency),
-+ SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency));
-+ break;
-+ default:
-+ return 0;
-+ }
-+ break;
-+ }
-+ default:
-+ return -ENOENT;
-+ }
-+
-+ if (spa_pod_filter(&b, &result.param, param, filter) < 0)
-+ goto next;
-+
-+ spa_node_emit_result(&this->hooks, seq, 0, &result);
-+
-+ if (++count != num)
-+ goto next;
-+
-+ return 0;
-+}
-+
-+static int do_reslave(struct spa_loop *loop,
-+ bool async,
-+ uint32_t seq,
-+ const void *data,
-+ size_t size,
-+ void *user_data)
-+{
-+ return 0;
-+}
-+
-+static inline bool is_slaved(struct impl *this)
-+{
-+ return this->position && this->clock && this->position->clock.id != this->clock->id;
-+}
-+
-+static int impl_node_set_io(struct spa_node *node, uint32_t id, void *data, size_t size)
-+{
-+ struct impl *this;
-+ bool slaved;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ switch (id) {
-+ case SPA_IO_Clock:
-+ this->clock = data;
-+ break;
-+ case SPA_IO_Position:
-+ this->position = data;
-+ break;
-+ default:
-+ return -ENOENT;
-+ }
-+
-+ slaved = is_slaved(this);
-+ if (this->started && slaved != this->slaved) {
-+ spa_log_debug(this->log, "sco-source %p: reslave %d->%d", this, this->slaved, slaved);
-+ this->slaved = slaved;
-+ spa_loop_invoke(this->data_loop, do_reslave, 0, NULL, 0, true, this);
-+ }
-+ return 0;
-+}
-+
-+static int impl_node_set_param(struct spa_node *node, uint32_t id, uint32_t flags,
-+ const struct spa_pod *param)
-+{
-+ struct impl *this;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ switch (id) {
-+ case SPA_PARAM_Props:
-+ {
-+ struct props *p = &this->props;
-+
-+ if (param == NULL) {
-+ reset_props(p);
-+ return 0;
-+ }
-+ spa_pod_parse_object(param,
-+ SPA_TYPE_OBJECT_Props, NULL,
-+ SPA_PROP_minLatency, SPA_POD_OPT_Int(&p->min_latency),
-+ SPA_PROP_maxLatency, SPA_POD_OPT_Int(&p->max_latency));
-+ break;
-+ }
-+ default:
-+ return -ENOENT;
-+ }
-+
-+ return 0;
-+}
-+
-+static void reset_buffers(struct port *port)
-+{
-+ uint32_t i;
-+
-+ spa_list_init(&port->free);
-+ spa_list_init(&port->ready);
-+
-+ for (i = 0; i < port->n_buffers; i++) {
-+ struct buffer *b = &port->buffers[i];
-+ spa_list_append(&port->free, &b->link);
-+ b->outstanding = false;
-+ }
-+}
-+
-+static bool read_data(struct impl *this, uint8_t *data, uint32_t size, uint32_t *total_read) {
-+ const uint32_t mtu_size = this->transport->read_mtu;
-+ uint32_t local_total_read = 0;
-+
-+ /* TODO: For now we assume the size is always a mutliple of mtu_size */
-+ while (local_total_read < (size - mtu_size)) {
-+ const int bytes_read = read(this->sock_fd, data, mtu_size);
-+ if (bytes_read == 0) {
-+ /* Stop */
-+ return false;
-+ } else if (bytes_read < 0) {
-+ /* Retry */
-+ if (errno == EINTR)
-+ continue;
-+
-+ /* Socked has no data so return total data read */
-+ if (errno == EAGAIN || errno == EWOULDBLOCK)
-+ goto done;
-+
-+ /* Print error and stop */
-+ spa_log_error(this->log, "read error: %s", strerror(errno));
-+ return false;
-+ }
-+
-+ data += bytes_read;
-+ local_total_read += bytes_read;
-+ }
-+
-+done:
-+ if (total_read)
-+ *total_read = local_total_read;
-+ return true;
-+}
-+
-+static void sco_on_ready_read(struct spa_source *source)
-+{
-+ struct impl *this = source->data;
-+ struct port *port = &this->port;
-+ struct buffer *buffer;
-+ struct spa_data *buffer_data;
-+ uint32_t total_read;
-+
-+ /* update the current pts */
-+ clock_gettime(CLOCK_MONOTONIC, &this->now);
-+
-+ /* check if we have a new buffer */
-+ if (spa_list_is_empty(&port->free)) {
-+ spa_log_warn(this->log, "waiting for buffer");
-+ return;
-+ }
-+
-+ /* get the buffer data */
-+ buffer = spa_list_first(&port->free, struct buffer, link);
-+ buffer_data = &buffer->buf->datas[0];
-+ spa_assert(buffer_data->data);
-+
-+ /* read data */
-+ if (!read_data(this, buffer_data->data, buffer_data->maxsize, &total_read))
-+ goto stop;
-+ if (total_read == 0)
-+ return;
-+
-+ /* update the buffer offset, size and stride */
-+ buffer_data->chunk->offset = 0;
-+ buffer_data->chunk->size = total_read;
-+ buffer_data->chunk->stride = port->frame_size;
-+
-+ /* update the sample count */
-+ this->sample_count += buffer_data->chunk->size / port->frame_size;
-+
-+ /* remove the buffer from the free list and add it to the ready list */
-+ spa_list_remove(&buffer->link);
-+ buffer->outstanding = true;
-+ spa_list_append(&port->ready, &buffer->link);
-+
-+ /* Notify we are ready for the next buffer */
-+ spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_BUFFER);
-+
-+ return;
-+
-+stop:
-+ if (this->source.loop)
-+ spa_loop_remove_source(this->data_loop, &this->source);
-+}
-+
-+static int do_start(struct impl *this)
-+{
-+ int val;
-+ bool do_accept;
-+
-+ /* Dont do anything if the node has already started */
-+ if (this->started)
-+ return 0;
-+
-+ /* Make sure the transport is valid */
-+ spa_return_val_if_fail (this->transport != NULL, -EIO);
-+
-+ /* Do accept if Gateway; otherwise do connect for Head Unit */
-+ do_accept = this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY;
-+
-+ /* acquire the socked fd (false -> connect | true -> accept) */
-+ this->sock_fd = spa_bt_transport_acquire(this->transport, do_accept);
-+ if (this->sock_fd < 0)
-+ return -1;
-+
-+ /* Set the write MTU */
-+ val = this->transport->write_mtu;
-+ if (setsockopt(this->sock_fd, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) < 0)
-+ spa_log_warn(this->log, "sco-source %p: SO_SNDBUF %m", this);
-+
-+ /* Set the read MTU */
-+ val = this->transport->read_mtu;
-+ if (setsockopt(this->sock_fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) < 0)
-+ spa_log_warn(this->log, "sco-source %p: SO_RCVBUF %m", this);
-+
-+ /* Set the priority */
-+ val = 6;
-+ if (setsockopt(this->sock_fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0)
-+ spa_log_warn(this->log, "SO_PRIORITY failed: %m");
-+
-+ /* Reset the buffers and sample count */
-+ reset_buffers(&this->port);
-+ this->sample_count = 0;
-+
-+ /* Add the ready read callback */
-+ this->source.data = this;
-+ this->source.fd = this->sock_fd;
-+ this->source.func = sco_on_ready_read;
-+ this->source.mask = SPA_IO_IN;
-+ this->source.rmask = 0;
-+ spa_loop_add_source(this->data_loop, &this->source);
-+
-+ /* Set the started flag */
-+ this->started = true;
-+
-+ return 0;
-+}
-+
-+static int do_remove_source(struct spa_loop *loop,
-+ bool async,
-+ uint32_t seq,
-+ const void *data,
-+ size_t size,
-+ void *user_data)
-+{
-+ struct impl *this = user_data;
-+
-+ if (this->source.loop)
-+ spa_loop_remove_source(this->data_loop, &this->source);
-+
-+ return 0;
-+}
-+
-+static int do_stop(struct impl *this)
-+{
-+ int res = 0;
-+
-+ if (!this->started)
-+ return 0;
-+
-+ spa_log_debug(this->log, "sco-source %p: stop", this);
-+
-+ spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this);
-+
-+ this->started = false;
-+
-+ if (this->transport) {
-+ /* Release the transport */
-+ res = spa_bt_transport_release(this->transport);
-+
-+ /* Shutdown and close the socket */
-+ shutdown(this->sock_fd, SHUT_RDWR);
-+ close(this->sock_fd);
-+ this->sock_fd = -1;
-+ }
-+
-+ return res;
-+}
-+
-+static int impl_node_send_command(struct spa_node *node, const struct spa_command *command)
-+{
-+ struct impl *this;
-+ struct port *port;
-+ int res;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+ spa_return_val_if_fail(command != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+ port = &this->port;
-+
-+ switch (SPA_NODE_COMMAND_ID(command)) {
-+ case SPA_NODE_COMMAND_Start:
-+ if (!port->have_format)
-+ return -EIO;
-+ if (port->n_buffers == 0)
-+ return -EIO;
-+ if ((res = do_start(this)) < 0)
-+ return res;
-+ break;
-+ case SPA_NODE_COMMAND_Pause:
-+ if ((res = do_stop(this)) < 0)
-+ return res;
-+ break;
-+ default:
-+ return -ENOTSUP;
-+ }
-+ return 0;
-+}
-+
-+static const struct spa_dict_item node_info_items[] = {
-+ { "media.class", "Audio/Source" },
-+ { "node.driver", "true" },
-+};
-+
-+static void emit_node_info(struct impl *this, bool full)
-+{
-+ if (full)
-+ this->info.change_mask = this->info_all;
-+ if (this->info.change_mask) {
-+ this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items);
-+ spa_node_emit_info(&this->hooks, &this->info);
-+ this->info.change_mask = 0;
-+ }
-+}
-+
-+static void emit_port_info(struct impl *this, struct port *port, bool full)
-+{
-+ if (full)
-+ port->info.change_mask = port->info_all;
-+ if (port->info.change_mask) {
-+ spa_node_emit_port_info(&this->hooks,
-+ SPA_DIRECTION_OUTPUT, 0, &port->info);
-+ port->info.change_mask = 0;
-+ }
-+}
-+
-+static int
-+impl_node_add_listener(struct spa_node *node,
-+ struct spa_hook *listener,
-+ const struct spa_node_events *events,
-+ void *data)
-+{
-+ struct impl *this;
-+ struct spa_hook_list save;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+ spa_hook_list_isolate(&this->hooks, &save, listener, events, data);
-+
-+ emit_node_info(this, true);
-+ emit_port_info(this, &this->port, true);
-+
-+ spa_hook_list_join(&this->hooks, &save);
-+
-+ return 0;
-+}
-+
-+static int
-+impl_node_set_callbacks(struct spa_node *node,
-+ const struct spa_node_callbacks *callbacks,
-+ void *data)
-+{
-+ struct impl *this;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ this->callbacks = SPA_CALLBACKS_INIT(callbacks, data);
-+
-+ return 0;
-+}
-+
-+static int impl_node_add_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id,
-+ const struct spa_dict *props)
-+{
-+ return -ENOTSUP;
-+}
-+
-+static int impl_node_remove_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id)
-+{
-+ return -ENOTSUP;
-+}
-+
-+static int
-+impl_node_port_enum_params(struct spa_node *node, int seq,
-+ enum spa_direction direction, uint32_t port_id,
-+ uint32_t id, uint32_t start, uint32_t num,
-+ const struct spa_pod *filter)
-+{
-+
-+ struct impl *this;
-+ struct port *port;
-+ struct spa_pod *param;
-+ struct spa_pod_builder b = { 0 };
-+ uint8_t buffer[1024];
-+ struct spa_result_node_params result;
-+ uint32_t count = 0;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+ spa_return_val_if_fail(num != 0, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
-+ port = &this->port;
-+
-+ result.id = id;
-+ result.next = start;
-+ next:
-+ result.index = result.next++;
-+
-+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
-+
-+ switch (id) {
-+ case SPA_PARAM_EnumFormat:
-+ if (result.index > 0)
-+ return 0;
-+
-+ if (this->transport == NULL)
-+ return -EIO;
-+
-+ /* set the info structure */
-+ struct spa_audio_info_raw info = { 0, };
-+ info.format = SPA_AUDIO_FORMAT_S16;
-+ info.channels = 1;
-+ info.position[0] = SPA_AUDIO_CHANNEL_MONO;
-+
-+ /* TODO: For now we only handle HSP profiles which has always CVSD format,
-+ * but we eventually need to support HFP that can have both CVSD and MSBC formats */
-+
-+ /* CVSD format has a rate of 8kHz
-+ * MSBC format has a rate of 16kHz */
-+ info.rate = 8000;
-+
-+ /* build the param */
-+ param = spa_format_audio_raw_build(&b, id, &info);
-+ break;
-+
-+ case SPA_PARAM_Format:
-+ if (!port->have_format)
-+ return -EIO;
-+ if (result.index > 0)
-+ return 0;
-+
-+ param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw);
-+ break;
-+
-+ case SPA_PARAM_Buffers:
-+ if (!port->have_format)
-+ return -EIO;
-+ if (result.index > 0)
-+ return 0;
-+
-+ param = spa_pod_builder_add_object(&b,
-+ SPA_TYPE_OBJECT_ParamBuffers, id,
-+ /* 8 buffers are enough to make sure we always have one available when decoding */
-+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 8, MAX_BUFFERS),
-+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
-+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(this->props.max_latency * port->frame_size),
-+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->frame_size),
-+ SPA_PARAM_BUFFERS_align, SPA_POD_Int(16));
-+ break;
-+
-+ case SPA_PARAM_Meta:
-+ switch (result.index) {
-+ case 0:
-+ param = spa_pod_builder_add_object(&b,
-+ SPA_TYPE_OBJECT_ParamMeta, id,
-+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
-+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
-+ break;
-+ default:
-+ return 0;
-+ }
-+ break;
-+
-+ default:
-+ return -ENOENT;
-+ }
-+
-+ /* TODO: why filer is != NULL when linking it with sco-sink? */
-+ /* if filter is null sco-source cannot be linked with sco-sink,
-+ * so for now we always pass NULL */
-+ if (spa_pod_filter(&b, &result.param, param, NULL) < 0)
-+ goto next;
-+
-+ spa_node_emit_result(&this->hooks, seq, 0, &result);
-+
-+ if (++count != num)
-+ goto next;
-+
-+ return 0;
-+}
-+
-+static int clear_buffers(struct impl *this, struct port *port)
-+{
-+ do_stop(this);
-+ if (port->n_buffers > 0) {
-+ spa_list_init(&port->free);
-+ spa_list_init(&port->ready);
-+ port->n_buffers = 0;
-+ }
-+ return 0;
-+}
-+
-+static int port_set_format(struct impl *this, struct port *port,
-+ uint32_t flags,
-+ const struct spa_pod *format)
-+{
-+ int err;
-+
-+ if (format == NULL) {
-+ spa_log_info(this->log, "clear format");
-+ clear_buffers(this, port);
-+ port->have_format = false;
-+ } else {
-+ struct spa_audio_info info = { 0 };
-+
-+ if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0)
-+ return err;
-+
-+ if (info.media_type != SPA_MEDIA_TYPE_audio ||
-+ info.media_subtype != SPA_MEDIA_SUBTYPE_raw)
-+ return -EINVAL;
-+
-+ if (spa_format_audio_raw_parse(format, &info.info.raw) < 0)
-+ return -EINVAL;
-+
-+ port->frame_size = info.info.raw.channels * 2;
-+ port->current_format = info;
-+ port->have_format = true;
-+ }
-+
-+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
-+ if (port->have_format) {
-+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS;
-+ port->info.flags = SPA_PORT_FLAG_CAN_USE_BUFFERS | SPA_PORT_FLAG_LIVE;
-+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE;
-+ port->info.rate = SPA_FRACTION(1, port->current_format.info.raw.rate);
-+ port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE);
-+ port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ);
-+ } else {
-+ port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
-+ port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
-+ }
-+ emit_port_info(this, port, false);
-+
-+ return 0;
-+}
-+
-+static int
-+impl_node_port_set_param(struct spa_node *node,
-+ enum spa_direction direction, uint32_t port_id,
-+ uint32_t id, uint32_t flags,
-+ const struct spa_pod *param)
-+{
-+ struct impl *this;
-+ struct port *port;
-+ int res;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL);
-+ port = &this->port;
-+
-+ switch (id) {
-+ case SPA_PARAM_Format:
-+ res = port_set_format(this, port, flags, param);
-+ break;
-+ default:
-+ res = -ENOENT;
-+ break;
-+ }
-+ return res;
-+}
-+
-+static int
-+impl_node_port_use_buffers(struct spa_node *node,
-+ enum spa_direction direction,
-+ uint32_t port_id, struct spa_buffer **buffers, uint32_t n_buffers)
-+{
-+ struct impl *this;
-+ struct port *port;
-+ uint32_t i;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
-+ port = &this->port;
-+
-+ spa_log_info(this->log, "use buffers %d", n_buffers);
-+
-+ if (!port->have_format)
-+ return -EIO;
-+
-+ clear_buffers(this, port);
-+
-+ for (i = 0; i < n_buffers; i++) {
-+ struct buffer *b = &port->buffers[i];
-+ struct spa_data *d = buffers[i]->datas;
-+
-+ b->buf = buffers[i];
-+ b->id = i;
-+
-+ b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h));
-+
-+ if (!((d[0].type == SPA_DATA_MemFd ||
-+ d[0].type == SPA_DATA_DmaBuf ||
-+ d[0].type == SPA_DATA_MemPtr) && d[0].data != NULL)) {
-+ spa_log_error(this->log, NAME " %p: need mapped memory", this);
-+ return -EINVAL;
-+ }
-+ spa_list_append(&port->free, &b->link);
-+ b->outstanding = false;
-+ }
-+ port->n_buffers = n_buffers;
-+
-+ return 0;
-+}
-+
-+static int
-+impl_node_port_alloc_buffers(struct spa_node *node,
-+ enum spa_direction direction,
-+ uint32_t port_id,
-+ struct spa_pod **params,
-+ uint32_t n_params,
-+ struct spa_buffer **buffers,
-+ uint32_t *n_buffers)
-+{
-+ struct impl *this;
-+ struct port *port;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+ spa_return_val_if_fail(buffers != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
-+ port = &this->port;
-+
-+ if (!port->have_format)
-+ return -EIO;
-+
-+ return -ENOTSUP;
-+}
-+
-+static int
-+impl_node_port_set_io(struct spa_node *node,
-+ enum spa_direction direction,
-+ uint32_t port_id,
-+ uint32_t id,
-+ void *data, size_t size)
-+{
-+ struct impl *this;
-+ struct port *port;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
-+ port = &this->port;
-+
-+ switch (id) {
-+ case SPA_IO_Buffers:
-+ port->io = data;
-+ break;
-+ default:
-+ return -ENOENT;
-+ }
-+ return 0;
-+}
-+
-+static void recycle_buffer(struct impl *this, struct port *port, uint32_t buffer_id)
-+{
-+ struct buffer *b = &port->buffers[buffer_id];
-+
-+ if (b->outstanding) {
-+ spa_log_trace(this->log, NAME " %p: recycle buffer %u", this, buffer_id);
-+ spa_list_append(&port->free, &b->link);
-+ b->outstanding = false;
-+ }
-+}
-+
-+static int impl_node_port_reuse_buffer(struct spa_node *node, uint32_t port_id, uint32_t buffer_id)
-+{
-+ struct impl *this;
-+ struct port *port;
-+
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+
-+ spa_return_val_if_fail(port_id == 0, -EINVAL);
-+ port = &this->port;
-+
-+ if (port->n_buffers == 0)
-+ return -EIO;
-+
-+ if (buffer_id >= port->n_buffers)
-+ return -EINVAL;
-+
-+ recycle_buffer(this, port, buffer_id);
-+
-+ return 0;
-+}
-+
-+static int impl_node_process(struct spa_node *node)
-+{
-+ struct impl *this;
-+ struct port *port;
-+ struct spa_io_buffers *io;
-+ struct buffer *b;
-+
-+ /* get IO */
-+ spa_return_val_if_fail(node != NULL, -EINVAL);
-+
-+ this = SPA_CONTAINER_OF(node, struct impl, node);
-+ port = &this->port;
-+ io = port->io;
-+ spa_return_val_if_fail(io != NULL, -EIO);
-+
-+ /* don't do anything if IO does not need a buffer */
-+ if (io->status != SPA_STATUS_NEED_BUFFER)
-+ return io->status;
-+
-+ /* Recycle previously played buffer */
-+ if (io->buffer_id != SPA_ID_INVALID &&
-+ io->buffer_id < port->n_buffers) {
-+ spa_log_debug(this->log, "recycling buffer_id=%d", io->buffer_id);
-+ recycle_buffer(this, port, io->buffer_id);
-+ io->buffer_id = SPA_ID_INVALID;
-+ }
-+
-+ /* Check if we have new buffers in the queue */
-+ if (spa_list_is_empty(&port->ready))
-+ return SPA_STATUS_HAVE_BUFFER;
-+
-+ /* Pop the new buffer from the queue */
-+ b = spa_list_first(&port->ready, struct buffer, link);
-+ spa_list_remove(&b->link);
-+
-+ /* Set the new buffer in IO to be played */
-+ io->buffer_id = b->id;
-+ io->status = SPA_STATUS_HAVE_BUFFER;
-+
-+ return SPA_STATUS_HAVE_BUFFER;
-+}
-+
-+static const struct spa_node impl_node = {
-+ SPA_VERSION_NODE,
-+ .add_listener = impl_node_add_listener,
-+ .set_callbacks = impl_node_set_callbacks,
-+ .enum_params = impl_node_enum_params,
-+ .set_param = impl_node_set_param,
-+ .set_io = impl_node_set_io,
-+ .send_command = impl_node_send_command,
-+ .add_port = impl_node_add_port,
-+ .remove_port = impl_node_remove_port,
-+ .port_enum_params = impl_node_port_enum_params,
-+ .port_set_param = impl_node_port_set_param,
-+ .port_use_buffers = impl_node_port_use_buffers,
-+ .port_alloc_buffers = impl_node_port_alloc_buffers,
-+ .port_set_io = impl_node_port_set_io,
-+ .port_reuse_buffer = impl_node_port_reuse_buffer,
-+ .process = impl_node_process,
-+};
-+
-+static void transport_destroy(void *data)
-+{
-+ struct impl *this = data;
-+ spa_log_debug(this->log, "transport %p destroy", this->transport);
-+ this->transport = NULL;
-+}
-+
-+static const struct spa_bt_transport_events transport_events = {
-+ SPA_VERSION_BT_TRANSPORT_EVENTS,
-+ .destroy = transport_destroy,
-+};
-+
-+static int impl_get_interface(struct spa_handle *handle, uint32_t type, void **interface)
-+{
-+ struct impl *this;
-+
-+ spa_return_val_if_fail(handle != NULL, -EINVAL);
-+ spa_return_val_if_fail(interface != NULL, -EINVAL);
-+
-+ this = (struct impl *) handle;
-+
-+ if (type == SPA_TYPE_INTERFACE_Node)
-+ *interface = &this->node;
-+ else
-+ return -ENOENT;
-+
-+ return 0;
-+}
-+
-+static int impl_clear(struct spa_handle *handle)
-+{
-+ return 0;
-+}
-+
-+static size_t
-+impl_get_size(const struct spa_handle_factory *factory,
-+ const struct spa_dict *params)
-+{
-+ return sizeof(struct impl);
-+}
-+
-+static int
-+impl_init(const struct spa_handle_factory *factory,
-+ struct spa_handle *handle,
-+ const struct spa_dict *info,
-+ const struct spa_support *support,
-+ uint32_t n_support)
-+{
-+ struct impl *this;
-+ struct port *port;
-+ uint32_t i;
-+
-+ spa_return_val_if_fail(factory != NULL, -EINVAL);
-+ spa_return_val_if_fail(handle != NULL, -EINVAL);
-+
-+ handle->get_interface = impl_get_interface;
-+ handle->clear = impl_clear;
-+
-+ this = (struct impl *) handle;
-+
-+ for (i = 0; i < n_support; i++) {
-+ if (support[i].type == SPA_TYPE_INTERFACE_Log)
-+ this->log = support[i].data;
-+ else if (support[i].type == SPA_TYPE_INTERFACE_DataLoop)
-+ this->data_loop = support[i].data;
-+ else if (support[i].type == SPA_TYPE_INTERFACE_MainLoop)
-+ this->main_loop = support[i].data;
-+ }
-+ if (this->data_loop == NULL) {
-+ spa_log_error(this->log, "a data loop is needed");
-+ return -EINVAL;
-+ }
-+ if (this->main_loop == NULL) {
-+ spa_log_error(this->log, "a main loop is needed");
-+ return -EINVAL;
-+ }
-+
-+ this->node = impl_node;
-+ spa_hook_list_init(&this->hooks);
-+
-+ reset_props(&this->props);
-+
-+ /* set the node info */
-+ this->info_all = SPA_NODE_CHANGE_MASK_FLAGS |
-+ SPA_NODE_CHANGE_MASK_PROPS |
-+ SPA_NODE_CHANGE_MASK_PARAMS;
-+ this->info = SPA_NODE_INFO_INIT();
-+ this->info.flags = SPA_NODE_FLAG_RT;
-+ this->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ);
-+ this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
-+ this->info.params = this->params;
-+ this->info.n_params = 2;
-+
-+ /* set the port info */
-+ port = &this->port;
-+ port->info_all = SPA_PORT_CHANGE_MASK_FLAGS |
-+ SPA_PORT_CHANGE_MASK_PARAMS;
-+ port->info = SPA_PORT_INFO_INIT();
-+ port->info.change_mask = SPA_PORT_CHANGE_MASK_FLAGS;
-+ port->info.flags = SPA_PORT_FLAG_CAN_USE_BUFFERS |
-+ SPA_PORT_FLAG_LIVE |
-+ SPA_PORT_FLAG_TERMINAL;
-+ port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
-+ port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ);
-+ port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ);
-+ port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
-+ port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
-+ port->info.params = port->params;
-+ port->info.n_params = 5;
-+
-+ /* Init the buffer lists */
-+ spa_list_init(&port->ready);
-+ spa_list_init(&port->free);
-+
-+ for (i = 0; info && i < info->n_items; i++) {
-+ if (strcmp(info->items[i].key, "bluez5.transport") == 0)
-+ sscanf(info->items[i].value, "%p", &this->transport);
-+ }
-+ if (this->transport == NULL) {
-+ spa_log_error(this->log, "a transport is needed");
-+ return -EINVAL;
-+ }
-+ spa_bt_transport_add_listener(this->transport,
-+ &this->transport_listener, &transport_events, this);
-+ this->sock_fd = -1;
-+
-+ return 0;
-+}
-+
-+static const struct spa_interface_info impl_interfaces[] = {
-+ {SPA_TYPE_INTERFACE_Node,},
-+};
-+
-+static int
-+impl_enum_interface_info(const struct spa_handle_factory *factory,
-+ const struct spa_interface_info **info, uint32_t *index)
-+{
-+ spa_return_val_if_fail(factory != NULL, -EINVAL);
-+ spa_return_val_if_fail(info != NULL, -EINVAL);
-+ spa_return_val_if_fail(index != NULL, -EINVAL);
-+
-+ switch (*index) {
-+ case 0:
-+ *info = &impl_interfaces[*index];
-+ break;
-+ default:
-+ return 0;
-+ }
-+ (*index)++;
-+ return 1;
-+}
-+
-+static const struct spa_dict_item info_items[] = {
-+ { "factory.author", "Collabora Ltd. <contact@collabora.com>" },
-+ { "factory.description", "Capture bluetooth audio with sco (hsp/hfp)" },
-+};
-+
-+static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items);
-+
-+struct spa_handle_factory spa_sco_source_factory = {
-+ SPA_VERSION_HANDLE_FACTORY,
-+ NAME,
-+ &info,
-+ impl_get_size,
-+ impl_init,
-+ impl_enum_interface_info,
-+};
---
-2.23.0.rc1
-
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-device-add-name-field-in-spa_device_object_info.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-device-add-name-field-in-spa_device_object_info.patch
deleted file mode 100644
index ef1bd61e..00000000
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-device-add-name-field-in-spa_device_object_info.patch
+++ /dev/null
@@ -1,39 +0,0 @@
-From 0e3df7c3612fadf5319efb231fcd16ef16cd6e1a Mon Sep 17 00:00:00 2001
-From: Julian Bouzas <julian.bouzas@collabora.com>
-Date: Thu, 29 Aug 2019 13:58:13 -0400
-Subject: [PATCH] device: add name field in spa_device_object_info
-
-Upstream-Status: Pending
----
- spa/include/spa/monitor/device.h | 1 +
- src/pipewire/device.c | 2 +-
- 2 files changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/spa/include/spa/monitor/device.h b/spa/include/spa/monitor/device.h
-index 765e96f8..51a467b1 100644
---- a/spa/include/spa/monitor/device.h
-+++ b/spa/include/spa/monitor/device.h
-@@ -59,6 +59,7 @@ struct spa_device_object_info {
-
- uint32_t type;
- const struct spa_handle_factory *factory;
-+ const char *name;
-
- #define SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS (1u<<0)
- #define SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS (1u<<1)
-diff --git a/src/pipewire/device.c b/src/pipewire/device.c
-index 11907d13..4cfac06e 100644
---- a/src/pipewire/device.c
-+++ b/src/pipewire/device.c
-@@ -389,7 +389,7 @@ static void device_add(struct pw_device *device, uint32_t id,
- pw_properties_update(props, info->props);
-
- node = pw_node_new(device->core,
-- device->info.name,
-+ info->name ? info->name : device->info.name,
- props,
- sizeof(struct node_data) +
- spa_handle_factory_get_size(info->factory, info->props));
---
-2.23.0.rc1
-
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0019-bluez-add-transport-name-and-use-it-when-emitting-no.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0019-bluez-add-transport-name-and-use-it-when-emitting-no.patch
deleted file mode 100644
index 746707e0..00000000
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0019-bluez-add-transport-name-and-use-it-when-emitting-no.patch
+++ /dev/null
@@ -1,90 +0,0 @@
-From df485216dde74507e5ecb27b9663ab5107c6c5be Mon Sep 17 00:00:00 2001
-From: Julian Bouzas <julian.bouzas@collabora.com>
-Date: Thu, 29 Aug 2019 13:59:10 -0400
-Subject: [PATCH] bluez: add transport name and use it when emitting nodes
-
-Upstream-Status: Pending
----
- spa/plugins/bluez5/bluez5-device.c | 1 +
- spa/plugins/bluez5/bluez5-monitor.c | 23 +++++++++++++++++++++++
- spa/plugins/bluez5/defs.h | 1 +
- 3 files changed, 25 insertions(+)
-
-diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c
-index 40a340c9..c4380e7a 100644
---- a/spa/plugins/bluez5/bluez5-device.c
-+++ b/spa/plugins/bluez5/bluez5-device.c
-@@ -84,6 +84,7 @@ static void emit_node (struct impl *this, struct spa_bt_transport *t, const stru
- info = SPA_DEVICE_OBJECT_INFO_INIT();
- info.type = SPA_TYPE_INTERFACE_Node;
- info.factory = factory;
-+ info.name = t->name;
- info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
-
- /* Pass the transport pointer as a property */
-diff --git a/spa/plugins/bluez5/bluez5-monitor.c b/spa/plugins/bluez5/bluez5-monitor.c
-index 2a243715..2914323b 100644
---- a/spa/plugins/bluez5/bluez5-monitor.c
-+++ b/spa/plugins/bluez5/bluez5-monitor.c
-@@ -864,6 +864,26 @@ static void transport_free(struct spa_bt_transport *transport)
- free(transport);
- }
-
-+static void transport_update_name(struct spa_bt_transport *t) {
-+ switch (t->profile) {
-+ case SPA_BT_PROFILE_A2DP_SOURCE:
-+ case SPA_BT_PROFILE_A2DP_SINK:
-+ snprintf (t->name, 256, "bluez5.a2dp %s", t->device->name);
-+ break;
-+ case SPA_BT_PROFILE_HSP_HS:
-+ case SPA_BT_PROFILE_HFP_HF:
-+ snprintf (t->name, 256, "bluez5.headunit %s", t->device->name);
-+ break;
-+ case SPA_BT_PROFILE_HSP_AG:
-+ case SPA_BT_PROFILE_HFP_AG:
-+ snprintf (t->name, 256, "bluez5.gateway %s", t->device->name);
-+ break;
-+ default:
-+ snprintf (t->name, 256, "bluez5.unknown %s", t->device->name);
-+ break;
-+ }
-+}
-+
- static int transport_update_props(struct spa_bt_transport *transport,
- DBusMessageIter *props_iter,
- DBusMessageIter *invalidated_iter)
-@@ -893,9 +913,11 @@ static int transport_update_props(struct spa_bt_transport *transport,
- switch (spa_bt_profile_from_uuid(value)) {
- case SPA_BT_PROFILE_A2DP_SOURCE:
- transport->profile = SPA_BT_PROFILE_A2DP_SINK;
-+ transport_update_name(transport);
- break;
- case SPA_BT_PROFILE_A2DP_SINK:
- transport->profile = SPA_BT_PROFILE_A2DP_SOURCE;
-+ transport_update_name(transport);
- break;
- default:
- spa_log_warn(monitor->log, "unknown profile %s", value);
-@@ -1743,6 +1765,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag
- t->device = d;
- spa_list_append(&t->device->transport_list, &t->device_link);
- t->profile = profile;
-+ transport_update_name(t);
-
- td = t->user_data;
- td->rfcomm.func = rfcomm_event;
-diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h
-index 7402cdf4..933a6413 100644
---- a/spa/plugins/bluez5/defs.h
-+++ b/spa/plugins/bluez5/defs.h
-@@ -207,6 +207,7 @@ struct spa_bt_transport {
- struct spa_bt_device *device;
- struct spa_list device_link;
- enum spa_bt_profile profile;
-+ char name[256];
- enum spa_bt_transport_state state;
- int codec;
- void *configuration;
---
-2.23.0.rc1
-
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0020-a2dp-sink-check-if-transport-is-valid-before-releasi.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0020-a2dp-sink-check-if-transport-is-valid-before-releasi.patch
deleted file mode 100644
index 746d2451..00000000
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0020-a2dp-sink-check-if-transport-is-valid-before-releasi.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-From 411fd3d4e7b3f076a14c8eae7be976ce17b686ca Mon Sep 17 00:00:00 2001
-From: Julian Bouzas <julian.bouzas@collabora.com>
-Date: Fri, 30 Aug 2019 08:45:11 -0400
-Subject: [PATCH] a2dp-sink: check if transport is valid before releasing it
-
-Upstream-Status: Pending
----
- spa/plugins/bluez5/a2dp-sink.c | 5 +++--
- 1 file changed, 3 insertions(+), 2 deletions(-)
-
-diff --git a/spa/plugins/bluez5/a2dp-sink.c b/spa/plugins/bluez5/a2dp-sink.c
-index d6d9e7d6..041d75bb 100644
---- a/spa/plugins/bluez5/a2dp-sink.c
-+++ b/spa/plugins/bluez5/a2dp-sink.c
-@@ -866,7 +866,7 @@ static int do_remove_source(struct spa_loop *loop,
-
- static int do_stop(struct impl *this)
- {
-- int res;
-+ int res = 0;
-
- if (!this->started)
- return 0;
-@@ -877,7 +877,8 @@ static int do_stop(struct impl *this)
-
- this->started = false;
-
-- res = spa_bt_transport_release(this->transport);
-+ if (this->transport)
-+ res = spa_bt_transport_release(this->transport);
-
- return res;
- }
---
-2.23.0.rc1
-
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb b/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb
index 011354d8..54ec92bf 100644
--- a/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb
@@ -1,30 +1,23 @@
require pipewire.inc
SRC_URI = "gitsm://github.com/PipeWire/pipewire;protocol=https;branch=work \
- file://0001-spa-include-install-missing-headers.patch \
- file://0002-extensions-implement-Endpoint-ClientEndpoint-interfa.patch \
- file://0003-pipewire-cli-add-support-for-printing-endpoint-info-.patch \
- file://0004-pipewire-cli-add-command-to-modify-endpoint-control-.patch \
- file://0005-arm-build-with-mno-unaligned-access.patch \
- file://0006-logger-print-timestamps-on-logged-messages.patch \
- file://0007-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch \
- file://0008-audio-dsp-allow-mode-to-be-set-with-a-property.patch \
- file://0009-audioconvert-do-setup-internal-links-and-buffers-als.patch \
- file://0010-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch \
- file://0011-gst-pwaudioringbuffer-make-the-buffer-size-sensitive.patch \
- file://0012-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch \
- file://0013-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch \
- file://0014-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch \
- file://0015-audioconvert-fmtconvert-assume-F32-on-the-other-port.patch \
- file://0016-a2dpsink-fix-infinite-loop-when-buffer-could-not-be-.patch \
- file://0017-bluez5-add-sco-sink-and-sco-src-nodes.patch \
- file://0018-device-add-name-field-in-spa_device_object_info.patch \
- file://0019-bluez-add-transport-name-and-use-it-when-emitting-no.patch \
- file://0020-a2dp-sink-check-if-transport-is-valid-before-releasi.patch \
- file://0021-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch \
+ file://0001-arm-build-with-mno-unaligned-access.patch \
+ file://0002-logger-print-timestamps-on-logged-messages.patch \
+ file://0003-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch \
+ file://0004-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch \
+ file://0005-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch \
+ file://0006-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch \
+ file://0007-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch \
+ file://0008-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch \
+ file://0009-alsa-do-not-expose-non-interleaved-formats-since-the.patch \
+ file://0010-bluez-monitor-fix-usage-of-pw_properties_setf-withou.patch \
+ file://0011-meson-revert-version-check-to-require-meson-0.47-not.patch \
+ file://0012-extensions-implement-new-session-manager-extension.patch \
+ file://0013-pipewire-cli-add-support-for-printing-endpoint-info-.patch \
+ file://0014-daemon-config-remote-load-module-session-manager-by-.patch \
"
-SRCREV = "4be788962e60891237f1f018627bf709ae3981e6"
+SRCREV = "d3c7acb137134bddff3bc8a8964600252d3fb674"
PV = "0.2.90+git${SRCPV}+2"
S = "${WORKDIR}/git"