Skip to content
Snippets Groups Projects
Commit 4ec61d79 authored by Julian Bouzas's avatar Julian Bouzas
Browse files

config-static-nodes: add config static nodes module

This module allows wireplumber to create static nodes that match a specific
device using a spa node factory. Matching is optional, and if there is no match,
the node will always be created.
parent 3396470f
No related branches found
No related tags found
No related merge requests found
...@@ -42,6 +42,19 @@ shared_library( ...@@ -42,6 +42,19 @@ shared_library(
dependencies : [gio_dep, wp_dep, pipewire_dep], 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( shared_library(
'wireplumber-module-config-endpoint', 'wireplumber-module-config-endpoint',
[ [
......
/* 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);
}
/* 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);
}
/* 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
/* 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;
}
}
/* 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
...@@ -31,6 +31,9 @@ load-module C libwireplumber-module-monitor { ...@@ -31,6 +31,9 @@ load-module C libwireplumber-module-monitor {
"factory": <"api.v4l2.enum.udev"> "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 # Implements endpoint creation based on TOML configuration files
load-module C libwireplumber-module-config-endpoint load-module C libwireplumber-module-config-endpoint
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment