mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-09 02:48:05 +02:00
lib: Add collection proxy API
This proxy uses the metadata interface to represent collections.
This commit is contained in:
parent
8b42a5a3ae
commit
59f98e38e4
8 changed files with 1402 additions and 4 deletions
991
lib/wp/collection.c
Normal file
991
lib/wp/collection.c
Normal file
|
|
@ -0,0 +1,991 @@
|
|||
/* 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);
|
||||
}
|
||||
102
lib/wp/collection.h
Normal file
102
lib/wp/collection.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2026 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef __WIREPLUMBER_COLLECTION_H__
|
||||
#define __WIREPLUMBER_COLLECTION_H__
|
||||
|
||||
#include "global-proxy.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/*!
|
||||
* \brief An extension of WpProxyFeatures for WpCollection objects
|
||||
* \ingroup wpcollection
|
||||
*/
|
||||
typedef enum { /*< flags >*/
|
||||
/*! caches collection data locally */
|
||||
WP_COLLECTION_FEATURE_DATA = (WP_PROXY_FEATURE_CUSTOM_START << 0),
|
||||
} WpCollectionFeatures;
|
||||
|
||||
/*!
|
||||
* \brief The WpCollection GType
|
||||
* \ingroup wpcollection
|
||||
*/
|
||||
#define WP_TYPE_COLLECTION (wp_collection_get_type ())
|
||||
|
||||
WP_API
|
||||
G_DECLARE_FINAL_TYPE (WpCollection, wp_collection, WP, COLLECTION,
|
||||
WpGlobalProxy)
|
||||
|
||||
WP_API
|
||||
const gchar *wp_collection_get_name (WpCollection * self);
|
||||
|
||||
WP_API
|
||||
gsize wp_collection_get_size (WpCollection * self);
|
||||
|
||||
WP_API
|
||||
gboolean wp_collection_contains_global (WpCollection * self, guint32 global_id);
|
||||
|
||||
WP_API
|
||||
void wp_collection_collect_global (WpCollection * self, guint32 global_id);
|
||||
|
||||
WP_API
|
||||
void wp_collection_drop_global (WpCollection * self, guint32 global_id);
|
||||
|
||||
WP_API
|
||||
void wp_collection_clear (WpCollection * self);
|
||||
|
||||
WP_API
|
||||
WpIterator * wp_collection_new_iterator (WpCollection * self);
|
||||
|
||||
|
||||
/* WpImplCollection */
|
||||
|
||||
/*!
|
||||
* \brief The WpImplCollection GType
|
||||
* \ingroup wpcollection
|
||||
*/
|
||||
#define WP_TYPE_IMPL_COLLECTION (wp_impl_collection_get_type ())
|
||||
|
||||
WP_API
|
||||
G_DECLARE_FINAL_TYPE (WpImplCollection, wp_impl_collection, WP, IMPL_COLLECTION,
|
||||
WpProxy)
|
||||
|
||||
WP_API
|
||||
WpImplCollection * wp_impl_collection_new (WpCore * core, const gchar *name,
|
||||
WpProperties *properties);
|
||||
|
||||
WP_API
|
||||
WpProperties * wp_impl_collection_get_properties (WpImplCollection *self);
|
||||
|
||||
WP_API
|
||||
const gchar * wp_impl_collection_get_name (WpImplCollection *self);
|
||||
|
||||
WP_API
|
||||
gsize wp_impl_collection_get_size (WpImplCollection * self);
|
||||
|
||||
WP_API
|
||||
gboolean wp_impl_collection_contains_global (WpImplCollection * self,
|
||||
guint32 global_id);
|
||||
|
||||
WP_API
|
||||
void wp_impl_collection_collect_global (WpImplCollection * self,
|
||||
guint32 global_id);
|
||||
|
||||
WP_API
|
||||
void wp_impl_collection_drop_global (WpImplCollection * self,
|
||||
guint32 global_id);
|
||||
|
||||
WP_API
|
||||
void wp_impl_collection_clear (WpImplCollection * self);
|
||||
|
||||
WP_API
|
||||
WpIterator * wp_impl_collection_new_iterator (WpImplCollection * self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
|
@ -4,6 +4,7 @@ wp_lib_sources = files(
|
|||
'component-loader.c',
|
||||
'conf.c',
|
||||
'core.c',
|
||||
'collection.c',
|
||||
'device.c',
|
||||
'error.c',
|
||||
'event.c',
|
||||
|
|
@ -52,6 +53,7 @@ wp_lib_headers = files(
|
|||
'component-loader.h',
|
||||
'conf.h',
|
||||
'core.h',
|
||||
'collection.h',
|
||||
'defs.h',
|
||||
'device.h',
|
||||
'error.h',
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@
|
|||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <pipewire/impl.h>
|
||||
|
||||
#include "registry.h"
|
||||
#include "object-manager.h"
|
||||
#include "log.h"
|
||||
#include "collection.h"
|
||||
|
||||
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-registry")
|
||||
|
||||
|
|
@ -73,16 +76,26 @@ object_manager_destroyed (gpointer data, GObject * om)
|
|||
/* find the subclass of WpPipewireGloabl that can handle
|
||||
the given pipewire interface type of the given version */
|
||||
static inline GType
|
||||
find_proxy_instance_type (const char * type, guint32 version)
|
||||
find_proxy_instance_type (const char * type, guint32 version,
|
||||
const struct spa_dict *props)
|
||||
{
|
||||
g_autofree GType *children;
|
||||
g_autofree GType *children = NULL;
|
||||
guint n_children;
|
||||
|
||||
/* Check if this is a collection */
|
||||
if (g_str_equal (type, PW_TYPE_INTERFACE_Metadata) &&
|
||||
version == PW_VERSION_METADATA &&
|
||||
props && spa_atob (spa_dict_lookup (props, "wireplumber.collection"))) {
|
||||
return WP_TYPE_COLLECTION;
|
||||
}
|
||||
|
||||
/* Otherwise find the matching proxy non-collection type */
|
||||
children = g_type_children (WP_TYPE_GLOBAL_PROXY, &n_children);
|
||||
|
||||
for (guint i = 0; i < n_children; i++) {
|
||||
WpProxyClass *klass = (WpProxyClass *) g_type_class_ref (children[i]);
|
||||
if (g_strcmp0 (klass->pw_iface_type, type) == 0 &&
|
||||
if (children[i] != WP_TYPE_COLLECTION &&
|
||||
g_strcmp0 (klass->pw_iface_type, type) == 0 &&
|
||||
klass->pw_iface_version == version) {
|
||||
g_type_class_unref (klass);
|
||||
return children[i];
|
||||
|
|
@ -100,7 +113,7 @@ registry_global (void *data, uint32_t id, uint32_t permissions,
|
|||
const char *type, uint32_t version, const struct spa_dict *props)
|
||||
{
|
||||
WpRegistry *self = data;
|
||||
GType gtype = find_proxy_instance_type (type, version);
|
||||
GType gtype = find_proxy_instance_type (type, version, props);
|
||||
|
||||
wp_debug_object (wp_registry_get_core (self),
|
||||
"global:%u perm:0x%x type:%s/%u -> %s",
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ wp_init (WpInitFlags flags)
|
|||
g_type_ensure (WP_TYPE_NODE);
|
||||
g_type_ensure (WP_TYPE_PORT);
|
||||
g_type_ensure (WP_TYPE_FACTORY);
|
||||
g_type_ensure (WP_TYPE_COLLECTION);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "component-loader.h"
|
||||
#include "conf.h"
|
||||
#include "core.h"
|
||||
#include "collection.h"
|
||||
#include "device.h"
|
||||
#include "error.h"
|
||||
#include "event-dispatcher.h"
|
||||
|
|
|
|||
281
tests/wp/collection.c
Normal file
281
tests/wp/collection.c
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2026 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <pipewire/extensions/session-manager/keys.h>
|
||||
|
||||
#include "../common/base-test-fixture.h"
|
||||
|
||||
typedef struct {
|
||||
WpBaseTestFixture base;
|
||||
|
||||
WpObjectManager *om;
|
||||
WpCollection *collection;
|
||||
|
||||
guint32 last_collected_id;
|
||||
guint32 last_dropped_id;
|
||||
} TestFixture;
|
||||
|
||||
static void
|
||||
test_collection_setup (TestFixture *self, gconstpointer user_data)
|
||||
{
|
||||
wp_base_test_fixture_setup (&self->base, WP_BASE_TEST_FLAG_CLIENT_CORE);
|
||||
self->om = wp_object_manager_new ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_collection_teardown (TestFixture *self, gconstpointer user_data)
|
||||
{
|
||||
g_clear_object (&self->om);
|
||||
wp_base_test_fixture_teardown (&self->base);
|
||||
}
|
||||
|
||||
static void
|
||||
on_collection_added (WpObjectManager *om, WpCollection *collection,
|
||||
TestFixture *fixture)
|
||||
{
|
||||
g_assert_true (WP_IS_COLLECTION (collection));
|
||||
|
||||
g_assert_null (fixture->collection);
|
||||
fixture->collection = WP_COLLECTION (collection);
|
||||
|
||||
g_main_loop_quit (fixture->base.loop);
|
||||
}
|
||||
|
||||
static void
|
||||
on_collection_removed (WpObjectManager *om, WpCollection *collection,
|
||||
TestFixture *fixture)
|
||||
{
|
||||
g_assert_true (WP_IS_COLLECTION (collection));
|
||||
|
||||
g_assert_nonnull (fixture->collection);
|
||||
fixture->collection = NULL;
|
||||
|
||||
g_main_loop_quit (fixture->base.loop);
|
||||
}
|
||||
|
||||
static void
|
||||
on_global_collected (WpCollection *om, guint32 global_id, TestFixture *fixture)
|
||||
{
|
||||
fixture->last_collected_id = global_id;
|
||||
g_main_loop_quit (fixture->base.loop);
|
||||
}
|
||||
|
||||
static void
|
||||
on_global_dropped (WpCollection *om, guint32 global_id, TestFixture *fixture)
|
||||
{
|
||||
fixture->last_dropped_id = global_id;
|
||||
g_main_loop_quit (fixture->base.loop);
|
||||
}
|
||||
|
||||
static void
|
||||
test_impl_collection_activated (WpObject * impl_collection, GAsyncResult * res,
|
||||
TestFixture *fixture)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
g_assert_true (wp_object_activate_finish (impl_collection, res, &error));
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_assert_true (WP_IS_IMPL_COLLECTION (impl_collection));
|
||||
|
||||
g_main_loop_quit (fixture->base.loop);
|
||||
}
|
||||
|
||||
static void
|
||||
test_collection_basic (TestFixture *fixture, gconstpointer data)
|
||||
{
|
||||
g_autoptr (WpImplCollection) impl_collection = NULL;
|
||||
|
||||
/* Install object manager on the client side */
|
||||
g_signal_connect (fixture->om, "object-added",
|
||||
(GCallback) on_collection_added, fixture);
|
||||
g_signal_connect (fixture->om, "object-removed",
|
||||
(GCallback) on_collection_removed, fixture);
|
||||
wp_object_manager_add_interest (fixture->om, WP_TYPE_COLLECTION, NULL);
|
||||
wp_object_manager_request_object_features (fixture->om, WP_TYPE_COLLECTION,
|
||||
WP_OBJECT_FEATURES_ALL);
|
||||
wp_core_install_object_manager (fixture->base.client_core, fixture->om);
|
||||
|
||||
/* Create the collection */
|
||||
impl_collection = wp_impl_collection_new (fixture->base.core, "my-collection",
|
||||
NULL);
|
||||
g_assert_nonnull (impl_collection);
|
||||
|
||||
/* Export the collection */
|
||||
wp_object_activate (WP_OBJECT (impl_collection), WP_OBJECT_FEATURES_ALL,
|
||||
NULL, (GAsyncReadyCallback) test_impl_collection_activated, fixture);
|
||||
g_main_loop_run (fixture->base.loop);
|
||||
|
||||
/* Run again so the collection is added in the client object manager */
|
||||
g_assert_null (fixture->collection);
|
||||
g_main_loop_run (fixture->base.loop);
|
||||
g_assert_cmpuint (wp_object_manager_get_n_objects (fixture->om), ==, 1);
|
||||
g_assert_nonnull (fixture->collection);
|
||||
|
||||
/* Check name */
|
||||
{
|
||||
const gchar *name = wp_collection_get_name (fixture->collection);
|
||||
g_assert_cmpstr (name, ==, "my-collection");
|
||||
}
|
||||
|
||||
/* Check properties */
|
||||
{
|
||||
g_autoptr (WpProperties) props = NULL;
|
||||
props = wp_global_proxy_get_global_properties (
|
||||
WP_GLOBAL_PROXY (fixture->collection));
|
||||
const gchar *str = wp_properties_get (props, "wireplumber.collection");
|
||||
g_assert_cmpstr (str, ==, "true");
|
||||
}
|
||||
|
||||
/* Handle collection signals */
|
||||
g_signal_connect (fixture->collection, "global-collected",
|
||||
(GCallback) on_global_collected, fixture);
|
||||
g_signal_connect (fixture->collection, "global-dropped",
|
||||
(GCallback) on_global_dropped, fixture);
|
||||
|
||||
/* Make sure collection does not have any globals */
|
||||
g_assert_cmpuint (wp_collection_get_size (fixture->collection), ==, 0);
|
||||
|
||||
/* Collect the first global */
|
||||
fixture->last_collected_id = 0;
|
||||
wp_collection_collect_global (fixture->collection, 42);
|
||||
g_main_loop_run (fixture->base.loop);
|
||||
g_assert_cmpuint (fixture->last_collected_id, ==, 42);
|
||||
g_assert_cmpuint (wp_collection_get_size (fixture->collection), ==, 1);
|
||||
g_assert_true (wp_collection_contains_global (fixture->collection, 42));
|
||||
g_assert_cmpuint (wp_impl_collection_get_size (impl_collection), ==, 1);
|
||||
g_assert_true (wp_impl_collection_contains_global (impl_collection, 42));
|
||||
|
||||
/* Collect the second global */
|
||||
fixture->last_collected_id = 0;
|
||||
wp_collection_collect_global (fixture->collection, 99);
|
||||
g_main_loop_run (fixture->base.loop);
|
||||
g_assert_cmpuint (fixture->last_collected_id, ==, 99);
|
||||
g_assert_cmpuint (wp_collection_get_size (fixture->collection), ==, 2);
|
||||
g_assert_true (wp_collection_contains_global (fixture->collection, 99));
|
||||
g_assert_cmpuint (wp_impl_collection_get_size (impl_collection), ==, 2);
|
||||
g_assert_true (wp_impl_collection_contains_global (impl_collection, 99));
|
||||
|
||||
/* Iterate the globals from the client side */
|
||||
{
|
||||
g_autoptr (WpIterator) iter = NULL;
|
||||
g_auto (GValue) val = G_VALUE_INIT;
|
||||
gboolean has_first = FALSE;
|
||||
gboolean has_second = FALSE;
|
||||
guint count = 0;
|
||||
|
||||
iter = wp_collection_new_iterator (fixture->collection);
|
||||
g_assert_nonnull (iter);
|
||||
|
||||
while (wp_iterator_next (iter, &val)) {
|
||||
guint32 global_id = g_value_get_uint (&val);
|
||||
if (global_id == 42)
|
||||
has_first = TRUE;
|
||||
if (global_id == 99)
|
||||
has_second = TRUE;
|
||||
count++;
|
||||
g_value_unset (&val);
|
||||
}
|
||||
|
||||
g_assert_true (has_first);
|
||||
g_assert_true (has_second);
|
||||
g_assert_cmpuint (count, ==, 2);
|
||||
}
|
||||
|
||||
/* Iterate the globals from the impl side */
|
||||
{
|
||||
g_autoptr (WpIterator) iter = NULL;
|
||||
g_auto (GValue) val = G_VALUE_INIT;
|
||||
gboolean has_first = FALSE;
|
||||
gboolean has_second = FALSE;
|
||||
guint count = 0;
|
||||
|
||||
iter = wp_impl_collection_new_iterator (impl_collection);
|
||||
g_assert_nonnull (iter);
|
||||
|
||||
while (wp_iterator_next (iter, &val)) {
|
||||
guint32 global_id = g_value_get_uint (&val);
|
||||
if (global_id == 42)
|
||||
has_first = TRUE;
|
||||
if (global_id == 99)
|
||||
has_second = TRUE;
|
||||
count++;
|
||||
g_value_unset (&val);
|
||||
}
|
||||
|
||||
g_assert_true (has_first);
|
||||
g_assert_true (has_second);
|
||||
g_assert_cmpuint (count, ==, 2);
|
||||
}
|
||||
|
||||
/* Drop the first global */
|
||||
fixture->last_dropped_id = 0;
|
||||
wp_collection_drop_global (fixture->collection, 42);
|
||||
g_main_loop_run (fixture->base.loop);
|
||||
g_assert_cmpuint (fixture->last_dropped_id, ==, 42);
|
||||
g_assert_cmpuint (wp_collection_get_size (fixture->collection), ==, 1);
|
||||
g_assert_false (wp_collection_contains_global (fixture->collection, 42));
|
||||
g_assert_cmpuint (wp_impl_collection_get_size (impl_collection), ==, 1);
|
||||
g_assert_false (wp_impl_collection_contains_global (impl_collection, 42));
|
||||
|
||||
/* Collect an existing global and make sure nothing happens */
|
||||
fixture->last_collected_id = 0;
|
||||
wp_collection_collect_global (fixture->collection, 99);
|
||||
g_assert_cmpuint (wp_collection_get_size (fixture->collection), ==, 1);
|
||||
g_assert_true (wp_collection_contains_global (fixture->collection, 99));
|
||||
g_assert_true (wp_impl_collection_contains_global (impl_collection, 99));
|
||||
|
||||
/* Drop an unexisting global and make sure nothing happens */
|
||||
fixture->last_dropped_id = 0;
|
||||
wp_collection_drop_global (fixture->collection, 42);
|
||||
g_assert_cmpuint (wp_collection_get_size (fixture->collection), ==, 1);
|
||||
g_assert_false (wp_collection_contains_global (fixture->collection, 42));
|
||||
g_assert_cmpuint (wp_impl_collection_get_size (impl_collection), ==, 1);
|
||||
g_assert_false (wp_impl_collection_contains_global (impl_collection, 42));
|
||||
|
||||
/* Drop the second global */
|
||||
fixture->last_dropped_id = 0;
|
||||
wp_collection_drop_global (fixture->collection, 99);
|
||||
g_main_loop_run (fixture->base.loop);
|
||||
g_assert_cmpuint (fixture->last_dropped_id, ==, 99);
|
||||
g_assert_cmpuint (wp_collection_get_size (fixture->collection), ==, 0);
|
||||
g_assert_false (wp_collection_contains_global (fixture->collection, 99));
|
||||
g_assert_false (wp_impl_collection_contains_global (impl_collection, 99));
|
||||
|
||||
/* Collect a global from the impl side */
|
||||
fixture->last_collected_id = 0;
|
||||
wp_impl_collection_collect_global (impl_collection, 36);
|
||||
g_main_loop_run (fixture->base.loop);
|
||||
g_assert_cmpuint (fixture->last_collected_id, ==, 36);
|
||||
g_assert_cmpuint (wp_collection_get_size (fixture->collection), ==, 1);
|
||||
g_assert_true (wp_collection_contains_global (fixture->collection, 36));
|
||||
g_assert_cmpuint (wp_impl_collection_get_size (impl_collection), ==, 1);
|
||||
g_assert_true (wp_impl_collection_contains_global (impl_collection, 36));
|
||||
|
||||
/* Drop the global from the impl size */
|
||||
fixture->last_dropped_id = 0;
|
||||
wp_impl_collection_drop_global (impl_collection, 36);
|
||||
g_main_loop_run (fixture->base.loop);
|
||||
g_assert_cmpuint (fixture->last_dropped_id, ==, 36);
|
||||
g_assert_cmpuint (wp_collection_get_size (fixture->collection), ==, 0);
|
||||
g_assert_false (wp_collection_contains_global (fixture->collection, 36));
|
||||
g_assert_cmpuint (wp_impl_collection_get_size (impl_collection), ==, 0);
|
||||
g_assert_false (wp_impl_collection_contains_global (impl_collection, 36));
|
||||
}
|
||||
|
||||
gint
|
||||
main (gint argc, gchar *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
wp_init (WP_INIT_ALL);
|
||||
|
||||
g_test_add ("/wp/collection/basic", TestFixture, NULL,
|
||||
test_collection_setup, test_collection_basic, test_collection_teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -3,6 +3,13 @@ common_env = common_test_env
|
|||
common_env.set('G_TEST_SRCDIR', meson.current_source_dir())
|
||||
common_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
|
||||
|
||||
test(
|
||||
'test-collection',
|
||||
executable('test-collection', 'collection.c',
|
||||
dependencies: common_deps),
|
||||
env: common_env,
|
||||
)
|
||||
|
||||
test(
|
||||
'test-component-loader',
|
||||
executable('test-component-loader', 'component-loader.c',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue