Skip to content
Snippets Groups Projects
object-manager.c 12.1 KiB
Newer Older
/* 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
{
  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) {
    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,
    GType gtype, GVariant * constraints,
    WpProxyFeatures wanted_features)
{
  struct interest *i;

  g_return_if_fail (WP_IS_OBJECT_MANAGER (self));
  g_return_if_fail (g_type_is_a (gtype, WP_TYPE_PROXY));
  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->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_type_is_a (global->type, i->g_type)
        && (!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,
    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);
  }
}