Newer
Older
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <spa/utils/keys.h>
#include <pipewire/pipewire.h>
#include <wp/wp.h>
#include "config-policy.h"
#include "parser-endpoint-link.h"

Julian Bouzas
committed
struct link_info {
WpBaseEndpoint *ep;
guint32 stream_id;
gboolean keep;
};
static void
link_info_destroy (gpointer p)
{
struct link_info *li = p;
g_return_if_fail (li);
g_clear_object (&li->ep);
g_slice_free (struct link_info, li);
}
struct _WpConfigPolicy
{
WpPolicy parent;
WpConfiguration *config;
gboolean pending_rescan;
WpBaseEndpoint *pending_endpoint;

Julian Bouzas
committed
gboolean endpoint_handled;
};
enum {
PROP_0,
PROP_CONFIG,
};
enum {
SIGNAL_DONE,
N_SIGNALS
};
static guint signals[N_SIGNALS];
G_DEFINE_TYPE (WpConfigPolicy, wp_config_policy, WP_TYPE_POLICY)
static void
on_endpoint_link_created (GObject *initable, GAsyncResult *res, gpointer p)
{
WpConfigPolicy *self = p;
g_autoptr (WpBaseEndpointLink) link = NULL;
g_autoptr (WpBaseEndpoint) src_ep = NULL;
g_autoptr (WpBaseEndpoint) sink_ep = NULL;
link = wp_base_endpoint_link_new_finish(initable, res, &error);
/* Log linking info */
if (error) {
g_warning ("Could not link endpoints: %s\n", error->message);
return;
}
g_return_if_fail (link);
src_ep = wp_base_endpoint_link_get_source_endpoint (link);
sink_ep = wp_base_endpoint_link_get_sink_endpoint (link);
g_info ("Sucessfully linked '%s' to '%s'\n", wp_base_endpoint_get_name (src_ep),
wp_base_endpoint_get_name (sink_ep));
/* Emit the done signal */
if (self->pending_endpoint) {
gboolean is_capture =
wp_base_endpoint_get_direction (self->pending_endpoint) == PW_DIRECTION_INPUT;
if (self->pending_endpoint == (is_capture ? sink_ep : src_ep)) {
g_autoptr (WpBaseEndpoint) pending_endpoint =
g_steal_pointer (&self->pending_endpoint);
g_signal_emit (self, signals[SIGNAL_DONE], 0, pending_endpoint, link);

Julian Bouzas
committed
wp_config_policy_handle_pending_link (WpConfigPolicy *self,
struct link_info *li, WpBaseEndpoint *target)
{
g_autoptr (WpCore) core = wp_policy_get_core (WP_POLICY (self));

Julian Bouzas
committed
gboolean is_capture = wp_base_endpoint_get_direction (li->ep) == PW_DIRECTION_INPUT;
gboolean is_linked = wp_base_endpoint_is_linked (li->ep);
gboolean target_linked = wp_base_endpoint_is_linked (target);
g_debug ("Trying to link with '%s' to target '%s', ep_capture:%d, "

Julian Bouzas
committed
"ep_linked:%d, target_linked:%d", wp_base_endpoint_get_name (li->ep),
wp_base_endpoint_get_name (target), is_capture, is_linked, target_linked);
/* Check if the endpoint is already linked with the proper target */

Julian Bouzas
committed
GPtrArray *links = wp_base_endpoint_get_links (li->ep);
WpBaseEndpointLink *l = g_ptr_array_index (links, 0);
g_autoptr (WpBaseEndpoint) src_ep = wp_base_endpoint_link_get_source_endpoint (l);
g_autoptr (WpBaseEndpoint) sink_ep = wp_base_endpoint_link_get_sink_endpoint (l);
WpBaseEndpoint *existing_target = is_capture ? src_ep : sink_ep;
if (existing_target == target) {
/* linked to correct target so do nothing */
g_debug ("Endpoint '%s' is already linked correctly",

Julian Bouzas
committed
wp_base_endpoint_get_name (li->ep));
return FALSE;
} else {
/* linked to the wrong target so unlink and continue */
g_debug ("Unlinking endpoint '%s' from its previous target",

Julian Bouzas
committed
wp_base_endpoint_get_name (li->ep));
wp_base_endpoint_link_destroy (l);
}
}
/* Unlink the target links that are not kept if endpoint is capture */
if (!is_capture && target_linked) {
GPtrArray *links = wp_base_endpoint_get_links (target);
for (guint i = 0; i < links->len; i++) {
WpBaseEndpointLink *l = g_ptr_array_index (links, i);
if (!wp_base_endpoint_link_is_kept (l))
wp_base_endpoint_link_destroy (l);
}
}
/* Link the client with the target */
if (is_capture) {

Julian Bouzas
committed
wp_base_endpoint_link_new (core, target, li->stream_id, li->ep,
WP_STREAM_ID_NONE, li->keep, on_endpoint_link_created, self);

Julian Bouzas
committed
wp_base_endpoint_link_new (core, li->ep, WP_STREAM_ID_NONE, target,
li->stream_id, li->keep, on_endpoint_link_created, self);

Julian Bouzas
committed
static WpBaseEndpoint*
wp_config_policy_get_data_target (WpConfigPolicy *self, const char *ep_role,
const struct WpParserEndpointLinkData *data, guint32 *stream_id)

Julian Bouzas
committed
g_autoptr (WpCore) core = wp_policy_get_core (WP_POLICY (self));
GVariantBuilder b;
GVariant *target_data = NULL;

Julian Bouzas
committed
g_return_val_if_fail (data, NULL);
/* Create the target gvariant */
g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&b, "{sv}",
"data", g_variant_new_uint64 ((guint64) data));

Julian Bouzas
committed
if (ep_role)
g_variant_builder_add (&b, "{sv}", "role", g_variant_new_string (ep_role));
target_data = g_variant_builder_end (&b);
/* Find the target endpoint */

Julian Bouzas
committed
return wp_policy_find_endpoint (core, target_data, stream_id);
static guint
wp_config_policy_get_endpoint_lowest_priority_stream_id (WpBaseEndpoint *ep)
g_autoptr (GVariant) streams = NULL;
g_autoptr (GVariant) child = NULL;
GVariantIter *iter;
guint lowest_priority = G_MAXUINT;
guint res = WP_STREAM_ID_NONE;
guint priority;
guint id;
g_return_val_if_fail (ep, WP_STREAM_ID_NONE);
streams = wp_base_endpoint_list_streams (ep);
g_return_val_if_fail (streams, WP_STREAM_ID_NONE);
g_variant_get (streams, "aa{sv}", &iter);
while ((child = g_variant_iter_next_value (iter))) {
g_variant_lookup (child, "id", "u", &id);
g_variant_lookup (child, "priority", "u", &priority);
if (priority <= lowest_priority) {
lowest_priority = priority;
res = id;
}
}
g_variant_iter_free (iter);
wp_config_policy_find_endpoint (WpPolicy *policy, GVariant *props,
guint32 *stream_id)
{

Julian Bouzas
committed
g_autoptr (WpCore) core = wp_policy_get_core (policy);
g_autoptr (WpPolicyManager) pmgr = wp_policy_manager_get_instance (core);
g_autoptr (WpSession) session = wp_policy_manager_get_session (pmgr);
const struct WpParserEndpointLinkData *data = NULL;
g_autoptr (GPtrArray) endpoints = NULL;
guint i;
WpBaseEndpoint *target = NULL;
g_autoptr (WpProxy) proxy = NULL;
g_autoptr (WpProperties) p = NULL;
const char *role = NULL;
/* Get the data from props */
g_variant_lookup (props, "data", "t", &data);
if (!data)
return NULL;

Julian Bouzas
committed
/* If target-endpoint data was defined in the configuration file, find the
* matching endpoint based on target-endpoint data */
if (data->has_te) {
/* Get all the endpoints matching the media class */
endpoints = wp_policy_manager_list_endpoints (pmgr,
data->te.endpoint_data.media_class);
if (!endpoints)
return NULL;
/* Get the first endpoint that matches target data */
for (i = 0; i < endpoints->len; i++) {
target = g_ptr_array_index (endpoints, i);
if (wp_parser_endpoint_link_matches_endpoint_data (target,
&data->te.endpoint_data))
break;
}
}

Julian Bouzas
committed
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
/* Otherwise, use the default session endpoint if the session is valid */
else if (session) {
/* Get the default type */
WpDefaultEndpointType type;
switch (data->me.endpoint_data.direction) {
case PW_DIRECTION_INPUT:
type = WP_DEFAULT_ENDPOINT_TYPE_AUDIO_SOURCE;
break;
case PW_DIRECTION_OUTPUT:
type = WP_DEFAULT_ENDPOINT_TYPE_AUDIO_SINK;
break;
default:
g_warn_if_reached ();
return NULL;
}
/* Get all the endpoints */
endpoints = wp_policy_manager_list_endpoints (pmgr, NULL);
if (!endpoints)
return NULL;
/* Find the default session endpoint */
for (i = 0; i < endpoints->len; i++) {
target = g_ptr_array_index (endpoints, i);
guint def_id = wp_session_get_default_endpoint (session, type);
if (def_id == wp_base_endpoint_get_global_id (target))
break;
}

Julian Bouzas
committed
/* If no target data has been defined and session is not valid, return null */
else
return NULL;
/* If target did not match any data, return NULL */
if (i >= endpoints->len)
return NULL;
/* Set the stream id */
if (stream_id) {
if (target) {
g_variant_lookup (props, "role", "&s", &role);
/* The target stream has higher priority than the endpoint stream */
const char *prioritized = data->te.stream ? data->te.stream : role;
*stream_id = prioritized ?
wp_base_endpoint_find_stream (target, prioritized) :
wp_config_policy_get_endpoint_lowest_priority_stream_id (target);
} else {
*stream_id = WP_STREAM_ID_NONE;
}
}
return g_object_ref (target);
}
static gint

Julian Bouzas
committed
link_info_compare_func (gconstpointer a, gconstpointer b, gpointer data)

Julian Bouzas
committed
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
WpBaseEndpoint *target = data;
struct link_info *li_a = *(struct link_info *const *)a;
struct link_info *li_b = *(struct link_info *const *)b;
g_autoptr (GVariant) stream_a = NULL;
g_autoptr (GVariant) stream_b = NULL;
guint priority_a = 0;
guint priority_b = 0;
gint res = 0;
/* Get the role priority of a */
stream_a = wp_base_endpoint_get_stream (target, li_a->stream_id);
if (stream_a)
g_variant_lookup (stream_a, "priority", "u", &priority_a);
/* Get the role priority of b */
stream_b = wp_base_endpoint_get_stream (target, li_b->stream_id);
if (stream_b)
g_variant_lookup (stream_b, "priority", "u", &priority_b);
/* We want to sort from high priority to low priority */
res = priority_b - priority_a;
/* If both priorities are the same, sort by creation time */
if (res == 0)
res = wp_base_endpoint_get_creation_time (li_b->ep) -
wp_base_endpoint_get_creation_time (li_a->ep);

Julian Bouzas
committed
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
return res;
}
static void
wp_config_policy_add_link_info (WpConfigPolicy *self, GHashTable *table,
WpBaseEndpoint *ep)
{
g_autoptr (WpConfigParser) parser = NULL;
const struct WpParserEndpointLinkData *data = NULL;
const char *ep_role = wp_base_endpoint_get_role (ep);
struct link_info *res = NULL;
WpBaseEndpoint *target = NULL;
guint32 stream_id = WP_STREAM_ID_NONE;
/* Get the parser for the endpoint-link extension */
parser = wp_configuration_get_parser (self->config,
WP_PARSER_ENDPOINT_LINK_EXTENSION);
/* Get the matched endpoint data from the parser */
data = wp_config_parser_get_matched_data (parser, G_OBJECT (ep));
if (!data)
return;
/* Get the target */
target = wp_config_policy_get_data_target (self, ep_role, data, &stream_id);
if (!target)
return;
/* Create the link info */
res = g_slice_new0 (struct link_info);
res->ep = g_object_ref (ep);
res->stream_id = stream_id;
res->keep = data->el.keep;
/* Get the link infos for the target, or create a new one if not found */
GPtrArray *link_infos = g_hash_table_lookup (table, target);
if (!link_infos) {
link_infos = g_ptr_array_new_with_free_func (link_info_destroy);
g_ptr_array_add (link_infos, res);
g_hash_table_insert (table, g_object_ref (target), link_infos);
} else {
g_ptr_array_add (link_infos, res);
}
}
static void
links_table_handle_foreach (gpointer key, gpointer value, gpointer data)
{
WpConfigPolicy *self = data;
WpBaseEndpoint *target = key;
GPtrArray *link_infos = value;
/* Sort the link infos by role and creation time */
g_ptr_array_sort_with_data (link_infos, link_info_compare_func, target);
/* Handle the first endpoint and also the ones with keep=true */
for (guint i = 0; i < link_infos->len; i++) {
struct link_info *li = g_ptr_array_index (link_infos, i);
if (i == 0 || li->keep)
if (wp_config_policy_handle_pending_link (self, li, target))
self->endpoint_handled = li->ep == self->pending_endpoint;
}
}
static void
wp_config_policy_sync_rescan (WpCore *core, GAsyncResult *res, gpointer data)
{
WpConfigPolicy *self = WP_CONFIG_POLICY (data);
g_autoptr (WpPolicyManager) pmgr = wp_policy_manager_get_instance (core);
g_autoptr (GPtrArray) endpoints = NULL;

Julian Bouzas
committed

Julian Bouzas
committed
/* Set handle to false to know if pending endpoint was handled in this loop */
self->endpoint_handled = FALSE;
/* Handle all endpoints when rescanning */
endpoints = wp_policy_manager_list_endpoints (pmgr, NULL);
if (endpoints) {

Julian Bouzas
committed
/* Create the links table <target, array-of-link-info> */
GHashTable *links_table = g_hash_table_new_full (g_direct_hash,
g_direct_equal, g_object_unref, (GDestroyNotify) g_ptr_array_unref);

Julian Bouzas
committed
/* Fill the links table */
for (guint i = 0; i < endpoints->len; i++) {

Julian Bouzas
committed
WpBaseEndpoint *ep = g_ptr_array_index (endpoints, i);
wp_config_policy_add_link_info (self, links_table, ep);

Julian Bouzas
committed
/* Handle the links */
g_hash_table_foreach (links_table, links_table_handle_foreach, self);
/* Clean up */
g_clear_pointer (&links_table, g_hash_table_unref);
}
/* If endpoint was not handled, we are done */

Julian Bouzas
committed
if (!self->endpoint_handled) {
g_signal_emit (self, signals[SIGNAL_DONE], 0, self->pending_endpoint,
NULL);
g_clear_object (&self->pending_endpoint);
}
self->pending_rescan = FALSE;
}
static void
wp_config_policy_rescan (WpConfigPolicy *self, WpBaseEndpoint *ep)
{
if (self->pending_rescan)
return;
/* Check if there is a pending link while a new endpoint is added/removed */
if (self->pending_endpoint) {
g_warning ("Not handling endpoint '%s' beacause of pending link",
wp_base_endpoint_get_name (ep));
return;
}
g_autoptr (WpCore) core = wp_policy_get_core (WP_POLICY (self));
if (!core)
return;
self->pending_endpoint = g_object_ref (ep);
wp_core_sync (core, NULL, (GAsyncReadyCallback)wp_config_policy_sync_rescan,
self);
self->pending_rescan = TRUE;
}
static void
wp_config_policy_endpoint_added (WpPolicy *policy, WpBaseEndpoint *ep)
{
WpConfigPolicy *self = WP_CONFIG_POLICY (policy);
wp_config_policy_rescan (self, ep);
}
static void
wp_config_policy_endpoint_removed (WpPolicy *policy, WpBaseEndpoint *ep)
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
{
WpConfigPolicy *self = WP_CONFIG_POLICY (policy);
wp_config_policy_rescan (self, ep);
}
static void
wp_config_policy_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpConfigPolicy *self = WP_CONFIG_POLICY (object);
switch (property_id) {
case PROP_CONFIG:
self->config = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_config_policy_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpConfigPolicy *self = WP_CONFIG_POLICY (object);
switch (property_id) {
case PROP_CONFIG:
g_value_take_object (value, g_object_ref (self->config));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_config_policy_constructed (GObject * object)
{
WpConfigPolicy *self = WP_CONFIG_POLICY (object);
/* Add the parsers */
wp_configuration_add_extension (self->config,
WP_PARSER_ENDPOINT_LINK_EXTENSION, WP_TYPE_PARSER_ENDPOINT_LINK);
/* Parse the file */
wp_configuration_reload (self->config, WP_PARSER_ENDPOINT_LINK_EXTENSION);
G_OBJECT_CLASS (wp_config_policy_parent_class)->constructed (object);
}
static void
wp_config_policy_finalize (GObject *object)
{
WpConfigPolicy *self = WP_CONFIG_POLICY (object);
/* Remove the extensions from the configuration */
wp_configuration_remove_extension (self->config,
WP_PARSER_ENDPOINT_LINK_EXTENSION);
/* Clear the configuration */
g_clear_object (&self->config);
G_OBJECT_CLASS (wp_config_policy_parent_class)->finalize (object);
}
static void
wp_config_policy_init (WpConfigPolicy *self)
{
self->pending_rescan = FALSE;
}
static void
wp_config_policy_class_init (WpConfigPolicyClass *klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpPolicyClass *policy_class = (WpPolicyClass *) klass;
object_class->constructed = wp_config_policy_constructed;
object_class->finalize = wp_config_policy_finalize;
object_class->set_property = wp_config_policy_set_property;
object_class->get_property = wp_config_policy_get_property;
policy_class->endpoint_added = wp_config_policy_endpoint_added;
policy_class->endpoint_removed = wp_config_policy_endpoint_removed;
policy_class->find_endpoint = wp_config_policy_find_endpoint;
/* Properties */
g_object_class_install_property (object_class, PROP_CONFIG,
g_param_spec_object ("configuration", "configuration",
"The configuration this policy is based on", WP_TYPE_CONFIGURATION,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
/* Signals */
signals[SIGNAL_DONE] = g_signal_new ("done",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 2, WP_TYPE_BASE_ENDPOINT, WP_TYPE_BASE_ENDPOINT_LINK);