mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2025-12-31 19:00:07 +01:00
- make it a GObject so that it can emit its own signals and so that it can be shared between multiple proxies - share the WpProps instance between endpoints, endpoint-streams and their underlying nodes - introduce the concept of the caching mode that redirects _set to _set_param of the proxy that actually has the props; this allows shared WpProps to actually set changes on the correct proxy in a transparent way - change methods to consume the ref of the pod and reflect that also on wp_proxy_set_prop() - refactor the export process on endpoints & endpoint-streams so that they always get all the required features (info, props, bound) and make it async so that we can take time to prepare the underlying node to have FEATURE_PROPS - update the props & endpoint unit tests, bringing back all the checks that the endpoint unit test used to have
484 lines
12 KiB
C
484 lines
12 KiB
C
/* WirePlumber
|
|
*
|
|
* Copyright © 2020 Collabora Ltd.
|
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
/**
|
|
* SECTION: props
|
|
* @title: Dynamic Proxy Properties
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "wp-props"
|
|
|
|
#include "props.h"
|
|
#include "debug.h"
|
|
#include "spa-type.h"
|
|
#include "wpenums.h"
|
|
#include <spa/param/param.h>
|
|
|
|
struct entry
|
|
{
|
|
guint32 id;
|
|
gchar *description;
|
|
WpSpaPod *type;
|
|
WpSpaPod *value;
|
|
};
|
|
|
|
struct entry *
|
|
entry_new (void)
|
|
{
|
|
struct entry *e = g_slice_new0 (struct entry);
|
|
return e;
|
|
}
|
|
|
|
static void
|
|
entry_free (struct entry *e)
|
|
{
|
|
g_free (e->description);
|
|
g_clear_pointer (&e->type, wp_spa_pod_unref);
|
|
g_clear_pointer (&e->value, wp_spa_pod_unref);
|
|
g_slice_free (struct entry, e);
|
|
}
|
|
|
|
struct _WpProps
|
|
{
|
|
GObject parent;
|
|
|
|
GWeakRef proxy;
|
|
WpPropsMode mode;
|
|
GList *entries;
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_PROXY,
|
|
PROP_MODE,
|
|
};
|
|
|
|
enum
|
|
{
|
|
SIGNAL_PROP_CHANGED,
|
|
LAST_SIGNAL,
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
/**
|
|
* WpProps:
|
|
*
|
|
* #WpProps handles dynamic properties on PipeWire objects, which are
|
|
* known in PipeWire as "params" of type `SPA_PARAM_Props`.
|
|
*
|
|
* #WpProps has two modes of operation:
|
|
* - %WP_PROPS_MODE_CACHE: In this mode, this object caches properties that are
|
|
* actually stored and discovered from the associated proxy object.
|
|
* When setting a property, the property is first set on the proxy and the
|
|
* cache is updated asynchronously (so wp_props_get() will not immediately
|
|
* return the value that was set with wp_props_set()).
|
|
* - %WP_PROPS_MODE_STORE: In this mode, this object is the actual store of
|
|
* properties. This is used by object implementations, such as #WpImplSession.
|
|
* Before storing anything, properties need to be registered with
|
|
* wp_props_register().
|
|
*/
|
|
G_DEFINE_TYPE (WpProps, wp_props, G_TYPE_OBJECT)
|
|
|
|
static void
|
|
wp_props_init (WpProps * self)
|
|
{
|
|
g_weak_ref_init (&self->proxy, NULL);
|
|
}
|
|
|
|
static void
|
|
wp_props_finalize (GObject * object)
|
|
{
|
|
WpProps * self = WP_PROPS (object);
|
|
|
|
g_list_free_full (self->entries, (GDestroyNotify) entry_free);
|
|
g_weak_ref_clear (&self->proxy);
|
|
|
|
G_OBJECT_CLASS (wp_props_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
wp_props_set_property (GObject * object, guint property_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
WpProps *self = WP_PROPS (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_PROXY:
|
|
g_weak_ref_set (&self->proxy, g_value_get_object (value));
|
|
break;
|
|
case PROP_MODE:
|
|
self->mode = g_value_get_enum (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_props_get_property (GObject * object, guint property_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
WpProps *self = WP_PROPS (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_PROXY:
|
|
g_value_take_object (value, g_weak_ref_get (&self->proxy));
|
|
break;
|
|
case PROP_MODE:
|
|
g_value_set_enum (value, self->mode);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_props_class_init (WpPropsClass * klass)
|
|
{
|
|
GObjectClass * object_class = (GObjectClass *) klass;
|
|
|
|
object_class->finalize = wp_props_finalize;
|
|
object_class->set_property = wp_props_set_property;
|
|
object_class->get_property = wp_props_get_property;
|
|
|
|
g_object_class_install_property (object_class, PROP_PROXY,
|
|
g_param_spec_object ("proxy", "proxy", "The proxy", WP_TYPE_PROXY,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (object_class, PROP_MODE,
|
|
g_param_spec_enum ("mode", "mode", "The mode",
|
|
WP_TYPE_PROPS_MODE, WP_PROPS_MODE_CACHE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* WpProps::prop-changed:
|
|
* @self: the props
|
|
* @name: the name of the property that changed
|
|
*/
|
|
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);
|
|
}
|
|
|
|
static struct entry *
|
|
find_entry (WpProps * self, const gchar * name)
|
|
{
|
|
GList *l = self->entries;
|
|
guint32 id;
|
|
|
|
if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PROPS, name, &id,
|
|
NULL, NULL)) {
|
|
wp_critical_object (self, "prop id name '%s' is not registered", name);
|
|
return NULL;
|
|
}
|
|
|
|
while (l && ((struct entry *) l->data)->id != id)
|
|
l = g_list_next (l);
|
|
if (!l)
|
|
return NULL;
|
|
|
|
return (struct entry *) l->data;
|
|
}
|
|
|
|
/* public */
|
|
|
|
/**
|
|
* wp_props_new:
|
|
* @mode: the mode
|
|
* @proxy: (transfer none) (nullable): the associated proxy; can be %NULL
|
|
* if @mode is %WP_PROPS_MODE_STORE
|
|
*
|
|
* Returns: (transfer full): the newly created #WpProps object
|
|
*/
|
|
WpProps *
|
|
wp_props_new (WpPropsMode mode, WpProxy * proxy)
|
|
{
|
|
return g_object_new (WP_TYPE_PROPS, "mode", mode, "proxy", proxy, NULL);
|
|
}
|
|
|
|
/**
|
|
* wp_props_register:
|
|
* @self: the props
|
|
* @name: the name (registered spa type nick) of the property
|
|
* @description: the description of the property
|
|
* @pod: (transfer full): a pod that gives the type and the default value
|
|
*
|
|
* Registers a new property. This can only be used in %WP_PROPS_MODE_STORE mode.
|
|
*
|
|
* @name must be a valid spa type nickname, registered in the
|
|
* %WP_SPA_TYPE_TABLE_PROPS table.
|
|
*
|
|
* @pod can be a value (which is taken as the default value) or a choice
|
|
* (which defines the allowed values for this property)
|
|
*/
|
|
void
|
|
wp_props_register (WpProps * self, const gchar * name,
|
|
const gchar * description, WpSpaPod * pod)
|
|
{
|
|
guint32 id;
|
|
|
|
g_return_if_fail (self->mode == WP_PROPS_MODE_STORE);
|
|
|
|
if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PROPS, name, &id,
|
|
NULL, NULL)) {
|
|
wp_critical_object (self, "prop id name '%s' is not registered", name);
|
|
return;
|
|
}
|
|
|
|
struct entry *e = entry_new ();
|
|
e->id = id;
|
|
e->description = g_strdup (description);
|
|
e->type = pod;
|
|
e->value = wp_spa_pod_is_choice (e->type) ?
|
|
wp_spa_pod_get_choice_child (e->type) : wp_spa_pod_ref (e->type);
|
|
self->entries = g_list_append (self->entries, e);
|
|
}
|
|
|
|
/**
|
|
* wp_props_register_from_info:
|
|
* @self: the props
|
|
* @pod: (transfer full): a `SPA_TYPE_OBJECT_PropInfo` pod
|
|
*
|
|
* Registers a new property using the information of the provided PropInfo @pod
|
|
*/
|
|
void
|
|
wp_props_register_from_info (WpProps * self, WpSpaPod * pod)
|
|
{
|
|
g_autoptr (WpSpaPod) prop_info = pod;
|
|
guint32 id;
|
|
const gchar *description;
|
|
g_autoptr (WpSpaPod) type = NULL;
|
|
|
|
if (!wp_spa_pod_get_object (prop_info,
|
|
"PropInfo", NULL,
|
|
"id", "I", &id,
|
|
"name", "s", &description,
|
|
"type", "P", &type,
|
|
NULL)) {
|
|
wp_warning_boxed (WP_TYPE_SPA_POD, prop_info, "bad prop info object");
|
|
return;
|
|
}
|
|
|
|
struct entry *e = entry_new ();
|
|
e->id = id;
|
|
e->description = g_strdup (description);
|
|
e->type = wp_spa_pod_ref (type);
|
|
e->value = wp_spa_pod_is_choice (e->type) ?
|
|
wp_spa_pod_get_choice_child (e->type) : wp_spa_pod_ref (e->type);
|
|
self->entries = g_list_append (self->entries, e);
|
|
}
|
|
|
|
/**
|
|
* wp_props_iterate_prop_info:
|
|
* @self: the props
|
|
*
|
|
* Returns: (transfer full): a #WpIterator that iterates over #WpSpaPod items
|
|
* where each pod is an object of type `SPA_TYPE_OBJECT_PropInfo`, and thus
|
|
* contains the id, the description and the type of each property.
|
|
*/
|
|
WpIterator *
|
|
wp_props_iterate_prop_info (WpProps * self)
|
|
{
|
|
g_autoptr (GPtrArray) res =
|
|
g_ptr_array_new_with_free_func ((GDestroyNotify) wp_spa_pod_unref);
|
|
|
|
g_return_val_if_fail (WP_IS_PROPS (self), NULL);
|
|
|
|
for (GList *l = self->entries; l != NULL; l = g_list_next (l)) {
|
|
struct entry * e = (struct entry *) l->data;
|
|
g_ptr_array_add (res, wp_spa_pod_new_object (
|
|
"PropInfo", "PropInfo",
|
|
"id", "I", e->id,
|
|
"name", "s", e->description,
|
|
"type", "P", e->type,
|
|
NULL));
|
|
}
|
|
|
|
return wp_iterator_new_ptr_array (g_steal_pointer (&res), WP_TYPE_SPA_POD);
|
|
}
|
|
|
|
/**
|
|
* wp_props_get_all:
|
|
* @self: the props
|
|
*
|
|
* Returns: (transfer full): a pod object of type `SPA_TYPE_OBJECT_Props`
|
|
* that contains all the properties, as they would appear on the PipeWire
|
|
* object
|
|
*/
|
|
WpSpaPod *
|
|
wp_props_get_all (WpProps * self)
|
|
{
|
|
g_autoptr (WpSpaPodBuilder) b = NULL;
|
|
|
|
g_return_val_if_fail (WP_IS_PROPS (self), NULL);
|
|
|
|
b = wp_spa_pod_builder_new_object ("Props", "Props");
|
|
for (GList *l = self->entries; l != NULL; l = g_list_next (l)) {
|
|
struct entry * e = (struct entry *) l->data;
|
|
if (e->id && e->value) {
|
|
wp_spa_pod_builder_add_property_id (b, e->id);
|
|
wp_spa_pod_builder_add_pod (b, e->value);
|
|
}
|
|
}
|
|
|
|
return wp_spa_pod_builder_end (b);
|
|
}
|
|
|
|
/**
|
|
* wp_props_get:
|
|
* @self: the props
|
|
* @name: the name (registered spa type nick) of the property to get
|
|
*
|
|
* Returns: (transfer full) (nullable): a pod with the current value of the
|
|
* property or %NULL if the property is not found
|
|
*/
|
|
WpSpaPod *
|
|
wp_props_get (WpProps * self, const gchar * name)
|
|
{
|
|
struct entry * e;
|
|
|
|
g_return_val_if_fail (WP_IS_PROPS (self), NULL);
|
|
|
|
if (!(e = find_entry (self, name)))
|
|
return NULL;
|
|
return wp_spa_pod_ref (e->value);
|
|
}
|
|
|
|
static void
|
|
wp_props_set_on_proxy (WpProps * self, const gchar * name, WpSpaPod * pod)
|
|
{
|
|
g_autoptr (WpSpaPod) val = pod;
|
|
g_autoptr (WpProxy) proxy = g_weak_ref_get (&self->proxy);
|
|
g_autoptr (WpSpaPod) param = NULL;
|
|
|
|
g_return_if_fail (proxy != NULL);
|
|
|
|
if (name) {
|
|
param = wp_spa_pod_new_object (
|
|
"Props", "Props",
|
|
name, "P", val,
|
|
NULL);
|
|
} else {
|
|
param = wp_spa_pod_ref (pod);
|
|
}
|
|
|
|
/* our store will be updated by the param event */
|
|
wp_proxy_set_param (proxy, "Props", param);
|
|
}
|
|
|
|
static void
|
|
wp_props_store_single (WpProps * self, const gchar * name, WpSpaPod * pod)
|
|
{
|
|
g_autoptr (WpSpaPod) val = pod;
|
|
struct entry * e;
|
|
|
|
if (!(e = find_entry (self, name))) {
|
|
wp_warning_object (self, "prop '%s' is not registered", name);
|
|
return;
|
|
}
|
|
|
|
wp_trace_object (self, "storing '%s', entry:%p", name, e);
|
|
|
|
/* TODO check the type */
|
|
|
|
if (!wp_spa_pod_equal (e->value, val)) {
|
|
g_clear_pointer (&e->value, wp_spa_pod_unref);
|
|
e->value = wp_spa_pod_ensure_unique_owner (g_steal_pointer (&val));
|
|
g_signal_emit (self, signals[SIGNAL_PROP_CHANGED], 0, name);
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_props_store_many (WpProps * self, WpSpaPod * pod)
|
|
{
|
|
g_autoptr (WpSpaPod) props = pod;
|
|
g_autoptr (WpIterator) it = NULL;
|
|
g_auto (GValue) item = G_VALUE_INIT;
|
|
|
|
for (it = wp_spa_pod_iterate (props);
|
|
wp_iterator_next (it, &item);
|
|
g_value_unset (&item)) {
|
|
WpSpaPod *p = g_value_get_boxed (&item);
|
|
const char *name = NULL;
|
|
WpSpaPod *val = NULL;
|
|
|
|
if (!wp_spa_pod_get_property (p, &name, &val)) {
|
|
wp_warning_object (self, "failed to get property name & value");
|
|
continue;
|
|
}
|
|
wp_props_store (self, name, val);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* wp_props_set:
|
|
* @self: the props
|
|
* @name: (nullable): the name (registered spa type nick) of the property to set
|
|
* @value: (transfer full): the value to set
|
|
*
|
|
* Sets the property specified with @name to have the given @value.
|
|
* If the mode is %WP_PROPS_MODE_CACHE, this property will be set on the
|
|
* associated proxy first and will be updated asynchronously.
|
|
*
|
|
* If @name is %NULL, then @value must be an object of type
|
|
* `SPA_TYPE_OBJECT_Props`, which may contain multiple properties to set.
|
|
*
|
|
* If any value actually changes, the #WpProps::prop-changed signal will be
|
|
* emitted.
|
|
*/
|
|
void
|
|
wp_props_set (WpProps * self, const gchar * name, WpSpaPod * value)
|
|
{
|
|
g_return_if_fail (WP_IS_PROPS (self));
|
|
g_return_if_fail (value != NULL);
|
|
|
|
switch (self->mode) {
|
|
case WP_PROPS_MODE_CACHE:
|
|
wp_props_set_on_proxy (self, name, value);
|
|
break;
|
|
case WP_PROPS_MODE_STORE:
|
|
if (name)
|
|
wp_props_store_single (self, name, value);
|
|
else
|
|
wp_props_store_many (self, value);
|
|
break;
|
|
default:
|
|
g_return_if_reached ();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* wp_props_store:
|
|
* @self: the props
|
|
* @name: (nullable): the name (registered spa type nick) of the property to set
|
|
* @value: (transfer full): the value to set
|
|
*
|
|
* Stores the given @value for the property specified with @name.
|
|
* This method always stores, even if the mode is %WP_PROPS_MODE_CACHE. This is
|
|
* useful for caching implementations only.
|
|
*
|
|
* If @name is %NULL, then @value must be an object of type
|
|
* `SPA_TYPE_OBJECT_Props`, which may contain multiple properties to set.
|
|
*
|
|
* If any value actually changes, the #WpProps::prop-changed signal will be
|
|
* emitted.
|
|
*/
|
|
void
|
|
wp_props_store (WpProps * self, const gchar * name, WpSpaPod * value)
|
|
{
|
|
g_return_if_fail (WP_IS_PROPS (self));
|
|
g_return_if_fail (value != NULL);
|
|
|
|
if (name)
|
|
wp_props_store_single (self, name, value);
|
|
else
|
|
wp_props_store_many (self, value);
|
|
}
|