wireplumber/lib/wp/proxy.c

1077 lines
31 KiB
C
Raw Normal View History

2019-06-10 15:07:54 -04:00
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
2019-06-10 15:07:54 -04:00
*
* SPDX-License-Identifier: MIT
*/
2020-02-17 15:39:19 +02:00
/**
* SECTION: WpProxy
*
*/
#define G_LOG_DOMAIN "wp-proxy"
2019-06-10 15:07:54 -04:00
#include "proxy.h"
#include "debug.h"
#include "core.h"
#include "error.h"
#include "wpenums.h"
#include "private.h"
#include <pipewire/pipewire.h>
2020-01-09 12:39:45 -05:00
#include <pipewire/impl.h>
#include <pipewire/extensions/metadata.h>
#include <pipewire/extensions/client-node.h>
#include <pipewire/extensions/session-manager.h>
#include <spa/debug/types.h>
#include <spa/pod/builder.h>
#include <spa/utils/result.h>
2019-06-10 15:07:54 -04:00
typedef struct _WpProxyPrivate WpProxyPrivate;
struct _WpProxyPrivate
{
/* properties */
GWeakRef core;
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
WpGlobal *global;
struct pw_proxy *pw_proxy;
2019-06-10 15:07:54 -04:00
/* The proxy listener */
struct spa_hook listener;
/* augment state */
WpProxyFeatures ft_ready;
GPtrArray *augment_tasks; // element-type: GTask*
GHashTable *async_tasks; // <int seq, GTask*>
/* props cache */
WpSpaProps props;
2019-06-10 15:07:54 -04:00
};
enum {
PROP_0,
PROP_CORE,
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
PROP_GLOBAL,
PROP_GLOBAL_PERMISSIONS,
PROP_GLOBAL_PROPERTIES,
PROP_FEATURES,
PROP_PW_PROXY,
PROP_INFO,
PROP_PROPERTIES,
PROP_PARAM_INFO,
PROP_BOUND_ID,
2019-06-10 15:07:54 -04:00
};
2019-06-18 10:09:40 -04:00
enum
{
SIGNAL_PW_PROXY_CREATED,
SIGNAL_PW_PROXY_DESTROYED,
2020-03-31 15:04:25 +03:00
SIGNAL_BOUND,
SIGNAL_PARAM,
SIGNAL_PROP_CHANGED,
2019-06-18 10:09:40 -04:00
LAST_SIGNAL,
};
static guint wp_proxy_signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE_WITH_PRIVATE (WpProxy, wp_proxy, G_TYPE_OBJECT)
2019-06-10 15:07:54 -04:00
static void
proxy_event_destroy (void *data)
{
/* hold a reference to the proxy because unref-ing the tasks might
destroy the proxy, in case the registry is no longer holding a reference */
g_autoptr (WpProxy) self = g_object_ref (WP_PROXY (data));
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
GHashTableIter iter;
GTask *task;
2019-06-10 15:07:54 -04:00
wp_trace_object (self, "destroyed pw_proxy %p (%u)", priv->pw_proxy,
priv->global ? priv->global->id : pw_proxy_get_bound_id (priv->pw_proxy));
spa_hook_remove (&priv->listener);
priv->pw_proxy = NULL;
g_signal_emit (self, wp_proxy_signals[SIGNAL_PW_PROXY_DESTROYED], 0);
2019-06-10 15:07:54 -04:00
/* Return error if the pw_proxy destruction happened while the async
* init or augment of this proxy object was in progress */
if (priv->augment_tasks->len > 0) {
GError *err = g_error_new (WP_DOMAIN_LIBRARY,
WP_LIBRARY_ERROR_OPERATION_FAILED,
"pipewire proxy destroyed before finishing");
wp_proxy_augment_error (self, err);
}
g_hash_table_iter_init (&iter, priv->async_tasks);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &task)) {
g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
WP_LIBRARY_ERROR_OPERATION_FAILED,
"pipewire proxy destroyed before finishing");
g_hash_table_iter_remove (&iter);
}
}
static void
proxy_event_bound (void *data, uint32_t global_id)
{
WpProxy *self = WP_PROXY (data);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
/* we generally make the assumption here that the bound id is the
same as the global id, but while this **is** it's intended use,
the truth is that the bound id **can** be changed anytime with
pw_proxy_set_bound_id() and this can be very bad... */
g_warn_if_fail (!priv->global || priv->global->id == global_id);
wp_proxy_set_feature_ready (self, WP_PROXY_FEATURE_BOUND);
/* construct a WpGlobal if it was not already there */
if (!priv->global) {
g_autoptr (WpCore) core = g_weak_ref_get (&priv->core);
wp_registry_prepare_new_global (wp_core_get_registry (core),
global_id, PW_PERM_RWX, WP_GLOBAL_FLAG_OWNED_BY_PROXY,
G_TYPE_FROM_INSTANCE (self), self, NULL, &priv->global);
}
g_signal_emit (self, wp_proxy_signals[SIGNAL_BOUND], 0, global_id);
}
static void
proxy_event_removed (void *data)
{
wp_trace_object (data, "removed");
}
2019-06-10 15:07:54 -04:00
static const struct pw_proxy_events proxy_events = {
PW_VERSION_PROXY_EVENTS,
.destroy = proxy_event_destroy,
.bound = proxy_event_bound,
.removed = proxy_event_removed,
2019-06-10 15:07:54 -04:00
};
void
wp_proxy_set_pw_proxy (WpProxy * self, struct pw_proxy * proxy)
{
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
if (!proxy)
return;
g_return_if_fail (priv->pw_proxy == NULL);
priv->pw_proxy = proxy;
pw_proxy_add_listener (priv->pw_proxy, &priv->listener, &proxy_events,
self);
/* inform subclasses and listeners */
g_signal_emit (self, wp_proxy_signals[SIGNAL_PW_PROXY_CREATED], 0,
priv->pw_proxy);
/* declare the feature as ready */
wp_proxy_set_feature_ready (self, WP_PROXY_FEATURE_PW_PROXY);
}
static void
wp_proxy_init (WpProxy * self)
{
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
g_weak_ref_init (&priv->core, NULL);
priv->augment_tasks = g_ptr_array_new_with_free_func (g_object_unref);
priv->async_tasks = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, g_object_unref);
}
static void
wp_proxy_dispose (GObject * object)
{
WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
wp_trace_object (object, "dispose (global %u; pw_proxy %p)",
priv->global ? priv->global->id : 0, priv->pw_proxy);
if (priv->global)
wp_global_rm_flag (priv->global, WP_GLOBAL_FLAG_OWNED_BY_PROXY);
/* this will trigger proxy_event_destroy() if the pw_proxy exists */
if (priv->pw_proxy)
pw_proxy_destroy (priv->pw_proxy);
G_OBJECT_CLASS (wp_proxy_parent_class)->dispose (object);
}
2019-06-10 15:07:54 -04:00
static void
wp_proxy_finalize (GObject * object)
{
WpProxy *self = WP_PROXY (object);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
g_clear_pointer (&priv->augment_tasks, g_ptr_array_unref);
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
g_clear_pointer (&priv->global, wp_global_unref);
g_weak_ref_clear (&priv->core);
g_clear_pointer (&priv->async_tasks, g_hash_table_unref);
wp_spa_props_clear (&priv->props);
2019-06-10 15:07:54 -04:00
G_OBJECT_CLASS (wp_proxy_parent_class)->finalize (object);
}
static void
wp_proxy_set_gobj_property (GObject * object, guint property_id,
2019-06-10 15:07:54 -04:00
const GValue * value, GParamSpec * pspec)
{
WpProxy *self = WP_PROXY (object);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
2019-06-10 15:07:54 -04:00
switch (property_id) {
case PROP_CORE:
g_weak_ref_set (&priv->core, g_value_get_object (value));
break;
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
case PROP_GLOBAL:
priv->global = g_value_dup_boxed (value);
break;
case PROP_PW_PROXY:
wp_proxy_set_pw_proxy (self, g_value_get_pointer (value));
break;
2019-06-10 15:07:54 -04:00
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_proxy_get_gobj_property (GObject * object, guint property_id, GValue * value,
2019-06-10 15:07:54 -04:00
GParamSpec * pspec)
{
WpProxy *self = WP_PROXY (object);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
2019-06-10 15:07:54 -04:00
switch (property_id) {
case PROP_CORE:
g_value_take_object (value, g_weak_ref_get (&priv->core));
break;
case PROP_GLOBAL_PERMISSIONS:
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
g_value_set_uint (value, priv->global ? priv->global->permissions : 0);
break;
case PROP_GLOBAL_PROPERTIES:
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
g_value_set_boxed (value, priv->global ? priv->global->properties : NULL);
break;
case PROP_FEATURES:
g_value_set_flags (value, priv->ft_ready);
break;
case PROP_PW_PROXY:
g_value_set_pointer (value, priv->pw_proxy);
break;
case PROP_INFO:
g_value_set_pointer (value, (gpointer) wp_proxy_get_info (self));
break;
case PROP_PROPERTIES:
g_value_take_boxed (value, wp_proxy_get_properties (self));
2019-06-10 15:07:54 -04:00
break;
case PROP_PARAM_INFO:
g_value_take_variant (value, wp_proxy_get_param_info (self));
break;
case PROP_BOUND_ID:
g_value_set_uint (value, wp_proxy_get_bound_id (self));
break;
2019-06-10 15:07:54 -04:00
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_proxy_default_augment (WpProxy * self, WpProxyFeatures features)
2019-06-10 15:07:54 -04:00
{
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
2019-06-10 15:07:54 -04:00
/* ensure we have a pw_proxy, as we can't have
* any other feature without first having that */
if (!priv->pw_proxy && features != 0)
features |= WP_PROXY_FEATURE_PW_PROXY;
/* if we don't have a pw_proxy, we have to assume that this WpProxy
* represents a global object from the registry; we have no other way
* to get a pw_proxy */
if (features & WP_PROXY_FEATURE_PW_PROXY) {
if (priv->global == NULL) {
wp_proxy_augment_error (self, g_error_new (WP_DOMAIN_LIBRARY,
WP_LIBRARY_ERROR_INVALID_ARGUMENT,
"No global specified; cannot bind pw_proxy"));
return;
}
/* bind */
wp_proxy_set_pw_proxy (self, wp_global_bind (priv->global));
}
if (features & WP_PROXY_FEATURE_PROPS) {
WpProxyClass *klass = WP_PROXY_GET_CLASS (self);
uint32_t ids[] = { SPA_PARAM_Props };
if (!klass->enum_params || !klass->subscribe_params) {
wp_proxy_augment_error (self, g_error_new (WP_DOMAIN_LIBRARY,
WP_LIBRARY_ERROR_OPERATION_FAILED,
"Proxy does not support enum/subscribe params API"));
return;
}
klass->enum_params (self, SPA_PARAM_PropInfo, 0, -1, NULL);
klass->subscribe_params (self, ids, SPA_N_ELEMENTS (ids));
}
}
2019-06-10 15:07:54 -04:00
static void
wp_proxy_class_init (WpProxyClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->dispose = wp_proxy_dispose;
2019-06-10 15:07:54 -04:00
object_class->finalize = wp_proxy_finalize;
object_class->get_property = wp_proxy_get_gobj_property;
object_class->set_property = wp_proxy_set_gobj_property;
2019-06-10 15:07:54 -04:00
klass->augment = wp_proxy_default_augment;
2019-06-10 15:07:54 -04:00
/* Install the properties */
g_object_class_install_property (object_class, PROP_CORE,
g_param_spec_object ("core", "core", "The WpCore", WP_TYPE_CORE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
g_object_class_install_property (object_class, PROP_GLOBAL,
g_param_spec_boxed ("global", "global", "Internal WpGlobal object",
wp_global_get_type (),
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_GLOBAL_PERMISSIONS,
g_param_spec_uint ("global-permissions", "global-permissions",
"The pipewire global permissions", 0, G_MAXUINT, 0,
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_GLOBAL_PROPERTIES,
g_param_spec_boxed ("global-properties", "global-properties",
"The pipewire global properties", WP_TYPE_PROPERTIES,
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_FEATURES,
g_param_spec_flags ("features", "features",
"The ready WpProxyFeatures on this proxy", WP_TYPE_PROXY_FEATURES, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_PW_PROXY,
g_param_spec_pointer ("pw-proxy", "pw-proxy", "The struct pw_proxy *",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_INFO,
g_param_spec_pointer ("info", "info", "The native info structure",
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_PROPERTIES,
g_param_spec_boxed ("properties", "properties",
"The pipewire properties of the object", WP_TYPE_PROPERTIES,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2019-06-18 10:09:40 -04:00
g_object_class_install_property (object_class, PROP_PARAM_INFO,
g_param_spec_variant ("param-info", "param-info",
"The param info of the object", G_VARIANT_TYPE ("a{ss}"), NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_BOUND_ID,
g_param_spec_uint ("bound-id", "bound-id",
"The id that this object has on the registry", 0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2019-06-18 10:09:40 -04:00
/* Signals */
wp_proxy_signals[SIGNAL_PW_PROXY_CREATED] = g_signal_new (
"pw-proxy-created", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (WpProxyClass, pw_proxy_created), NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_POINTER);
wp_proxy_signals[SIGNAL_PW_PROXY_DESTROYED] = g_signal_new (
"pw-proxy-destroyed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (WpProxyClass, pw_proxy_destroyed), NULL, NULL, NULL,
G_TYPE_NONE, 0);
2020-03-31 15:04:25 +03:00
wp_proxy_signals[SIGNAL_BOUND] = g_signal_new (
"bound", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (WpProxyClass, bound), NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_UINT);
wp_proxy_signals[SIGNAL_PROP_CHANGED] = g_signal_new (
"prop-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING);
2019-06-10 15:07:54 -04:00
}
/* private */
void
wp_proxy_destroy (WpProxy *self)
{
WpProxyPrivate *priv;
g_return_if_fail (WP_IS_PROXY (self));
priv = wp_proxy_get_instance_private (self);
if (priv->pw_proxy)
pw_proxy_destroy (priv->pw_proxy);
}
/**
* wp_proxy_request_destroy:
* @self: the proxy
*
* Requests the PipeWire server to destroy the object represented by this proxy.
* If the server allows it, the object will be destroyed and the
* WpProxy::pw-proxy-destroyed signal will be emitted. If the server does
* not allow it, nothing will happen.
*
* This is mostly useful for destroying #WpLink and #WpEndpointLink objects.
*/
void
wp_proxy_request_destroy (WpProxy * self)
{
WpProxyPrivate *priv;
g_autoptr (WpCore) core = NULL;
WpRegistry *reg;
g_return_if_fail (WP_IS_PROXY (self));
priv = wp_proxy_get_instance_private (self);
core = wp_proxy_get_core (self);
if (priv->pw_proxy && core) {
reg = wp_core_get_registry (core);
pw_registry_destroy (reg->pw_registry,
pw_proxy_get_bound_id (priv->pw_proxy));
}
}
void
wp_proxy_augment (WpProxy * self,
WpProxyFeatures ft_wanted, GCancellable * cancellable,
GAsyncReadyCallback callback, gpointer user_data)
{
WpProxyPrivate *priv;
WpProxyFeatures missing = 0;
g_autoptr (GTask) task = NULL;
g_return_if_fail (WP_IS_PROXY (self));
g_return_if_fail (WP_PROXY_GET_CLASS (self)->augment);
priv = wp_proxy_get_instance_private (self);
task = g_task_new (self, cancellable, callback, user_data);
/* find which features are wanted but missing from the "ready" set */
missing = (priv->ft_ready ^ ft_wanted) & ft_wanted;
/* if the features are not ready, call augment(),
* otherwise signal the callback directly */
if (missing != 0) {
g_task_set_task_data (task, GUINT_TO_POINTER (missing), NULL);
g_ptr_array_add (priv->augment_tasks, g_steal_pointer (&task));
WP_PROXY_GET_CLASS (self)->augment (self, missing);
} else {
g_task_return_boolean (task, TRUE);
}
}
gboolean
wp_proxy_augment_finish (WpProxy * self, GAsyncResult * res,
GError ** error)
{
g_return_val_if_fail (WP_IS_PROXY (self), FALSE);
g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
return g_task_propagate_boolean (G_TASK (res), error);
}
void
wp_proxy_set_feature_ready (WpProxy * self, WpProxyFeatures feature)
{
WpProxyPrivate *priv;
guint i;
g_return_if_fail (WP_IS_PROXY (self));
priv = wp_proxy_get_instance_private (self);
/* feature already marked as ready */
if (priv->ft_ready & feature)
return;
priv->ft_ready |= feature;
if (wp_log_level_is_enabled (G_LOG_LEVEL_DEBUG)) {
g_autofree gchar *str = g_flags_to_string (WP_TYPE_PROXY_FEATURES,
priv->ft_ready);
wp_debug_object (self, "features changed: %s", str);
}
g_object_notify (G_OBJECT (self), "features");
/* hold a reference to the proxy because unref-ing the tasks might
destroy the proxy, in case the registry is no longer holding a reference */
g_object_ref (self);
/* return from the task if all the wanted features are now ready */
for (i = priv->augment_tasks->len; i > 0; i--) {
GTask *task = g_ptr_array_index (priv->augment_tasks, i - 1);
WpProxyFeatures wanted = GPOINTER_TO_UINT (g_task_get_task_data (task));
if ((priv->ft_ready & wanted) == wanted) {
g_task_return_boolean (task, TRUE);
/* this is safe as long as we are traversing the array backwards */
g_ptr_array_remove_index_fast (priv->augment_tasks, i - 1);
}
}
g_object_unref (self);
}
/**
* wp_proxy_augment_error:
* @self: the proxy
* @error: (transfer full): the error
*
* Reports an error that occured during the augment process
*/
void
wp_proxy_augment_error (WpProxy * self, GError * error)
{
WpProxyPrivate *priv;
guint i;
g_return_if_fail (WP_IS_PROXY (self));
priv = wp_proxy_get_instance_private (self);
for (i = 0; i < priv->augment_tasks->len; i++) {
GTask *task = g_ptr_array_index (priv->augment_tasks, i);
g_task_return_error (task, g_error_copy (error));
}
g_ptr_array_set_size (priv->augment_tasks, 0);
g_error_free (error);
}
WpProxyFeatures
wp_proxy_get_features (WpProxy * self)
{
WpProxyPrivate *priv;
g_return_val_if_fail (WP_IS_PROXY (self), 0);
priv = wp_proxy_get_instance_private (self);
return priv->ft_ready;
}
/**
* wp_proxy_get_core:
* @self: the proxy
*
* Returns: (transfer full): the core that created this proxy
*/
WpCore *
wp_proxy_get_core (WpProxy * self)
{
WpProxyPrivate *priv;
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
priv = wp_proxy_get_instance_private (self);
return g_weak_ref_get (&priv->core);
}
guint32
wp_proxy_get_global_permissions (WpProxy * self)
{
WpProxyPrivate *priv;
g_return_val_if_fail (WP_IS_PROXY (self), 0);
priv = wp_proxy_get_instance_private (self);
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
return priv->global ? priv->global->permissions : 0;
}
/**
* wp_proxy_get_global_properties:
*
* Returns: (transfer full): the global properties of the proxy
*/
WpProperties *
wp_proxy_get_global_properties (WpProxy * self)
{
WpProxyPrivate *priv;
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
priv = wp_proxy_get_instance_private (self);
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
if (!priv->global || !priv->global->properties)
return NULL;
return wp_properties_ref (priv->global->properties);
}
struct pw_proxy *
2019-06-10 15:07:54 -04:00
wp_proxy_get_pw_proxy (WpProxy * self)
{
WpProxyPrivate *priv;
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
priv = wp_proxy_get_instance_private (self);
return priv->pw_proxy;
}
/**
* wp_proxy_get_info:
* @self: the proxy
*
* Requires %WP_PROXY_FEATURE_INFO
*
* Returns: the pipewire info structure of this object
* (pw_node_info, pw_port_info, etc...)
*/
gconstpointer
wp_proxy_get_info (WpProxy * self)
{
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
g_warn_if_fail (priv->ft_ready & WP_PROXY_FEATURE_INFO);
return (WP_PROXY_GET_CLASS (self)->get_info) ?
WP_PROXY_GET_CLASS (self)->get_info (self) : NULL;
}
/**
* wp_proxy_get_properties:
* @self: the proxy
*
* Requires %WP_PROXY_FEATURE_INFO
*
* Returns: (transfer full): the pipewire properties of this object;
* normally these are the properties that are part of the info structure
*/
WpProperties *
wp_proxy_get_properties (WpProxy * self)
{
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
g_warn_if_fail (priv->ft_ready & WP_PROXY_FEATURE_INFO);
return (WP_PROXY_GET_CLASS (self)->get_properties) ?
WP_PROXY_GET_CLASS (self)->get_properties (self) : NULL;
}
/**
* wp_proxy_get_property:
* @self: the proxy
* @key: the property name
*
* Returns the value of a single pipewire property. This is the same as getting
* the whole properties structure with wp_proxy_get_properties() and accessing
* a single property with wp_properties_get(), but saves one call
* and having to clean up the #WpProperties reference count afterwards.
*
* The value is owned by the proxy, but it is guaranteed to stay alive
* until execution returns back to the event loop.
*
* Requires %WP_PROXY_FEATURE_INFO
*
* Returns: (transfer none) (nullable): the value of the pipewire property @key
* or %NULL if the property doesn't exist
*/
const gchar *
wp_proxy_get_property (WpProxy * self, const gchar * key)
{
/* the proxy always keeps a ref to the data, so it's safe
to discard the ref count of the WpProperties */
g_autoptr (WpProperties) props = NULL;
props = wp_proxy_get_properties (self);
return props ? wp_properties_get (props, key) : NULL;
}
/**
* wp_proxy_get_param_info:
* @self: the proxy
*
* Returns the available parameters of this proxy. The return value is
* a variant of type `a{ss}`, where the key of each map entry is a spa param
* type id (the same ids that you can pass in wp_proxy_enum_params())
* and the value is a string that can contain the following letters,
* each of them representing a flag:
* - `r`: the param is readable (`SPA_PARAM_INFO_READ`)
* - `w`: the param is writable (`SPA_PARAM_INFO_WRITE`)
* - `s`: the param was updated (`SPA_PARAM_INFO_SERIAL`)
*
* For params that are readable, you can query them with wp_proxy_enum_params()
*
* Params that are writable can be set with wp_proxy_set_param()
*
* Requires %WP_PROXY_FEATURE_INFO
*
* Returns: (transfer full) (nullable): a variant of type `a{ss}` or %NULL
* if the proxy does not support params at all
*/
GVariant *
wp_proxy_get_param_info (WpProxy * self)
{
g_auto (GVariantBuilder) b =
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_DICTIONARY);
guint n_params = 0;
struct spa_param_info *info;
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
g_warn_if_fail (priv->ft_ready & WP_PROXY_FEATURE_INFO);
info = (WP_PROXY_GET_CLASS (self)->get_param_info) ?
WP_PROXY_GET_CLASS (self)->get_param_info (self, &n_params) : NULL;
if (!info || n_params == 0)
return NULL;
for (guint i = 0; i < n_params; i++) {
const gchar *nick = NULL;
gchar flags[4];
guint flags_idx = 0;
wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, info[i].id, NULL, &nick,
NULL);
g_return_val_if_fail (nick != NULL, NULL);
if (info[i].flags & SPA_PARAM_INFO_READ)
flags[flags_idx++] = 'r';
if (info[i].flags & SPA_PARAM_INFO_WRITE)
flags[flags_idx++] = 'w';
if (info[i].flags & SPA_PARAM_INFO_SERIAL)
flags[flags_idx++] = 's';
flags[flags_idx] = '\0';
g_variant_builder_add (&b, "{ss}", nick, flags);
}
return g_variant_builder_end (&b);
}
/**
* wp_proxy_get_bound_id:
* @self: the proxy
*
* Returns the bound id, which is the id that this object has on the
* pipewire registry (a.k.a. the global id). The object must have the
* %WP_PROXY_FEATURE_BOUND feature before this method can be called.
*
* Returns: the bound id of this object
*/
guint32
wp_proxy_get_bound_id (WpProxy * self)
{
g_return_val_if_fail (WP_IS_PROXY (self), 0);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
g_warn_if_fail (priv->ft_ready & WP_PROXY_FEATURE_BOUND);
return priv->pw_proxy ? pw_proxy_get_bound_id (priv->pw_proxy) : SPA_ID_INVALID;
}
static void
wp_proxy_register_async_task (WpProxy * self, int seq, GTask * task)
{
WpProxyPrivate *priv;
g_return_if_fail (WP_IS_PROXY (self));
g_return_if_fail (g_task_is_valid (task, self));
priv = wp_proxy_get_instance_private (self);
g_hash_table_insert (priv->async_tasks, GINT_TO_POINTER (seq), task);
}
static GTask *
wp_proxy_find_async_task (WpProxy * self, int seq, gboolean steal)
{
WpProxyPrivate *priv;
GTask *task = NULL;
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
priv = wp_proxy_get_instance_private (self);
if (steal)
g_hash_table_steal_extended (priv->async_tasks, GINT_TO_POINTER (seq),
NULL, (gpointer *) &task);
else
task = g_hash_table_lookup (priv->async_tasks, GINT_TO_POINTER (seq));
return task;
}
static void
enum_params_done (WpCore * core, GAsyncResult * res, gpointer data)
{
int seq = GPOINTER_TO_INT (g_task_get_source_tag (G_TASK (data)));
WpProxy *proxy = g_task_get_source_object (G_TASK (data));
g_autoptr (GTask) task = NULL;
g_autoptr (GError) error = NULL;
/* finish the sync task */
wp_core_sync_finish (core, res, &error);
/* find the enum params task in the hash table to steal the reference */
task = wp_proxy_find_async_task (proxy, seq, TRUE);
g_return_if_fail (task != NULL);
if (error)
g_task_return_error (task, g_steal_pointer (&error));
else {
GPtrArray *params = g_task_get_task_data (task);
g_task_return_pointer (task, g_ptr_array_ref (params),
(GDestroyNotify) g_ptr_array_unref);
}
}
/**
* wp_proxy_enum_params:
* @self: the proxy
* @id: (nullable): the parameter id to enumerate or %NULL for all parameters
* @filter: (nullable): a param filter or %NULL
* @cancellable: (nullable): a cancellable for the async operation
* @callback: (scope async): a callback to call with the result
* @user_data: (closure): data to pass to @callback
*
* Enumerate object parameters. This will asynchronously return the result,
* or an error, by calling the given @callback. The result is going to
* be a #WpIterator containing #WpSpaPod objects, which can be retrieved
* with wp_proxy_enum_params_finish().
*/
void
wp_proxy_enum_params (WpProxy * self, const gchar * id,
const WpSpaPod *filter, GCancellable * cancellable,
GAsyncReadyCallback callback, gpointer user_data)
{
g_autoptr (GTask) task = NULL;
guint32 id_num = 0;
int seq;
GPtrArray *params;
g_return_if_fail (WP_IS_PROXY (self));
/* create task for enum_params */
task = g_task_new (self, cancellable, callback, user_data);
params = g_ptr_array_new_with_free_func ((GDestroyNotify) wp_spa_pod_unref);
g_task_set_task_data (task, params, (GDestroyNotify) g_ptr_array_unref);
if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, &id_num,
NULL, NULL)) {
wp_critical_object (self, "invalid param id: %s", id);
return;
}
/* call enum_params */
seq = (WP_PROXY_GET_CLASS (self)->enum_params) ?
WP_PROXY_GET_CLASS (self)->enum_params (self, id_num, 0, -1, filter) :
-ENOTSUP;
if (G_UNLIKELY (seq < 0)) {
wp_message_object (self, "enum_params failed: %s", spa_strerror (seq));
g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
WP_LIBRARY_ERROR_OPERATION_FAILED, "enum_params failed: %s",
spa_strerror (seq));
return;
}
g_task_set_source_tag (task, GINT_TO_POINTER (seq));
wp_proxy_register_async_task (self, seq, g_object_ref (task));
/* call sync */
g_autoptr (WpCore) core = wp_proxy_get_core (self);
wp_core_sync (core, cancellable, (GAsyncReadyCallback) enum_params_done,
task);
}
/**
* wp_proxy_enum_params_finish:
* @self: the proxy
* @res: the async result
* @error: (out) (optional): the reported error of the operation, if any
*
* Returns: (transfer full) (nullable): an iterator to iterate over the
* collected params, or %NULL if the operation resulted in error;
* the items in the iterator are #WpSpaPod
*/
WpIterator *
wp_proxy_enum_params_finish (WpProxy * self, GAsyncResult * res,
GError ** error)
{
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
g_return_val_if_fail (g_task_is_valid (res, self), NULL);
GPtrArray *array = g_task_propagate_pointer (G_TASK (res), error);
if (!array)
return NULL;
return wp_iterator_new_ptr_array (array, WP_TYPE_SPA_POD);
}
/**
* wp_proxy_set_param:
* @self: the proxy
* @id: the parameter id to set
* @param: the parameter to set
*
* Sets a parameter on the object.
*/
void
wp_proxy_set_param (WpProxy * self, const gchar * id, const WpSpaPod *param)
{
guint32 id_num = 0;
gint ret;
g_return_if_fail (WP_IS_PROXY (self));
if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, &id_num,
NULL, NULL)) {
wp_critical_object (self, "invalid param id: %s", id);
return;
}
ret = (WP_PROXY_GET_CLASS (self)->set_param) ?
WP_PROXY_GET_CLASS (self)->set_param (self, id_num, 0, param) :
-ENOTSUP;
if (G_UNLIKELY (ret < 0)) {
wp_message_object (self, "set_param failed: %s", spa_strerror (ret));
}
}
/**
* wp_proxy_iterate_prop_info:
* @self: the proxy
*
* Requires %WP_PROXY_FEATURE_PROPS
*
* Returns: (transfer full) (nullable): an iterator to iterate over the
* `SPA_PARAM_PropInfo` params, or %NULL if the object has no props;
* the items in the iterator are #WpSpaPod
*/
WpIterator *
wp_proxy_iterate_prop_info (WpProxy * self)
{
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
g_return_val_if_fail (priv->ft_ready & WP_PROXY_FEATURE_PROPS, NULL);
GPtrArray *array = wp_spa_props_build_propinfo (&priv->props);
if (!array)
return NULL;
return wp_iterator_new_ptr_array (array, WP_TYPE_SPA_POD);
}
/**
* wp_proxy_get_prop:
* @self: the proxy
* @prop_name: the prop name
*
* Requires %WP_PROXY_FEATURE_PROPS
*
* Returns: (transfer full) (nullable): the spa pod containing the value
* of this prop, or %NULL if @prop_name does not exist on this proxy
*/
WpSpaPod *
wp_proxy_get_prop (WpProxy * self, const gchar * prop_name)
{
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
g_return_val_if_fail (priv->ft_ready & WP_PROXY_FEATURE_PROPS, NULL);
return wp_spa_props_get_stored (&priv->props, prop_name);
}
/**
* wp_proxy_set_prop:
* @self: the proxy
* @prop_name: the prop name
* @value: the new value for this prop, as a spa pod
*
* Sets a single property in the `SPA_PARAM_Props` param of this object.
*/
void
wp_proxy_set_prop (WpProxy * self, const gchar * prop_name,
const WpSpaPod * value)
{
g_return_if_fail (WP_IS_PROXY (self));
g_return_if_fail (value != NULL);
g_autoptr (WpSpaPod) param = wp_spa_pod_new_object (
"Props", "Props",
prop_name, "P", value,
NULL);
/* our spa_props will be updated by the param event */
wp_proxy_set_param (self, "Props", param);
}
void
wp_proxy_handle_event_param (void * proxy, int seq, uint32_t id,
uint32_t index, uint32_t next, const struct spa_pod *param)
{
WpProxy *self = WP_PROXY (proxy);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
g_autoptr (WpSpaPod) w_param = wp_spa_pod_new_regular_wrap (param);
GTask *task;
/* if this param event was emited because of enum_params(),
* copy the param in the result array of that API */
task = wp_proxy_find_async_task (self, seq, FALSE);
if (task) {
GPtrArray *array = g_task_get_task_data (task);
g_ptr_array_add (array, wp_spa_pod_copy (w_param));
}
/* else consider this to be a prop update, either triggered from augment()
* or because we are subscribed to props */
else {
switch (id) {
case SPA_PARAM_PropInfo:
if (!(priv->ft_ready & WP_PROXY_FEATURE_PROPS)) {
wp_trace_object (self, "got PropInfo");
wp_trace_boxed (WP_TYPE_SPA_POD, w_param, "PropInfo pod");
wp_spa_props_register_from_prop_info (&priv->props, w_param);
}
break;
case SPA_PARAM_Props: {
g_autoptr (GPtrArray) changed_ids =
g_ptr_array_new_with_free_func (g_free);
wp_trace_object (self, "got Props");
wp_trace_boxed (WP_TYPE_SPA_POD, w_param, "Props pod");
wp_spa_props_store_from_props (&priv->props, w_param, changed_ids);
wp_proxy_set_feature_ready (self, WP_PROXY_FEATURE_PROPS);
for (guint i = 0; i < changed_ids->len; i++) {
const gchar *prop_id = g_ptr_array_index (changed_ids, i);
g_signal_emit (self, wp_proxy_signals[SIGNAL_PROP_CHANGED], 0,
prop_id);
}
break;
}
default:
/* and ignore other kinds of params for now */
break;
}
}
}
WpSpaProps *
wp_proxy_get_spa_props (WpProxy * self)
{
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
return &priv->props;
}