/* GStreamer * Copyright (C) 2005 Stefan Kost * * gstchildproxy.c: interface for multi child elements * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:gstchildproxy * @title: GstChildProxy * @short_description: Interface for multi child elements. * @see_also: #GstBin * * This interface abstracts handling of property sets for elements with * children. Imagine elements such as mixers or polyphonic generators. They all * have multiple #GstPad or some kind of voice objects. Another use case are * container elements like #GstBin. * The element implementing the interface acts as a parent for those child * objects. * * By implementing this interface the child properties can be accessed from the * parent element by using gst_child_proxy_get() and gst_child_proxy_set(). * * Property names are written as "child-name::property-name". The whole naming * scheme is recursive. Thus "child1::child2::property" is valid too, if * "child1" and "child2" implement the #GstChildProxy interface. */ #include "gst_private.h" #include "gstchildproxy.h" #include /* signals */ enum { CHILD_ADDED, CHILD_REMOVED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static GObject * gst_child_proxy_default_get_child_by_name (GstChildProxy * parent, const gchar * name) { guint count, i; GObject *object, *result; gchar *object_name; g_return_val_if_fail (GST_IS_CHILD_PROXY (parent), NULL); g_return_val_if_fail (name != NULL, NULL); result = NULL; count = gst_child_proxy_get_children_count (parent); for (i = 0; i < count; i++) { gboolean eq; if (!(object = gst_child_proxy_get_child_by_index (parent, i))) continue; if (!GST_IS_OBJECT (object)) { goto next; } object_name = gst_object_get_name (GST_OBJECT_CAST (object)); if (object_name == NULL) { g_warning ("child %u of parent %s has no name", i, GST_OBJECT_NAME (parent)); goto next; } eq = g_str_equal (object_name, name); g_free (object_name); if (eq) { result = object; break; } next: gst_object_unref (object); } return result; } /** * gst_child_proxy_get_child_by_name: * @parent: the parent object to get the child from * @name: the child's name * * Looks up a child element by the given name. * * This virtual method has a default implementation that uses #GstObject * together with gst_object_get_name(). If the interface is to be used with * #GObjects, this methods needs to be overridden. * * Returns: (transfer full) (nullable): the child object or %NULL if * not found. Unref after usage. * * MT safe. */ GObject * gst_child_proxy_get_child_by_name (GstChildProxy * parent, const gchar * name) { GstChildProxyInterface *iface; g_return_val_if_fail (GST_IS_CHILD_PROXY (parent), 0); iface = GST_CHILD_PROXY_GET_INTERFACE (parent); if (iface->get_child_by_name != NULL) return iface->get_child_by_name (parent, name); return NULL; } /** * gst_child_proxy_get_child_by_index: * @parent: the parent object to get the child from * @index: the child's position in the child list * * Fetches a child by its number. * * Returns: (transfer full) (nullable): the child object or %NULL if * not found (index too high). Unref after usage. * * MT safe. */ GObject * gst_child_proxy_get_child_by_index (GstChildProxy * parent, guint index) { GstChildProxyInterface *iface; g_return_val_if_fail (GST_IS_CHILD_PROXY (parent), NULL); iface = GST_CHILD_PROXY_GET_INTERFACE (parent); if (iface->get_child_by_index != NULL) return iface->get_child_by_index (parent, index); return NULL; } /** * gst_child_proxy_get_children_count: * @parent: the parent object * * Gets the number of child objects this parent contains. * * Returns: the number of child objects * * MT safe. */ guint gst_child_proxy_get_children_count (GstChildProxy * parent) { GstChildProxyInterface *iface; g_return_val_if_fail (GST_IS_CHILD_PROXY (parent), 0); iface = GST_CHILD_PROXY_GET_INTERFACE (parent); if (iface->get_children_count != NULL) return iface->get_children_count (parent); return 0; } /** * gst_child_proxy_lookup: * @object: child proxy object to lookup the property in * @name: name of the property to look up * @target: (out) (allow-none) (transfer full): pointer to a #GObject that * takes the real object to set property on * @pspec: (out) (allow-none) (transfer none): pointer to take the #GParamSpec * describing the property * * Looks up which object and #GParamSpec would be effected by the given @name. * * MT safe. * * Returns: %TRUE if @target and @pspec could be found. %FALSE otherwise. In that * case the values for @pspec and @target are not modified. Unref @target after * usage. For plain GObjects @target is the same as @object. */ gboolean gst_child_proxy_lookup (GstChildProxy * object, const gchar * name, GObject ** target, GParamSpec ** pspec) { GObject *obj; gboolean res = FALSE; gchar **names, **current; g_return_val_if_fail (GST_IS_CHILD_PROXY (object), FALSE); g_return_val_if_fail (name != NULL, FALSE); obj = G_OBJECT (g_object_ref (object)); current = names = g_strsplit (name, "::", -1); /* find the owner of the property */ while (current[1]) { GObject *next; if (!GST_IS_CHILD_PROXY (obj)) { GST_INFO ("object %s is not a parent, so you cannot request a child by name %s", (GST_IS_OBJECT (obj) ? GST_OBJECT_NAME (obj) : ""), current[0]); break; } next = gst_child_proxy_get_child_by_name (GST_CHILD_PROXY (obj), current[0]); if (!next) { GST_INFO ("no such object %s", current[0]); break; } gst_object_unref (obj); obj = next; current++; } /* look for psec */ if (current[1] == NULL) { GParamSpec *spec = g_object_class_find_property (G_OBJECT_GET_CLASS (obj), current[0]); if (spec == NULL) { GST_INFO ("no param spec named %s", current[0]); } else { if (pspec) *pspec = spec; if (target) { g_object_ref (obj); *target = obj; } res = TRUE; } } gst_object_unref (obj); g_strfreev (names); return res; } /** * gst_child_proxy_get_property: * @object: object to query * @name: name of the property * @value: (out caller-allocates): a #GValue that should take the result. * * Gets a single property using the GstChildProxy mechanism. * You are responsible for freeing it by calling g_value_unset() */ void gst_child_proxy_get_property (GstChildProxy * object, const gchar * name, GValue * value) { GParamSpec *pspec; GObject *target; g_return_if_fail (GST_IS_CHILD_PROXY (object)); g_return_if_fail (name != NULL); g_return_if_fail (G_IS_VALUE (value)); if (!gst_child_proxy_lookup (object, name, &target, &pspec)) goto not_found; g_object_get_property (target, pspec->name, value); gst_object_unref (target); return; not_found: { g_warning ("no property %s in object %s", name, (GST_IS_OBJECT (object) ? GST_OBJECT_NAME (object) : "")); return; } } /** * gst_child_proxy_get_valist: * @object: the object to query * @first_property_name: name of the first property to get * @var_args: return location for the first property, followed optionally by more name/return location pairs, followed by %NULL * * Gets properties of the parent object and its children. */ void gst_child_proxy_get_valist (GstChildProxy * object, const gchar * first_property_name, va_list var_args) { const gchar *name; gchar *error = NULL; GValue value = { 0, }; GParamSpec *pspec; GObject *target; g_return_if_fail (GST_IS_CHILD_PROXY (object)); name = first_property_name; /* iterate over pairs */ while (name) { if (!gst_child_proxy_lookup (object, name, &target, &pspec)) goto not_found; g_value_init (&value, pspec->value_type); g_object_get_property (target, pspec->name, &value); gst_object_unref (target); G_VALUE_LCOPY (&value, var_args, 0, &error); if (error) goto cant_copy; g_value_unset (&value); name = va_arg (var_args, gchar *); } return; not_found: { g_warning ("no property %s in object %s", name, (GST_IS_OBJECT (object) ? GST_OBJECT_NAME (object) : "")); return; } cant_copy: { g_warning ("error copying value %s in object %s: %s", pspec->name, (GST_IS_OBJECT (object) ? GST_OBJECT_NAME (object) : ""), error); g_value_unset (&value); return; } } /** * gst_child_proxy_get: * @object: the parent object * @first_property_name: name of the first property to get * @...: return location for the first property, followed optionally by more name/return location pairs, followed by %NULL * * Gets properties of the parent object and its children. */ void gst_child_proxy_get (GstChildProxy * object, const gchar * first_property_name, ...) { va_list var_args; g_return_if_fail (GST_IS_CHILD_PROXY (object)); va_start (var_args, first_property_name); gst_child_proxy_get_valist (object, first_property_name, var_args); va_end (var_args); } /** * gst_child_proxy_set_property: * @object: the parent object * @name: name of the property to set * @value: new #GValue for the property * * Sets a single property using the GstChildProxy mechanism. */ void gst_child_proxy_set_property (GstChildProxy * object, const gchar * name, const GValue * value) { GParamSpec *pspec; GObject *target; g_return_if_fail (GST_IS_CHILD_PROXY (object)); g_return_if_fail (name != NULL); g_return_if_fail (G_IS_VALUE (value)); if (!gst_child_proxy_lookup (object, name, &target, &pspec)) goto not_found; g_object_set_property (target, pspec->name, value); gst_object_unref (target); return; not_found: { g_warning ("cannot set property %s on object %s", name, (GST_IS_OBJECT (object) ? GST_OBJECT_NAME (object) : "")); return; } } /** * gst_child_proxy_set_valist: * @object: the parent object * @first_property_name: name of the first property to set * @var_args: value for the first property, followed optionally by more name/value pairs, followed by %NULL * * Sets properties of the parent object and its children. */ void gst_child_proxy_set_valist (GstChildProxy * object, const gchar * first_property_name, va_list var_args) { const gchar *name; gchar *error = NULL; GValue value = { 0, }; GParamSpec *pspec; GObject *target; g_return_if_fail (GST_IS_CHILD_PROXY (object)); name = first_property_name; /* iterate over pairs */ while (name) { if (!gst_child_proxy_lookup (object, name, &target, &pspec)) goto not_found; G_VALUE_COLLECT_INIT (&value, pspec->value_type, var_args, G_VALUE_NOCOPY_CONTENTS, &error); if (error) goto cant_copy; g_object_set_property (target, pspec->name, &value); gst_object_unref (target); g_value_unset (&value); name = va_arg (var_args, gchar *); } return; not_found: { g_warning ("no property %s in object %s", name, (GST_IS_OBJECT (object) ? GST_OBJECT_NAME (object) : "")); return; } cant_copy: { g_warning ("error copying value %s in object %s: %s", pspec->name, (GST_IS_OBJECT (object) ? GST_OBJECT_NAME (object) : ""), error); g_value_unset (&value); gst_object_unref (target); g_free (error); return; } } /** * gst_child_proxy_set: * @object: the parent object * @first_property_name: name of the first property to set * @...: value for the first property, followed optionally by more name/value pairs, followed by %NULL * * Sets properties of the parent object and its children. */ void gst_child_proxy_set (GstChildProxy * object, const gchar * first_property_name, ...) { va_list var_args; g_return_if_fail (GST_IS_CHILD_PROXY (object)); va_start (var_args, first_property_name); gst_child_proxy_set_valist (object, first_property_name, var_args); va_end (var_args); } /** * gst_child_proxy_child_added: * @parent: the parent object * @child: the newly added child * @name: the name of the new child * * Emits the "child-added" signal. */ void gst_child_proxy_child_added (GstChildProxy * parent, GObject * child, const gchar * name) { g_signal_emit (parent, signals[CHILD_ADDED], 0, child, name); } /** * gst_child_proxy_child_removed: * @parent: the parent object * @child: the removed child * @name: the name of the old child * * Emits the "child-removed" signal. */ void gst_child_proxy_child_removed (GstChildProxy * parent, GObject * child, const gchar * name) { g_signal_emit (parent, signals[CHILD_REMOVED], 0, child, name); } /* gobject methods */ static void gst_child_proxy_class_init (gpointer g_class, gpointer class_data) { GstChildProxyInterface *iface = (GstChildProxyInterface *) g_class; iface->get_child_by_name = gst_child_proxy_default_get_child_by_name; } static void gst_child_proxy_base_init (gpointer g_class) { static gboolean initialized = FALSE; if (!initialized) { /* create interface signals and properties here. */ /** * GstChildProxy::child-added: * @child_proxy: the #GstChildProxy * @object: the #GObject that was added * @name: the name of the new child * * Will be emitted after the @object was added to the @child_proxy. */ signals[CHILD_ADDED] = g_signal_new ("child-added", G_TYPE_FROM_CLASS (g_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GstChildProxyInterface, child_added), NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_STRING); /** * GstChildProxy::child-removed: * @child_proxy: the #GstChildProxy * @object: the #GObject that was removed * @name: the name of the old child * * Will be emitted after the @object was removed from the @child_proxy. */ signals[CHILD_REMOVED] = g_signal_new ("child-removed", G_TYPE_FROM_CLASS (g_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GstChildProxyInterface, child_removed), NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_STRING); initialized = TRUE; } } GType gst_child_proxy_get_type (void) { static gsize type = 0; if (g_once_init_enter (&type)) { GType _type; static const GTypeInfo info = { sizeof (GstChildProxyInterface), gst_child_proxy_base_init, /* base_init */ NULL, /* base_finalize */ gst_child_proxy_class_init, /* class_init */ NULL, /* class_finalize */ NULL, /* class_data */ 0, 0, /* n_preallocs */ NULL /* instance_init */ }; _type = g_type_register_static (G_TYPE_INTERFACE, "GstChildProxy", &info, 0); g_type_interface_add_prerequisite (_type, G_TYPE_OBJECT); g_once_init_leave (&type, (gsize) _type); } return type; }