/* WirePlumber * * Copyright © 2019 Collabora Ltd. * @author George Kiagiadakis <george.kiagiadakis@collabora.com> * * SPDX-License-Identifier: MIT */ /** * SECTION: WpProperties * * #WpProperties is a data structure that contains string key-value pairs, * which are used to send/receive/attach arbitrary properties to PipeWire * objects. * * This could be thought of as a hash table with strings as both keys and * values. However, the reason that this class exists instead of using * #GHashTable directly is that in reality it wraps the PipeWire native * `struct spa_dict` and `struct pw_properties` and therefore it can be * easily passed to PipeWire function calls that require a `struct spa_dict *` * or a `struct pw_properties *` as arguments. Or alternatively, it can easily * wrap a `struct spa_dict *` or a `struct pw_properties *` that was given * from the PipeWire API without necessarily doing an expensive copy operation. * * #WpProperties normally wraps a `struct pw_properties`, unless it was created * with wp_properties_new_wrap_dict(), in which case it wraps a * `struct spa_dict` and it is immutable (you cannot add/remove/modify any * key-value pair). * * In most cases, it actually owns the `struct pw_properties` * internally and manages its lifetime. The exception to that rule is when * #WpProperties is constructed with wp_properties_new_wrap(), in which case * the ownership of the `struct pw_properties` remains outside. This must * be used with care, as the `struct pw_properties` may be free'ed externally. * * #WpProperties is reference-counted with wp_properties_ref() and * wp_properties_unref(). */ #define G_LOG_DOMAIN "wp-properties" #include "properties.h" #include "private.h" #include <errno.h> #include <pipewire/properties.h> enum { FLAG_IS_DICT = (1<<1), FLAG_NO_OWNERSHIP = (1<<2), }; struct _WpProperties { grefcount ref; guint32 flags; union { struct pw_properties *props; const struct spa_dict *dict; }; }; G_DEFINE_BOXED_TYPE(WpProperties, wp_properties, wp_properties_ref, wp_properties_unref) /** * wp_properties_new_empty: * * Creates a new empty properties set * * Returns: (transfer full): the newly constructed properties set */ WpProperties * wp_properties_new_empty (void) { WpProperties * self = g_slice_new0 (WpProperties); g_ref_count_init (&self->ref); self->flags = 0; self->props = pw_properties_new (NULL, NULL); return self; } /** * wp_properties_new: * @key: a property name * @...: a property value, followed by any number of further property * key-value pairs, followed by %NULL * * Constructs a new properties set that contains the given properties * * Returns: (transfer full): the newly constructed properties set */ WpProperties * wp_properties_new (const gchar * key, ...) { WpProperties * self; va_list varargs; va_start(varargs, key); self = wp_properties_new_valist (key, varargs); va_end(varargs); return self; } /** * wp_properties_new_valist: * @key: a property name * @args: the variable arguments passed to wp_properties_new() * * This is the `va_list` version of wp_properties_new() * * Returns: (transfer full): the newly constructed properties set */ WpProperties * wp_properties_new_valist (const gchar * key, va_list args) { WpProperties * self = wp_properties_new_empty (); const gchar *value; while (key != NULL) { value = va_arg(args, gchar *); if (value && key[0]) wp_properties_set (self, key, value); key = va_arg(args, gchar *); } return self; } /** * wp_properties_new_string: * @str: a string containing a whitespace separated list of key=value pairs * (ex. "key1=value1 key2=value2") * * Constructs a new properties set that contains the properties that can * be parsed from the given string * * Returns: (transfer full): the newly constructed properties set */ WpProperties * wp_properties_new_string (const gchar * str) { WpProperties * self; g_return_val_if_fail (str != NULL, NULL); self = g_slice_new0 (WpProperties); g_ref_count_init (&self->ref); self->flags = 0; self->props = pw_properties_new_string (str); return self; } /** * wp_properties_new_wrap: * @props: a native `pw_properties` structure to wrap * * Constructs a new #WpProperties that wraps the given @props structure, * allowing reading & writing properties on that @props structure through * the #WpProperties API. * * Care must be taken when using this function, since the returned #WpProperties * object does not own the @props structure. Therefore, if the owner decides * to free @props, the returned #WpProperties will crash when used. In addition, * the returned #WpProperties object will not try to free @props when destroyed. * * Returns: (transfer full): the newly constructed properties set */ WpProperties * wp_properties_new_wrap (struct pw_properties * props) { WpProperties * self; g_return_val_if_fail (props != NULL, NULL); self = g_slice_new0 (WpProperties); g_ref_count_init (&self->ref); self->flags = FLAG_NO_OWNERSHIP; self->props = props; return self; } /** * wp_properties_new_take: * @props: a native `pw_properties` structure to wrap * * Constructs a new #WpProperties that wraps the given @props structure, * allowing reading & writing properties on that @props structure through * the #WpProperties API. * * In constrast with wp_properties_new_wrap(), this function assumes ownership * of the @props structure, so it will try to free @props when it is destroyed. * * Returns: (transfer full): the newly constructed properties set */ WpProperties * wp_properties_new_take (struct pw_properties * props) { WpProperties * self; g_return_val_if_fail (props != NULL, NULL); self = g_slice_new0 (WpProperties); g_ref_count_init (&self->ref); self->flags = 0; self->props = props; return self; } /** * wp_properties_new_copy: * @props: a native `pw_properties` structure to copy * * Constructs a new #WpProperties that contains a copy of all the properties * contained in the given @props structure. * * Returns: (transfer full): the newly constructed properties set */ WpProperties * wp_properties_new_copy (const struct pw_properties * props) { WpProperties * self; g_return_val_if_fail (props != NULL, NULL); self = g_slice_new0 (WpProperties); g_ref_count_init (&self->ref); self->flags = 0; self->props = pw_properties_copy (props); return self; } /** * wp_properties_new_wrap_dict: * @dict: a native `spa_dict` structure to wrap * * Constructs a new #WpProperties that wraps the given @dict structure, * allowing reading properties from that @dict through the #WpProperties API. * * Note that the returned object does not own the @dict, so care must be taken * not to free it externally while this #WpProperties object is alive. * * In addition, note that the returned #WpProperties object is immutable. That * means that you cannot add or modify any properties on it, since there is * no defined method for modifying a `struct spa_dict`. If you need to change * this properties set later, you should make a copy with wp_properties_copy(). * * Returns: (transfer full): the newly constructed properties set */ WpProperties * wp_properties_new_wrap_dict (const struct spa_dict * dict) { WpProperties * self; g_return_val_if_fail (dict != NULL, NULL); self = g_slice_new0 (WpProperties); g_ref_count_init (&self->ref); self->flags = FLAG_NO_OWNERSHIP | FLAG_IS_DICT; self->dict = dict; return self; } /** * wp_properties_new_copy_dict: * @dict: a native `spa_dict` structure to copy * * Constructs a new #WpProperties that contains a copy of all the properties * contained in the given @dict structure. * * Returns: (transfer full): the newly constructed properties set */ WpProperties * wp_properties_new_copy_dict (const struct spa_dict * dict) { WpProperties * self; g_return_val_if_fail (dict != NULL, NULL); self = g_slice_new0 (WpProperties); g_ref_count_init (&self->ref); self->flags = 0; self->props = pw_properties_new_dict (dict); return self; } /** * wp_properties_copy: * @other: a properties object * * Constructs and returns a new #WpProperties object that contains a copy * of all the properties contained in @other. * * Returns: (transfer full): the newly constructed properties set */ WpProperties * wp_properties_copy (WpProperties * other) { return wp_properties_new_copy_dict (wp_properties_peek_dict (other)); } static void wp_properties_free (WpProperties * self) { if (!(self->flags & FLAG_NO_OWNERSHIP)) pw_properties_free (self->props); g_slice_free (WpProperties, self); } /** * wp_properties_ref: * @self: a properties object * * Returns: (transfer full): @self with an additional reference count on it */ WpProperties * wp_properties_ref (WpProperties * self) { g_ref_count_inc (&self->ref); return self; } /** * wp_properties_unref: * @self: (transfer full): a properties object * * Decreases the reference count on @self and frees it when the ref count * reaches zero. */ void wp_properties_unref (WpProperties * self) { if (g_ref_count_dec (&self->ref)) wp_properties_free (self); } /** * wp_properties_ensure_unique_owner: * @self: (transfer full): a properties object * * Ensures that the given properties set is uniquely owned, which means: * - its reference count is 1 * - it is not wrapping a native `spa_dict` or `pw_properties` object * * If @self is not uniquely owned already, then it is unrefed and a copy of * it is returned instead. You should always consider @self as unsafe to use * after this call and you should use the returned object instead. * * Returns: (transfer full): the uniquely owned properties object */ WpProperties * wp_properties_ensure_unique_owner (WpProperties * self) { if (!g_ref_count_compare (&self->ref, 1) || self->flags & (FLAG_IS_DICT | FLAG_NO_OWNERSHIP)) { WpProperties *copy = wp_properties_copy (self); wp_properties_unref (self); return copy; } return self; } /** * wp_properties_update: * @self: a properties object * @props: a properties set that contains properties to update * * Updates (adds new or modifies existing) properties in @self, using the * given @props as a source. Any properties that are not contained in @props * are left untouched. * * Returns: the number of properties that were changed */ gint wp_properties_update (WpProperties * self, WpProperties * props) { g_return_val_if_fail (self != NULL, -EINVAL); g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL); return pw_properties_update (self->props, wp_properties_peek_dict (props)); } /** * wp_properties_update_from_dict: * @self: a properties object * @dict: a `spa_dict` that contains properties to update * * Updates (adds new or modifies existing) properties in @self, using the * given @dict as a source. Any properties that are not contained in @dict * are left untouched. * * Returns: the number of properties that were changed */ gint wp_properties_update_from_dict (WpProperties * self, const struct spa_dict * dict) { g_return_val_if_fail (self != NULL, -EINVAL); g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL); return pw_properties_update (self->props, dict); } /** * wp_properties_add: * @self: a properties object * @props: a properties set that contains properties to add * * Adds new properties in @self, using the given @props as a source. * Properties (keys) from @props that are already contained in @self * are not modified, unlike what happens with wp_properties_update(). * Properties in @self that are not contained in @props are left untouched. * * Returns: the number of properties that were changed */ gint wp_properties_add (WpProperties * self, WpProperties * props) { g_return_val_if_fail (self != NULL, -EINVAL); g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL); return pw_properties_add (self->props, wp_properties_peek_dict (props)); } /** * wp_properties_add_from_dict: * @self: a properties object * @dict: a `spa_dict` that contains properties to add * * Adds new properties in @self, using the given @dict as a source. * Properties (keys) from @dict that are already contained in @self * are not modified, unlike what happens with wp_properties_update_from_dict(). * Properties in @self that are not contained in @dict are left untouched. * * Returns: the number of properties that were changed */ gint wp_properties_add_from_dict (WpProperties * self, const struct spa_dict * dict) { g_return_val_if_fail (self != NULL, -EINVAL); g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL); return pw_properties_add (self->props, dict); } /** * wp_properties_update_keys: * @self: a properties set * @props: a properties set that contains properties to update * @key1: a property to update * @...: a list of additional properties to update, followed by %NULL * * Updates (adds new or modifies existing) properties in @self, using the * given @props as a source. * Unlike wp_properties_update(), this function only updates properties that * have one of the specified keys; the rest is left untouched. * * Returns: the number of properties that were changed */ gint wp_properties_update_keys (WpProperties * self, WpProperties * props, const gchar * key1, ...) { gint changed = 0; const gchar *value; g_return_val_if_fail (self != NULL, -EINVAL); g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL); va_list args; va_start (args, key1); for (; key1; key1 = va_arg (args, const gchar *)) { if ((value = wp_properties_get (props, key1)) != NULL) changed += wp_properties_set (self, key1, value); } return changed; } /** * wp_properties_update_keys_from_dict: * @self: a properties set * @dict: a `spa_dict` that contains properties to update * @key1: a property to update * @...: a list of additional properties to update, followed by %NULL * * Updates (adds new or modifies existing) properties in @self, using the * given @dict as a source. * Unlike wp_properties_update_from_dict(), this function only updates * properties that have one of the specified keys; the rest is left untouched. * * Returns: the number of properties that were changed */ gint wp_properties_update_keys_from_dict (WpProperties * self, const struct spa_dict * dict, const gchar * key1, ...) { gint changed = 0; const gchar *value; g_return_val_if_fail (self != NULL, -EINVAL); g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL); va_list args; va_start (args, key1); for (; key1; key1 = va_arg (args, const gchar *)) { if ((value = spa_dict_lookup (dict, key1)) != NULL) changed += wp_properties_set (self, key1, value); } return changed; } /** * wp_properties_update_keys_array: * @self: a properties set * @props: a properties set that contains properties to update * @keys: (array zero-terminated=1): the properties to update * * The same as wp_properties_update_keys(), using a NULL-terminated array * for specifying the keys to update * * Returns: the number of properties that were changed */ gint wp_properties_update_keys_array (WpProperties * self, WpProperties * props, const gchar * keys[]) { g_return_val_if_fail (self != NULL, -EINVAL); g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL); return pw_properties_update_keys (self->props, wp_properties_peek_dict (props), keys); } /** * wp_properties_add_keys: * @self: a properties set * @props: a properties set that contains properties to add * @key1: a property to add * @...: a list of additional properties to add, followed by %NULL * * Adds new properties in @self, using the given @props as a source. * Unlike wp_properties_add(), this function only adds properties that * have one of the specified keys; the rest is left untouched. * * Returns: the number of properties that were changed */ gint wp_properties_add_keys (WpProperties * self, WpProperties * props, const gchar * key1, ...) { gint changed = 0; const gchar *value; g_return_val_if_fail (self != NULL, -EINVAL); g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL); va_list args; va_start (args, key1); for (; key1; key1 = va_arg (args, const gchar *)) { if ((value = wp_properties_get (props, key1)) == NULL) continue; if (wp_properties_get (self, key1) == NULL) changed += wp_properties_set (self, key1, value); } return changed; } /** * wp_properties_add_keys_from_dict: * @self: a properties set * @dict: a `spa_dict` that contains properties to add * @key1: a property to add * @...: a list of additional properties to add, followed by %NULL * * Adds new properties in @self, using the given @dict as a source. * Unlike wp_properties_add_from_dict(), this function only adds * properties that have one of the specified keys; the rest is left untouched. * * Returns: the number of properties that were changed */ gint wp_properties_add_keys_from_dict (WpProperties * self, const struct spa_dict * dict, const gchar * key1, ...) { gint changed = 0; const gchar *value; g_return_val_if_fail (self != NULL, -EINVAL); g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL); va_list args; va_start (args, key1); for (; key1; key1 = va_arg (args, const gchar *)) { if ((value = spa_dict_lookup (dict, key1)) == NULL) continue; if (wp_properties_get (self, key1) == NULL) changed += wp_properties_set (self, key1, value); } return changed; } /** * wp_properties_add_keys_array: * @self: a properties set * @props: a properties set that contains properties to add * @keys: (array zero-terminated=1): the properties to add * * The same as wp_properties_add_keys(), using a NULL-terminated array * for specifying the keys to add * * Returns: the number of properties that were changed */ gint wp_properties_add_keys_array (WpProperties * self, WpProperties * props, const gchar * keys[]) { g_return_val_if_fail (self != NULL, -EINVAL); g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL); return pw_properties_add_keys (self->props, wp_properties_peek_dict (props), keys); } /** * wp_properties_get: * @self: a properties object * @key: a property key * * Returns: (transfer none) (nullable): the value of the property identified * with @key, or %NULL if this property is not contained in @self */ const gchar * wp_properties_get (WpProperties * self, const gchar * key) { g_return_val_if_fail (self != NULL, NULL); g_return_val_if_fail (key != NULL, NULL); return spa_dict_lookup (wp_properties_peek_dict (self), key); } /** * wp_properties_set: * @self: a properties object * @key: a property key * @value: (nullable): a property value * * Sets the given property @key - @value pair on @self. If the property * already existed, the value is overwritten with the new one. * * If the @value is %NULL, then the specified property is removed from @self * * Returns: %1 if the property was changed. %0 if nothing was changed because * the property already existed with the same value or because the key to * remove did not exist. */ gint wp_properties_set (WpProperties * self, const gchar * key, const gchar * value) { g_return_val_if_fail (self != NULL, -EINVAL); g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL); return pw_properties_set (self->props, key, value); } /** * wp_properties_setf: * @self: a properties object * @key: a property key * @format: a printf-style format to be formatted and set as a value for * this property @key * @...: arguments for @format * * Formats the given @format string with the specified arguments and sets the * result as a value of the property specified with @key * * Returns: %1 if the property was changed. %0 if nothing was changed because * the property already existed with the same value */ gint wp_properties_setf (WpProperties * self, const gchar * key, const gchar * format, ...) { gint res; va_list varargs; va_start (varargs, format); res = wp_properties_setf_valist (self, key, format, varargs); va_end (varargs); return res; } /** * wp_properties_setf_valist: * @self: a properties object * @key: a property key * @format: a printf-style format to be formatted and set as a value for * this property @key * @args: the variable arguments passed to wp_properties_setf() * * This is the `va_list` version of wp_properties_setf() * * Returns: %1 if the property was changed. %0 if nothing was changed because * the property already existed with the same value */ gint wp_properties_setf_valist (WpProperties * self, const gchar * key, const gchar * format, va_list args) { g_return_val_if_fail (self != NULL, -EINVAL); g_return_val_if_fail (!(self->flags & FLAG_IS_DICT), -EINVAL); return pw_properties_setva (self->props, key, format, args); } struct dict_iterator_data { WpProperties *properties; const struct spa_dict_item *item; }; static void dict_iterator_reset (WpIterator *it) { struct dict_iterator_data *it_data = wp_iterator_get_user_data (it); it_data->item = wp_properties_peek_dict (it_data->properties)->items; } static gboolean dict_iterator_next (WpIterator *it, GValue *item) { struct dict_iterator_data *it_data = wp_iterator_get_user_data (it); const struct spa_dict *dict = wp_properties_peek_dict (it_data->properties); if ((it_data->item - dict->items) < dict->n_items) { g_value_init (item, G_TYPE_POINTER); g_value_set_pointer (item, (gpointer) it_data->item); it_data->item++; return TRUE; } return FALSE; } static gboolean dict_iterator_fold (WpIterator *it, WpIteratorFoldFunc func, GValue *ret, gpointer data) { struct dict_iterator_data *it_data = wp_iterator_get_user_data (it); const struct spa_dict *dict = wp_properties_peek_dict (it_data->properties); const struct spa_dict_item *i; spa_dict_for_each (i, dict) { g_auto (GValue) item = G_VALUE_INIT; g_value_init (&item, G_TYPE_POINTER); g_value_set_pointer (&item, (gpointer) i); if (!func (&item, ret, data)) return FALSE; } return TRUE; } static void dict_iterator_finalize (WpIterator *it) { struct dict_iterator_data *it_data = wp_iterator_get_user_data (it); wp_properties_unref (it_data->properties); } static const WpIteratorMethods dict_iterator_methods = { .reset = dict_iterator_reset, .next = dict_iterator_next, .fold = dict_iterator_fold, .finalize = dict_iterator_finalize, }; /** * wp_properties_iterate: * @self: a properties object * * Returns: (transfer full): an iterator that iterates over the properties. * Use wp_properties_iterator_item_get_key() and * wp_properties_iterator_item_get_value() to parse the items returned by * this iterator. */ WpIterator * wp_properties_iterate (WpProperties * self) { g_autoptr (WpIterator) it = NULL; struct dict_iterator_data *it_data; g_return_val_if_fail (self != NULL, NULL); it = wp_iterator_new (&dict_iterator_methods, sizeof (struct dict_iterator_data)); it_data = wp_iterator_get_user_data (it); it_data->properties = wp_properties_ref (self); it_data->item = wp_properties_peek_dict (it_data->properties)->items; return g_steal_pointer (&it); } /** * wp_properties_iterator_item_get_key: * @item: a #GValue that was returned from the #WpIterator of * wp_properties_iterate() * * Returns: (transfer none): the property key of the @item */ const gchar * wp_properties_iterator_item_get_key (const GValue * item) { const struct spa_dict_item *dict_item = g_value_get_pointer (item); g_return_val_if_fail (dict_item != NULL, NULL); return dict_item->key; } /** * wp_properties_iterator_item_get_value: * @item: a #GValue that was returned from the #WpIterator of * wp_properties_iterate() * * Returns: (transfer none): the property value of the @item */ const gchar * wp_properties_iterator_item_get_value (const GValue * item) { const struct spa_dict_item *dict_item = g_value_get_pointer (item); g_return_val_if_fail (dict_item != NULL, NULL); return dict_item->value; } /** * wp_properties_peek_dict: * @self: a properties object * * Returns: (transfer none): the internal properties set as a `struct spa_dict *` */ const struct spa_dict * wp_properties_peek_dict (WpProperties * self) { g_return_val_if_fail (self != NULL, NULL); return (self->flags & FLAG_IS_DICT) ? self->dict : &self->props->dict; } /** * wp_properties_to_pw_properties: * @self: a properties object * * Returns: (transfer full): a copy of the properties in @self * as a `struct pw_properties` */ struct pw_properties * wp_properties_to_pw_properties (WpProperties * self) { g_return_val_if_fail (self != NULL, NULL); return pw_properties_new_dict (wp_properties_peek_dict (self)); } /** * wp_properties_matches: * @self: a properties object * @other: a set of properties to match * * Checks if the property values contained in @other are matching with the * values in @self. * * If a property is contained in one set and not the other, the result is not * affected. If a property is contained in both sets, then the value of the * property in @other is interpreted as a glob-style pattern * (using g_pattern_match_simple()) and the value in @self is checked to * see if it matches with this pattern. * * Returns: %TRUE if all matches were successfull, %FALSE if at least one * property value did not match */ gboolean wp_properties_matches (WpProperties * self, WpProperties *other) { const struct spa_dict * dict; const struct spa_dict_item *item; const gchar *value; g_return_val_if_fail (self != NULL, FALSE); /* Check if the property values match the ones from 'other' */ dict = wp_properties_peek_dict (self); spa_dict_for_each(item, dict) { value = wp_properties_get (other, item->key); if (value && !g_pattern_match_simple (value, item->value)) return FALSE; } return TRUE; }