summaryrefslogtreecommitdiffstats
path: root/meta-pipewire/recipes-multimedia/pipewire
diff options
context:
space:
mode:
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"