/* WirePlumber * * Copyright © 2019-2020 Collabora Ltd. * @author Raghavendra Rao <raghavendra.rao@collabora.com> * * SPDX-License-Identifier: MIT */ /** * SECTION: WpMetadata * * The #WpMetadata class allows accessing the properties and methods of * Pipewire Jack metadata object (`struct pw_metadata`). * */ #define G_LOG_DOMAIN "wp-metadata" #include "metadata.h" #include "spa-type.h" #include "spa-pod.h" #include "debug.h" #include "private.h" #include "error.h" #include "wpenums.h" #include <pipewire/pipewire.h> #include <pipewire/array.h> #include <pipewire/extensions/metadata.h> #include <spa/pod/builder.h> #include <spa/pod/parser.h> #include <spa/pod/filter.h> /* WpMetadata */ typedef struct _WpMetadataPrivate WpMetadataPrivate; struct _WpMetadataPrivate { struct pw_metadata *iface; struct spa_hook listener; struct spa_hook_list hooks; struct pw_properties *properties; struct pw_array metadata; struct pw_proxy *proxy; }; G_DEFINE_TYPE_WITH_PRIVATE (WpMetadata, wp_metadata, WP_TYPE_PROXY) static void wp_metadata_init (WpMetadata * self) { } static void wp_metadata_finalize (GObject * object) { G_OBJECT_CLASS (wp_metadata_parent_class)->finalize (object); } static void wp_metadata_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { WpMetadata *self = WP_METADATA (proxy); WpMetadataPrivate *priv = wp_metadata_get_instance_private (self); priv->iface = (struct pw_metadata *) pw_proxy; } static void wp_metadata_class_init (WpMetadataClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; object_class->finalize = wp_metadata_finalize; proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Metadata; proxy_class->pw_iface_version = PW_VERSION_METADATA; proxy_class->pw_proxy_created = wp_metadata_pw_proxy_created; } /* WpImplMetadata */ typedef struct _WpImplMetadata WpImplMetadata; struct _WpImplMetadata { WpMetadata parent; struct spa_interface iface; struct spa_hook_list hooks; gboolean subscribed; }; G_DEFINE_TYPE (WpImplMetadata, wp_impl_metadata, WP_TYPE_METADATA) #define pw_metadata_emit(hooks,method,version,...) \ spa_hook_list_call_simple(hooks, struct pw_metadata_events, \ method, version, ##__VA_ARGS__) #define pw_metadata_emit_property(hooks,...) \ pw_metadata_emit(hooks,property, 0, ##__VA_ARGS__) struct item { uint32_t subject; char *key; char *type; char *value; }; static void clear_item(struct item *item) { free(item->key); free(item->type); free(item->value); spa_zero(*item); } static void set_item(struct item *item, uint32_t subject, const char *key, const char *type, const char *value) { item->subject = subject; item->key = strdup(key); item->type = strdup(type); item->value = strdup(value); } static void emit_properties(WpImplMetadata *self, const struct spa_dict *dict) { struct item *item; WpMetadataPrivate *priv = wp_metadata_get_instance_private (WP_METADATA (self)); pw_array_for_each(item, &priv->metadata) { wp_info_object (self, "metadata : %d %s %s %s", item->subject, item->key, item->type, item->value); pw_metadata_emit_property(&priv->hooks, item->subject, item->key, item->type, item->value); } } static int impl_add_listener(void *object, struct spa_hook *listener, const struct pw_metadata_events *events, void *data) { WpImplMetadata *self = WP_IMPL_METADATA (object); WpMetadataPrivate *priv = wp_metadata_get_instance_private (WP_METADATA (self)); struct spa_hook_list save; spa_hook_list_isolate (&priv->hooks, &save, listener, events, data); emit_properties(self, &priv->properties->dict); spa_hook_list_join (&priv->hooks, &save); return 0; } static struct item * find_item(WpImplMetadata *self, uint32_t subject, const char *key) { struct item *item; WpMetadataPrivate *priv = wp_metadata_get_instance_private (WP_METADATA (self)); pw_array_for_each(item, &priv->metadata) { if (item->subject == subject && (key == NULL || !strcmp(item->key, key))) { return item; } } return NULL; } static int clear_subjects(WpImplMetadata *self, uint32_t subject) { struct item *item; uint32_t removed = 0; WpMetadataPrivate *priv = wp_metadata_get_instance_private (WP_METADATA (self)); while (true) { item = find_item(self, subject, NULL); if (item == NULL) break; wp_debug_object (self, "remove id:%d key:%s", subject, item->key); clear_item(item); pw_array_remove(&priv->metadata, item); removed++; } if (removed > 0) pw_metadata_emit_property(&priv->hooks, subject, NULL, NULL, NULL); return 0; } static void clear_items(WpImplMetadata *self) { struct item *item; WpMetadataPrivate *priv = wp_metadata_get_instance_private (WP_METADATA (self)); pw_array_consume(item, &priv->metadata) clear_subjects(self, item->subject); pw_array_reset(&priv->metadata); } static int impl_set_property(void *object, uint32_t subject, const char *key, const char *type, const char *value) { WpImplMetadata *self = WP_IMPL_METADATA (object); WpMetadataPrivate *priv; struct item *item = NULL; g_return_val_if_fail (WP_IS_IMPL_METADATA (self), -1); priv = wp_metadata_get_instance_private (WP_METADATA (self)); if (key == NULL) return clear_subjects(self, subject); item = find_item(self, subject, key); if (item == NULL) { if (value == NULL) return 0; item = pw_array_add(&priv->metadata, sizeof(*item)); if (item == NULL) return -errno; } else { clear_item(item); } if (value != NULL) { if (type == NULL) type = "string"; set_item(item, subject, key, type, value); wp_debug_object (self, "%p: add id:%d key:%s type:%s value:%s", self, subject, key, type, value); } else { type = NULL; pw_array_remove(&priv->metadata, item); wp_debug_object(self, "%p: remove id:%d key:%s", self, subject, key); } pw_metadata_emit_property(&priv->hooks, subject, key, type, value); return 0; } static int impl_clear(void *object) { WpImplMetadata *self = WP_IMPL_METADATA (object); WpMetadataPrivate *priv = wp_metadata_get_instance_private (WP_METADATA (self)); clear_items(self); pw_array_clear(&priv->metadata); pw_properties_free(priv->properties); return 0; } static const struct pw_metadata_methods impl_metadata = { PW_VERSION_METADATA_METHODS, .add_listener = impl_add_listener, .set_property = impl_set_property, .clear = impl_clear, }; static void wp_impl_metadata_init (WpImplMetadata * self) { WpMetadataPrivate *priv = wp_metadata_get_instance_private (WP_METADATA (self)); self->iface = SPA_INTERFACE_INIT ( PW_TYPE_INTERFACE_Metadata, PW_VERSION_METADATA, &impl_metadata, self); spa_hook_list_init (&priv->hooks); priv->iface = (struct pw_metadata *) &self->iface; priv->properties = pw_properties_new(NULL, NULL); pw_array_init(&priv->metadata, 4096); } static void wp_impl_metadata_finalize (GObject * object) { G_OBJECT_CLASS (wp_impl_metadata_parent_class)->finalize (object); } static void wp_impl_metadata_augment (WpProxy * proxy, WpProxyFeatures features) { WpImplMetadata *self = WP_IMPL_METADATA (proxy); WpMetadataPrivate *priv = wp_metadata_get_instance_private (WP_METADATA (self)); /* PW_PROXY depends on BOUND */ if (features & WP_PROXY_FEATURE_PW_PROXY) features |= WP_PROXY_FEATURE_BOUND; if (features & WP_PROXY_FEATURE_BOUND) { g_autoptr (WpCore) core = wp_proxy_get_core (proxy); struct pw_core *pw_core = wp_core_get_pw_core (core); /* no pw_core -> we are not connected */ if (!pw_core) { wp_proxy_augment_error (proxy, g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, "The WirePlumber core is not connected; " "object cannot be exported to PipeWire")); wp_critical(G_LOG_DOMAIN "metadata : FAIL - Exiting %s",__FUNCTION__); return; } wp_proxy_set_pw_proxy (proxy, pw_core_export (pw_core, PW_TYPE_INTERFACE_Metadata, &priv->properties->dict, priv->iface, 0)); } } static void wp_impl_metadata_class_init (WpImplMetadataClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; object_class->finalize = wp_impl_metadata_finalize; proxy_class->augment = wp_impl_metadata_augment; proxy_class->enum_params = NULL; proxy_class->subscribe_params = NULL; proxy_class->pw_proxy_created = NULL; } WpImplMetadata * wp_impl_metadata_new (WpCore * core) { g_return_val_if_fail (WP_IS_CORE (core), NULL); return g_object_new (WP_TYPE_IMPL_METADATA, "core", core, NULL); }