mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-02-01 07:00:24 +01:00
+ 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.
454 lines
12 KiB
C
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);
|
|
}
|
|
}
|