diff --git a/modules/meson.build b/modules/meson.build index 015f1a363a8cf3d0ec090c0dbce89b8835d75528..11d674db669f9f21db3c0964ead0e877bf3a5ff8 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -42,6 +42,19 @@ shared_library( dependencies : [gio_dep, wp_dep, pipewire_dep], ) +shared_library( + 'wireplumber-module-config-static-nodes', + [ + 'module-config-static-nodes/parser-node.c', + 'module-config-static-nodes/context.c', + 'module-config-static-nodes.c', + ], + c_args : [common_c_args, '-DG_LOG_DOMAIN="m-config-static-nodes"'], + install : true, + install_dir : wireplumber_module_dir, + dependencies : [wp_dep, wptoml_dep, pipewire_dep], +) + shared_library( 'wireplumber-module-config-endpoint', [ diff --git a/modules/module-config-static-nodes.c b/modules/module-config-static-nodes.c new file mode 100644 index 0000000000000000000000000000000000000000..68777da9d9f70802a88fc5cc9b900b2c0ab2299e --- /dev/null +++ b/modules/module-config-static-nodes.c @@ -0,0 +1,18 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas <julian.bouzas@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#include <wp/wp.h> + +#include "module-config-static-nodes/context.h" + +void +wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) +{ + WpConfigStaticNodesContext *ctx = wp_config_static_nodes_context_new (core); + wp_module_set_destroy_callback (module, g_object_unref, ctx); +} diff --git a/modules/module-config-static-nodes/context.c b/modules/module-config-static-nodes/context.c new file mode 100644 index 0000000000000000000000000000000000000000..99d6744f2e2c574ad206bb41a2797abd886574b8 --- /dev/null +++ b/modules/module-config-static-nodes/context.c @@ -0,0 +1,233 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas <julian.bouzas@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#include <pipewire/pipewire.h> + +#include <wp/wp.h> + +#include "parser-node.h" +#include "context.h" + +struct _WpConfigStaticNodesContext +{ + GObject parent; + + /* Props */ + GWeakRef core; + + WpObjectManager *devices_om; + GPtrArray *static_nodes; +}; + +enum { + PROP_0, + PROP_CORE, +}; + +enum { + SIGNAL_NODE_CREATED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS]; + +G_DEFINE_TYPE (WpConfigStaticNodesContext, wp_config_static_nodes_context, + G_TYPE_OBJECT) + +static void +wp_config_static_nodes_context_create_node (WpConfigStaticNodesContext *self, + const struct WpParserNodeData *node_data) +{ + g_autoptr (WpProxy) node_proxy = NULL; + g_autoptr (WpCore) core = g_weak_ref_get (&self->core); + g_return_if_fail (core); + + /* Create the node */ + node_proxy = node_data->n.local ? + wp_core_create_local_object (core, node_data->n.factory, + PW_TYPE_INTERFACE_Node, PW_VERSION_NODE_PROXY, node_data->n.props) : + wp_core_create_remote_object (core, node_data->n.factory, + PW_TYPE_INTERFACE_Node, PW_VERSION_NODE_PROXY, node_data->n.props); + if (!node_proxy) { + g_warning ("WpConfigStaticNodesContext:%p: failed to create node: %s", self, + g_strerror (errno)); + return; + } + + /* Add the node to the array */ + g_ptr_array_add (self->static_nodes, g_object_ref (node_proxy)); + g_debug ("WpConfigStaticNodesContext:%p: added static node: %s", self, + node_data->n.factory); + + /* Emit the node-created signal */ + g_signal_emit (self, signals[SIGNAL_NODE_CREATED], 0, node_proxy); +} + +static void +on_device_added (WpObjectManager *om, WpProxy *proxy, gpointer p) +{ + WpConfigStaticNodesContext *self = p; + g_autoptr (WpProperties) dev_props = + wp_proxy_device_get_properties (WP_PROXY_DEVICE (proxy)); + g_autoptr (WpCore) core = g_weak_ref_get (&self->core); + g_autoptr (WpConfiguration) config = wp_configuration_get_instance (core); + g_autoptr (WpConfigParser) parser = NULL; + const struct WpParserNodeData *node_data = NULL; + + /* Get the parser node data and skip the node if not found */ + parser = wp_configuration_get_parser (config, WP_PARSER_NODE_EXTENSION); + node_data = wp_config_parser_get_matched_data (parser, dev_props); + if (!node_data) + return; + + /* Create the node */ + wp_config_static_nodes_context_create_node (self, node_data); +} + +static gboolean +parser_node_foreach_func (const struct WpParserNodeData *node_data, + gpointer data) +{ + WpConfigStaticNodesContext *self = data; + + /* Only create nodes that don't have match-device info */ + if (!node_data->has_md) { + wp_config_static_nodes_context_create_node (self, node_data); + return TRUE; + } + + return TRUE; +} + +static void +start_static_nodes (WpConfigStaticNodesContext *self) +{ + g_autoptr (WpCore) core = g_weak_ref_get (&self->core); + g_autoptr (WpConfiguration) config = wp_configuration_get_instance (core); + g_autoptr (WpConfigParser) parser = + wp_configuration_get_parser (config, WP_PARSER_NODE_EXTENSION); + + /* Create static nodes without match-device */ + wp_parser_node_foreach (WP_PARSER_NODE (parser), parser_node_foreach_func, + self); +} + +static void +wp_config_static_nodes_context_constructed (GObject * object) +{ + WpConfigStaticNodesContext *self = WP_CONFIG_STATIC_NODES_CONTEXT (object); + g_autoptr (WpCore) core = g_weak_ref_get (&self->core); + g_autoptr (WpConfiguration) config = wp_configuration_get_instance (core); + + /* Add the node parser and parse the node files */ + wp_configuration_add_extension (config, WP_PARSER_NODE_EXTENSION, + WP_TYPE_PARSER_NODE); + wp_configuration_reload (config, WP_PARSER_NODE_EXTENSION); + + /* Install the object manager */ + wp_core_install_object_manager (core, self->devices_om); + + /* Start creating static nodes when the connected callback is triggered */ + g_signal_connect_object (core, "remote-state-changed::connected", + (GCallback) start_static_nodes, self, G_CONNECT_SWAPPED); + + G_OBJECT_CLASS (wp_config_static_nodes_context_parent_class)->constructed (object); +} + +static void +wp_config_static_nodes_context_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + WpConfigStaticNodesContext *self = WP_CONFIG_STATIC_NODES_CONTEXT (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_config_static_nodes_context_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + WpConfigStaticNodesContext *self = WP_CONFIG_STATIC_NODES_CONTEXT (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_config_static_nodes_context_finalize (GObject *object) +{ + WpConfigStaticNodesContext *self = WP_CONFIG_STATIC_NODES_CONTEXT (object); + + g_clear_object (&self->devices_om); + g_clear_pointer (&self->static_nodes, g_ptr_array_unref); + + g_autoptr (WpCore) core = g_weak_ref_get (&self->core); + if (core) { + g_autoptr (WpConfiguration) config = wp_configuration_get_instance (core); + wp_configuration_remove_extension (config, WP_PARSER_NODE_EXTENSION); + } + g_weak_ref_clear (&self->core); + + G_OBJECT_CLASS (wp_config_static_nodes_context_parent_class)->finalize (object); +} + +static void +wp_config_static_nodes_context_init (WpConfigStaticNodesContext *self) +{ + self->static_nodes = g_ptr_array_new_with_free_func (g_object_unref); + self->devices_om = wp_object_manager_new (); + + /* Only handle devices */ + wp_object_manager_add_proxy_interest (self->devices_om, + PW_TYPE_INTERFACE_Device, NULL, WP_PROXY_FEATURE_INFO); + g_signal_connect (self->devices_om, "object-added", + (GCallback) on_device_added, self); +} + +static void +wp_config_static_nodes_context_class_init (WpConfigStaticNodesContextClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->constructed = wp_config_static_nodes_context_constructed; + object_class->finalize = wp_config_static_nodes_context_finalize; + object_class->set_property = wp_config_static_nodes_context_set_property; + object_class->get_property = wp_config_static_nodes_context_get_property; + + /* Properties */ + g_object_class_install_property (object_class, PROP_CORE, + g_param_spec_object ("core", "core", "The wireplumber core", + WP_TYPE_CORE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + /* Signals */ + signals[SIGNAL_NODE_CREATED] = g_signal_new ("node-created", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, WP_TYPE_PROXY); +} + +WpConfigStaticNodesContext * +wp_config_static_nodes_context_new (WpCore *core) +{ + return g_object_new (wp_config_static_nodes_context_get_type (), + "core", core, + NULL); +} diff --git a/modules/module-config-static-nodes/context.h b/modules/module-config-static-nodes/context.h new file mode 100644 index 0000000000000000000000000000000000000000..bdd9ba3b639a40306b3e8e010b76d9739ecf8691 --- /dev/null +++ b/modules/module-config-static-nodes/context.h @@ -0,0 +1,24 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas <julian.bouzas@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_CONFIG_STATIC_NODES_CONTEXT_H__ +#define __WIREPLUMBER_CONFIG_STATIC_NODES_CONTEXT_H__ + +#include <wp/wp.h> + +G_BEGIN_DECLS + +#define WP_TYPE_CONFIG_STATIC_NODES_CONTEXT (wp_config_static_nodes_context_get_type ()) +G_DECLARE_FINAL_TYPE (WpConfigStaticNodesContext, wp_config_static_nodes_context, + WP, CONFIG_STATIC_NODES_CONTEXT, GObject); + +WpConfigStaticNodesContext * wp_config_static_nodes_context_new (WpCore *core); + +G_END_DECLS + +#endif diff --git a/modules/module-config-static-nodes/parser-node.c b/modules/module-config-static-nodes/parser-node.c new file mode 100644 index 0000000000000000000000000000000000000000..f14f82fe61c40f690e9daa498c6e6bf44bec0e6f --- /dev/null +++ b/modules/module-config-static-nodes/parser-node.c @@ -0,0 +1,247 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas <julian.bouzas@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#include <wptoml/wptoml.h> + +#include <pipewire/pipewire.h> + +#include "parser-node.h" + +struct _WpParserNode +{ + GObject parent; + + GPtrArray *datas; +}; + +static void wp_parser_node_config_parser_init (gpointer iface, + gpointer iface_data); + +G_DEFINE_TYPE_WITH_CODE (WpParserNode, wp_parser_node, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (WP_TYPE_CONFIG_PARSER, + wp_parser_node_config_parser_init)) + +static void +wp_parser_node_data_destroy (gpointer p) +{ + struct WpParserNodeData *data = p; + + /* Free the strings */ + g_clear_pointer (&data->md.props, wp_properties_unref); + g_clear_pointer (&data->n.factory, g_free); + g_clear_pointer (&data->n.props, wp_properties_unref); + + g_slice_free (struct WpParserNodeData, data); +} + +static void +parse_properties_for_each (const WpTomlTable *table, gpointer user_data) +{ + WpProperties *props = user_data; + g_return_if_fail (props); + + /* Skip unparsed tables */ + if (!table) + return; + + /* Parse the name and value */ + g_autofree gchar *name = wp_toml_table_get_string (table, "name"); + g_autofree gchar *value = wp_toml_table_get_string (table, "value"); + + /* Set the property */ + if (name && value) + wp_properties_set (props, name, value); +} + +static WpProperties * +parse_properties (WpTomlTable *table, const char *name) +{ + WpProperties *props = wp_properties_new_empty (); + + g_autoptr (WpTomlTableArray) properties = NULL; + properties = wp_toml_table_get_array_table (table, name); + if (properties) + wp_toml_table_array_for_each (properties, parse_properties_for_each, props); + + return props; +} + +static struct WpParserNodeData * +wp_parser_node_data_new (const gchar *location) +{ + g_autoptr (WpTomlFile) file = NULL; + g_autoptr (WpTomlTable) table = NULL, md = NULL, n = NULL; + struct WpParserNodeData *res = NULL; + + /* File format: + * ------------ + * [match-device] + * priority (uint32) + * properties (WpProperties) + * + * [node] + * factory (string) + * local (boolean) + * properties (WpProperties) + */ + + /* Get the TOML file */ + file = wp_toml_file_new (location); + if (!file) + return NULL; + + /* Get the file table */ + table = wp_toml_file_get_table (file); + if (!table) + return NULL; + + /* Create the node data */ + res = g_slice_new0(struct WpParserNodeData); + + /* Get the match-device table */ + res->has_md = FALSE; + md = wp_toml_table_get_table (table, "match-device"); + if (md) { + res->has_md = TRUE; + + /* Get the priority from the match-device table */ + res->md.priority = 0; + wp_toml_table_get_uint32 (md, "priority", &res->md.priority); + + /* Get the match device properties */ + res->md.props = parse_properties (md, "properties"); + } + + /* Get the node table */ + n = wp_toml_table_get_table (table, "node"); + if (!n) + goto error; + + /* Get factory from the node table */ + res->n.factory = wp_toml_table_get_string (n, "factory"); + + /* Get local from the node table */ + res->n.local = FALSE; + wp_toml_table_get_boolean (n, "local", &res->n.local); + + /* Get the node properties */ + res->n.props = parse_properties (n, "properties"); + + return res; + +error: + g_clear_pointer (&res, wp_parser_node_data_destroy); + return NULL; +} + +static gint +compare_datas_func (gconstpointer a, gconstpointer b) +{ + struct WpParserNodeData *da = *(struct WpParserNodeData *const *)a; + struct WpParserNodeData *db = *(struct WpParserNodeData *const *)b; + + return db->md.priority - da->md.priority; +} + +static gboolean +wp_parser_node_add_file (WpConfigParser *parser, + const gchar *name) +{ + WpParserNode *self = WP_PARSER_NODE (parser); + struct WpParserNodeData *data; + + /* Parse the file */ + data = wp_parser_node_data_new (name); + if (!data) { + g_warning ("Failed to parse configuration file '%s'", name); + return FALSE; + } + + /* Add the data to the array */ + g_ptr_array_add(self->datas, data); + + /* Sort the array by priority */ + g_ptr_array_sort(self->datas, compare_datas_func); + + return TRUE; +} + +static gconstpointer +wp_parser_node_get_matched_data (WpConfigParser *parser, gpointer data) +{ + WpParserNode *self = WP_PARSER_NODE (parser); + WpProperties *props = data; + const struct WpParserNodeData *d = NULL; + + g_return_val_if_fail (props, NULL); + + /* Find the first data that matches device properties */ + for (guint i = 0; i < self->datas->len; i++) { + d = g_ptr_array_index(self->datas, i); + if (d->has_md && wp_properties_matches (props, d->md.props)) + return d; + } + + return NULL; +} + +static void +wp_parser_node_reset (WpConfigParser *parser) +{ + WpParserNode *self = WP_PARSER_NODE (parser); + + g_ptr_array_set_size (self->datas, 0); +} + +static void +wp_parser_node_config_parser_init (gpointer iface, gpointer iface_data) +{ + WpConfigParserInterface *cp_iface = iface; + + cp_iface->add_file = wp_parser_node_add_file; + cp_iface->get_matched_data = wp_parser_node_get_matched_data; + cp_iface->reset = wp_parser_node_reset; +} + +static void +wp_parser_node_finalize (GObject * object) +{ + WpParserNode *self = WP_PARSER_NODE (object); + + g_clear_pointer (&self->datas, g_ptr_array_unref); + + G_OBJECT_CLASS (wp_parser_node_parent_class)->finalize (object); +} + +static void +wp_parser_node_init (WpParserNode * self) +{ + self->datas = g_ptr_array_new_with_free_func (wp_parser_node_data_destroy); +} + +static void +wp_parser_node_class_init (WpParserNodeClass * klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->finalize = wp_parser_node_finalize; +} + +void +wp_parser_node_foreach (WpParserNode *self, WpParserNodeForeachFunction f, + gpointer data) +{ + const struct WpParserNodeData *d; + + for (guint i = 0; i < self->datas->len; i++) { + d = g_ptr_array_index(self->datas, i); + if (!f (d, data)) + break; + } +} diff --git a/modules/module-config-static-nodes/parser-node.h b/modules/module-config-static-nodes/parser-node.h new file mode 100644 index 0000000000000000000000000000000000000000..ee66052d124a4166e92b4c55103bc7366fc9b837 --- /dev/null +++ b/modules/module-config-static-nodes/parser-node.h @@ -0,0 +1,41 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas <julian.bouzas@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_PARSER_NODE_H__ +#define __WIREPLUMBER_PARSER_NODE_H__ + +#include <wp/wp.h> + +G_BEGIN_DECLS + +#define WP_PARSER_NODE_EXTENSION "node" + +struct WpParserNodeData { + struct MatchDevice { + guint priority; + WpProperties *props; + } md; + gboolean has_md; + struct Node { + char *factory; + gboolean local; + WpProperties *props; + } n; +}; + +#define WP_TYPE_PARSER_NODE (wp_parser_node_get_type ()) +G_DECLARE_FINAL_TYPE (WpParserNode, wp_parser_node, WP, PARSER_NODE, GObject) + +typedef gboolean (*WpParserNodeForeachFunction) ( + const struct WpParserNodeData *parser_data, gpointer data); +void wp_parser_node_foreach (WpParserNode *self, WpParserNodeForeachFunction f, + gpointer data); + +G_END_DECLS + +#endif diff --git a/src/config/wireplumber.conf b/src/config/wireplumber.conf index e16d6247b6da94406bd92a621917e32653a7c140..763432154721eea6ca6eff60e0a01a9e8bf0adb1 100644 --- a/src/config/wireplumber.conf +++ b/src/config/wireplumber.conf @@ -31,6 +31,9 @@ load-module C libwireplumber-module-monitor { "factory": <"api.v4l2.enum.udev"> } +# Implements static nodes creation based on TOML configuration files +load-module C libwireplumber-module-config-static-nodes + # Implements endpoint creation based on TOML configuration files load-module C libwireplumber-module-config-endpoint