modules: add config endpoint module
This commit is contained in:
@@ -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(
|
||||
|
39
modules/module-config-endpoint.c
Normal file
39
modules/module-config-endpoint.c
Normal file
@@ -0,0 +1,39 @@
|
||||
/* 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);
|
||||
}
|
296
modules/module-config-endpoint/context.c
Normal file
296
modules/module-config-endpoint/context.c
Normal file
@@ -0,0 +1,296 @@
|
||||
/* 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);
|
||||
}
|
24
modules/module-config-endpoint/context.h
Normal file
24
modules/module-config-endpoint/context.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/* 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
|
272
modules/module-config-endpoint/parser-endpoint.c
Normal file
272
modules/module-config-endpoint/parser-endpoint.c
Normal file
@@ -0,0 +1,272 @@
|
||||
/* 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 *parser, gpointer data)
|
||||
{
|
||||
WpParserEndpoint *self = WP_PARSER_ENDPOINT (parser);
|
||||
WpProxyNode *node = WP_PROXY_NODE (data);
|
||||
const struct WpParserEndpointData *d = NULL;
|
||||
g_autoptr (WpProperties) props = NULL;
|
||||
|
||||
g_return_val_if_fail (node, NULL);
|
||||
props = wp_proxy_node_get_properties (node);
|
||||
|
||||
/* Find the first data that matches node */
|
||||
for (guint i = 0; i < self->datas->len; i++) {
|
||||
d = g_ptr_array_index(self->datas, i);
|
||||
if (wp_properties_matches (props, d->mn.props))
|
||||
return d;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
wp_parser_endpoint_reset (WpConfigParser *parser)
|
||||
{
|
||||
WpParserEndpoint *self = WP_PARSER_ENDPOINT (parser);
|
||||
|
||||
g_ptr_array_set_size (self->datas, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_parser_endpoint_config_parser_init (gpointer iface, gpointer iface_data)
|
||||
{
|
||||
WpConfigParserInterface *cp_iface = iface;
|
||||
|
||||
cp_iface->add_file = wp_parser_endpoint_add_file;
|
||||
cp_iface->get_matched_data = wp_parser_endpoint_get_matched_data;
|
||||
cp_iface->reset = wp_parser_endpoint_reset;
|
||||
}
|
||||
|
||||
static void
|
||||
wp_parser_endpoint_finalize (GObject * object)
|
||||
{
|
||||
WpParserEndpoint *self = WP_PARSER_ENDPOINT (object);
|
||||
|
||||
g_clear_pointer (&self->datas, g_ptr_array_unref);
|
||||
|
||||
G_OBJECT_CLASS (wp_parser_endpoint_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_parser_endpoint_init (WpParserEndpoint * self)
|
||||
{
|
||||
self->datas = g_ptr_array_new_with_free_func(
|
||||
wp_parser_endpoint_data_destroy);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_parser_endpoint_class_init (WpParserEndpointClass * klass)
|
||||
{
|
||||
GObjectClass *object_class = (GObjectClass *) klass;
|
||||
|
||||
object_class->finalize = wp_parser_endpoint_finalize;
|
||||
}
|
40
modules/module-config-endpoint/parser-endpoint.h
Normal file
40
modules/module-config-endpoint/parser-endpoint.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2019 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef __WIREPLUMBER_PARSER_ENDPOINT_H__
|
||||
#define __WIREPLUMBER_PARSER_ENDPOINT_H__
|
||||
|
||||
#include <wp/wp.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define WP_PARSER_ENDPOINT_EXTENSION "endpoint"
|
||||
|
||||
struct WpParserEndpointData {
|
||||
struct MatchNode {
|
||||
guint priority;
|
||||
WpProperties *props;
|
||||
} mn;
|
||||
struct Endpoint {
|
||||
char *name;
|
||||
char *media_class;
|
||||
guint direction;
|
||||
guint priority;
|
||||
WpProperties *props;
|
||||
char *type;
|
||||
char *streams;
|
||||
} e;
|
||||
};
|
||||
|
||||
#define WP_TYPE_PARSER_ENDPOINT (wp_parser_endpoint_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (WpParserEndpoint, wp_parser_endpoint,
|
||||
WP, PARSER_ENDPOINT, GObject)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
223
modules/module-config-endpoint/parser-streams.c
Normal file
223
modules/module-config-endpoint/parser-streams.c
Normal file
@@ -0,0 +1,223 @@
|
||||
/* 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-streams.h"
|
||||
|
||||
struct _WpParserStreams
|
||||
{
|
||||
GObject parent;
|
||||
|
||||
GPtrArray *datas;
|
||||
};
|
||||
|
||||
const struct WpParserStreamsStreamData *
|
||||
wp_parser_streams_find_stream (const struct WpParserStreamsData *data,
|
||||
const char *name)
|
||||
{
|
||||
for (guint i = 0; i < data->n_streams; i++) {
|
||||
const struct WpParserStreamsStreamData *s = data->streams + i;
|
||||
if (g_strcmp0 (s->name, name) == 0)
|
||||
return s;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct WpParserStreamsStreamData *
|
||||
wp_parser_streams_get_lowest_stream (const struct WpParserStreamsData *data)
|
||||
{
|
||||
const struct WpParserStreamsStreamData *res = NULL;
|
||||
guint lowest = G_MAXUINT;
|
||||
for (guint i = 0; i < data->n_streams; i++) {
|
||||
const struct WpParserStreamsStreamData *s = data->streams + i;
|
||||
if (s->priority < lowest) {
|
||||
lowest = s->priority;
|
||||
res = s;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void wp_parser_streams_config_parser_init (gpointer iface,
|
||||
gpointer iface_data);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (WpParserStreams, wp_parser_streams,
|
||||
G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (WP_TYPE_CONFIG_PARSER,
|
||||
wp_parser_streams_config_parser_init))
|
||||
|
||||
static void
|
||||
wp_parser_streams_data_destroy (gpointer p)
|
||||
{
|
||||
struct WpParserStreamsData *data = p;
|
||||
|
||||
/* Clear the location */
|
||||
g_clear_pointer (&data->location, g_free);
|
||||
|
||||
/* Clear the streams */
|
||||
for (guint i = 0; i < data->n_streams; i++) {
|
||||
struct WpParserStreamsStreamData *s = data->streams + i;
|
||||
g_clear_pointer (&s->name, g_free);
|
||||
}
|
||||
data->n_streams = 0;
|
||||
|
||||
g_slice_free (struct WpParserStreamsData, data);
|
||||
}
|
||||
|
||||
static void
|
||||
streams_for_each (const WpTomlTable *table, gpointer user_data)
|
||||
{
|
||||
struct WpParserStreamsData *data = user_data;
|
||||
struct WpParserStreamsStreamData *stream = NULL;
|
||||
g_return_if_fail (data);
|
||||
|
||||
/* Make sure we don't parse more MAX_STREAMS streams */
|
||||
if (data->n_streams >= MAX_STREAMS)
|
||||
return;
|
||||
|
||||
/* Skip unparsed tables */
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
/* Parse the mandatory name */
|
||||
stream = data->streams + data->n_streams;
|
||||
stream->name = wp_toml_table_get_string (table, "name");
|
||||
if (!stream->name)
|
||||
return;
|
||||
|
||||
/* Parse the optional priority */
|
||||
stream->priority = 0;
|
||||
wp_toml_table_get_uint32 (table, "priority", &stream->priority);
|
||||
|
||||
/* Increment the number of streams */
|
||||
data->n_streams++;
|
||||
}
|
||||
|
||||
|
||||
static struct WpParserStreamsData *
|
||||
wp_parser_streams_data_new (const gchar *location)
|
||||
{
|
||||
g_autoptr (WpTomlFile) file = NULL;
|
||||
g_autoptr (WpTomlTable) table = NULL;
|
||||
g_autoptr (WpTomlTableArray) streams = NULL;
|
||||
struct WpParserStreamsData *res = NULL;
|
||||
|
||||
/* File format:
|
||||
* ------------
|
||||
* [[streams]]
|
||||
* name (string)
|
||||
* priority (uint32)
|
||||
*/
|
||||
|
||||
/* 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 streams data */
|
||||
res = g_slice_new0 (struct WpParserStreamsData);
|
||||
|
||||
/* Set the location */
|
||||
res->location = g_strdup (location);
|
||||
|
||||
/* Parse the streams */
|
||||
res->n_streams = 0;
|
||||
streams = wp_toml_table_get_array_table (table, "streams");
|
||||
if (streams)
|
||||
wp_toml_table_array_for_each (streams, streams_for_each, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
wp_parser_streams_add_file (WpConfigParser *parser,
|
||||
const gchar *name)
|
||||
{
|
||||
WpParserStreams *self = WP_PARSER_STREAMS (parser);
|
||||
struct WpParserStreamsData *data;
|
||||
|
||||
/* Parse the file */
|
||||
data = wp_parser_streams_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);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gconstpointer
|
||||
wp_parser_streams_get_matched_data (WpConfigParser *parser, gpointer data)
|
||||
{
|
||||
WpParserStreams *self = WP_PARSER_STREAMS (parser);
|
||||
const char *location = data;
|
||||
const struct WpParserStreamsData *d = NULL;
|
||||
|
||||
/* Find the first data that matches location */
|
||||
for (guint i = 0; i < self->datas->len; i++) {
|
||||
d = g_ptr_array_index(self->datas, i);
|
||||
if (g_strrstr (d->location, location))
|
||||
return d;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
wp_parser_streams_reset (WpConfigParser *parser)
|
||||
{
|
||||
WpParserStreams *self = WP_PARSER_STREAMS (parser);
|
||||
|
||||
g_ptr_array_set_size (self->datas, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_parser_streams_config_parser_init (gpointer iface,
|
||||
gpointer iface_data)
|
||||
{
|
||||
WpConfigParserInterface *cp_iface = iface;
|
||||
|
||||
cp_iface->add_file = wp_parser_streams_add_file;
|
||||
cp_iface->get_matched_data = wp_parser_streams_get_matched_data;
|
||||
cp_iface->reset = wp_parser_streams_reset;
|
||||
}
|
||||
|
||||
static void
|
||||
wp_parser_streams_finalize (GObject * object)
|
||||
{
|
||||
WpParserStreams *self = WP_PARSER_STREAMS (object);
|
||||
|
||||
g_clear_pointer (&self->datas, g_ptr_array_unref);
|
||||
|
||||
G_OBJECT_CLASS (wp_parser_streams_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_parser_streams_init (WpParserStreams * self)
|
||||
{
|
||||
self->datas = g_ptr_array_new_with_free_func (wp_parser_streams_data_destroy);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_parser_streams_class_init (WpParserStreamsClass * klass)
|
||||
{
|
||||
GObjectClass *object_class = (GObjectClass *) klass;
|
||||
|
||||
object_class->finalize = wp_parser_streams_finalize;
|
||||
}
|
44
modules/module-config-endpoint/parser-streams.h
Normal file
44
modules/module-config-endpoint/parser-streams.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2019 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef __WIREPLUMBER_PARSER_STREAMS_H__
|
||||
#define __WIREPLUMBER_PARSER_STREAMS_H__
|
||||
|
||||
#include <wp/wp.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define WP_PARSER_STREAMS_EXTENSION "streams"
|
||||
|
||||
/* For simplicity, we limit the number of streams */
|
||||
#define MAX_STREAMS 32
|
||||
|
||||
struct WpParserStreamsStreamData {
|
||||
char *name;
|
||||
guint priority;
|
||||
};
|
||||
|
||||
struct WpParserStreamsData {
|
||||
char *location;
|
||||
struct WpParserStreamsStreamData streams[MAX_STREAMS];
|
||||
guint n_streams;
|
||||
};
|
||||
|
||||
/* Helpers */
|
||||
const struct WpParserStreamsStreamData *wp_parser_streams_find_stream (
|
||||
const struct WpParserStreamsData *data, const char *name);
|
||||
const struct WpParserStreamsStreamData *wp_parser_streams_get_lowest_stream (
|
||||
const struct WpParserStreamsData *data);
|
||||
|
||||
#define WP_TYPE_PARSER_STREAMS (wp_parser_streams_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (WpParserStreams, wp_parser_streams,
|
||||
WP, PARSER_STREAMS, GObject);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
@@ -298,6 +298,7 @@ on_audio_adapter_created(GObject *initable, GAsyncResult *res,
|
||||
GVariantDict d;
|
||||
GVariantIter iter;
|
||||
const gchar *stream;
|
||||
guint priority;
|
||||
int i;
|
||||
|
||||
/* Get the proxy adapter */
|
||||
@@ -323,7 +324,7 @@ on_audio_adapter_created(GObject *initable, GAsyncResult *res,
|
||||
|
||||
/* Create the audio converters */
|
||||
g_variant_iter_init (&iter, self->streams);
|
||||
for (i = 0; g_variant_iter_next (&iter, "&s", &stream); i++) {
|
||||
for (i = 0; g_variant_iter_next (&iter, "(&su)", &stream, &priority); i++) {
|
||||
wp_audio_convert_new (WP_BASE_ENDPOINT(self), i, stream, direction,
|
||||
self->adapter, format, on_audio_convert_created, self);
|
||||
|
||||
@@ -331,7 +332,9 @@ on_audio_adapter_created(GObject *initable, GAsyncResult *res,
|
||||
g_variant_dict_init (&d, NULL);
|
||||
g_variant_dict_insert (&d, "id", "u", i);
|
||||
g_variant_dict_insert (&d, "name", "s", stream);
|
||||
wp_base_endpoint_register_stream (WP_BASE_ENDPOINT (self), g_variant_dict_end (&d));
|
||||
g_variant_dict_insert (&d, "priority", "u", priority);
|
||||
wp_base_endpoint_register_stream (WP_BASE_ENDPOINT (self),
|
||||
g_variant_dict_end (&d));
|
||||
}
|
||||
self->stream_count = i;
|
||||
}
|
||||
@@ -478,7 +481,7 @@ endpoint_class_init (WpPwAudioSoftdspEndpointClass * klass)
|
||||
g_object_class_install_property (object_class, PROP_STREAMS,
|
||||
g_param_spec_variant ("streams", "streams",
|
||||
"The stream names for the streams to create",
|
||||
G_VARIANT_TYPE ("as"), NULL,
|
||||
G_VARIANT_TYPE ("a(su)"), NULL,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (object_class, PROP_ROLE,
|
||||
@@ -513,7 +516,7 @@ audio_softdsp_endpoint_factory (WpFactory * factory, GType type, GVariant * prop
|
||||
if (!g_variant_lookup (properties, "proxy-node", "t", &node))
|
||||
return;
|
||||
streams = g_variant_lookup_value (properties, "streams",
|
||||
G_VARIANT_TYPE ("as"));
|
||||
G_VARIANT_TYPE ("a(su)"));
|
||||
|
||||
/* Create and return the softdsp endpoint object */
|
||||
g_async_initable_new_async (
|
||||
|
@@ -31,5 +31,8 @@ load-module C libwireplumber-module-monitor {
|
||||
"factory": <"api.v4l2.enum.udev">
|
||||
}
|
||||
|
||||
# Implements endpoint creation based on TOML configuration files
|
||||
load-module C libwireplumber-module-config-endpoint
|
||||
|
||||
# Implements linking clients to devices based on TOML configuration files
|
||||
load-module C libwireplumber-module-config-policy
|
||||
|
10
src/wireplumber/audio-sink.endpoint
Normal file
10
src/wireplumber/audio-sink.endpoint
Normal file
@@ -0,0 +1,10 @@
|
||||
[match-node]
|
||||
priotity = 0
|
||||
properties = [
|
||||
{ name = "media.class", value = "Audio/Sink" },
|
||||
]
|
||||
|
||||
[endpoint]
|
||||
direction = "input"
|
||||
type = "pw-audio-softdsp-endpoint"
|
||||
streams = "default.streams"
|
10
src/wireplumber/audio-source.endpoint
Normal file
10
src/wireplumber/audio-source.endpoint
Normal file
@@ -0,0 +1,10 @@
|
||||
[match-node]
|
||||
priotity = 0
|
||||
properties = [
|
||||
{ name = "media.class", value = "Audio/Source" },
|
||||
]
|
||||
|
||||
[endpoint]
|
||||
direction = "output"
|
||||
type = "pw-audio-softdsp-endpoint"
|
||||
streams = "default.streams"
|
9
src/wireplumber/stream-input-audio.endpoint
Normal file
9
src/wireplumber/stream-input-audio.endpoint
Normal file
@@ -0,0 +1,9 @@
|
||||
[match-node]
|
||||
priotity = 0
|
||||
properties = [
|
||||
{ name = "media.class", value = "Stream/Input/Audio" },
|
||||
]
|
||||
|
||||
[endpoint]
|
||||
direction = "input"
|
||||
type = "pw-audio-softdsp-endpoint"
|
9
src/wireplumber/stream-output-audio.endpoint
Normal file
9
src/wireplumber/stream-output-audio.endpoint
Normal file
@@ -0,0 +1,9 @@
|
||||
[match-node]
|
||||
priotity = 0
|
||||
properties = [
|
||||
{ name = "media.class", value = "Stream/Output/Audio" },
|
||||
]
|
||||
|
||||
[endpoint]
|
||||
direction = "output"
|
||||
type = "pw-audio-softdsp-endpoint"
|
183
tests/modules/config-endpoint.c
Normal file
183
tests/modules/config-endpoint.c
Normal file
@@ -0,0 +1,183 @@
|
||||
/* 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 "config-endpoint/endpoint-audiotestsrc.h"
|
||||
#include "../wp/test-server.h"
|
||||
#include "../../modules/module-config-endpoint/context.h"
|
||||
|
||||
typedef struct {
|
||||
WpTestServer server;
|
||||
|
||||
GMutex mutex;
|
||||
GCond cond;
|
||||
gboolean created;
|
||||
|
||||
GThread *loop_thread;
|
||||
GMainContext *context;
|
||||
GMainLoop *loop;
|
||||
|
||||
WpCore *core;
|
||||
} TestConfigEndpointFixture;
|
||||
|
||||
void wait_for_created (TestConfigEndpointFixture *self)
|
||||
{
|
||||
g_mutex_lock (&self->mutex);
|
||||
while (!self->created)
|
||||
g_cond_wait (&self->cond, &self->mutex);
|
||||
self->created = FALSE;
|
||||
g_mutex_unlock (&self->mutex);
|
||||
}
|
||||
|
||||
void signal_created (TestConfigEndpointFixture *self)
|
||||
{
|
||||
g_mutex_lock (&self->mutex);
|
||||
self->created = TRUE;
|
||||
g_cond_signal (&self->cond);
|
||||
g_mutex_unlock (&self->mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
create_audiotestsrc (TestConfigEndpointFixture *self)
|
||||
{
|
||||
pw_thread_loop_lock (self->server.thread_loop);
|
||||
pw_core_add_spa_lib (self->server.core, "audiotestsrc",
|
||||
"audiotestsrc/libspa-audiotestsrc");
|
||||
if (!pw_module_load (self->server.core, "libpipewire-module-spa-node",
|
||||
"audiotestsrc", NULL)) {
|
||||
pw_thread_loop_unlock (self->server.thread_loop);
|
||||
g_test_skip ("audiotestsrc SPA plugin is not installed");
|
||||
return;
|
||||
}
|
||||
pw_thread_loop_unlock (self->server.thread_loop);
|
||||
}
|
||||
|
||||
static void
|
||||
on_connected (WpCore *core, enum pw_remote_state new_state,
|
||||
TestConfigEndpointFixture *self)
|
||||
{
|
||||
/* Register the wp-endpoint-audiotestsrc */
|
||||
wp_factory_new (self->core, "wp-endpoint-audiotestsrc",
|
||||
wp_endpoint_audiotestsrc_factory);
|
||||
|
||||
/* Create the audiotestsrc node */
|
||||
create_audiotestsrc (self);
|
||||
|
||||
/* Signal we are done */
|
||||
signal_created (self);
|
||||
}
|
||||
|
||||
static void *
|
||||
loop_thread_start (void *d)
|
||||
{
|
||||
TestConfigEndpointFixture *self = d;
|
||||
|
||||
/* Create the main loop using the default thread context */
|
||||
self->context = g_main_context_get_thread_default ();
|
||||
self->loop = g_main_loop_new (self->context, FALSE);
|
||||
|
||||
/* Create the server */
|
||||
wp_test_server_setup (&self->server);
|
||||
|
||||
/* Create the core and connect to the server */
|
||||
g_autoptr (WpProperties) props = NULL;
|
||||
props = wp_properties_new (PW_KEY_REMOTE_NAME, self->server.name, NULL);
|
||||
self->core = wp_core_new (self->context, props);
|
||||
g_signal_connect (self->core, "remote-state-changed::connected",
|
||||
(GCallback) on_connected, self);
|
||||
wp_core_connect (self->core);
|
||||
|
||||
/* Run the main loop */
|
||||
g_main_loop_run (self->loop);
|
||||
|
||||
/* Clean up */
|
||||
g_clear_object (&self->core);
|
||||
wp_test_server_teardown (&self->server);
|
||||
g_clear_pointer (&self->loop, g_main_loop_unref);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
config_endpoint_setup (TestConfigEndpointFixture *self, gconstpointer data)
|
||||
{
|
||||
/* Data */
|
||||
g_mutex_init (&self->mutex);
|
||||
g_cond_init (&self->cond);
|
||||
self->created = FALSE;
|
||||
|
||||
/* Initialize main loop, server and core in a thread */
|
||||
self->loop_thread = g_thread_new("loop-thread", &loop_thread_start, self);
|
||||
|
||||
/* Wait for everything to be created */
|
||||
wait_for_created (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
loop_thread_stop (gpointer data)
|
||||
{
|
||||
TestConfigEndpointFixture *self = data;
|
||||
g_main_loop_quit (self->loop);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
config_endpoint_teardown (TestConfigEndpointFixture *self, gconstpointer data)
|
||||
{
|
||||
/* Stop the main loop and wait until it is done */
|
||||
g_autoptr (GSource) source = g_idle_source_new ();
|
||||
g_source_set_callback (source, loop_thread_stop, self, NULL);
|
||||
g_source_attach (source, self->context);
|
||||
g_thread_join (self->loop_thread);
|
||||
|
||||
/* Data */
|
||||
g_mutex_clear (&self->mutex);
|
||||
g_cond_clear (&self->cond);
|
||||
self->created = FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_audiotestsrc_created (WpConfigEndpointContext *ctx, WpEndpoint *ep,
|
||||
TestConfigEndpointFixture *f)
|
||||
{
|
||||
g_assert_nonnull (ep);
|
||||
signal_created (f);
|
||||
}
|
||||
|
||||
static void
|
||||
basic (TestConfigEndpointFixture *f, gconstpointer data)
|
||||
{
|
||||
/* Set the configuration path */
|
||||
g_autoptr (WpConfiguration) config = wp_configuration_get_instance (f->core);
|
||||
g_assert_nonnull (config);
|
||||
wp_configuration_add_path (config, "config-endpoint/basic");
|
||||
|
||||
/* Create the context and handle the endpoint-created callback */
|
||||
g_autoptr (WpConfigEndpointContext) ctx =
|
||||
wp_config_endpoint_context_new (f->core);
|
||||
g_assert_nonnull (ctx);
|
||||
g_signal_connect (ctx, "endpoint-created",
|
||||
(GCallback) on_audiotestsrc_created, f);
|
||||
|
||||
/* Wait for the endpoint to be created */
|
||||
wait_for_created (f);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
pw_init (NULL, NULL);
|
||||
|
||||
g_test_add ("/modules/config-endpoint/basic", TestConfigEndpointFixture,
|
||||
NULL, config_endpoint_setup, basic, config_endpoint_teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
[match-node]
|
||||
properties = [
|
||||
{ name = "media.class", value = "Audio/Source" },
|
||||
]
|
||||
|
||||
[endpoint]
|
||||
direction = "output"
|
||||
type = "wp-endpoint-audiotestsrc"
|
||||
streams = "default.streams"
|
15
tests/modules/config-endpoint/basic/default.streams
Normal file
15
tests/modules/config-endpoint/basic/default.streams
Normal file
@@ -0,0 +1,15 @@
|
||||
[[streams]]
|
||||
name = "0"
|
||||
priority = 0
|
||||
|
||||
[[streams]]
|
||||
name = "2"
|
||||
priority = 2
|
||||
|
||||
[[streams]]
|
||||
name = "3"
|
||||
priority = 3
|
||||
|
||||
[[streams]]
|
||||
name = "4"
|
||||
priority = 4
|
239
tests/modules/config-endpoint/endpoint-audiotestsrc.c
Normal file
239
tests/modules/config-endpoint/endpoint-audiotestsrc.c
Normal file
@@ -0,0 +1,239 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2019 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <wp/wp.h>
|
||||
|
||||
#include "endpoint-audiotestsrc.h"
|
||||
|
||||
struct _WpEndpointAudiotestsrc
|
||||
{
|
||||
WpBaseEndpoint parent;
|
||||
GTask *init_task;
|
||||
|
||||
/* Props */
|
||||
WpProxyNode *proxy_node;
|
||||
GVariant *streams;
|
||||
};
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_PROXY_NODE,
|
||||
PROP_STREAMS,
|
||||
};
|
||||
|
||||
static GAsyncInitableIface *wp_endpoint_audiotestsrc_parent_interface = NULL;
|
||||
static void wp_endpoint_audiotestsrc_async_initable_init (gpointer iface,
|
||||
gpointer iface_data);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (WpEndpointAudiotestsrc, wp_endpoint_audiotestsrc,
|
||||
WP_TYPE_BASE_ENDPOINT,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
|
||||
wp_endpoint_audiotestsrc_async_initable_init))
|
||||
|
||||
static WpProperties *
|
||||
wp_endpoint_audiotestsrc_get_properties (WpBaseEndpoint * ep)
|
||||
{
|
||||
WpEndpointAudiotestsrc *self = WP_ENDPOINT_AUDIOTESTSRC (ep);
|
||||
return wp_proxy_node_get_properties (self->proxy_node);
|
||||
}
|
||||
|
||||
static const char *
|
||||
wp_endpoint_audiotestsrc_get_role (WpBaseEndpoint * ep)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
wp_endpoint_audiotestsrc_prepare_link (WpBaseEndpoint * ep, guint32 stream_id,
|
||||
WpBaseEndpointLink * link, GVariant ** properties, GError ** error)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static const char *
|
||||
wp_endpoint_audiotestsrc_get_endpoint_link_factory (WpBaseEndpoint * ep)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
wp_endpoint_audiotestsrc_constructed (GObject * object)
|
||||
{
|
||||
WpEndpointAudiotestsrc *self = WP_ENDPOINT_AUDIOTESTSRC (object);
|
||||
GVariantDict d;
|
||||
GVariantIter iter;
|
||||
const gchar *stream;
|
||||
guint priority;
|
||||
int i;
|
||||
|
||||
if (self->streams) {
|
||||
g_variant_iter_init (&iter, self->streams);
|
||||
for (i = 0; g_variant_iter_next (&iter, "(&su)", &stream, &priority); i++) {
|
||||
g_variant_dict_init (&d, NULL);
|
||||
g_variant_dict_insert (&d, "id", "u", i);
|
||||
g_variant_dict_insert (&d, "name", "s", stream);
|
||||
g_variant_dict_insert (&d, "priority", "u", priority);
|
||||
wp_base_endpoint_register_stream (WP_BASE_ENDPOINT (self), g_variant_dict_end (&d));
|
||||
}
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (wp_endpoint_audiotestsrc_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_endpoint_audiotestsrc_set_property (GObject * object, guint property_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
WpEndpointAudiotestsrc *self = WP_ENDPOINT_AUDIOTESTSRC (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_PROXY_NODE:
|
||||
self->proxy_node = g_value_dup_object (value);
|
||||
break;
|
||||
case PROP_STREAMS:
|
||||
self->streams = g_value_dup_variant(value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wp_endpoint_audiotestsrc_get_property (GObject * object, guint property_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
WpEndpointAudiotestsrc *self = WP_ENDPOINT_AUDIOTESTSRC (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_PROXY_NODE:
|
||||
g_value_set_object (value, self->proxy_node);
|
||||
break;
|
||||
case PROP_STREAMS:
|
||||
g_value_set_variant (value, self->streams);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wp_endpoint_audiotestsrc_finalize (GObject * object)
|
||||
{
|
||||
WpEndpointAudiotestsrc *self = WP_ENDPOINT_AUDIOTESTSRC (object);
|
||||
|
||||
g_clear_object(&self->proxy_node);
|
||||
g_clear_pointer(&self->streams, g_variant_unref);
|
||||
|
||||
G_OBJECT_CLASS (wp_endpoint_audiotestsrc_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_endpoint_audiotestsrc_finish_creation (WpCore *core, GAsyncResult *res,
|
||||
WpEndpointAudiotestsrc *self)
|
||||
{
|
||||
g_task_return_boolean (self->init_task, TRUE);
|
||||
g_clear_object (&self->init_task);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_endpoint_audiotestsrc_init_async (GAsyncInitable *initable, int io_priority,
|
||||
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
|
||||
{
|
||||
WpEndpointAudiotestsrc *self = WP_ENDPOINT_AUDIOTESTSRC (initable);
|
||||
|
||||
self->init_task = g_task_new (initable, cancellable, callback, data);
|
||||
|
||||
wp_endpoint_audiotestsrc_parent_interface->init_async (initable, io_priority,
|
||||
cancellable, callback, data);
|
||||
|
||||
g_autoptr (WpCore) core = wp_base_endpoint_get_core (WP_BASE_ENDPOINT(self));
|
||||
if (core)
|
||||
wp_core_sync (core, NULL,
|
||||
(GAsyncReadyCallback) wp_endpoint_audiotestsrc_finish_creation, self);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_endpoint_audiotestsrc_async_initable_init (gpointer iface,
|
||||
gpointer iface_data)
|
||||
{
|
||||
GAsyncInitableIface *ai_iface = iface;
|
||||
wp_endpoint_audiotestsrc_parent_interface =
|
||||
g_type_interface_peek_parent (iface);
|
||||
ai_iface->init_async = wp_endpoint_audiotestsrc_init_async;
|
||||
}
|
||||
|
||||
static void
|
||||
wp_endpoint_audiotestsrc_init (WpEndpointAudiotestsrc * self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
wp_endpoint_audiotestsrc_class_init (WpEndpointAudiotestsrcClass * klass)
|
||||
{
|
||||
GObjectClass *object_class = (GObjectClass *) klass;
|
||||
WpBaseEndpointClass *endpoint_class = (WpBaseEndpointClass *) klass;
|
||||
|
||||
object_class->constructed = wp_endpoint_audiotestsrc_constructed;
|
||||
object_class->finalize = wp_endpoint_audiotestsrc_finalize;
|
||||
object_class->set_property = wp_endpoint_audiotestsrc_set_property;
|
||||
object_class->get_property = wp_endpoint_audiotestsrc_get_property;
|
||||
|
||||
endpoint_class->get_properties = wp_endpoint_audiotestsrc_get_properties;
|
||||
endpoint_class->get_role = wp_endpoint_audiotestsrc_get_role;
|
||||
endpoint_class->prepare_link = wp_endpoint_audiotestsrc_prepare_link;
|
||||
endpoint_class->get_endpoint_link_factory =
|
||||
wp_endpoint_audiotestsrc_get_endpoint_link_factory;
|
||||
|
||||
g_object_class_install_property (object_class, PROP_PROXY_NODE,
|
||||
g_param_spec_object ("proxy-node", "proxy-node",
|
||||
"The node this endpoint refers to", WP_TYPE_PROXY_NODE,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (object_class, PROP_STREAMS,
|
||||
g_param_spec_variant ("streams", "streams",
|
||||
"The stream names for the streams to register",
|
||||
G_VARIANT_TYPE ("a(su)"), NULL,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
}
|
||||
|
||||
void
|
||||
wp_endpoint_audiotestsrc_factory (WpFactory * factory, GType type,
|
||||
GVariant * properties, GAsyncReadyCallback ready, gpointer data)
|
||||
{
|
||||
g_autoptr (WpCore) core = NULL;
|
||||
const gchar *name, *media_class;
|
||||
guint direction;
|
||||
guint64 node;
|
||||
g_autoptr (GVariant) streams = NULL;
|
||||
|
||||
core = wp_factory_get_core(factory);
|
||||
g_return_if_fail (core);
|
||||
|
||||
if (!g_variant_lookup (properties, "name", "&s", &name))
|
||||
return;
|
||||
if (!g_variant_lookup (properties, "media-class", "&s", &media_class))
|
||||
return;
|
||||
if (!g_variant_lookup (properties, "direction", "u", &direction))
|
||||
return;
|
||||
if (!g_variant_lookup (properties, "proxy-node", "t", &node))
|
||||
return;
|
||||
streams = g_variant_lookup_value (properties, "streams",
|
||||
G_VARIANT_TYPE ("a(su)"));
|
||||
|
||||
g_async_initable_new_async (wp_endpoint_audiotestsrc_get_type (),
|
||||
G_PRIORITY_DEFAULT, NULL, ready, data,
|
||||
"core", core,
|
||||
"name", name,
|
||||
"media-class", media_class,
|
||||
"direction", direction,
|
||||
"proxy-node", (gpointer) node,
|
||||
"streams", streams,
|
||||
NULL);
|
||||
}
|
24
tests/modules/config-endpoint/endpoint-audiotestsrc.h
Normal file
24
tests/modules/config-endpoint/endpoint-audiotestsrc.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2019 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef __WIREPLUMBER_ENDPOINT_AUDIOTESTSRC_H__
|
||||
#define __WIREPLUMBER_ENDPOINT_AUDIOTESTSRC_H__
|
||||
|
||||
#include <wp/wp.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
G_DECLARE_FINAL_TYPE (WpEndpointAudiotestsrc, wp_endpoint_audiotestsrc, WP,
|
||||
ENDPOINT_AUDIOTESTSRC, WpBaseEndpoint)
|
||||
|
||||
void wp_endpoint_audiotestsrc_factory (WpFactory * factory, GType type,
|
||||
GVariant * properties, GAsyncReadyCallback ready, gpointer data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
@@ -15,6 +15,21 @@ test(
|
||||
env: common_env,
|
||||
)
|
||||
|
||||
test(
|
||||
'test-config-endpoint',
|
||||
executable('test-config-endpoint',
|
||||
[
|
||||
'config-endpoint.c',
|
||||
'config-endpoint/endpoint-audiotestsrc.c',
|
||||
'../../modules/module-config-endpoint/parser-endpoint.c',
|
||||
'../../modules/module-config-endpoint/parser-streams.c',
|
||||
'../../modules/module-config-endpoint/context.c',
|
||||
],
|
||||
dependencies: common_deps + [wptoml_dep]),
|
||||
env: common_env,
|
||||
workdir : meson.current_source_dir(),
|
||||
)
|
||||
|
||||
test(
|
||||
'test-config-policy',
|
||||
executable('test-config-policy',
|
||||
|
Reference in New Issue
Block a user