wireplumber/lib/wp/collection.c
Julian Bouzas 59f98e38e4 lib: Add collection proxy API
This proxy uses the metadata interface to represent collections.
2026-05-05 16:00:15 -04:00

991 lines
26 KiB
C

/* WirePlumber
*
* Copyright © 2026 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "collection.h"
#include "core.h"
#include "log.h"
#include "error.h"
#include "wpenums.h"
#include <pipewire/impl.h>
#include <pipewire/pipewire.h>
#include <pipewire/extensions/metadata.h>
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-collection")
/*! \defgroup wpcollection WpCollection */
/*!
* \struct WpCollection
*
* The WpCollection class allows accessing the properties and methods of
* PipeWire collection object (`struct pw_collection`).
*
* A WpCollection is constructed internally when a new collection object appears
* on the PipeWire registry and it is made available through the WpObjectManager
* API.
*
* \gsignals
*
* \par global_added
* \parblock
* \code
* void
* global_added_callback (WpCollection * self,
* guint global_id,
* gpointer user_data)
* \endcode
* Emitted when a gobal was added into the collection
*
* Parameters:
* - `global_id` - the added global id
*
* Flags: G_SIGNAL_RUN_LAST
* \endparblock
*
* \par global_removed
* \parblock
* \code
* void
* global_removed_callback (WpCollection * self,
* guint global_id,
* gpointer user_data)
* \endcode
* Emitted when a gobal was removed from the collection
*
* Parameters:
* - `global_id` - the removed global id
*
* Flags: G_SIGNAL_RUN_LAST
* \endparblock
*/
enum {
SIGNAL_GLOBAL_COLLECTED,
SIGNAL_GLOBAL_DROPPED,
N_SIGNALS,
};
static guint32 signals[N_SIGNALS] = {0};
/* data structure */
struct item
{
uint32_t subject;
gchar *key;
gchar *type;
gchar *value;
};
static void
set_item (struct item * item, uint32_t subject, const char * key,
const char * type, const char * value)
{
item->subject = subject;
item->key = key ? g_strdup (key) : NULL;
item->type = type ? g_strdup (type) : NULL;
item->value = value ? g_strdup (value) : NULL;
}
static void
clear_item (struct item * item)
{
g_clear_pointer (&item->key, g_free);
g_clear_pointer (&item->type, g_free);
g_clear_pointer (&item->value, g_free);
spa_zero (*item);
}
static struct item *
find_item (struct pw_array * metadata, uint32_t subject, const char * key)
{
struct item *item;
pw_array_for_each (item, metadata) {
if (item->subject == subject && (key == NULL || !strcmp (item->key, key)))
return item;
}
return NULL;
}
static void
clear_items (struct pw_array * metadata)
{
struct item *item;
pw_array_consume (item, metadata) {
clear_item (item);
pw_array_remove (metadata, item);
}
pw_array_reset (metadata);
}
struct _WpCollection
{
WpGlobalProxy parent;
struct pw_metadata *iface;
struct spa_hook listener;
struct pw_array metadata;
gboolean listener_added;
};
G_DEFINE_TYPE (WpCollection, wp_collection, WP_TYPE_GLOBAL_PROXY)
static void
wp_collection_init (WpCollection * self)
{
pw_array_init (&self->metadata, 4096);
}
static void
wp_collection_finalize (GObject * object)
{
WpCollection * self = WP_COLLECTION (object);
pw_array_clear (&self->metadata);
G_OBJECT_CLASS (wp_collection_parent_class)->finalize (object);
}
static WpObjectFeatures
wp_collection_get_supported_features (WpObject * object)
{
return WP_PROXY_FEATURE_BOUND | WP_COLLECTION_FEATURE_DATA;
}
enum {
STEP_BIND = WP_TRANSITION_STEP_CUSTOM_START,
STEP_CACHE
};
static guint
wp_collection_activate_get_next_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
g_return_val_if_fail (
missing & (WP_PROXY_FEATURE_BOUND | WP_COLLECTION_FEATURE_DATA),
WP_TRANSITION_STEP_ERROR);
/* bind if not already bound */
if (missing & WP_PROXY_FEATURE_BOUND)
return STEP_BIND;
else
return STEP_CACHE;
}
static void
wp_collection_activate_execute_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
switch (step) {
case STEP_CACHE:
/* just wait for initial_sync_done() */
break;
default:
WP_OBJECT_CLASS (wp_collection_parent_class)->
activate_execute_step (object, transition, step, missing);
break;
}
}
static int
metadata_event_property (void *object, uint32_t subject, const char *key,
const char *type, const char *value)
{
WpCollection *self = WP_COLLECTION (object);
struct item *item = NULL;
/* Clear subject if key is NULL */
if (key == NULL) {
while (true) {
guint32 global_id = SPA_ID_INVALID;
item = find_item (&self->metadata, subject, NULL);
if (item == NULL)
break;
pw_array_remove (&self->metadata, item);
if (spa_atou32 (item->key, &global_id, 0))
g_signal_emit (self, signals[SIGNAL_GLOBAL_DROPPED], 0, global_id);
clear_item (item);
}
return 0;
}
item = find_item (&self->metadata, subject, key);
if (item == NULL) {
if (value == NULL)
return 0;
item = pw_array_add (&self->metadata, sizeof (*item));
if (item == NULL)
return -errno;
} else {
clear_item (item);
}
if (value != NULL) {
guint32 global_id = SPA_ID_INVALID;
set_item (item, subject, key, type, value);
if (spa_atou32 (key, &global_id, 0))
g_signal_emit (self, signals[SIGNAL_GLOBAL_COLLECTED], 0, global_id);
} else {
guint32 global_id = SPA_ID_INVALID;
pw_array_remove (&self->metadata, item);
if (spa_atou32 (key, &global_id, 0))
g_signal_emit (self, signals[SIGNAL_GLOBAL_DROPPED], 0, global_id);
}
return 0;
}
static const struct pw_metadata_events metadata_events = {
PW_VERSION_METADATA_EVENTS,
.property = metadata_event_property,
};
static void
wp_collection_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
{
WpCollection *self = WP_COLLECTION (proxy);
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
self->iface = (struct pw_metadata *) pw_proxy;
pw_metadata_add_listener (self->iface, &self->listener, &metadata_events,
self);
self->listener_added = TRUE;
wp_object_update_features (WP_OBJECT (self), WP_COLLECTION_FEATURE_DATA, 0);
}
static void
wp_collection_pw_proxy_destroyed (WpProxy * proxy)
{
WpCollection *self = WP_COLLECTION (proxy);
if (self->listener_added) {
spa_hook_remove (&self->listener);
self->listener_added = FALSE;
}
clear_items (&self->metadata);
wp_object_update_features (WP_OBJECT (self), 0, WP_COLLECTION_FEATURE_DATA);
WP_PROXY_CLASS (wp_collection_parent_class)->pw_proxy_destroyed (proxy);
}
static void
wp_collection_class_init (WpCollectionClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpObjectClass *wpobject_class = (WpObjectClass *) klass;
WpProxyClass *proxy_class = (WpProxyClass *) klass;
object_class->finalize = wp_collection_finalize;
wpobject_class->get_supported_features = wp_collection_get_supported_features;
wpobject_class->activate_get_next_step = wp_collection_activate_get_next_step;
wpobject_class->activate_execute_step = wp_collection_activate_execute_step;
proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Metadata;
proxy_class->pw_iface_version = PW_VERSION_METADATA;
proxy_class->pw_proxy_created = wp_collection_pw_proxy_created;
proxy_class->pw_proxy_destroyed = wp_collection_pw_proxy_destroyed;
signals[SIGNAL_GLOBAL_COLLECTED] = g_signal_new ("global-collected",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_UINT);
signals[SIGNAL_GLOBAL_DROPPED] = g_signal_new ("global-dropped",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_UINT);
}
/*!
* \brief Gets the name of the collection
*
* \ingroup wpcollection
* \param self the collection object
* \returns (nullable): the name of the collection
*/
const gchar *
wp_collection_get_name (WpCollection * self)
{
g_autoptr (WpProperties) props = NULL;
g_return_val_if_fail (WP_IS_COLLECTION (self), NULL);
props = wp_global_proxy_get_global_properties (WP_GLOBAL_PROXY (self));
return props ? wp_properties_get (props, "collection.name") : NULL;
}
/*!
* \brief Gets the total number of globals the collection has collected
*
* \ingroup wpcollection
* \param self the collection object
* \returns the total number of globals the collection has collected
*/
gsize
wp_collection_get_size (WpCollection * self)
{
g_return_val_if_fail (WP_IS_COLLECTION (self), 0);
return pw_array_get_len (&self->metadata, struct item);
}
/*!
* \brief Checks if a global ID is collected in the collection
*
* \ingroup wpcollection
* \param self the collection object
* \param global_id the global ID to check
* \returns TRUE if the global ID is collected in the colleciton, FALSE
* otherwise.
*/
gboolean
wp_collection_contains_global (WpCollection * self, guint32 global_id)
{
struct item *item;
g_return_val_if_fail (WP_IS_COLLECTION (self), FALSE);
pw_array_for_each (item, &self->metadata) {
guint32 id = SPA_ID_INVALID;
if (spa_atou32 (item->key, &id, 0) && id == global_id)
return TRUE;
}
return FALSE;
}
/*!
* \brief Collects a global ID into the collection.
*
* \ingroup wpcollection
* \param self the collection object
* \param global_id the global ID to collect
*/
void
wp_collection_collect_global (WpCollection * self, guint32 global_id)
{
g_autofree gchar *global_id_str = NULL;
g_return_if_fail (WP_IS_COLLECTION (self));
g_return_if_fail (global_id != SPA_ID_INVALID);
global_id_str = g_strdup_printf ("%u", global_id);
pw_metadata_set_property (self->iface, 0, global_id_str, NULL, "collected");
}
/*!
* \brief Drops a global ID from the collection.
*
* \ingroup wpcollection
* \param self the collection object
* \param global_id the global ID to drop
*/
void
wp_collection_drop_global (WpCollection * self, guint32 global_id)
{
g_autofree gchar *global_id_str = NULL;
g_return_if_fail (WP_IS_COLLECTION (self));
g_return_if_fail (global_id != SPA_ID_INVALID);
global_id_str = g_strdup_printf ("%u", global_id);
pw_metadata_set_property (self->iface, 0, global_id_str, NULL, NULL);
}
/*!
* \brief Clears all collected global Ids
* \ingroup wpcollection
* \param self the collection object
*/
void
wp_collection_clear (WpCollection * self)
{
g_return_if_fail (WP_IS_COLLECTION (self));
pw_metadata_clear (self->iface);
}
struct collection_iterator_data
{
WpCollection *collection;
const struct item *item;
};
static void
collection_iterator_reset (WpIterator *it)
{
struct collection_iterator_data *it_data = wp_iterator_get_user_data (it);
WpCollection *self = it_data->collection;
it_data->item = pw_array_first (&self->metadata);
}
static gboolean
collection_iterator_next (WpIterator *it, GValue *item)
{
struct collection_iterator_data *it_data = wp_iterator_get_user_data (it);
WpCollection *self = it_data->collection;
while (pw_array_check (&self->metadata, it_data->item)) {
guint global_id = SPA_ID_INVALID;
if (spa_atou32 (it_data->item->key, &global_id, 0)) {
g_value_init (item, G_TYPE_UINT);
g_value_set_uint (item, global_id);
it_data->item++;
return TRUE;
}
it_data->item++;
}
return FALSE;
}
static void
collection_iterator_finalize (WpIterator *it)
{
struct collection_iterator_data *it_data = wp_iterator_get_user_data (it);
g_object_unref (it_data->collection);
}
static const WpIteratorMethods collection_iterator_methods = {
.version = WP_ITERATOR_METHODS_VERSION,
.reset = collection_iterator_reset,
.next = collection_iterator_next,
.finalize = collection_iterator_finalize,
};
/*!
* \brief Iterates over all the collected global IDs.
*
* \ingroup wpcollection
* \param self a collection object
* \returns (transfer full): an iterator that iterates over the collected global
* IDs. The type of the iterator item is an unsigned integer.
*/
WpIterator *
wp_collection_new_iterator (WpCollection * self)
{
g_autoptr (WpIterator) it = NULL;
struct collection_iterator_data *it_data;
g_return_val_if_fail (WP_IS_COLLECTION (self), NULL);
it = wp_iterator_new (&collection_iterator_methods,
sizeof (struct collection_iterator_data));
it_data = wp_iterator_get_user_data (it);
it_data->collection = g_object_ref (self);
it_data->item = pw_array_first (&self->metadata);
return g_steal_pointer (&it);
}
/*!
* \struct WpImplCollection
* The implementation side of the collection object.
*
* Activate this object with at least WP_PROXY_FEATURE_BOUND to export it to
* PipeWire.
*/
struct _WpImplCollection
{
WpProxy parent;
gchar *name;
WpProperties *properties;
struct pw_metadata *iface;
struct pw_impl_metadata *impl;
struct spa_hook listener;
struct pw_array metadata;
};
enum {
PROP_0,
PROP_NAME,
PROP_PROPERTIES,
};
enum {
IMPL_SIGNAL_GLOBAL_COLLECTED,
IMPL_SIGNAL_GLOBAL_DROPPED,
IMPL_N_SIGNALS,
};
static guint32 impl_signals[IMPL_N_SIGNALS] = {0};
G_DEFINE_TYPE (WpImplCollection, wp_impl_collection, WP_TYPE_PROXY)
static void
wp_impl_collection_init (WpImplCollection * self)
{
pw_array_init (&self->metadata, 4096);
}
static int
impl_metadata_event_property (void *object, uint32_t subject, const char *key,
const char *type, const char *value)
{
WpImplCollection *self = WP_IMPL_COLLECTION (object);
struct item *item = NULL;
/* Clear subject if key is NULL */
if (key == NULL) {
while (true) {
guint32 global_id = SPA_ID_INVALID;
item = find_item (&self->metadata, subject, NULL);
if (item == NULL)
break;
pw_array_remove (&self->metadata, item);
if (spa_atou32 (item->key, &global_id, 0))
g_signal_emit (self, impl_signals[IMPL_SIGNAL_GLOBAL_DROPPED], 0,
global_id);
clear_item (item);
}
return 0;
}
item = find_item (&self->metadata, subject, key);
if (item == NULL) {
if (value == NULL)
return 0;
item = pw_array_add (&self->metadata, sizeof (*item));
if (item == NULL)
return -errno;
} else {
clear_item (item);
}
if (value != NULL) {
guint32 global_id = SPA_ID_INVALID;
set_item (item, subject, key, type, value);
if (spa_atou32 (key, &global_id, 0))
g_signal_emit (self, impl_signals[IMPL_SIGNAL_GLOBAL_COLLECTED], 0,
global_id);
} else {
guint32 global_id = SPA_ID_INVALID;
pw_array_remove (&self->metadata, item);
if (spa_atou32 (key, &global_id, 0))
g_signal_emit (self, impl_signals[IMPL_SIGNAL_GLOBAL_DROPPED], 0,
global_id);
}
return 0;
}
static const struct pw_impl_metadata_events impl_metadata_events = {
PW_VERSION_IMPL_METADATA_EVENTS,
.property = impl_metadata_event_property,
};
static void
wp_impl_collection_constructed (GObject *object)
{
WpImplCollection *self = WP_IMPL_COLLECTION (object);
g_autoptr (WpCore) core = NULL;
struct pw_context *pw_context;
struct pw_properties *props;
core = wp_object_get_core (WP_OBJECT (self));
g_return_if_fail (core);
pw_context = wp_core_get_pw_context (core);
g_return_if_fail (pw_context);
/* Make sure the collection name and flag is set */
if (!self->properties)
self->properties = wp_properties_new_empty ();
wp_properties_set (self->properties, "collection.name", self->name);
wp_properties_set (self->properties, "wireplumber.collection", "true");
props = wp_properties_to_pw_properties (self->properties);
self->impl = pw_context_create_metadata (pw_context, self->name, props , 0);
g_return_if_fail (self->impl);
self->iface = pw_impl_metadata_get_implementation (self->impl);
g_return_if_fail (self->iface);
pw_impl_metadata_add_listener (self->impl, &self->listener,
&impl_metadata_events, self);
G_OBJECT_CLASS (wp_impl_collection_parent_class)->constructed (object);
}
static void
wp_impl_collection_finalize (GObject * object)
{
WpImplCollection *self = WP_IMPL_COLLECTION (object);
pw_array_clear (&self->metadata);
spa_hook_remove (&self->listener);
g_clear_pointer (&self->impl, pw_impl_metadata_destroy);
g_clear_pointer (&self->properties, wp_properties_unref);
g_clear_pointer (&self->name, g_free);
G_OBJECT_CLASS (wp_impl_collection_parent_class)->finalize (object);
}
static void
wp_impl_collection_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpImplCollection *self = WP_IMPL_COLLECTION (object);
switch (property_id) {
case PROP_NAME:
g_clear_pointer (&self->name, g_free);
self->name = g_value_dup_string (value);
break;
case PROP_PROPERTIES:
g_clear_pointer (&self->properties, wp_properties_unref);
self->properties = g_value_dup_boxed (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_impl_collection_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpImplCollection *self = WP_IMPL_COLLECTION (object);
switch (property_id) {
case PROP_NAME:
g_value_set_string (value, self->name);
break;
case PROP_PROPERTIES:
g_value_set_boxed (value, self->properties);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
enum {
STEP_EXPORT = WP_TRANSITION_STEP_CUSTOM_START,
};
static WpObjectFeatures
wp_impl_collection_get_supported_features (WpObject * object)
{
return WP_PROXY_FEATURE_BOUND;
}
static guint
wp_impl_collection_activate_get_next_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
g_return_val_if_fail (missing & (WP_PROXY_FEATURE_BOUND),
WP_TRANSITION_STEP_ERROR);
return STEP_EXPORT;
}
static void
wp_impl_collection_activate_execute_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
WpImplCollection *self = WP_IMPL_COLLECTION (object);
switch (step) {
case STEP_EXPORT: {
g_autoptr (WpCore) core = wp_object_get_core (object);
struct pw_core *pw_core = wp_core_get_pw_core (core);
const struct pw_properties *props = NULL;
/* no pw_core -> we are not connected */
if (!pw_core) {
wp_transition_return_error (WP_TRANSITION (transition), g_error_new (
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"The WirePlumber core is not connected; "
"object cannot be exported to PipeWire"));
return;
}
props = pw_impl_metadata_get_properties (self->impl);
wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_export (pw_core,
PW_TYPE_INTERFACE_Metadata, &props->dict, self->iface, 0));
break;
}
default:
WP_OBJECT_CLASS (wp_impl_collection_parent_class)->
activate_execute_step (object, transition, step, missing);
break;
}
}
static void
wp_impl_collection_class_init (WpImplCollectionClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpObjectClass *wpobject_class = (WpObjectClass *) klass;
WpProxyClass *proxy_class = (WpProxyClass *) klass;
object_class->constructed = wp_impl_collection_constructed;
object_class->finalize = wp_impl_collection_finalize;
object_class->set_property = wp_impl_collection_set_property;
object_class->get_property = wp_impl_collection_get_property;
wpobject_class->get_supported_features =
wp_impl_collection_get_supported_features;
wpobject_class->activate_get_next_step =
wp_impl_collection_activate_get_next_step;
wpobject_class->activate_execute_step =
wp_impl_collection_activate_execute_step;
proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Metadata;
proxy_class->pw_iface_version = PW_VERSION_METADATA;
g_object_class_install_property (object_class, PROP_NAME,
g_param_spec_string ("name", "name", "The collection name", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_PROPERTIES,
g_param_spec_boxed ("properties", "properties",
"The collection properties", WP_TYPE_PROPERTIES,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
impl_signals[IMPL_SIGNAL_GLOBAL_COLLECTED] = g_signal_new ("global-collected",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_UINT);
impl_signals[IMPL_SIGNAL_GLOBAL_DROPPED] = g_signal_new ("global-dropped",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_UINT);
}
/*!
* \brief Creates a new impl collection
* \ingroup wpcollection
* \param core the core
* \param name (nullable): the collection name
* \param properties (nullable) (transfer full): the collection properties
* \returns (transfer full): a new WpImplCollection
*/
WpImplCollection *
wp_impl_collection_new (WpCore * core, const gchar *name,
WpProperties *properties)
{
g_autoptr (WpProperties) props = properties;
g_return_val_if_fail (WP_IS_CORE (core), NULL);
return g_object_new (WP_TYPE_IMPL_COLLECTION,
"core", core,
"name", name,
"properties", props,
NULL);
}
/*!
* \brief Gets the properties of the impl collection
*
* \ingroup wpcollection
* \param self the impl collection object
\returns (transfer full) (nullable): the properties of the impl collection
*/
WpProperties *
wp_impl_collection_get_properties (WpImplCollection *self)
{
g_return_val_if_fail (WP_IS_IMPL_COLLECTION (self), NULL);
return self->properties ? wp_properties_ref (self->properties) : NULL;
}
/*!
* \brief Gets the name of the impl collection
*
* \ingroup wpcollection
* \param self the impl collection object
* \returns (nullable): the name of the impl collection
*/
const gchar *
wp_impl_collection_get_name (WpImplCollection *self)
{
g_return_val_if_fail (WP_IS_IMPL_COLLECTION (self), NULL);
return self->name;
}
/*!
* \brief Gets the total number of globals the impl collection has collected
*
* \ingroup wpcollection
* \param self the impl collection object
* \returns the total number of globals the impl collection has collected
*/
gsize
wp_impl_collection_get_size (WpImplCollection * self)
{
g_return_val_if_fail (WP_IS_IMPL_COLLECTION (self), 0);
return pw_array_get_len (&self->metadata, struct item);
}
/*!
* \brief Checks if a global ID is collected in the impl collection
*
* \ingroup wpcollection
* \param self the impl collection object
* \param global_id the global ID to check
* \returns TRUE if the global ID is collected in the colleciton, FALSE
* otherwise.
*/
gboolean
wp_impl_collection_contains_global (WpImplCollection * self, guint32 global_id)
{
struct item *item;
g_return_val_if_fail (WP_IS_IMPL_COLLECTION (self), FALSE);
pw_array_for_each (item, &self->metadata) {
guint32 id = SPA_ID_INVALID;
if (spa_atou32 (item->key, &id, 0) && id == global_id)
return TRUE;
}
return FALSE;
}
/*!
* \brief Collects a global ID into the impl collection.
*
* \ingroup wpcollection
* \param self the impl collection object
* \param global_id the global ID to collect
*/
void
wp_impl_collection_collect_global (WpImplCollection * self, guint32 global_id)
{
g_autofree gchar *global_id_str = NULL;
g_return_if_fail (WP_IS_IMPL_COLLECTION (self));
g_return_if_fail (global_id != SPA_ID_INVALID);
global_id_str = g_strdup_printf ("%u", global_id);
pw_metadata_set_property (self->iface, 0, global_id_str, NULL, "collected");
}
/*!
* \brief Drops a global ID from the impl collection.
*
* \ingroup wpcollection
* \param self the impl collection object
* \param global_id the global ID to drop
*/
void
wp_impl_collection_drop_global (WpImplCollection * self, guint32 global_id)
{
g_autofree gchar *global_id_str = NULL;
g_return_if_fail (WP_IS_IMPL_COLLECTION (self));
g_return_if_fail (global_id != SPA_ID_INVALID);
global_id_str = g_strdup_printf ("%u", global_id);
pw_metadata_set_property (self->iface, 0, global_id_str, NULL, NULL);
}
/*!
* \brief Clears all collected global Ids
* \ingroup wpcollection
* \param self the impl collection object
*/
void
wp_impl_collection_clear (WpImplCollection * self)
{
g_return_if_fail (WP_IS_IMPL_COLLECTION (self));
pw_metadata_clear (self->iface);
}
struct impl_collection_iterator_data
{
WpImplCollection *impl_collection;
const struct item *item;
};
static void
impl_collection_iterator_reset (WpIterator *it)
{
struct impl_collection_iterator_data *it_data =
wp_iterator_get_user_data (it);
WpImplCollection *self = it_data->impl_collection;
it_data->item = pw_array_first (&self->metadata);
}
static gboolean
impl_collection_iterator_next (WpIterator *it, GValue *item)
{
struct impl_collection_iterator_data *it_data =
wp_iterator_get_user_data (it);
WpImplCollection *self = it_data->impl_collection;
while (pw_array_check (&self->metadata, it_data->item)) {
guint global_id = SPA_ID_INVALID;
if (spa_atou32 (it_data->item->key, &global_id, 0)) {
g_value_init (item, G_TYPE_UINT);
g_value_set_uint (item, global_id);
it_data->item++;
return TRUE;
}
it_data->item++;
}
return FALSE;
}
static void
impl_collection_iterator_finalize (WpIterator *it)
{
struct impl_collection_iterator_data *it_data =
wp_iterator_get_user_data (it);
g_object_unref (it_data->impl_collection);
}
static const WpIteratorMethods impl_collection_iterator_methods = {
.version = WP_ITERATOR_METHODS_VERSION,
.reset = impl_collection_iterator_reset,
.next = impl_collection_iterator_next,
.finalize = impl_collection_iterator_finalize,
};
/*!
* \brief Iterates over all the collected global IDs.
*
* \ingroup wpcollection
* \param self an impl collection object
* \returns (transfer full): an iterator that iterates over the collected global
* IDs. The type of the iterator item is an unsigned integer.
*/
WpIterator *
wp_impl_collection_new_iterator (WpImplCollection * self)
{
g_autoptr (WpIterator) it = NULL;
struct impl_collection_iterator_data *it_data;
g_return_val_if_fail (WP_IS_IMPL_COLLECTION (self), NULL);
it = wp_iterator_new (&impl_collection_iterator_methods,
sizeof (struct impl_collection_iterator_data));
it_data = wp_iterator_get_user_data (it);
it_data->impl_collection = g_object_ref (self);
it_data->item = pw_array_first (&self->metadata);
return g_steal_pointer (&it);
}