wireplumber/lib/wp/object-manager.c
George Kiagiadakis d8ae151aba proxy: replace global-id with bound-id
+ use the pw_proxy API to find the bound id instead
of relying on WpGlobal

This has the advantage that it works also for exported
objects and for objects that have been created by calling
into a remote factory (such as the link-factory), so we can
now know the global id of all proxies, not only the ones
that have been created by the registry.
2020-02-10 18:58:41 +02:00

454 lines
12 KiB
C

/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "object-manager.h"
#include "private.h"
#include <pipewire/array.h>
struct interest
{
union {
char * proxy_type;
GType g_type;
};
gboolean for_proxy;
WpProxyFeatures wanted_features;
GVariant *constraints; // aa{sv}
};
struct _WpObjectManager
{
GObject parent;
GWeakRef core;
/* array of struct interest;
pw_array has a better API for our use case than GArray */
struct pw_array interests;
/* objects that we are interested in, with a strong ref */
GPtrArray *objects;
gboolean pending_objchanged;
};
enum {
PROP_0,
PROP_CORE,
};
enum {
SIGNAL_OBJECT_ADDED,
SIGNAL_OBJECT_REMOVED,
SIGNAL_OBJECTS_CHANGED,
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);
pw_array_init (&self->interests, sizeof (struct interest));
self->objects = g_ptr_array_new_with_free_func (g_object_unref);
self->pending_objchanged = FALSE;
}
static void
wp_object_manager_finalize (GObject * object)
{
WpObjectManager *self = WP_OBJECT_MANAGER (object);
struct interest *i;
g_clear_pointer (&self->objects, g_ptr_array_unref);
pw_array_for_each (i, &self->interests) {
if (i->for_proxy)
g_clear_pointer (&i->proxy_type, g_free);
g_clear_pointer (&i->constraints, g_variant_unref);
}
pw_array_clear (&self->interests);
g_weak_ref_clear (&self->core);
G_OBJECT_CLASS (wp_object_manager_parent_class)->finalize (object);
}
static void
wp_object_manager_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpObjectManager *self = WP_OBJECT_MANAGER (object);
switch (property_id) {
case PROP_CORE:
g_weak_ref_set (&self->core, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
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;
object_class->set_property = wp_object_manager_set_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_READWRITE | G_PARAM_STATIC_STRINGS));
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);
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);
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 *
wp_object_manager_new (void)
{
return g_object_new (WP_TYPE_OBJECT_MANAGER, NULL);
}
void
wp_object_manager_add_proxy_interest (WpObjectManager *self,
const gchar * iface_type, GVariant * constraints,
WpProxyFeatures wanted_features)
{
struct interest *i;
g_return_if_fail (WP_IS_OBJECT_MANAGER (self));
g_return_if_fail (iface_type != 0);
g_return_if_fail (constraints == NULL ||
g_variant_is_of_type (constraints, G_VARIANT_TYPE ("aa{sv}")));
/* grow the array by 1 struct interest and fill it in */
i = pw_array_add (&self->interests, sizeof (struct interest));
i->proxy_type = g_strdup (iface_type);
i->for_proxy = TRUE;
i->wanted_features = wanted_features;
i->constraints = constraints ? g_variant_ref_sink (constraints) : NULL;
}
void
wp_object_manager_add_object_interest (WpObjectManager *self,
GType gtype, GVariant * constraints)
{
struct interest *i;
g_return_if_fail (WP_IS_OBJECT_MANAGER (self));
g_return_if_fail (G_TYPE_IS_OBJECT (gtype));
g_return_if_fail (constraints == NULL ||
g_variant_is_of_type (constraints, G_VARIANT_TYPE ("aa{sv}")));
/* grow the array by 1 struct interest and fill it in */
i = pw_array_add (&self->interests, sizeof (struct interest));
i->g_type = gtype;
i->for_proxy = FALSE;
i->wanted_features = 0;
i->constraints = constraints ? g_variant_ref_sink (constraints) : NULL;
}
/**
* wp_object_manager_get_objects:
* @self: the object manager
* @type_filter: a #GType filter to get only the objects that are of this type,
* or 0 to return all the objects
*
* Returns: (transfer full) (element-type GObject*): all the objects managed
* by this #WpObjectManager that match the @type_filter
*/
GPtrArray *
wp_object_manager_get_objects (WpObjectManager *self, GType type_filter)
{
GPtrArray *result = g_ptr_array_new_with_free_func (g_object_unref);
guint i;
for (i = 0; i < self->objects->len; i++) {
gpointer obj = g_ptr_array_index (self->objects, i);
if (type_filter == 0 || g_type_is_a (G_OBJECT_TYPE (obj), type_filter)) {
g_ptr_array_add (result, g_object_ref (obj));
}
}
return result;
}
static gboolean
check_constraints (GVariant *constraints,
WpProperties *global_props,
GObject *object)
{
GVariantIter iter;
GVariant *c;
WpObjectManagerConstraintType ctype;
g_autoptr (WpProperties) props = NULL;
const gchar *prop_name, *prop_value;
/* pipewire properties are contained in a GObj property called "properties" */
if (object &&
g_object_class_find_property (G_OBJECT_GET_CLASS (object), "properties"))
g_object_get (object, "properties", &props, NULL);
g_variant_iter_init (&iter, constraints);
while (g_variant_iter_next (&iter, "@a{sv}", &c)) {
GVariantDict dict = G_VARIANT_DICT_INIT (c);
if (!g_variant_dict_lookup (&dict, "type", "i", &ctype)) {
g_warning ("Invalid object manager constraint without a type");
goto error;
}
switch (ctype) {
case WP_OBJECT_MANAGER_CONSTRAINT_PW_GLOBAL_PROPERTY:
if (!global_props)
goto next;
if (!g_variant_dict_lookup (&dict, "name", "&s", &prop_name)) {
g_warning ("property constraint is without a property name");
goto error;
}
if (!g_variant_dict_lookup (&dict, "value", "&s", &prop_value)) {
g_warning ("property constraint is without a property value");
goto error;
}
if (!g_strcmp0 (wp_properties_get (global_props, prop_name), prop_value))
goto match;
break;
case WP_OBJECT_MANAGER_CONSTRAINT_PW_PROPERTY:
if (!props)
goto next;
if (!g_variant_dict_lookup (&dict, "name", "&s", &prop_name)) {
g_warning ("property constraint is without a property name");
goto error;
}
if (!g_variant_dict_lookup (&dict, "value", "&s", &prop_value)) {
g_warning ("property constraint is without a property value");
goto error;
}
if (!g_strcmp0 (wp_properties_get (props, prop_name), prop_value))
goto match;
break;
case WP_OBJECT_MANAGER_CONSTRAINT_G_PROPERTY:
if (!object)
goto next;
if (!g_variant_dict_lookup (&dict, "name", "&s", &prop_name)) {
g_warning ("property constraint is without a property name");
goto error;
}
if (!g_variant_dict_lookup (&dict, "value", "&s", &prop_value)) {
g_warning ("property constraint is without a property value");
goto error;
}
if (!g_object_class_find_property (G_OBJECT_GET_CLASS (object), prop_name))
goto next;
if (({
g_auto (GValue) value = G_VALUE_INIT;
g_auto (GValue) str_value = G_VALUE_INIT;
g_object_get_property (object, prop_name, &value);
g_value_init (&str_value, G_TYPE_STRING);
g_value_transform (&value, &str_value) &&
!g_strcmp0 (g_value_get_string (&str_value), prop_value);
}))
goto match;
break;
default:
g_warning ("Unknown constraint type '%d'", ctype);
goto error;
}
next:
{
g_variant_dict_clear (&dict);
g_clear_pointer (&c, g_variant_unref);
continue;
}
match:
{
g_variant_dict_clear (&dict);
g_clear_pointer (&c, g_variant_unref);
return TRUE;
}
error:
{
g_autofree gchar *dbgstr = g_variant_print (c, TRUE);
g_warning ("offending constraint was: %s", dbgstr);
goto next;
}
}
return FALSE;
}
static gboolean
wp_object_manager_is_interested_in_object (WpObjectManager * self,
GObject * object)
{
struct interest *i;
pw_array_for_each (i, &self->interests) {
if (!i->for_proxy
&& g_type_is_a (G_OBJECT_TYPE (object), i->g_type)
&& (!i->constraints ||
check_constraints (i->constraints, NULL, object)))
{
return TRUE;
}
}
return FALSE;
}
static gboolean
wp_object_manager_is_interested_in_global (WpObjectManager * self,
WpGlobal * global, WpProxyFeatures * wanted_features)
{
struct interest *i;
pw_array_for_each (i, &self->interests) {
if (i->for_proxy
&& g_strcmp0 (i->proxy_type, global->type) == 0
&& (!i->constraints ||
check_constraints (i->constraints, global->properties, NULL)))
{
*wanted_features = i->wanted_features;
return TRUE;
}
}
return FALSE;
}
static void
sync_emit_objects_changed (WpCore *core, GAsyncResult *res, gpointer data)
{
g_autoptr (WpObjectManager) self = WP_OBJECT_MANAGER (data);
g_signal_emit (self, signals[SIGNAL_OBJECTS_CHANGED], 0);
self->pending_objchanged = FALSE;
}
static inline void
schedule_emit_objects_changed (WpObjectManager * self)
{
if (self->pending_objchanged)
return;
g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
if (core) {
wp_core_sync (core, NULL, (GAsyncReadyCallback)sync_emit_objects_changed,
g_object_ref (self));
self->pending_objchanged = TRUE;
}
}
static void
on_proxy_ready (GObject * proxy, GAsyncResult * res, gpointer data)
{
g_autoptr (WpObjectManager) self = WP_OBJECT_MANAGER (data);
g_ptr_array_add (self->objects, g_object_ref (proxy));
g_signal_emit (self, signals[SIGNAL_OBJECT_ADDED], 0, proxy);
schedule_emit_objects_changed (self);
}
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 (WpProxy) proxy = g_weak_ref_get (&global->proxy);
g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
if (!proxy) {
proxy = wp_proxy_new_global (core, global);
g_weak_ref_set (&global->proxy, proxy);
}
wp_proxy_augment (proxy, features, NULL, on_proxy_ready,
g_object_ref (self));
}
}
void
wp_object_manager_rm_global (WpObjectManager * self, guint32 id)
{
guint i;
for (i = 0; i < self->objects->len; i++) {
gpointer obj = g_ptr_array_index (self->objects, i);
if (WP_IS_PROXY (obj) && id == wp_proxy_get_bound_id (WP_PROXY (obj))) {
g_signal_emit (self, signals[SIGNAL_OBJECT_REMOVED], 0, obj);
g_ptr_array_remove_index_fast (self->objects, i);
schedule_emit_objects_changed (self);
return;
}
}
}
void
wp_object_manager_add_object (WpObjectManager * self, GObject * object)
{
if (wp_object_manager_is_interested_in_object (self, object)) {
g_ptr_array_add (self->objects, g_object_ref (object));
g_signal_emit (self, signals[SIGNAL_OBJECT_ADDED], 0, object);
schedule_emit_objects_changed (self);
}
}
void
wp_object_manager_rm_object (WpObjectManager * self, GObject * object)
{
guint index;
if (g_ptr_array_find (self->objects, object, &index)) {
g_signal_emit (self, signals[SIGNAL_OBJECT_REMOVED], 0, object);
g_ptr_array_remove_index_fast (self->objects, index);
schedule_emit_objects_changed (self);
}
}