-
George Kiagiadakis authored
so that we can filter proxies by id on object managers
George Kiagiadakis authoredso that we can filter proxies by id on object managers
object-manager.c 39.10 KiB
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
/**
* SECTION: WpObjectManager
*
* The #WpObjectManager class provides a way to collect a set of objects
* and be notified when objects that fulfill a certain set of criteria are
* created or destroyed.
*
* There are 4 kinds of objects that can be managed by a #WpObjectManager:
* * remote PipeWire global objects that are advertised on the registry;
* these are bound locally to subclasses of #WpProxy
* * remote PipeWire global objects that are created by calling a remote
* factory through the WirePlumber API; these are very similar to other
* global objects but it should be noted that the same #WpProxy instance
* that created them appears in the #WpObjectManager (as soon as its
* %WP_PROXY_FEATURE_BOUND is enabled)
* * local PipeWire objects that are being exported to PipeWire
* (#WpImplNode, WpImplEndpoint [private], etc); these appear in the
* #WpObjectManager as soon as they are exported (so, when their
* %WP_PROXY_FEATURE_BOUND is enabled)
* * WirePlumber-specific objects, such as WirePlumber factories
*
* To start an object manager, you first need to declare interest in a certain
* kind of object by calling wp_object_manager_add_interest() and then install
* it on the #WpCore with wp_core_install_object_manager().
*
* Upon installing a #WpObjectManager on a #WpCore, any pre-existing objects
* that match the interests of this #WpObjectManager will immediately become
* available to get through wp_object_manager_iterate() and the
* #WpObjectManager::object-added signal will be emitted for all of them.
*/
#define G_LOG_DOMAIN "wp-object-manager"
#include "object-manager.h"
#include "debug.h"
#include "private.h"
#include <pipewire/pipewire.h>
/* WpObjectManager */
struct _WpObjectManager
{
GObject parent;
GWeakRef core;
/* element-type: WpObjectInterest* */
GPtrArray *interests;
/* element-type: <GType, WpProxyFeatures> */
GHashTable *features;
/* objects that we are interested in, without a ref */
GPtrArray *objects;
gboolean installed;
gboolean changed;
guint pending_objects;
GSource *idle_source;
};
enum {
PROP_0,
PROP_CORE,
};
enum {
SIGNAL_OBJECT_ADDED,
SIGNAL_OBJECT_REMOVED,
SIGNAL_OBJECTS_CHANGED,
SIGNAL_INSTALLED,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (WpObjectManager, wp_object_manager, G_TYPE_OBJECT)
static void
wp_object_manager_init (WpObjectManager * self)
{
g_weak_ref_init (&self->core, NULL);
self->interests =
g_ptr_array_new_with_free_func ((GDestroyNotify) wp_object_interest_free);
self->features = g_hash_table_new (g_direct_hash, g_direct_equal);
self->objects = g_ptr_array_new ();
self->installed = FALSE;
self->changed = FALSE;
self->pending_objects = 0;
}
static void
wp_object_manager_finalize (GObject * object)
{
WpObjectManager *self = WP_OBJECT_MANAGER (object);
if (self->idle_source) {
g_source_destroy (self->idle_source);
g_clear_pointer (&self->idle_source, g_source_unref);
}
g_clear_pointer (&self->objects, g_ptr_array_unref);
g_clear_pointer (&self->features, g_hash_table_unref);
g_clear_pointer (&self->interests, g_ptr_array_unref);
g_weak_ref_clear (&self->core);
G_OBJECT_CLASS (wp_object_manager_parent_class)->finalize (object);
}
static void
wp_object_manager_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpObjectManager *self = WP_OBJECT_MANAGER (object);
switch (property_id) {
case PROP_CORE:
g_value_take_object (value, g_weak_ref_get (&self->core));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_object_manager_class_init (WpObjectManagerClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_object_manager_finalize;
object_class->get_property = wp_object_manager_get_property;
/* 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_READABLE | G_PARAM_STATIC_STRINGS));
/**
* WpObjectManager::object-added:
* @self: the object manager
* @object: (transfer none): the managed object that was just added
*
* Emitted when an object that matches the interests of this object manager
* is made available.
*/
signals[SIGNAL_OBJECT_ADDED] = g_signal_new (
"object-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT);
/**
* WpObjectManager::object-removed:
* @self: the object manager
* @object: (transfer none): the managed object that is being removed
*
* Emitted when an object that was previously added on this object manager
* is now being removed (and most likely destroyed). At the time that this
* signal is emitted, the object is still alive.
*/
signals[SIGNAL_OBJECT_REMOVED] = g_signal_new (
"object-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT);
/**
* WpObjectManager::objects-changed:
* @self: the object manager
*
* Emitted when one or more objects have been recently added or removed
* from this object manager. This signal is useful to get notified only once
* when multiple changes happen in a short timespan. The receiving callback
* may retrieve the updated list of objects by calling
* wp_object_manager_iterate()
*/
signals[SIGNAL_OBJECTS_CHANGED] = g_signal_new (
"objects-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
0, NULL, NULL, NULL, G_TYPE_NONE, 0);
/**
* WpObjectManager::installed:
* @self: the object manager
*
* This is emitted once after the object manager is installed with
* wp_core_install_object_manager(). If there are objects that need
* to be prepared asynchronously internally, emission of this signal is
* delayed until all objects are ready.
*/
signals[SIGNAL_INSTALLED] = g_signal_new (
"installed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
0, NULL, NULL, NULL, G_TYPE_NONE, 0);
}
/**
* wp_object_manager_new:
*
* Constructs a new object manager.
*
* Returns: (transfer full): the newly constructed object manager
*/
WpObjectManager *
wp_object_manager_new (void)
{
return g_object_new (WP_TYPE_OBJECT_MANAGER, NULL);
}
/**
* wp_object_manager_is_installed:
* @self: the object manager
*
* Returns: %TRUE if the object manager is installed (the
* WpObjectManager::installed has been emitted), %FALSE otherwise
*/
gboolean
wp_object_manager_is_installed (WpObjectManager * self)
{
g_return_val_if_fail (WP_IS_OBJECT_MANAGER (self), FALSE);
return self->installed;
}
/**
* wp_object_manager_add_interest:
* @self: the object manager
* @gtype: the #GType of the objects that we are declaring interest in
* @...: a list of constraints, terminated by %NULL
*
* Equivalent to:
* |[
* WpObjectInterest *i = wp_object_interest_new (gtype, ...);
* wp_object_manager_add_interest_full (self, i);
* ]|
*
* The constraints specified in the variable arguments must follow the rules
* documented in wp_object_interest_new().
*/
void
wp_object_manager_add_interest (WpObjectManager * self, GType gtype, ...)
{
WpObjectInterest *interest;
va_list args;
g_return_if_fail (WP_IS_OBJECT_MANAGER (self));
va_start (args, gtype);
interest = wp_object_interest_new_valist (gtype, &args);
wp_object_manager_add_interest_full (self, interest);
va_end (args);
}
/**
* wp_object_manager_add_interest_full: (rename-to wp_object_manager_add_interest)
* @self: the object manager
* @interest: (transfer full): the interest
*
* Declares interest in a certain kind of object. Interest consists of a #GType
* that the object must be an ancestor of (g_type_is_a must match) and
* optionally, a set of additional constraints on certain properties of the
* object. Refer to #WpObjectInterest for more details.
*/
void
wp_object_manager_add_interest_full (WpObjectManager *self,
WpObjectInterest * interest)
{
g_autoptr (GError) error = NULL;
g_return_if_fail (WP_IS_OBJECT_MANAGER (self));
if (G_UNLIKELY (!wp_object_interest_validate (interest, &error))) {
wp_critical_object (self, "interest validation failed: %s",
error->message);
wp_object_interest_free (interest);
return;
}
g_ptr_array_add (self->interests, interest);
}
/**
* wp_object_manager_request_proxy_features:
* @self: the object manager
* @proxy_type: the #WpProxy descendant type
* @wanted_features: the features to enable on this kind of proxy
*
* Requests the object manager to automatically prepare the @wanted_features
* on any managed object that is of the specified @proxy_type. These features
* will always be prepared before the object appears on the object manager.
*/
void
wp_object_manager_request_proxy_features (WpObjectManager *self,
GType proxy_type, WpProxyFeatures wanted_features)
{
g_autofree GType *children = NULL;
GType *child;
g_return_if_fail (WP_IS_OBJECT_MANAGER (self));
g_return_if_fail (g_type_is_a (proxy_type, WP_TYPE_PROXY));
g_hash_table_insert (self->features, GSIZE_TO_POINTER (proxy_type),
GUINT_TO_POINTER (wanted_features));
child = children = g_type_children (proxy_type, NULL);
while (*child) {
WpProxyFeatures existing_ft = (WpProxyFeatures) GPOINTER_TO_UINT (
g_hash_table_lookup (self->features, GSIZE_TO_POINTER (*child)));
g_hash_table_insert (self->features, GSIZE_TO_POINTER (*child),
GUINT_TO_POINTER (existing_ft | wanted_features));
child++;
}
}
/**
* wp_object_manager_get_n_objects:
* @self: the object manager
*
* Returns: the number of objects managed by this #WpObjectManager
*/
guint
wp_object_manager_get_n_objects (WpObjectManager * self)
{
g_return_val_if_fail (WP_IS_OBJECT_MANAGER (self), 0);
return self->objects->len;
}
struct om_iterator_data
{
WpObjectManager *om;
WpObjectInterest *interest;
guint index;
};
static void
om_iterator_reset (WpIterator *it)
{
struct om_iterator_data *it_data = wp_iterator_get_user_data (it);
it_data->index = 0;
}
static gboolean
om_iterator_next (WpIterator *it, GValue *item)
{
struct om_iterator_data *it_data = wp_iterator_get_user_data (it);
GPtrArray *objects = it_data->om->objects;
while (it_data->index < objects->len) {
gpointer obj = g_ptr_array_index (objects, it_data->index++);
/* take the next object that matches the interest, if any */
if (!it_data->interest ||
wp_object_interest_matches (it_data->interest, obj)) {
g_value_init_from_instance (item, obj);
return TRUE;
}
}
return FALSE;
}
static gboolean
om_iterator_fold (WpIterator *it, WpIteratorFoldFunc func, GValue *ret,
gpointer data)
{
struct om_iterator_data *it_data = wp_iterator_get_user_data (it);
gpointer *obj, *base;
guint len;
obj = base = it_data->om->objects->pdata;
len = it_data->om->objects->len;
while ((obj - base) < len) {
/* only pass matching objects to the fold func if we have an interest */
if (!it_data->interest ||
wp_object_interest_matches (it_data->interest, *obj)) {
g_auto (GValue) item = G_VALUE_INIT;
g_value_init_from_instance (&item, *obj);
if (!func (&item, ret, data))
return FALSE;
}
obj++;
}
return TRUE;
}
static void
om_iterator_finalize (WpIterator *it)
{
struct om_iterator_data *it_data = wp_iterator_get_user_data (it);
if (it_data->interest)
wp_object_interest_free (it_data->interest);
g_object_unref (it_data->om);
}
static const WpIteratorMethods om_iterator_methods = {
.reset = om_iterator_reset,
.next = om_iterator_next,
.fold = om_iterator_fold,
.finalize = om_iterator_finalize,
};
/**
* wp_object_manager_iterate:
* @self: the object manager
*
* Returns: (transfer full): a #WpIterator that iterates over all the managed
* objects of this object manager
*/
WpIterator *
wp_object_manager_iterate (WpObjectManager * self)
{
WpIterator *it;
struct om_iterator_data *it_data;
g_return_val_if_fail (WP_IS_OBJECT_MANAGER (self), NULL);
it = wp_iterator_new (&om_iterator_methods, sizeof (struct om_iterator_data));
it_data = wp_iterator_get_user_data (it);
it_data->om = g_object_ref (self);
it_data->index = 0;
return it;
}
/**
* wp_object_manager_iterate_filtered:
* @self: the object manager
* @gtype: the #GType of the objects to iterate through
* @...: a list of constraints, terminated by %NULL
*
* Equivalent to:
* |[
* WpObjectInterest *i = wp_object_interest_new (gtype, ...);
* return wp_object_manager_iterate_filtered_full (self, i);
* ]|
*
* The constraints specified in the variable arguments must follow the rules
* documented in wp_object_interest_new().
*
* Returns: (transfer full): a #WpIterator that iterates over all the matching
* objects of this object manager
*/
WpIterator *
wp_object_manager_iterate_filtered (WpObjectManager * self, GType gtype, ...)
{
WpObjectInterest *interest;
va_list args;
g_return_val_if_fail (WP_IS_OBJECT_MANAGER (self), NULL);
va_start (args, gtype);
interest = wp_object_interest_new_valist (gtype, &args);
va_end (args);
return wp_object_manager_iterate_filtered_full (self, interest);
}
/**
* wp_object_manager_iterate_filtered_full: (rename-to wp_object_manager_iterate_filtered)
* @self: the object manager
* @interest: (transfer full): the interest
*
* Iterates through all the objects managed by this object manager that
* match the specified @interest.
*
* Returns: (transfer full): a #WpIterator that iterates over all the matching
* objects of this object manager
*/
WpIterator *
wp_object_manager_iterate_filtered_full (WpObjectManager * self,
WpObjectInterest * interest)
{
WpIterator *it;
struct om_iterator_data *it_data;
g_autoptr (GError) error = NULL;
g_return_val_if_fail (WP_IS_OBJECT_MANAGER (self), NULL);
if (G_UNLIKELY (!wp_object_interest_validate (interest, &error))) {
wp_critical_object (self, "interest validation failed: %s",
error->message);
wp_object_interest_free (interest);
return NULL;
}
it = wp_iterator_new (&om_iterator_methods, sizeof (struct om_iterator_data));
it_data = wp_iterator_get_user_data (it);
it_data->om = g_object_ref (self);
it_data->interest = interest;
it_data->index = 0;
return it;
}
/**
* wp_object_manager_lookup:
* @self: the object manager
* @gtype: the #GType of the object to lookup
* @...: a list of constraints, terminated by %NULL
*
* Equivalent to:
* |[
* WpObjectInterest *i = wp_object_interest_new (gtype, ...);
* return wp_object_manager_lookup_full (self, i);
* ]|
*
* The constraints specified in the variable arguments must follow the rules
* documented in wp_object_interest_new().
*
* Returns: (type GObject)(transfer full)(nullable): the first managed object
* that matches the lookup interest, or %NULL if no object matches
*/
gpointer
wp_object_manager_lookup (WpObjectManager * self, GType gtype, ...)
{
WpObjectInterest *interest;
va_list args;
g_return_val_if_fail (WP_IS_OBJECT_MANAGER (self), NULL);
va_start (args, gtype);
interest = wp_object_interest_new_valist (gtype, &args);
va_end (args);
return wp_object_manager_lookup_full (self, interest);
}
/**
* wp_object_manager_lookup_full:
* @self: the object manager
* @interest: (transfer full): the interst
*
* Searches for an object that matches the specified @interest and returns
* it, if found. If more than one objects match, only the first one is returned.
* To find multiple objects that match certain criteria,
* wp_object_manager_iterate_filtered() is more suitable.
*
* Returns: (type GObject)(transfer full)(nullable): the first managed object
* that matches the lookup interest, or %NULL if no object matches
*/
gpointer
wp_object_manager_lookup_full (WpObjectManager * self,
WpObjectInterest * interest)
{
g_auto (GValue) ret = G_VALUE_INIT;
g_autoptr (WpIterator) it =
wp_object_manager_iterate_filtered_full (self, interest);
if (wp_iterator_next (it, &ret))
return g_value_dup_object (&ret);
return NULL;
}
static gboolean
wp_object_manager_is_interested_in_object (WpObjectManager * self,
GObject * object)
{
gint i;
WpObjectInterest *interest = NULL;
for (i = 0; i < self->interests->len; i++) {
interest = g_ptr_array_index (self->interests, i);
if (wp_object_interest_matches (interest, object))
return TRUE;
}
return FALSE;
}
static gboolean
wp_object_manager_is_interested_in_global (WpObjectManager * self,
WpGlobal * global, WpProxyFeatures * wanted_features)
{
gint i;
WpObjectInterest *interest = NULL;
for (i = 0; i < self->interests->len; i++) {
interest = g_ptr_array_index (self->interests, i);
if (wp_object_interest_matches_full (interest, global->type,
global->proxy, NULL, global->properties)) {
gpointer ft = g_hash_table_lookup (self->features,
GSIZE_TO_POINTER (global->type));
*wanted_features = (WpProxyFeatures) GPOINTER_TO_UINT (ft);
return TRUE;
}
}
return FALSE;
}
static gboolean
idle_emit_objects_changed (WpObjectManager * self)
{
g_clear_pointer (&self->idle_source, g_source_unref);
if (G_UNLIKELY (!self->installed)) {
wp_trace_object (self, "installed");
g_signal_emit (self, signals[SIGNAL_INSTALLED], 0);
self->installed = TRUE;
}
wp_trace_object (self, "emit objects-changed");
g_signal_emit (self, signals[SIGNAL_OBJECTS_CHANGED], 0);
return G_SOURCE_REMOVE;
}
static void
wp_object_manager_maybe_objects_changed (WpObjectManager * self)
{
wp_trace_object (self, "pending:%u changed:%d idle_source:%p installed:%d",
self->pending_objects, self->changed, self->idle_source, self->installed);
/* always wait until there are no pending objects */
if (self->pending_objects > 0)
return;
/* Emit 'objects-changed' when:
* - there are no pending objects
* - object-added or object-removed has been emitted at least once
*/
if (self->changed) {
self->changed = FALSE;
/* schedule emission in idle; if it is already scheduled from earlier,
there is nothing to do; we will emit objects-changed once for all
changes... win-win */
if (!self->idle_source) {
g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
if (core) {
wp_core_idle_add (core, &self->idle_source,
(GSourceFunc) idle_emit_objects_changed, self, NULL);
}
}
}
/* Emit 'installed' when:
* - there are no pending objects
* - !changed: there was no object added
* - !installed: not already installed
* - the registry does not have pending globals; these may be interesting
* to our object manager, so let's wait a bit until they are released
* and re-evaluate again later
* - the registry has globals; if we are on early startup where we don't
* have any globals yet, wait...
*/
else if (!self->installed) {
g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
if (core) {
WpRegistry *reg = wp_core_get_registry (core);
if (reg->tmp_globals->len == 0 && reg->globals->len != 0) {
wp_trace_object (self, "installed");
g_signal_emit (self, signals[SIGNAL_INSTALLED], 0);
self->installed = TRUE;
}
}
}
}
static void
on_proxy_ready (GObject * proxy, GAsyncResult * res, gpointer data)
{
g_autoptr (WpObjectManager) self = WP_OBJECT_MANAGER (data);
g_autoptr (GError) error = NULL;
self->pending_objects--;
if (!wp_proxy_augment_finish (WP_PROXY (proxy), res, &error)) {
wp_message_object (self, "proxy augment failed: %s", error->message);
} else {
wp_trace_object (self, "added: " WP_OBJECT_FORMAT, WP_OBJECT_ARGS (proxy));
g_ptr_array_add (self->objects, proxy);
g_signal_emit (self, signals[SIGNAL_OBJECT_ADDED], 0, proxy);
self->changed = TRUE;
}
wp_object_manager_maybe_objects_changed (self);
}
/* caller must also call wp_object_manager_maybe_objects_changed() after */
static void
wp_object_manager_add_global (WpObjectManager * self, WpGlobal * global)
{
WpProxyFeatures features = 0;
if (wp_object_manager_is_interested_in_global (self, global, &features)) {
g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
self->pending_objects++;
if (!global->proxy)
global->proxy = g_object_new (global->type,
"core", core,
"global", global,
NULL);
wp_trace_object (self, "adding global:%u -> " WP_OBJECT_FORMAT,
global->id, WP_OBJECT_ARGS (global->proxy));
wp_proxy_augment (global->proxy, features, NULL, on_proxy_ready,
g_object_ref (self));
}
}
/* caller must also call wp_object_manager_maybe_objects_changed() after */
static void
wp_object_manager_add_object (WpObjectManager * self, gpointer object)
{
if (wp_object_manager_is_interested_in_object (self, object)) {
wp_trace_object (self, "added: " WP_OBJECT_FORMAT, WP_OBJECT_ARGS (object));
g_ptr_array_add (self->objects, object);
g_signal_emit (self, signals[SIGNAL_OBJECT_ADDED], 0, object);
self->changed = TRUE;
}
}
/* caller must also call wp_object_manager_maybe_objects_changed() after */
static void
wp_object_manager_rm_object (WpObjectManager * self, gpointer object)
{
guint index;
if (g_ptr_array_find (self->objects, object, &index)) {
g_ptr_array_remove_index_fast (self->objects, index);
g_signal_emit (self, signals[SIGNAL_OBJECT_REMOVED], 0, object);
self->changed = TRUE;
}
}
/*
* WpRegistry
*
* The registry keeps track of registered objects on the wireplumber core.
* There are 3 kinds of registered objects:
*
* 1) PipeWire global objects, which live in another process.
*
* These objects are represented by a WpGlobal with the
* WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY flag set. They appear when
* the registry_global() event is fired and are removed by
* registry_global_remove(). These objects do not have an associated
* WpProxy, unless there is at least one WpObjectManager that is interested
* in them. In this case, a WpProxy is constructed and it is owned by the
* WpGlobal until the global is removed by the registry_global_remove() event.
*
* 2) PipeWire global objects, which were constructed by this process, either
* by calling into a remove factory (see wp_node_new_from_factory()) or
* by exporting a local object (WpImplNode etc...).
*
* These objects are also represented by a WpGlobal, which may however be
* constructed before they appear on the registry. The associated WpProxy
* calls into wp_registry_prepare_new_global() at the time it receives
* the 'bound' event and creates a global that has the
* WP_GLOBAL_FLAG_OWNED_BY_PROXY flag enabled. As the flag name suggests,
* these globals are "owned" by the WpProxy and the WpGlobal has no ref
* on the WpProxy itself. This allows destroying the proxy in client code
* by dropping its last reference.
*
* Normally, these global objects also appear on the pipewire registry. When
* this happens, the WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY flag is also added
* and that keeps an additional reference on the global (both flags must
* be dropped before the WpGlobal is destroyed).
*
* In some cases, such an object might appear first on the registry and
* then receive the 'bound' event. In order to handle this situation, globals
* are not advertised immediately when they appear on the registry, but
* they are added on a tmp_globals list instead, which is emptied on the
* next core sync. In all cases, the proxy 'bound' and the registry 'global'
* events will be fired in the same sync cycle, so we can catch a late
* 'bound' event and still associate the proxy with the WpGlobal before
* object managers are notified about the existence of this global.
*
* 3) WirePlumber global objects (WpModule, WpPlugin, WpSiFactory).
*
* These are local objects that have nothing to do with PipeWire. They do not
* have a global id and they are also not subclasses of WpProxy. The registry
* always owns a reference on them, so that they are kept alive for as long
* as the WpCore is alive.
*/
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "wp-registry"
static void
wp_registry_notify_add_object (WpRegistry *self, gpointer object)
{
for (guint i = 0; i < self->object_managers->len; i++) {
WpObjectManager *om = g_ptr_array_index (self->object_managers, i);
wp_object_manager_add_object (om, object);
wp_object_manager_maybe_objects_changed (om);
}
}
static void
wp_registry_notify_rm_object (WpRegistry *self, gpointer object)
{
for (guint i = 0; i < self->object_managers->len; i++) {
WpObjectManager *om = g_ptr_array_index (self->object_managers, i);
wp_object_manager_rm_object (om, object);
wp_object_manager_maybe_objects_changed (om);
}
}
static void
object_manager_destroyed (gpointer data, GObject * om)
{
WpRegistry *self = data;
g_ptr_array_remove_fast (self->object_managers, om);
}
/* find the subclass of WpProxy that can handle
the given pipewire interface type of the given version */
static inline GType
find_proxy_instance_type (const char * type, guint32 version)
{
g_autofree GType *children;
guint n_children;
children = g_type_children (WP_TYPE_PROXY, &n_children);
for (gint i = 0; i < n_children; i++) {
WpProxyClass *klass = (WpProxyClass *) g_type_class_ref (children[i]);
if (g_strcmp0 (klass->pw_iface_type, type) == 0 &&
klass->pw_iface_version == version) {
g_type_class_unref (klass);
return children[i];
}
g_type_class_unref (klass);
}
return WP_TYPE_PROXY;
}
/* called by the registry when a global appears */
static void
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);
wp_debug_object (wp_registry_get_core (self),
"global:%u perm:0x%x type:%s/%u -> %s",
id, permissions, type, version, g_type_name (gtype));
wp_registry_prepare_new_global (self, id, permissions,
WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY, gtype, NULL, props, NULL);
}
/* called by the registry when a global is removed */
static void
registry_global_remove (void *data, uint32_t id)
{
WpRegistry *self = data;
WpGlobal *global = NULL;
if (id < self->globals->len)
global = g_ptr_array_index (self->globals, id);
/* if not found, look in the tmp_globals, as it may still not be exposed */
if (!global) {
for (guint i = 0; i < self->tmp_globals->len; i++) {
WpGlobal *g = g_ptr_array_index (self->tmp_globals, i);
if (g->id == id) {
global = g;
break;
}
}
}
g_return_if_fail (global &&
global->flags & WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY);
wp_debug_object (wp_registry_get_core (self),
"global removed:%u type:%s", id, g_type_name (global->type));
wp_global_rm_flag (global, WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY);
}
static const struct pw_registry_events registry_events = {
PW_VERSION_REGISTRY_EVENTS,
.global = registry_global,
.global_remove = registry_global_remove,
};
void
wp_registry_init (WpRegistry *self)
{
self->globals =
g_ptr_array_new_with_free_func ((GDestroyNotify) wp_global_unref);
self->tmp_globals =
g_ptr_array_new_with_free_func ((GDestroyNotify) wp_global_unref);
self->objects = g_ptr_array_new_with_free_func (g_object_unref);
self->object_managers = g_ptr_array_new ();
}
void
wp_registry_clear (WpRegistry *self)
{
wp_registry_detach (self);
g_clear_pointer (&self->globals, g_ptr_array_unref);
g_clear_pointer (&self->tmp_globals, g_ptr_array_unref);
/* remove all the registered objects
this will normally also destroy the object managers, eventually, since
they are normally ref'ed by modules, which are registered objects */
{
g_autoptr (GPtrArray) objlist = g_steal_pointer (&self->objects);
while (objlist->len > 0) {
g_autoptr (GObject) object = g_ptr_array_steal_index_fast (objlist,
objlist->len - 1);
wp_registry_notify_rm_object (self, object);
}
}
/* in case there are any object managers left,
remove the weak ref on them and let them be... */
{
g_autoptr (GPtrArray) object_mgrs;
GObject *om;
object_mgrs = g_steal_pointer (&self->object_managers);
while (object_mgrs->len > 0) {
om = g_ptr_array_steal_index_fast (object_mgrs, object_mgrs->len - 1);
g_object_weak_unref (om, object_manager_destroyed, self);
}
}
}
void
wp_registry_attach (WpRegistry *self, struct pw_core *pw_core)
{
self->pw_registry = pw_core_get_registry (pw_core,
PW_VERSION_REGISTRY, 0);
pw_registry_add_listener (self->pw_registry, &self->listener,
®istry_events, self);
}
void
wp_registry_detach (WpRegistry *self)
{
if (self->pw_registry) {
spa_hook_remove (&self->listener);
pw_proxy_destroy ((struct pw_proxy *) self->pw_registry);
self->pw_registry = NULL;
}
/* remove pipewire globals */
GPtrArray *objlist = self->globals;
while (objlist && objlist->len > 0) {
g_autoptr (WpGlobal) global = g_ptr_array_steal_index_fast (objlist,
objlist->len - 1);
if (!global)
continue;
if (global->proxy)
wp_registry_notify_rm_object (self, global->proxy);
/* remove the APPEARS_ON_REGISTRY flag to unref the proxy if it is owned
by the registry; set registry to NULL to avoid further interference */
global->registry = NULL;
wp_global_rm_flag (global, WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY);
/* the registry's ref on global is dropped here; it may still live if
there is a proxy that owns a ref on it, but global->registry is set
to NULL, so there is no further interference */
}
/* drop tmp globals as well */
objlist = self->tmp_globals;
while (objlist && objlist->len > 0) {
g_autoptr (WpGlobal) global = g_ptr_array_steal_index_fast (objlist,
objlist->len - 1);
wp_global_rm_flag (global, WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY);
}
}
static void
expose_tmp_globals (WpCore *core, GAsyncResult *res, WpRegistry *self)
{
g_autoptr (GError) error = NULL;
g_autoptr (GPtrArray) tmp_globals = NULL;
if (!wp_core_sync_finish (core, res, &error))
wp_warning_object (core, "core sync error: %s", error->message);
/* in case the registry was cleared in the meantime... */
if (G_UNLIKELY (!self->tmp_globals))
return;
/* steal the tmp_globals list and replace it with an empty one */
tmp_globals = self->tmp_globals;
self->tmp_globals =
g_ptr_array_new_with_free_func ((GDestroyNotify) wp_global_unref);
wp_debug_object (core, "exposing %u new globals", tmp_globals->len);
/* traverse in the order that the globals appeared on the registry */
for (guint i = 0; i < tmp_globals->len; i++) {
WpGlobal *g = g_ptr_array_index (tmp_globals, i);
/* if global was already removed, drop it */
if (g->flags == 0)
continue;
/* set the registry, so that wp_global_rm_flag() can work full-scale */
g->registry = self;
/* store it in the globals list */
if (self->globals->len <= g->id)
g_ptr_array_set_size (self->globals, g->id + 1);
g_ptr_array_index (self->globals, g->id) = wp_global_ref (g);
}
/* notify object managers */
for (guint i = 0; i < self->object_managers->len; i++) {
WpObjectManager *om = g_ptr_array_index (self->object_managers, i);
for (guint i = 0; i < tmp_globals->len; i++) {
WpGlobal *g = g_ptr_array_index (tmp_globals, i);
/* if global was already removed, drop it */
if (g->flags == 0)
continue;
wp_object_manager_add_global (om, g);
}
wp_object_manager_maybe_objects_changed (om);
}
}
/*
* wp_registry_prepare_new_global:
* @new_global: (out) (transfer full) (optional): the new global
*
* This is normally called up to 2 times in the same sync cycle:
* one from registry_global(), another from the proxy bound event
* Unfortunately the order in which those 2 events happen is specific
* to the implementation of the object, which is why this is implemented
* with a temporary globals list that get exposed later to the object managers
*/
void
wp_registry_prepare_new_global (WpRegistry * self, guint32 id,
guint32 permissions, guint32 flag, GType type,
WpProxy *proxy, const struct spa_dict *props,
WpGlobal ** new_global)
{
g_autoptr (WpGlobal) global = NULL;
WpCore *core = wp_registry_get_core (self);
g_return_if_fail (flag != 0);
g_return_if_fail (self->globals->len <= id ||
g_ptr_array_index (self->globals, id) == NULL);
for (guint i = 0; i < self->tmp_globals->len; i++) {
WpGlobal *g = g_ptr_array_index (self->tmp_globals, i);
if (g->id == id) {
global = wp_global_ref (g);
break;
}
}
wp_debug_object (core, "%s WpGlobal:%u type:%s proxy:%p",
global ? "reuse" : "new", id, g_type_name (type),
(global && global->proxy) ? global->proxy : proxy);
if (!global) {
global = g_rc_box_new0 (WpGlobal);
global->flags = flag;
global->id = id;
global->type = type;
global->permissions = permissions;
global->properties = props ?
wp_properties_new_copy_dict (props) : wp_properties_new_empty ();
global->proxy = proxy;
g_ptr_array_add (self->tmp_globals, wp_global_ref (global));
/* ensure we have 'object.id' so that we can filter by id on object managers */
wp_properties_setf (global->properties, PW_KEY_OBJECT_ID, "%u", global->id);
/* schedule exposing when adding the first global */
if (self->tmp_globals->len == 1) {
wp_core_sync (core, NULL, (GAsyncReadyCallback) expose_tmp_globals, self);
}
} else {
/* store the most permissive permissions */
if (permissions > global->permissions)
global->permissions = permissions;
global->flags |= flag;
/* store the most deep type (i.e. WpImplNode instead of WpNode),
so that object-manager interests can work more accurately
if the interest is on a specific subclass */
if (g_type_depth (type) > g_type_depth (global->type))
global->type = type;
if (proxy) {
g_return_if_fail (global->proxy == NULL);
global->proxy = proxy;
}
if (props)
wp_properties_update_from_dict (global->properties, props);
}
if (new_global)
*new_global = g_steal_pointer (&global);
}
/*
* wp_registry_find_object:
* @reg: the registry
* @func: (scope call): a function that takes the object being searched
* as the first argument and @data as the second. it should return TRUE if
* the object is found or FALSE otherwise
* @data: the second argument to @func
*
* Finds a registered object
*
* Returns: (transfer full) (type GObject *) (nullable): the registered object
* or NULL if not found
*/
gpointer
wp_registry_find_object (WpRegistry *reg, GEqualFunc func, gconstpointer data)
{
GObject *object;
guint i;
/* prevent bad things when called from within wp_registry_clear() */
if (G_UNLIKELY (!reg->objects))
return NULL;
for (i = 0; i < reg->objects->len; i++) {
object = g_ptr_array_index (reg->objects, i);
if (func (object, data))
return g_object_ref (object);
}
return NULL;
}
/*
* wp_registry_register_object:
* @reg: the registry
* @obj: (transfer full) (type GObject*): the object to register
*
* Registers @obj with the core, making it appear on #WpObjectManager
* instances as well. The core will also maintain a ref to that object
* until it is removed.
*/
void
wp_registry_register_object (WpRegistry *reg, gpointer obj)
{
g_return_if_fail (G_IS_OBJECT (obj));
/* prevent bad things when called from within wp_registry_clear() */
if (G_UNLIKELY (!reg->objects)) {
g_object_unref (obj);
return;
}
g_ptr_array_add (reg->objects, obj);
/* notify object managers */
wp_registry_notify_add_object (reg, obj);
}
/*
* wp_registry_remove_object:
* @reg: the registry
* @obj: (transfer none) (type GObject*): a pointer to the object to remove
*
* Detaches and unrefs the specified object from this core
*/
void
wp_registry_remove_object (WpRegistry *reg, gpointer obj)
{
g_return_if_fail (G_IS_OBJECT (obj));
/* prevent bad things when called from within wp_registry_clear() */
if (G_UNLIKELY (!reg->objects))
return;
/* notify object managers */
wp_registry_notify_rm_object (reg, obj);
g_ptr_array_remove_fast (reg->objects, obj);
}
/**
* wp_core_install_object_manager:
* @self: the core
* @om: (transfer none): a #WpObjectManager
*
* Installs the object manager on this core, activating its internal management
* engine. This will immediately emit signals about objects added on @om
* if objects that the @om is interested in were in existence already.
*/
void
wp_core_install_object_manager (WpCore * self, WpObjectManager * om)
{
WpRegistry *reg;
guint i;
g_return_if_fail (WP_IS_CORE (self));
g_return_if_fail (WP_IS_OBJECT_MANAGER (om));
reg = wp_core_get_registry (self);
g_object_weak_ref (G_OBJECT (om), object_manager_destroyed, reg);
g_ptr_array_add (reg->object_managers, om);
g_weak_ref_set (&om->core, self);
/* add pre-existing objects to the object manager,
in case it's interested in them */
for (i = 0; i < reg->globals->len; i++) {
WpGlobal *g = g_ptr_array_index (reg->globals, i);
/* check if null because the globals array can have gaps */
if (g)
wp_object_manager_add_global (om, g);
}
for (i = 0; i < reg->objects->len; i++) {
GObject *o = g_ptr_array_index (reg->objects, i);
wp_object_manager_add_object (om, o);
}
wp_object_manager_maybe_objects_changed (om);
}
/* WpGlobal */
G_DEFINE_BOXED_TYPE (WpGlobal, wp_global, wp_global_ref, wp_global_unref)
void
wp_global_rm_flag (WpGlobal *global, guint rm_flag)
{
WpRegistry *reg = global->registry;
/* no flag to remove */
if (!(global->flags & rm_flag))
return;
/* global was owned by the proxy; by removing the flag, we clear out
also the proxy pointer, which is presumably no longer valid and we
notify all listeners that the proxy is gone */
if (rm_flag == WP_GLOBAL_FLAG_OWNED_BY_PROXY) {
global->flags &= ~WP_GLOBAL_FLAG_OWNED_BY_PROXY;
if (reg && global->proxy) {
wp_registry_notify_rm_object (reg, global->proxy);
}
global->proxy = NULL;
}
/* registry removed the global */
else if (rm_flag == WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY) {
global->flags &= ~WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY;
/* destroy the proxy if it exists */
if (global->proxy) {
if (reg)
wp_registry_notify_rm_object (reg, global->proxy);
wp_proxy_destroy (global->proxy);
/* if the proxy is not owning the global, unref it */
if (global->flags == 0)
g_object_unref (global->proxy);
global->proxy = NULL;
}
}
/* drop the registry's ref on global when it has no flags anymore */
if (global->flags == 0 && reg) {
g_clear_pointer (&g_ptr_array_index (reg->globals, global->id), wp_global_unref);
}
}
struct pw_proxy *
wp_global_bind (WpGlobal * global)
{
g_return_val_if_fail (global->proxy, NULL);
g_return_val_if_fail (global->registry, NULL);
WpProxyClass *klass = WP_PROXY_GET_CLASS (global->proxy);
return pw_registry_bind (global->registry->pw_registry, global->id,
klass->pw_iface_type, klass->pw_iface_version, 0);
}