Commit acda80d7 authored by Julian Bouzas's avatar Julian Bouzas
Browse files

modules: add config endpoint module

parent 50f06baf
......@@ -43,26 +43,17 @@ shared_library(
)
shared_library(
'wireplumber-module-pw-audio-client',
'wireplumber-module-config-endpoint',
[
'module-pw-audio-client.c',
'module-config-endpoint/parser-endpoint.c',
'module-config-endpoint/parser-streams.c',
'module-config-endpoint/context.c',
'module-config-endpoint.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pw-audio-client"'],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-config-endpoint"'],
install : true,
install_dir : wireplumber_module_dir,
dependencies : [wp_dep, pipewire_dep],
)
shared_library(
'wireplumber-module-pw-alsa-udev',
[
'module-pw-alsa-udev.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pw-alsa-udev"'],
install : true,
install_dir : wireplumber_module_dir,
dependencies : [wp_dep, pipewire_dep],
dependencies : [wp_dep, wptoml_dep, pipewire_dep],
)
shared_library(
......
/* 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 "module-config-endpoint/context.h"
struct module_data
{
WpConfigEndpointContext *ctx;
};
static void
module_destroy (gpointer d)
{
struct module_data *data = d;
g_clear_object (&data->ctx);
g_slice_free (struct module_data, data);
}
void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
{
struct module_data *data;
/* Create the module data */
data = g_slice_new0 (struct module_data);
data->ctx = wp_config_endpoint_context_new (core);
/* Set the module destroy callback */
wp_module_set_destroy_callback (module, module_destroy, data);
}
/* 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-endpoint.h"
#include "parser-streams.h"
#include "context.h"
struct _WpConfigEndpointContext
{
GObject parent;
/* Props */
GWeakRef core;
WpObjectManager *om;
GHashTable *registered_endpoints;
};
enum {
PROP_0,
PROP_CORE,
};
enum {
SIGNAL_ENDPOINT_CREATED,
N_SIGNALS
};
static guint signals[N_SIGNALS];
G_DEFINE_TYPE (WpConfigEndpointContext, wp_config_endpoint_context,
G_TYPE_OBJECT)
static void
on_endpoint_created (GObject *initable, GAsyncResult *res, gpointer d)
{
WpConfigEndpointContext *self = d;
g_autoptr (WpBaseEndpoint) endpoint = NULL;
g_autoptr (WpProxy) proxy = NULL;
guint global_id = 0;
GError *error = NULL;
/* Get the endpoint */
endpoint = wp_base_endpoint_new_finish (initable, res, &error);
if (error) {
g_warning ("Failed to create endpoint: %s", error->message);
return;
}
/* Get the endpoint global id */
g_object_get (endpoint, "proxy-node", &proxy, NULL);
global_id = wp_proxy_get_global_id (proxy);
/* Register the endpoint and add it to the table */
wp_base_endpoint_register (endpoint);
g_hash_table_insert (self->registered_endpoints, GUINT_TO_POINTER (global_id),
g_object_ref (endpoint));
/* Emit the endpoint-created signal */
g_signal_emit (self, signals[SIGNAL_ENDPOINT_CREATED], 0, endpoint);
}
static GVariant *
create_streams_variant (WpConfiguration *config, const char *streams)
{
g_autoptr (WpConfigParser) parser = NULL;
const struct WpParserStreamsData *streams_data = NULL;
g_autoptr (GVariantBuilder) ba = NULL;
if (!streams || !config)
return NULL;
/* Get the streams parser */
parser = wp_configuration_get_parser (config, WP_PARSER_STREAMS_EXTENSION);
if (!parser)
return NULL;
/* Get the streams data */
streams_data = wp_config_parser_get_matched_data (parser, (gpointer)streams);
if (!streams_data || streams_data->n_streams <= 0)
return NULL;
/* Build the variant array with the stream name and priority */
ba = g_variant_builder_new (G_VARIANT_TYPE ("a(su)"));
g_variant_builder_init (ba, G_VARIANT_TYPE_ARRAY);
for (guint i = 0; i < streams_data->n_streams; i++)
g_variant_builder_add (ba, "(su)", streams_data->streams[i].name,
streams_data->streams[i].priority);
return g_variant_new ("a(su)", ba);
}
static void
on_node_added (WpObjectManager *om, WpProxy *proxy, gpointer d)
{
WpConfigEndpointContext *self = d;
g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
g_autoptr (WpConfiguration) config = wp_configuration_get_instance (core);
WpProxyNode *proxy_node = WP_PROXY_NODE (proxy);
g_autoptr (WpProperties) props = wp_proxy_node_get_properties (proxy_node);
g_autoptr (WpConfigParser) parser = NULL;
const struct WpParserEndpointData *endpoint_data = NULL;
GVariantBuilder b;
g_autoptr (GVariant) endpoint_props = NULL;
const char *media_class = NULL, *name = NULL;
g_autoptr (GVariant) streams_variant = NULL;
/* Get the linked and ep streams data */
parser = wp_configuration_get_parser (config, WP_PARSER_ENDPOINT_EXTENSION);
endpoint_data = wp_config_parser_get_matched_data (parser, proxy_node);
if (!endpoint_data)
return;
/* Set the name if it is null */
name = endpoint_data->e.name;
if (!name)
name = wp_properties_get (props, PW_KEY_NODE_NAME);
/* Set the media class if it is null */
media_class = endpoint_data->e.media_class;
if (!media_class)
media_class = wp_properties_get (props, PW_KEY_MEDIA_CLASS);
/* Create the streams variant */
streams_variant = create_streams_variant (config, endpoint_data->e.streams);
/* Set the properties */
g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&b, "{sv}",
"name", g_variant_new_take_string (g_strdup_printf ("%s", name)));
g_variant_builder_add (&b, "{sv}",
"media-class", g_variant_new_string (media_class));
g_variant_builder_add (&b, "{sv}",
"direction", g_variant_new_uint32 (endpoint_data->e.direction));
g_variant_builder_add (&b, "{sv}",
"proxy-node", g_variant_new_uint64 ((guint64) proxy));
if (streams_variant)
g_variant_builder_add (&b, "{sv}", "streams",
g_steal_pointer (&streams_variant));
endpoint_props = g_variant_builder_end (&b);
/* Create the endpoint async */
wp_factory_make (core, endpoint_data->e.type, WP_TYPE_BASE_ENDPOINT,
endpoint_props, on_endpoint_created, self);
}
static void
on_node_removed (WpObjectManager *om, WpProxy *proxy, gpointer d)
{
WpConfigEndpointContext *self = d;
WpBaseEndpoint *endpoint = NULL;
guint32 id = wp_proxy_get_global_id (proxy);
/* Get the endpoint */
endpoint = g_hash_table_lookup (self->registered_endpoints,
GUINT_TO_POINTER(id));
if (!endpoint)
return;
/* Unregister the endpoint and remove it from the table */
wp_base_endpoint_unregister (endpoint);
g_hash_table_remove (self->registered_endpoints, GUINT_TO_POINTER(id));
}
static void
wp_config_endpoint_context_constructed (GObject * object)
{
WpConfigEndpointContext *self = WP_CONFIG_ENDPOINT_CONTEXT (object);
g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
g_return_if_fail (core);
g_autoptr (WpConfiguration) config = wp_configuration_get_instance (core);
g_return_if_fail (config);
/* Add the endpoint and streams parsers */
wp_configuration_add_extension (config, WP_PARSER_ENDPOINT_EXTENSION,
WP_TYPE_PARSER_ENDPOINT);
wp_configuration_add_extension (config, WP_PARSER_STREAMS_EXTENSION,
WP_TYPE_PARSER_STREAMS);
/* Parse the files */
wp_configuration_reload (config, WP_PARSER_ENDPOINT_EXTENSION);
wp_configuration_reload (config, WP_PARSER_STREAMS_EXTENSION);
/* Install the object manager */
wp_core_install_object_manager (core, self->om);
G_OBJECT_CLASS (wp_config_endpoint_context_parent_class)->constructed (object);
}
static void
wp_config_endpoint_context_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpConfigEndpointContext *self = WP_CONFIG_ENDPOINT_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_endpoint_context_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpConfigEndpointContext *self = WP_CONFIG_ENDPOINT_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_endpoint_context_finalize (GObject *object)
{
WpConfigEndpointContext *self = WP_CONFIG_ENDPOINT_CONTEXT (object);
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_ENDPOINT_EXTENSION);
wp_configuration_remove_extension (config, WP_PARSER_STREAMS_EXTENSION);
}
g_weak_ref_clear (&self->core);
g_clear_object (&self->om);
g_clear_pointer (&self->registered_endpoints, g_hash_table_unref);
G_OBJECT_CLASS (wp_config_endpoint_context_parent_class)->finalize (object);
}
static void
wp_config_endpoint_context_init (WpConfigEndpointContext *self)
{
self->om = wp_object_manager_new ();
self->registered_endpoints = g_hash_table_new_full (g_direct_hash,
g_direct_equal, NULL, (GDestroyNotify) g_object_unref);
/* Only handle augmented nodes with info set */
wp_object_manager_add_proxy_interest (self->om, PW_TYPE_INTERFACE_Node, NULL,
WP_PROXY_FEATURE_INFO);
/* Register the global added/removed callbacks */
g_signal_connect(self->om, "object-added",
(GCallback) on_node_added, self);
g_signal_connect(self->om, "object-removed",
(GCallback) on_node_removed, self);
}
static void
wp_config_endpoint_context_class_init (WpConfigEndpointContextClass *klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->constructed = wp_config_endpoint_context_constructed;
object_class->finalize = wp_config_endpoint_context_finalize;
object_class->set_property = wp_config_endpoint_context_set_property;
object_class->get_property = wp_config_endpoint_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_ENDPOINT_CREATED] = g_signal_new ("endpoint-created",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 1, WP_TYPE_ENDPOINT);
}
WpConfigEndpointContext *
wp_config_endpoint_context_new (WpCore *core)
{
return g_object_new (wp_config_endpoint_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_ENDPOINT_CONTEXT_H__
#define __WIREPLUMBER_CONFIG_ENDPOINT_CONTEXT_H__
#include <wp/wp.h>
G_BEGIN_DECLS
#define WP_TYPE_CONFIG_ENDPOINT_CONTEXT (wp_config_endpoint_context_get_type ())
G_DECLARE_FINAL_TYPE (WpConfigEndpointContext, wp_config_endpoint_context,
WP, CONFIG_ENDPOINT_CONTEXT, GObject);
WpConfigEndpointContext * wp_config_endpoint_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-endpoint.h"
struct _WpParserEndpoint
{
GObject parent;
GPtrArray *datas;
};
static void wp_parser_endpoint_config_parser_init (gpointer iface,
gpointer iface_data);
G_DEFINE_TYPE_WITH_CODE (WpParserEndpoint, wp_parser_endpoint,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (WP_TYPE_CONFIG_PARSER,
wp_parser_endpoint_config_parser_init))
static void
wp_parser_endpoint_data_destroy (gpointer p)
{
struct WpParserEndpointData *data = p;
/* Free the strings */
g_clear_pointer (&data->mn.props, wp_properties_unref);
g_clear_pointer (&data->e.name, g_free);
g_clear_pointer (&data->e.media_class, g_free);
g_clear_pointer (&data->e.props, wp_properties_unref);
g_clear_pointer (&data->e.type, g_free);
g_clear_pointer (&data->e.streams, g_free);
g_slice_free (struct WpParserEndpointData, 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 guint
parse_endpoint_direction (const char *direction)
{
if (g_strcmp0 (direction, "input") == 0)
return PW_DIRECTION_INPUT;
else if (g_strcmp0 (direction, "output") == 0)
return PW_DIRECTION_OUTPUT;
g_return_val_if_reached (PW_DIRECTION_INPUT);
}
static struct WpParserEndpointData *
wp_parser_endpoint_data_new (const gchar *location)
{
g_autoptr (WpTomlFile) file = NULL;
g_autoptr (WpTomlTable) table = NULL, mn = NULL, e = NULL;
g_autoptr (WpTomlArray) streams = NULL;
struct WpParserEndpointData *res = NULL;
g_autofree char *direction = NULL;
/* File format:
* ------------
* [match-node]
* priority (uint32)
* properties (WpProperties)
*
* [endpoint]
* name (string)
* media_class (string)
* direction (string)
* priority (uint32)
* properties (WpProperties)
* type (string)
* streams (string)
*/
/* 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 endpoint data */
res = g_slice_new0(struct WpParserEndpointData);
/* Get the match-node table */
mn = wp_toml_table_get_table (table, "match-node");
if (!mn)
goto error;
/* Get the priority from the match-node table */
res->mn.priority = 0;
wp_toml_table_get_uint32 (mn, "priority", &res->mn.priority);
/* Get the match node properties */
res->mn.props = parse_properties (mn, "properties");
/* Get the endpoint table */
e = wp_toml_table_get_table (table, "endpoint");
if (!e)
goto error;
/* Get the name from the endpoint table */
res->e.name = wp_toml_table_get_string (e, "name");
/* Get the media class from the endpoint table */
res->e.media_class = wp_toml_table_get_string (e, "media_class");
/* Get the direction from the endpoint table */
direction = wp_toml_table_get_string (e, "direction");
if (!direction)
goto error;
res->e.direction = parse_endpoint_direction (direction);
/* Get the priority from the endpoint table */
res->mn.priority = 0;
wp_toml_table_get_uint32 (e, "priority", &res->e.priority);
/* Get the endpoint properties */
res->e.props = parse_properties (e, "properties");
/* Get the endpoint type */
res->e.type = wp_toml_table_get_string (e, "type");
if (!res->e.type)
goto error;
/* Get the endpoint streams */
res->e.streams = wp_toml_table_get_string (e, "streams");
return res;
error:
g_clear_pointer (&res, wp_parser_endpoint_data_destroy);
return NULL;
}
static gint
compare_datas_func (gconstpointer a, gconstpointer b)
{
struct WpParserEndpointData *da = *(struct WpParserEndpointData *const *)a;
struct WpParserEndpointData *db = *(struct WpParserEndpointData *const *)b;
return db->mn.priority - da->mn.priority;
}
static gboolean
wp_parser_endpoint_add_file (WpConfigParser *parser,
const gchar *name)
{
WpParserEndpoint *self = WP_PARSER_ENDPOINT (parser);
struct WpParserEndpointData *data;
/* Parse the file */
data = wp_parser_endpoint_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_endpoint_get_matched_data (WpConfigParser