policy: refactor/improve policy-node & session items to fix linking to monitors
* populate most session item properties from create-item.lua to keep things more compact and readable * use a standard naming scheme for the session item properties * use session item properties instead of node properties in policy-node.lua * improve policy-node's performance by converting the properties dictionary less times for each session item * refactor some policy logic and make things slighly more readable * change the accepted values for 'context' in wp_si_linkable_get_ports(); use "input" and "output" to keep things clear, because the previous use of NULL and "reverse" were implying that a node has only one "standard" direction, but this is complicated for sinks w/ monitors and duplex nodes * allow using monitors (which are Audio/Sink nodes in fact) as sources * treat Audio/Duplex nodes as sinks, like p-m-s does * respect the "stream.capture.sink" property of streams Fixes #66
This commit is contained in:
@@ -25,12 +25,9 @@ struct _WpSiAudioAdapter
|
|||||||
/* configuration */
|
/* configuration */
|
||||||
WpNode *node;
|
WpNode *node;
|
||||||
WpPort *port; /* only used for passthrough or convert mode */
|
WpPort *port; /* only used for passthrough or convert mode */
|
||||||
gchar name[96];
|
|
||||||
gchar media_class[32];
|
|
||||||
gboolean control_port;
|
gboolean control_port;
|
||||||
gboolean monitor;
|
gboolean monitor;
|
||||||
gboolean disable_dsp;
|
gboolean disable_dsp;
|
||||||
WpDirection direction;
|
|
||||||
WpDirection portconfig_direction;
|
WpDirection portconfig_direction;
|
||||||
gboolean is_device;
|
gboolean is_device;
|
||||||
gboolean dont_remix;
|
gboolean dont_remix;
|
||||||
@@ -66,12 +63,10 @@ si_audio_adapter_reset (WpSessionItem * item)
|
|||||||
/* reset */
|
/* reset */
|
||||||
g_clear_object (&self->node);
|
g_clear_object (&self->node);
|
||||||
g_clear_object (&self->port);
|
g_clear_object (&self->port);
|
||||||
self->name[0] = '\0';
|
|
||||||
self->media_class[0] = '\0';
|
|
||||||
self->control_port = FALSE;
|
self->control_port = FALSE;
|
||||||
self->monitor = FALSE;
|
self->monitor = FALSE;
|
||||||
self->disable_dsp = FALSE;
|
self->disable_dsp = FALSE;
|
||||||
self->portconfig_direction = self->direction = WP_DIRECTION_INPUT;
|
self->portconfig_direction = WP_DIRECTION_INPUT;
|
||||||
self->is_device = FALSE;
|
self->is_device = FALSE;
|
||||||
self->dont_remix = FALSE;
|
self->dont_remix = FALSE;
|
||||||
self->is_autoconnect = FALSE;
|
self->is_autoconnect = FALSE;
|
||||||
@@ -93,84 +88,45 @@ si_audio_adapter_configure (WpSessionItem * item, WpProperties *p)
|
|||||||
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
||||||
g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
|
g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
|
||||||
WpNode *node = NULL;
|
WpNode *node = NULL;
|
||||||
g_autoptr (WpProperties) node_props = NULL;
|
|
||||||
const gchar *str;
|
const gchar *str;
|
||||||
|
|
||||||
/* reset previous config */
|
/* reset previous config */
|
||||||
si_audio_adapter_reset (item);
|
si_audio_adapter_reset (item);
|
||||||
|
|
||||||
str = wp_properties_get (si_props, "node");
|
str = wp_properties_get (si_props, "item.node");
|
||||||
if (!str || sscanf(str, "%p", &node) != 1 || !WP_IS_NODE (node))
|
if (!str || sscanf(str, "%p", &node) != 1 || !WP_IS_NODE (node))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
node_props = wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (node));
|
str = wp_properties_get (si_props, PW_KEY_MEDIA_CLASS);
|
||||||
|
if (!str)
|
||||||
|
return FALSE;
|
||||||
|
if ((strstr (str, "Source") || strstr (str, "Output"))
|
||||||
|
&& !strstr (str, "Virtual")) {
|
||||||
|
self->portconfig_direction = WP_DIRECTION_OUTPUT;
|
||||||
|
}
|
||||||
|
|
||||||
str = wp_properties_get (node_props, PW_KEY_STREAM_DONT_REMIX);
|
str = wp_properties_get (si_props, "item.features.control-port");
|
||||||
|
self->control_port = str && pw_properties_parse_bool (str);
|
||||||
|
|
||||||
|
str = wp_properties_get (si_props, "item.features.monitor");
|
||||||
|
self->monitor = str && pw_properties_parse_bool (str);
|
||||||
|
|
||||||
|
str = wp_properties_get (si_props, "item.features.no-dsp");
|
||||||
|
self->disable_dsp = str && pw_properties_parse_bool (str);
|
||||||
|
|
||||||
|
str = wp_properties_get (si_props, "item.node.type");
|
||||||
|
self->is_device = !g_strcmp0 (str, "device");
|
||||||
|
|
||||||
|
str = wp_properties_get (si_props, PW_KEY_STREAM_DONT_REMIX);
|
||||||
self->dont_remix = str && pw_properties_parse_bool (str);
|
self->dont_remix = str && pw_properties_parse_bool (str);
|
||||||
|
|
||||||
str = wp_properties_get (node_props, PW_KEY_NODE_AUTOCONNECT);
|
str = wp_properties_get (si_props, PW_KEY_NODE_AUTOCONNECT);
|
||||||
self->is_autoconnect = str && pw_properties_parse_bool (str);
|
self->is_autoconnect = str && pw_properties_parse_bool (str);
|
||||||
|
|
||||||
str = wp_properties_get (si_props, "name");
|
|
||||||
if (str) {
|
|
||||||
strncpy (self->name, str, sizeof (self->name) - 1);
|
|
||||||
} else {
|
|
||||||
str = wp_properties_get (node_props, PW_KEY_NODE_NAME);
|
|
||||||
if (G_LIKELY (str))
|
|
||||||
strncpy (self->name, str, sizeof (self->name) - 1);
|
|
||||||
else
|
|
||||||
strncpy (self->name, "Unknown", sizeof (self->name) - 1);
|
|
||||||
wp_properties_set (si_props, "name", self->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
str = wp_properties_get (si_props, "media.class");
|
|
||||||
if (str) {
|
|
||||||
strncpy (self->media_class, str, sizeof (self->media_class) - 1);
|
|
||||||
} else {
|
|
||||||
str = wp_properties_get (node_props, PW_KEY_MEDIA_CLASS);
|
|
||||||
if (G_LIKELY (str))
|
|
||||||
strncpy (self->media_class, str, sizeof (self->media_class) - 1);
|
|
||||||
else
|
|
||||||
strncpy (self->media_class, "Unknown", sizeof (self->media_class) - 1);
|
|
||||||
wp_properties_set (si_props, "media.class", self->media_class);
|
|
||||||
}
|
|
||||||
self->is_device = strstr (self->media_class, "Stream") == NULL;
|
|
||||||
|
|
||||||
if (strstr (self->media_class, "Source") ||
|
|
||||||
strstr (self->media_class, "Output")) {
|
|
||||||
self->direction = WP_DIRECTION_OUTPUT;
|
|
||||||
self->portconfig_direction = (strstr (self->media_class, "Virtual")) ?
|
|
||||||
WP_DIRECTION_INPUT : self->direction;
|
|
||||||
}
|
|
||||||
wp_properties_setf (si_props, "direction", "%u", self->direction);
|
|
||||||
|
|
||||||
str = wp_properties_get (si_props, "enable.control.port");
|
|
||||||
if (str && sscanf(str, "%u", &self->control_port) != 1)
|
|
||||||
return FALSE;
|
|
||||||
if (!str)
|
|
||||||
wp_properties_setf (si_props, "enable.control.port", "%u",
|
|
||||||
self->control_port);
|
|
||||||
|
|
||||||
str = wp_properties_get (si_props, "enable.monitor");
|
|
||||||
if (str && sscanf(str, "%u", &self->monitor) != 1)
|
|
||||||
return FALSE;
|
|
||||||
if (!str)
|
|
||||||
wp_properties_setf (si_props, "enable.monitor", "%u", self->monitor);
|
|
||||||
|
|
||||||
str = wp_properties_get (si_props, "disable.dsp");
|
|
||||||
if (str && sscanf(str, "%u", &self->disable_dsp) != 1)
|
|
||||||
return FALSE;
|
|
||||||
if (!str)
|
|
||||||
wp_properties_setf (si_props, "disable.dsp", "%u", self->disable_dsp);
|
|
||||||
|
|
||||||
self->node = g_object_ref (node);
|
self->node = g_object_ref (node);
|
||||||
|
|
||||||
wp_properties_set (si_props, "si.factory.name", SI_FACTORY_NAME);
|
wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME);
|
||||||
wp_properties_setf (si_props, "is.device", "%u", self->is_device);
|
wp_session_item_set_properties (item, g_steal_pointer (&si_props));
|
||||||
wp_properties_setf (si_props, "dont.remix", "%u", self->dont_remix);
|
|
||||||
wp_properties_setf (si_props, "is.autoconnect", "%u", self->is_autoconnect);
|
|
||||||
wp_session_item_set_properties (WP_SESSION_ITEM (self),
|
|
||||||
g_steal_pointer (&si_props));
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,15 +567,16 @@ si_audio_adapter_get_ports (WpSiLinkable * item, const gchar * context)
|
|||||||
g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_ARRAY);
|
g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_ARRAY);
|
||||||
g_autoptr (WpIterator) it = NULL;
|
g_autoptr (WpIterator) it = NULL;
|
||||||
g_auto (GValue) val = G_VALUE_INIT;
|
g_auto (GValue) val = G_VALUE_INIT;
|
||||||
WpDirection direction = self->direction;
|
WpDirection direction;
|
||||||
guint32 node_id;
|
guint32 node_id;
|
||||||
|
|
||||||
/* context can only be NULL or "reverse" */
|
if (!g_strcmp0 (context, "output")) {
|
||||||
if (!g_strcmp0 (context, "reverse")) {
|
direction = WP_DIRECTION_OUTPUT;
|
||||||
direction = (self->direction == WP_DIRECTION_INPUT) ?
|
|
||||||
WP_DIRECTION_OUTPUT : WP_DIRECTION_INPUT;
|
|
||||||
}
|
}
|
||||||
else if (context != NULL) {
|
else if (!g_strcmp0 (context, "input")) {
|
||||||
|
direction = WP_DIRECTION_INPUT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
/* on any other context, return an empty list of ports */
|
/* on any other context, return an empty list of ports */
|
||||||
return g_variant_new_array (G_VARIANT_TYPE ("(uuu)"), NULL, 0);
|
return g_variant_new_array (G_VARIANT_TYPE ("(uuu)"), NULL, 0);
|
||||||
}
|
}
|
||||||
|
@@ -119,8 +119,7 @@ si_audio_endpoint_configure (WpSessionItem * item, WpProperties *p)
|
|||||||
if (!str)
|
if (!str)
|
||||||
wp_properties_setf (si_props, "priority", "%u", self->priority);
|
wp_properties_setf (si_props, "priority", "%u", self->priority);
|
||||||
|
|
||||||
wp_properties_set (si_props, "si.factory.name", SI_FACTORY_NAME);
|
wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME);
|
||||||
wp_properties_setf (si_props, "is.device", "%u", FALSE);
|
|
||||||
wp_session_item_set_properties (WP_SESSION_ITEM (self),
|
wp_session_item_set_properties (WP_SESSION_ITEM (self),
|
||||||
g_steal_pointer (&si_props));
|
g_steal_pointer (&si_props));
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@@ -356,15 +355,16 @@ si_audio_endpoint_get_ports (WpSiLinkable * item, const gchar * context)
|
|||||||
g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_ARRAY);
|
g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_ARRAY);
|
||||||
g_autoptr (WpIterator) it = NULL;
|
g_autoptr (WpIterator) it = NULL;
|
||||||
g_auto (GValue) val = G_VALUE_INIT;
|
g_auto (GValue) val = G_VALUE_INIT;
|
||||||
WpDirection direction = self->direction;
|
WpDirection direction;
|
||||||
guint32 node_id;
|
guint32 node_id;
|
||||||
|
|
||||||
/* context can only be either NULL or "reverse" */
|
if (!g_strcmp0 (context, "output")) {
|
||||||
if (!g_strcmp0 (context, "reverse")) {
|
direction = WP_DIRECTION_OUTPUT;
|
||||||
direction = (self->direction == WP_DIRECTION_INPUT) ?
|
|
||||||
WP_DIRECTION_OUTPUT : WP_DIRECTION_INPUT;
|
|
||||||
}
|
}
|
||||||
else if (context != NULL) {
|
else if (!g_strcmp0 (context, "input")) {
|
||||||
|
direction = WP_DIRECTION_INPUT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
/* on any other context, return an empty list of ports */
|
/* on any other context, return an empty list of ports */
|
||||||
return g_variant_new_array (G_VARIANT_TYPE ("(uuu)"), NULL, 0);
|
return g_variant_new_array (G_VARIANT_TYPE ("(uuu)"), NULL, 0);
|
||||||
}
|
}
|
||||||
|
@@ -19,9 +19,6 @@ struct _WpSiNode
|
|||||||
|
|
||||||
/* configuration */
|
/* configuration */
|
||||||
WpNode *node;
|
WpNode *node;
|
||||||
gchar name[96];
|
|
||||||
gchar media_class[32];
|
|
||||||
WpDirection direction;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void si_node_linkable_init (WpSiLinkableInterface * iface);
|
static void si_node_linkable_init (WpSiLinkableInterface * iface);
|
||||||
@@ -45,9 +42,6 @@ si_node_reset (WpSessionItem * item)
|
|||||||
|
|
||||||
/* reset */
|
/* reset */
|
||||||
g_clear_object (&self->node);
|
g_clear_object (&self->node);
|
||||||
self->name[0] = '\0';
|
|
||||||
self->media_class[0] = '\0';
|
|
||||||
self->direction = WP_DIRECTION_INPUT;
|
|
||||||
|
|
||||||
WP_SESSION_ITEM_CLASS (si_node_parent_class)->reset (item);
|
WP_SESSION_ITEM_CLASS (si_node_parent_class)->reset (item);
|
||||||
}
|
}
|
||||||
@@ -58,50 +52,18 @@ si_node_configure (WpSessionItem * item, WpProperties *p)
|
|||||||
WpSiNode *self = WP_SI_NODE (item);
|
WpSiNode *self = WP_SI_NODE (item);
|
||||||
g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
|
g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
|
||||||
WpNode *node = NULL;
|
WpNode *node = NULL;
|
||||||
g_autoptr (WpProperties) node_props = NULL;
|
|
||||||
const gchar *str;
|
const gchar *str;
|
||||||
|
|
||||||
/* reset previous config */
|
/* reset previous config */
|
||||||
si_node_reset (item);
|
si_node_reset (item);
|
||||||
|
|
||||||
str = wp_properties_get (si_props, "node");
|
str = wp_properties_get (si_props, "item.node");
|
||||||
if (!str || sscanf(str, "%p", &node) != 1 || !WP_IS_NODE (node))
|
if (!str || sscanf(str, "%p", &node) != 1 || !WP_IS_NODE (node))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
node_props = wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (node));
|
|
||||||
|
|
||||||
str = wp_properties_get (si_props, "name");
|
|
||||||
if (str) {
|
|
||||||
strncpy (self->name, str, sizeof (self->name) - 1);
|
|
||||||
} else {
|
|
||||||
str = wp_properties_get (node_props, PW_KEY_NODE_NAME);
|
|
||||||
if (G_LIKELY (str))
|
|
||||||
strncpy (self->name, str, sizeof (self->name) - 1);
|
|
||||||
else
|
|
||||||
strncpy (self->name, "Unknown", sizeof (self->name) - 1);
|
|
||||||
wp_properties_set (si_props, "name", self->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
str = wp_properties_get (si_props, "media.class");
|
|
||||||
if (str) {
|
|
||||||
strncpy (self->media_class, str, sizeof (self->media_class) - 1);
|
|
||||||
} else {
|
|
||||||
str = wp_properties_get (node_props, PW_KEY_MEDIA_CLASS);
|
|
||||||
if (G_LIKELY (str))
|
|
||||||
strncpy (self->media_class, str, sizeof (self->media_class) - 1);
|
|
||||||
else
|
|
||||||
strncpy (self->media_class, "Unknown", sizeof (self->media_class) - 1);
|
|
||||||
wp_properties_set (si_props, "media.class", self->media_class);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strstr (self->media_class, "Source") ||
|
|
||||||
strstr (self->media_class, "Output"))
|
|
||||||
self->direction = WP_DIRECTION_OUTPUT;
|
|
||||||
wp_properties_setf (si_props, "direction", "%u", self->direction);
|
|
||||||
|
|
||||||
self->node = g_object_ref (node);
|
self->node = g_object_ref (node);
|
||||||
|
|
||||||
wp_properties_set (si_props, "si.factory.name", SI_FACTORY_NAME);
|
wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME);
|
||||||
wp_session_item_set_properties (WP_SESSION_ITEM (self),
|
wp_session_item_set_properties (WP_SESSION_ITEM (self),
|
||||||
g_steal_pointer (&si_props));
|
g_steal_pointer (&si_props));
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@@ -188,15 +150,16 @@ si_node_get_ports (WpSiLinkable * item, const gchar * context)
|
|||||||
g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_ARRAY);
|
g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_ARRAY);
|
||||||
g_autoptr (WpIterator) it = NULL;
|
g_autoptr (WpIterator) it = NULL;
|
||||||
g_auto (GValue) val = G_VALUE_INIT;
|
g_auto (GValue) val = G_VALUE_INIT;
|
||||||
WpDirection direction = self->direction;
|
WpDirection direction;
|
||||||
guint32 node_id;
|
guint32 node_id;
|
||||||
|
|
||||||
/* context can only be NULL, "reverse" */
|
if (!g_strcmp0 (context, "output")) {
|
||||||
if (!g_strcmp0 (context, "reverse")) {
|
direction = WP_DIRECTION_OUTPUT;
|
||||||
direction = (self->direction == WP_DIRECTION_INPUT) ?
|
|
||||||
WP_DIRECTION_OUTPUT : WP_DIRECTION_INPUT;
|
|
||||||
}
|
}
|
||||||
else if (context != NULL) {
|
else if (!g_strcmp0 (context, "input")) {
|
||||||
|
direction = WP_DIRECTION_INPUT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
/* on any other context, return an empty list of ports */
|
/* on any other context, return an empty list of ports */
|
||||||
return g_variant_new_array (G_VARIANT_TYPE ("(uuu)"), NULL, 0);
|
return g_variant_new_array (G_VARIANT_TYPE ("(uuu)"), NULL, 0);
|
||||||
}
|
}
|
||||||
|
@@ -117,7 +117,7 @@ si_standard_link_configure (WpSessionItem * item, WpProperties * p)
|
|||||||
g_weak_ref_set(&self->out_item, out_item);
|
g_weak_ref_set(&self->out_item, out_item);
|
||||||
g_weak_ref_set(&self->in_item, in_item);
|
g_weak_ref_set(&self->in_item, in_item);
|
||||||
|
|
||||||
wp_properties_set (si_props, "si.factory.name", SI_FACTORY_NAME);
|
wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME);
|
||||||
wp_session_item_set_properties (WP_SESSION_ITEM (self),
|
wp_session_item_set_properties (WP_SESSION_ITEM (self),
|
||||||
g_steal_pointer (&si_props));
|
g_steal_pointer (&si_props));
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@@ -402,7 +402,7 @@ configure_and_link (WpSiStandardLink *self, WpSiAdapter *main,
|
|||||||
} else {
|
} else {
|
||||||
const gchar *str = NULL;
|
const gchar *str = NULL;
|
||||||
gboolean disable_dsp = FALSE;
|
gboolean disable_dsp = FALSE;
|
||||||
str = wp_session_item_get_property (WP_SESSION_ITEM (main), "disable.dsp");
|
str = wp_session_item_get_property (WP_SESSION_ITEM (main), "item.features.no-dsp");
|
||||||
disable_dsp = str && pw_properties_parse_bool (str);
|
disable_dsp = str && pw_properties_parse_bool (str);
|
||||||
wp_si_adapter_set_ports_format (main, NULL,
|
wp_si_adapter_set_ports_format (main, NULL,
|
||||||
disable_dsp ? "passthrough" : "dsp",
|
disable_dsp ? "passthrough" : "dsp",
|
||||||
@@ -427,21 +427,21 @@ configure_and_link_adapters (WpSiStandardLink *self, WpTransition *transition)
|
|||||||
g_return_if_fail (si_out);
|
g_return_if_fail (si_out);
|
||||||
g_return_if_fail (si_in);
|
g_return_if_fail (si_in);
|
||||||
|
|
||||||
str = wp_session_item_get_property (WP_SESSION_ITEM (si_out), "is.device");
|
str = wp_session_item_get_property (WP_SESSION_ITEM (si_out), "item.node.type");
|
||||||
out_is_device = str && pw_properties_parse_bool (str);
|
out_is_device = !g_strcmp0 (str, "device");
|
||||||
str = wp_session_item_get_property (WP_SESSION_ITEM (si_in), "is.device");
|
str = wp_session_item_get_property (WP_SESSION_ITEM (si_in), "item.node.type");
|
||||||
in_is_device = str && pw_properties_parse_bool (str);
|
in_is_device = !g_strcmp0 (str, "device");
|
||||||
|
|
||||||
str = wp_session_item_get_property (WP_SESSION_ITEM (si_out), "si.factory.name");
|
str = wp_session_item_get_property (WP_SESSION_ITEM (si_out), "item.factory.name");
|
||||||
out_is_device = (str && !g_strcmp0 (str, "si-audio-endpoint") && !in_is_device)
|
out_is_device = (str && !g_strcmp0 (str, "si-audio-endpoint") && !in_is_device)
|
||||||
|| out_is_device;
|
|| out_is_device;
|
||||||
str = wp_session_item_get_property (WP_SESSION_ITEM (si_in), "si.factory.name");
|
str = wp_session_item_get_property (WP_SESSION_ITEM (si_in), "item.factory.name");
|
||||||
in_is_device = (str && !g_strcmp0 (str, "si-audio-endpoint") && !out_is_device)
|
in_is_device = (str && !g_strcmp0 (str, "si-audio-endpoint") && !out_is_device)
|
||||||
|| in_is_device;
|
|| in_is_device;
|
||||||
|
|
||||||
str = wp_session_item_get_property (WP_SESSION_ITEM (si_out), "dont.remix");
|
str = wp_session_item_get_property (WP_SESSION_ITEM (si_out), "stream.dont-remix");
|
||||||
out_dont_remix = str && pw_properties_parse_bool (str);
|
out_dont_remix = str && pw_properties_parse_bool (str);
|
||||||
str = wp_session_item_get_property (WP_SESSION_ITEM (si_in), "dont.remix");
|
str = wp_session_item_get_property (WP_SESSION_ITEM (si_in), "stream.dont-remix");
|
||||||
in_dont_remix = str && pw_properties_parse_bool (str);
|
in_dont_remix = str && pw_properties_parse_bool (str);
|
||||||
|
|
||||||
wp_debug_object (self, "out [device:%d, dont_remix %d], "
|
wp_debug_object (self, "out [device:%d, dont_remix %d], "
|
||||||
|
@@ -6,6 +6,11 @@ default_policy.policy = {
|
|||||||
["move"] = true, -- moves session items when metadata target.node changes
|
["move"] = true, -- moves session items when metadata target.node changes
|
||||||
["follow"] = true, -- moves session items to the default device when it has changed
|
["follow"] = true, -- moves session items to the default device when it has changed
|
||||||
|
|
||||||
|
-- Set to 'true' to disable channel splitting & merging on nodes and enable
|
||||||
|
-- passthrough of audio in the same format as the format of the device.
|
||||||
|
-- Note that this breaks JACK support; it is generally not recommended
|
||||||
|
["audio.no-dsp"] = false,
|
||||||
|
|
||||||
-- how much to lower the volume of lower priority streams when ducking
|
-- how much to lower the volume of lower priority streams when ducking
|
||||||
-- note that this is a linear volume modifier (not cubic as in pulseaudio)
|
-- note that this is a linear volume modifier (not cubic as in pulseaudio)
|
||||||
["duck.level"] = 0.3,
|
["duck.level"] = 0.3,
|
||||||
@@ -32,7 +37,7 @@ function default_policy.enable()
|
|||||||
load_script("static-endpoints.lua", default_policy.endpoints)
|
load_script("static-endpoints.lua", default_policy.endpoints)
|
||||||
|
|
||||||
-- Create items for nodes that appear in the graph
|
-- Create items for nodes that appear in the graph
|
||||||
load_script("create-item.lua")
|
load_script("create-item.lua", default_policy.policy)
|
||||||
|
|
||||||
-- Link nodes to each other to make media flow in the graph
|
-- Link nodes to each other to make media flow in the graph
|
||||||
load_script("policy-node.lua", default_policy.policy)
|
load_script("policy-node.lua", default_policy.policy)
|
||||||
|
@@ -5,8 +5,54 @@
|
|||||||
--
|
--
|
||||||
-- SPDX-License-Identifier: MIT
|
-- SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
-- Receive script arguments from config.lua
|
||||||
|
local config = ...
|
||||||
|
|
||||||
items = {}
|
items = {}
|
||||||
|
|
||||||
|
function configProperties(node)
|
||||||
|
local np = node.properties
|
||||||
|
local properties = {
|
||||||
|
["item.node"] = node,
|
||||||
|
["item.plugged.usec"] = GLib.get_monotonic_time(),
|
||||||
|
["item.features.no-dsp"] = config["audio.no-dsp"],
|
||||||
|
["item.features.monitor"] = true,
|
||||||
|
["item.features.control-port"] = false,
|
||||||
|
["node.id"] = node["bound-id"],
|
||||||
|
["client.id"] = np["client.id"],
|
||||||
|
["object.path"] = np["object.path"],
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v in pairs(np) do
|
||||||
|
if k:find("^node") or k:find("^stream") or k:find("^media") then
|
||||||
|
properties[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local media_class = properties["media.class"] or ""
|
||||||
|
|
||||||
|
if not properties["media.type"] then
|
||||||
|
for _, i in ipairs({ "Audio", "Video", "Midi" }) do
|
||||||
|
if media_class:find(i) then
|
||||||
|
properties["media.type"] = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
properties["item.node.type"] =
|
||||||
|
media_class:find("^Stream/") and "stream" or "device"
|
||||||
|
|
||||||
|
if media_class:find("Sink") or
|
||||||
|
media_class:find("Input") or
|
||||||
|
media_class:find("Duplex") then
|
||||||
|
properties["item.node.direction"] = "input"
|
||||||
|
elseif media_class:find("Source") or media_class:find("Output") then
|
||||||
|
properties["item.node.direction"] = "output"
|
||||||
|
end
|
||||||
|
return properties
|
||||||
|
end
|
||||||
|
|
||||||
function addItem (node, item_type)
|
function addItem (node, item_type)
|
||||||
local id = node["bound-id"]
|
local id = node["bound-id"]
|
||||||
|
|
||||||
@@ -14,12 +60,7 @@ function addItem (node, item_type)
|
|||||||
items[id] = SessionItem ( item_type )
|
items[id] = SessionItem ( item_type )
|
||||||
|
|
||||||
-- configure item
|
-- configure item
|
||||||
if not items[id]:configure {
|
if not items[id]:configure(configProperties(node)) then
|
||||||
["node"] = node,
|
|
||||||
["enable.monitor"] = true,
|
|
||||||
["disable.dsp"] = false,
|
|
||||||
["item.plugged.usec"] = GLib.get_monotonic_time(),
|
|
||||||
} then
|
|
||||||
Log.warning(items[id], "failed to configure item for node " .. tostring(id))
|
Log.warning(items[id], "failed to configure item for node " .. tostring(id))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@@ -67,27 +67,17 @@ function createLink (si, si_target_ep)
|
|||||||
local media_class = node.properties["media.class"]
|
local media_class = node.properties["media.class"]
|
||||||
local target_media_class = si_target_ep.properties["media.class"]
|
local target_media_class = si_target_ep.properties["media.class"]
|
||||||
local out_item = nil
|
local out_item = nil
|
||||||
local out_context = nil
|
|
||||||
local in_item = nil
|
local in_item = nil
|
||||||
local in_context = nil
|
|
||||||
|
|
||||||
if string.find (media_class, "Input") or
|
if string.find (media_class, "Input") or
|
||||||
string.find (media_class, "Sink") then
|
string.find (media_class, "Sink") then
|
||||||
-- capture
|
-- capture
|
||||||
out_item = si_target_ep
|
out_item = si_target_ep
|
||||||
in_item = si
|
in_item = si
|
||||||
if string.find (target_media_class, "Input") or
|
|
||||||
string.find (target_media_class, "Sink") then
|
|
||||||
out_context = "reverse"
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
-- playback
|
-- playback
|
||||||
out_item = si
|
out_item = si
|
||||||
in_item = si_target_ep
|
in_item = si_target_ep
|
||||||
if string.find (target_media_class, "Output") or
|
|
||||||
string.find (target_media_class, "Source") then
|
|
||||||
in_context = "reverse"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Log.info (string.format("link %s <-> %s",
|
Log.info (string.format("link %s <-> %s",
|
||||||
@@ -99,8 +89,8 @@ function createLink (si, si_target_ep)
|
|||||||
if not si_link:configure {
|
if not si_link:configure {
|
||||||
["out.item"] = out_item,
|
["out.item"] = out_item,
|
||||||
["in.item"] = in_item,
|
["in.item"] = in_item,
|
||||||
["out.item.port.context"] = out_context,
|
["out.item.port.context"] = "output",
|
||||||
["in.item.port.context"] = in_context,
|
["in.item.port.context"] = "input",
|
||||||
["is.policy.endpoint.client.link"] = true,
|
["is.policy.endpoint.client.link"] = true,
|
||||||
["media.role"] = si_target_ep.properties["role"],
|
["media.role"] = si_target_ep.properties["role"],
|
||||||
["target.media.class"] = target_media_class,
|
["target.media.class"] = target_media_class,
|
||||||
@@ -216,7 +206,7 @@ siendpoints_om = ObjectManager { Interest { type = "SiEndpoint" }}
|
|||||||
silinkables_om = ObjectManager { Interest { type = "SiLinkable",
|
silinkables_om = ObjectManager { Interest { type = "SiLinkable",
|
||||||
-- only handle si-audio-adapter and si-node
|
-- only handle si-audio-adapter and si-node
|
||||||
Constraint {
|
Constraint {
|
||||||
"si.factory.name", "c", "si-audio-adapter", "si-node", type = "pw-global" },
|
"item.factory.name", "c", "si-audio-adapter", "si-node", type = "pw-global" },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
silinks_om = ObjectManager { Interest { type = "SiLink",
|
silinks_om = ObjectManager { Interest { type = "SiLink",
|
||||||
|
@@ -59,21 +59,17 @@ function createLink (si_ep, si_target)
|
|||||||
local target_node = si_target:get_associated_proxy ("node")
|
local target_node = si_target:get_associated_proxy ("node")
|
||||||
local target_media_class = target_node.properties["media.class"]
|
local target_media_class = target_node.properties["media.class"]
|
||||||
local out_item = nil
|
local out_item = nil
|
||||||
local out_context = nil
|
|
||||||
local in_item = nil
|
local in_item = nil
|
||||||
local in_context = nil
|
|
||||||
|
|
||||||
if string.find (target_media_class, "Input") or
|
if string.find (target_media_class, "Input") or
|
||||||
string.find (target_media_class, "Sink") then
|
string.find (target_media_class, "Sink") then
|
||||||
-- capture
|
-- capture
|
||||||
in_item = si_target
|
in_item = si_target
|
||||||
out_item = si_ep
|
out_item = si_ep
|
||||||
out_context = "reverse"
|
|
||||||
else
|
else
|
||||||
-- playback
|
-- playback
|
||||||
in_item = si_ep
|
in_item = si_ep
|
||||||
out_item = si_target
|
out_item = si_target
|
||||||
in_context = "reverse"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Log.info (string.format("link %s <-> %s",
|
Log.info (string.format("link %s <-> %s",
|
||||||
@@ -85,8 +81,8 @@ function createLink (si_ep, si_target)
|
|||||||
if not si_link:configure {
|
if not si_link:configure {
|
||||||
["out.item"] = out_item,
|
["out.item"] = out_item,
|
||||||
["in.item"] = in_item,
|
["in.item"] = in_item,
|
||||||
["out.item.port.context"] = out_context,
|
["out.item.port.context"] = "output",
|
||||||
["in.item.port.context"] = in_context,
|
["in.item.port.context"] = "input",
|
||||||
["passive"] = true,
|
["passive"] = true,
|
||||||
["is.policy.endpoint.device.link"] = true,
|
["is.policy.endpoint.device.link"] = true,
|
||||||
} then
|
} then
|
||||||
@@ -196,16 +192,21 @@ end
|
|||||||
|
|
||||||
default_nodes = Plugin.find("default-nodes-api")
|
default_nodes = Plugin.find("default-nodes-api")
|
||||||
siendpoints_om = ObjectManager { Interest { type = "SiEndpoint" }}
|
siendpoints_om = ObjectManager { Interest { type = "SiEndpoint" }}
|
||||||
silinkables_om = ObjectManager { Interest { type = "SiLinkable",
|
silinkables_om = ObjectManager {
|
||||||
-- only handle device si-audio-adapter items
|
Interest {
|
||||||
Constraint { "si.factory.name", "=", "si-audio-adapter", type = "pw-global" },
|
type = "SiLinkable",
|
||||||
Constraint { "is.device", "=", true, type = "pw-global" },
|
-- only handle device si-audio-adapter items
|
||||||
|
Constraint { "item.factory.name", "=", "si-audio-adapter", type = "pw-global" },
|
||||||
|
Constraint { "item.node.type", "=", "device", type = "pw-global" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
silinks_om = ObjectManager {
|
||||||
|
Interest {
|
||||||
|
type = "SiLink",
|
||||||
|
-- only handle links created by this policy
|
||||||
|
Constraint { "is.policy.endpoint.device.link", "=", true, type = "pw-global" },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
silinks_om = ObjectManager { Interest { type = "SiLink",
|
|
||||||
-- only handle links created by this policy
|
|
||||||
Constraint { "is.policy.endpoint.device.link", "=", true, type = "pw-global" },
|
|
||||||
} }
|
|
||||||
|
|
||||||
-- listen for default node changes if config.follow is enabled
|
-- listen for default node changes if config.follow is enabled
|
||||||
if config.follow then
|
if config.follow then
|
||||||
|
@@ -14,46 +14,35 @@ config.follow = config.follow or false
|
|||||||
|
|
||||||
local pending_rescan = false
|
local pending_rescan = false
|
||||||
|
|
||||||
function createLink (si, si_target)
|
function parseBool(var)
|
||||||
local node = si:get_associated_proxy ("node")
|
return var and (var == "true" or var == "1")
|
||||||
local target_node = si_target:get_associated_proxy ("node")
|
end
|
||||||
local media_class = node.properties["media.class"]
|
|
||||||
local target_media_class = target_node.properties["media.class"]
|
|
||||||
local out_item = nil
|
|
||||||
local out_context = nil
|
|
||||||
local in_item = nil
|
|
||||||
local in_context = nil
|
|
||||||
|
|
||||||
if string.find (media_class, "Input") or
|
function createLink (si, si_target)
|
||||||
string.find (media_class, "Sink") then
|
local out_item = nil
|
||||||
-- capture
|
local in_item = nil
|
||||||
out_item = si_target
|
|
||||||
in_item = si
|
if si.properties["item.node.direction"] == "output" then
|
||||||
if string.find (target_media_class, "Input") or
|
|
||||||
string.find (target_media_class, "Sink") then
|
|
||||||
out_context = "reverse"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- playback
|
-- playback
|
||||||
out_item = si
|
out_item = si
|
||||||
in_item = si_target
|
in_item = si_target
|
||||||
if string.find (target_media_class, "Output") or
|
else
|
||||||
string.find (target_media_class, "Source") then
|
-- capture
|
||||||
in_context = "reverse"
|
in_item = si
|
||||||
end
|
out_item = si_target
|
||||||
end
|
end
|
||||||
|
|
||||||
Log.info (string.format("link %s <-> %s",
|
Log.info (string.format("link %s <-> %s",
|
||||||
tostring(node.properties["node.name"]),
|
tostring(si.properties["node.name"]),
|
||||||
tostring(target_node.properties["node.name"])))
|
tostring(si_target.properties["node.name"])))
|
||||||
|
|
||||||
-- create and configure link
|
-- create and configure link
|
||||||
local si_link = SessionItem ( "si-standard-link" )
|
local si_link = SessionItem ( "si-standard-link" )
|
||||||
if not si_link:configure {
|
if not si_link:configure {
|
||||||
["out.item"] = out_item,
|
["out.item"] = out_item,
|
||||||
["in.item"] = in_item,
|
["in.item"] = in_item,
|
||||||
["out.item.port.context"] = out_context,
|
["out.item.port.context"] = "output",
|
||||||
["in.item.port.context"] = in_context,
|
["in.item.port.context"] = "input",
|
||||||
["is.policy.item.link"] = true,
|
["is.policy.item.link"] = true,
|
||||||
} then
|
} then
|
||||||
Log.warning (si_link, "failed to configure si-standard-link")
|
Log.warning (si_link, "failed to configure si-standard-link")
|
||||||
@@ -74,286 +63,245 @@ function createLink (si, si_target)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function directionEqual (mc_a, mc_b)
|
function canLink (properties, si_target)
|
||||||
return (string.find (mc_a, "Input") or string.find (mc_a, "Sink")) ==
|
local target_properties = si_target.properties
|
||||||
(string.find (mc_b, "Input") or string.find (mc_b, "Sink"))
|
|
||||||
end
|
|
||||||
|
|
||||||
function canLinkCheck (node_link_group, si_target, hops)
|
-- nodes must have the same media type
|
||||||
local target_node = si_target:get_associated_proxy ("node")
|
if properties["media.type"] ~= target_properties["media.type"] then
|
||||||
local target_node_link_group = target_node.properties["node.link-group"]
|
|
||||||
local targer_media_class = target_node.properties["media.class"]
|
|
||||||
|
|
||||||
if hops == 8 then
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- allow linking if target has not link-group property
|
-- nodes must have opposite direction, or otherwise they must be both input
|
||||||
if not target_node_link_group then
|
-- and the target must have a monitor (so the target will be used as a source)
|
||||||
return true
|
local function isMonitor(properties)
|
||||||
|
return properties["item.node.direction"] == "input" and
|
||||||
|
parseBool(properties["item.features.monitor"]) and
|
||||||
|
properties["item.factory.name"] == "si-audio-adapter"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- do not allow linking if target has the same link-group
|
if properties["item.node.direction"] == target_properties["item.node.direction"]
|
||||||
if node_link_group == target_node_link_group then
|
and not isMonitor(target_properties) then
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- make sure target is not linked with another node with same link group
|
|
||||||
for si_n in silinkables_om:iterate() do
|
|
||||||
if si_n.id ~= si_target.id then
|
|
||||||
local n = si_n:get_associated_proxy ("node")
|
|
||||||
local n_media_class = n.properties["media.class"]
|
|
||||||
if directionEqual (n_media_class, targer_media_class) then
|
|
||||||
local n_link_group = n.properties["node.link-group"]
|
|
||||||
if n_link_group ~= nil and n_link_group == target_node_link_group then
|
|
||||||
|
|
||||||
-- iterate peers and return false if one of them cannot link
|
|
||||||
for silink in silinks_om:iterate() do
|
|
||||||
local out_id = tonumber(silink.properties["out.item.id"])
|
|
||||||
local in_id = tonumber(silink.properties["in.item.id"])
|
|
||||||
if out_id == si_n.id or in_id == si_n.id then
|
|
||||||
local is_out = out_id == si_n.id and true or false
|
|
||||||
for peer in silinkables_om:iterate() do
|
|
||||||
if peer.id == (is_out and in_id or out_id) then
|
|
||||||
if not canLinkCheck (node_link_group, si_peer, hops + 1) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function isMonitorNode (node)
|
|
||||||
local stream_monitor = node.properties["stream.monitor"]
|
|
||||||
if stream_monitor then
|
|
||||||
return stream_monitor == "true" or stream_monitor == "1"
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function canLink (node, si_target)
|
|
||||||
local target_node = si_target:get_associated_proxy ("node")
|
|
||||||
local target_media_class = target_node.properties["media.class"]
|
|
||||||
local media_class = node.properties["media.class"]
|
|
||||||
|
|
||||||
-- Make sure node is a monitor if we want to link with same media class
|
|
||||||
if media_class == target_media_class and not isMonitorNode (node) then
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check link group
|
-- check link group
|
||||||
local node_link_group = node.properties["node.link-group"]
|
local function canLinkGroupCheck (link_group, si_target, hops)
|
||||||
if node_link_group ~= nil then
|
local target_props = si_target.properties
|
||||||
return canLinkCheck (node_link_group, si_target, 0)
|
local target_link_group = target_props["node.link-group"]
|
||||||
else
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function findTargetByTargetNodeMetadata (node)
|
if hops == 8 then
|
||||||
local node_id = node['bound-id']
|
return false
|
||||||
local metadata = metadata_om:lookup()
|
|
||||||
if metadata then
|
|
||||||
local value = metadata:find(node_id, "target.node")
|
|
||||||
if value then
|
|
||||||
for si_target in silinkables_om:iterate() do
|
|
||||||
local target_node = si_target:get_associated_proxy ("node")
|
|
||||||
if target_node["bound-id"] == tonumber(value) and
|
|
||||||
canLink (node, si_target) then
|
|
||||||
return si_target
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function findTargetByNodeTargetProperty (node)
|
-- allow linking if target has no link-group property
|
||||||
local target_id_str = node.properties["node.target"]
|
if not target_link_group then
|
||||||
if target_id_str then
|
return true
|
||||||
for si_target in silinkables_om:iterate() do
|
|
||||||
local target_node = si_target:get_associated_proxy ("node")
|
|
||||||
local target_props = target_node.properties
|
|
||||||
if (target_node["bound-id"] == tonumber(target_id_str) or
|
|
||||||
target_props["node.name"] == target_id_str or
|
|
||||||
target_props["object.path"] == target_id_str) and
|
|
||||||
canLink (node, si_target) then
|
|
||||||
return si_target
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function findTargetByDefaultNode (node, target_media_class)
|
-- do not allow linking if target has the same link-group
|
||||||
local def_id = default_nodes:call("get-default-node", target_media_class)
|
if link_group == target_link_group then
|
||||||
if def_id ~= Id.INVALID then
|
return false
|
||||||
for si_target in silinkables_om:iterate() do
|
|
||||||
local target_node = si_target:get_associated_proxy ("node")
|
|
||||||
if target_node["bound-id"] == def_id and
|
|
||||||
canLink (node, si_target) then
|
|
||||||
return si_target
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function findTargetByFirstAvailable (node, target_media_class)
|
-- make sure target is not linked with another node with same link group
|
||||||
for si_target in silinkables_om:iterate() do
|
-- start by locating other nodes in the target's link-group, in opposite direction
|
||||||
local target_node = si_target:get_associated_proxy ("node")
|
for n in linkables_om:iterate {
|
||||||
if target_node.properties["media.class"] == target_media_class and
|
Constraint { "id", "!", si_target.id, type = "gobject" },
|
||||||
canLink (node, si_target) then
|
Constraint { "item.node.direction", "!", target_props["item.node.direction"] },
|
||||||
return si_target
|
Constraint { "node.link-group", "=", target_link_group },
|
||||||
end
|
} do
|
||||||
end
|
-- iterate their peers and return false if one of them cannot link
|
||||||
return nil
|
for silink in links_om:iterate() do
|
||||||
end
|
local out_id = tonumber(silink.properties["out.item.id"])
|
||||||
|
local in_id = tonumber(silink.properties["in.item.id"])
|
||||||
function findDefinedTarget (node)
|
if out_id == n.id or in_id == n.id then
|
||||||
local si_target = findTargetByTargetNodeMetadata (node)
|
local peer_id = (out_id == n.id) and in_id or out_id
|
||||||
if not si_target then
|
local peer = linkables_om:lookup {
|
||||||
si_target = findTargetByNodeTargetProperty (node)
|
Constraint { "id", "=", peer_id, type = "gobject" },
|
||||||
end
|
}
|
||||||
return si_target
|
if peer and not canLinkGroupCheck (link_group, peer, hops + 1) then
|
||||||
end
|
return false
|
||||||
|
|
||||||
function findUndefinedTarget (node)
|
|
||||||
local target_class_assoc = {
|
|
||||||
["Stream/Input/Audio"] = "Audio/Source",
|
|
||||||
["Stream/Output/Audio"] = "Audio/Sink",
|
|
||||||
["Stream/Input/Video"] = "Video/Source",
|
|
||||||
}
|
|
||||||
local si_target = nil
|
|
||||||
|
|
||||||
local media_class = node.properties["media.class"]
|
|
||||||
local target_media_class = target_class_assoc[media_class]
|
|
||||||
if target_media_class then
|
|
||||||
si_target = findTargetByDefaultNode (node, target_media_class)
|
|
||||||
if not si_target then
|
|
||||||
si_target = findTargetByFirstAvailable (node, target_media_class)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return si_target
|
|
||||||
end
|
|
||||||
|
|
||||||
function getSiLinkAndSiPeer (si, target_media_class)
|
|
||||||
for silink in silinks_om:iterate() do
|
|
||||||
local out_id = tonumber(silink.properties["out.item.id"])
|
|
||||||
local in_id = tonumber(silink.properties["in.item.id"])
|
|
||||||
if out_id == si.id or in_id == si.id then
|
|
||||||
local is_out = out_id == si.id and true or false
|
|
||||||
for peer in silinkables_om:iterate() do
|
|
||||||
if peer.id == (is_out and in_id or out_id) then
|
|
||||||
local peer_node = peer:get_associated_proxy ("node")
|
|
||||||
local peer_media_class = peer_node.properties["media.class"]
|
|
||||||
if peer_media_class == target_media_class then
|
|
||||||
return silink, peer
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local link_group = properties["node.link-group"]
|
||||||
|
if link_group then
|
||||||
|
return canLinkGroupCheck (link_group, si_target, 0)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Try to locate a valid target node that was explicitly defined by the user
|
||||||
|
-- Use the target.node metadata, if config.move is enabled,
|
||||||
|
-- then use the node.target property that was set on the node
|
||||||
|
-- `properties` must be the properties dictionary of the session item
|
||||||
|
-- that is currently being handled
|
||||||
|
function findDefinedTarget (properties)
|
||||||
|
local function findTargetByTargetNodeMetadata (properties)
|
||||||
|
local node_id = properties["node.id"]
|
||||||
|
local metadata = metadata_om:lookup()
|
||||||
|
local target_id = metadata and metadata:find(node_id, "target.node") or nil
|
||||||
|
if target_id and tonumber(target_id) > 0 then
|
||||||
|
local si_target = linkables_om:lookup {
|
||||||
|
Constraint { "node.id", "=", target_id },
|
||||||
|
}
|
||||||
|
if si_target and canLink (properties, si_target) then
|
||||||
|
return si_target
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function findTargetByNodeTargetProperty (properties)
|
||||||
|
local target_id = properties["node.target"]
|
||||||
|
if target_id then
|
||||||
|
for si_target in linkables_om:iterate() do
|
||||||
|
local target_props = si_target.properties
|
||||||
|
if (target_props["node.id"] == target_id or
|
||||||
|
target_props["node.name"] == target_id or
|
||||||
|
target_props["object.path"] == target_id) and
|
||||||
|
canLink (properties, si_target) then
|
||||||
|
return si_target
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return (config.move and findTargetByTargetNodeMetadata (properties) or nil)
|
||||||
|
or findTargetByNodeTargetProperty (properties)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Try to locate a valid target node that was NOT explicitly defined by the user
|
||||||
|
-- `properties` must be the properties dictionary of the session item
|
||||||
|
-- that is currently being handled
|
||||||
|
function findUndefinedTarget (properties)
|
||||||
|
local function findTargetByDefaultNode (properties, target_direction)
|
||||||
|
local target_media_class =
|
||||||
|
properties["media.type"] ..
|
||||||
|
(target_direction == "input" and "/Sink" or "/Source")
|
||||||
|
local def_id = default_nodes:call("get-default-node", target_media_class)
|
||||||
|
if def_id ~= Id.INVALID then
|
||||||
|
local si_target = linkables_om:lookup {
|
||||||
|
Constraint { "node.id", "=", def_id },
|
||||||
|
}
|
||||||
|
if si_target and canLink (properties, si_target) then
|
||||||
|
return si_target
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function findTargetByFirstAvailable (properties, target_direction)
|
||||||
|
for si_target in linkables_om:iterate {
|
||||||
|
Constraint { "item.node.type", "=", "device" },
|
||||||
|
Constraint { "item.node.direction", "=", target_direction },
|
||||||
|
Constraint { "media.type", "=", properties["media.type"] },
|
||||||
|
} do
|
||||||
|
if canLink (properties, si_target) then
|
||||||
|
return si_target
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local target_direction = nil
|
||||||
|
if properties["item.node.direction"] == "output" or
|
||||||
|
(properties["item.node.direction"] == "input" and
|
||||||
|
parseBool(properties["stream.capture.sink"])) then
|
||||||
|
target_direction = "input"
|
||||||
|
else
|
||||||
|
target_direction = "output"
|
||||||
|
end
|
||||||
|
|
||||||
|
return findTargetByDefaultNode (properties, target_direction)
|
||||||
|
or findTargetByFirstAvailable (properties, target_direction)
|
||||||
|
end
|
||||||
|
|
||||||
|
function getSiLinkAndSiPeer (si, si_props)
|
||||||
|
local self_id_key = (si_props["item.node.direction"] == "output") and
|
||||||
|
"out.item.id" or "in.item.id"
|
||||||
|
local peer_id_key = (si_props["item.node.direction"] == "output") and
|
||||||
|
"in.item.id" or "out.item.id"
|
||||||
|
local silink = links_om:lookup { Constraint { self_id_key, "=", si.id } }
|
||||||
|
if silink then
|
||||||
|
local peer_id = tonumber(silink.properties[peer_id_key])
|
||||||
|
local peer = linkables_om:lookup {
|
||||||
|
Constraint { "id", "=", peer_id, type = "gobject" },
|
||||||
|
}
|
||||||
|
return silink, peer
|
||||||
end
|
end
|
||||||
return nil, nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function isSiLinkableValid (si)
|
function checkLinkable(si)
|
||||||
-- only handle session items that has a node associated proxy
|
|
||||||
local node = si:get_associated_proxy ("node")
|
|
||||||
if not node or not node.properties then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- only handle stream session items
|
-- only handle stream session items
|
||||||
local media_class = node.properties["media.class"]
|
local si_props = si.properties
|
||||||
if not media_class or not string.find (media_class, "Stream") then
|
if not si_props or si_props["item.node.type"] ~= "stream" then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Determine if we can handle item by this policy
|
-- Determine if we can handle item by this policy
|
||||||
local media_role = node.properties["media.role"]
|
local media_role = si_props["media.role"]
|
||||||
if siendpoints_om:get_n_objects () > 0 and media_role ~= nil then
|
if endpoints_om:get_n_objects () > 0 and media_role ~= nil then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
return true
|
return true, si_props
|
||||||
end
|
end
|
||||||
|
|
||||||
function getNodeAutoconnect (node)
|
function handleLinkable (si)
|
||||||
local auto_connect = node.properties["node.autoconnect"]
|
local valid, si_props = checkLinkable(si)
|
||||||
if auto_connect then
|
if not valid then
|
||||||
return (auto_connect == "true" or auto_connect == "1")
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function getNodeReconnect (node)
|
|
||||||
local dont_reconnect = node.properties["node.dont-reconnect"]
|
|
||||||
if dont_reconnect then
|
|
||||||
return not (dont_reconnect == "true" or dont_reconnect == "1")
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function handleSiLinkable (si)
|
|
||||||
-- check if item is valid
|
|
||||||
if not isSiLinkableValid (si) then
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local node = si:get_associated_proxy ("node")
|
-- check if we need to link this node at all
|
||||||
local media_class = node.properties["media.class"] or ""
|
local autoconnect = parseBool(si_props["node.autoconnect"])
|
||||||
Log.info (si, "handling item " .. tostring(node.properties["node.name"]))
|
|
||||||
|
|
||||||
local autoconnect = getNodeAutoconnect (node)
|
|
||||||
if not autoconnect then
|
if not autoconnect then
|
||||||
Log.info (si, "node does not need to be autoconnected")
|
Log.debug (si, tostring(si_props["node.name"]) .. " does not need to be autoconnected")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Log.info (si, "handling item: " .. tostring(si_props["node.name"]))
|
||||||
|
|
||||||
-- get reconnect
|
-- get reconnect
|
||||||
local reconnect = getNodeReconnect (node)
|
local reconnect = not parseBool(si_props["node.dont-reconnect"])
|
||||||
|
|
||||||
-- find target
|
-- find target
|
||||||
local si_target = findDefinedTarget (node)
|
local si_target = findDefinedTarget (si_props)
|
||||||
if not si_target and not reconnect then
|
if not si_target and not reconnect then
|
||||||
Log.info (si, "removing item and node")
|
Log.info (si, "... destroy node")
|
||||||
si:remove()
|
local node = si:get_associated_proxy ("node")
|
||||||
node:request_destroy()
|
node:request_destroy()
|
||||||
return
|
return
|
||||||
elseif not si_target and reconnect then
|
elseif not si_target and reconnect then
|
||||||
si_target = findUndefinedTarget (node)
|
si_target = findUndefinedTarget (si_props)
|
||||||
end
|
end
|
||||||
if not si_target then
|
if not si_target then
|
||||||
Log.info (si, "target not found")
|
Log.info (si, "... target not found")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if item is linked to proper target, otherwise re-link
|
-- Check if item is linked to proper target, otherwise re-link
|
||||||
local target_node = si_target:get_associated_proxy ("node")
|
local si_link, si_peer = getSiLinkAndSiPeer (si, si_props)
|
||||||
local target_media_class = target_node.properties["media.class"] or ""
|
|
||||||
local si_link, si_peer = getSiLinkAndSiPeer (si, target_media_class)
|
|
||||||
if si_link then
|
if si_link then
|
||||||
if si_peer and si_peer.id == si_target.id then
|
if si_peer and si_peer.id == si_target.id then
|
||||||
Log.debug (si, "already linked to proper target")
|
Log.debug (si, "... already linked to proper target")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- only remove old link if active, otherwise schedule pending rescan
|
-- only remove old link if active, otherwise schedule rescan
|
||||||
if ((si_link:get_active_features() & Feature.SessionItem.ACTIVE) ~= 0) then
|
if ((si_link:get_active_features() & Feature.SessionItem.ACTIVE) ~= 0) then
|
||||||
si_link:remove ()
|
si_link:remove ()
|
||||||
Log.info (si, "moving to new target")
|
Log.info (si, "... moving to new target")
|
||||||
else
|
else
|
||||||
pending_rescan = true
|
pending_rescan = true
|
||||||
Log.info (si, "scheduled pending rescan")
|
Log.info (si, "... scheduled rescan")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -362,64 +310,69 @@ function handleSiLinkable (si)
|
|||||||
createLink (si, si_target)
|
createLink (si, si_target)
|
||||||
end
|
end
|
||||||
|
|
||||||
function unhandleSiLinkable (si)
|
function unhandleLinkable (si)
|
||||||
-- check if item is valid
|
local valid, si_props = checkLinkable(si)
|
||||||
if not isSiLinkableValid (si) then
|
if not valid then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local node = si:get_associated_proxy ("node")
|
Log.info (si, "unhandling item: " .. tostring(si_props["node.name"]))
|
||||||
Log.info (si, "unhandling item " .. tostring(node.properties["node.name"]))
|
|
||||||
|
|
||||||
-- remove any links associated with this item
|
-- remove any links associated with this item
|
||||||
for silink in silinks_om:iterate() do
|
for silink in links_om:iterate() do
|
||||||
local out_id = tonumber (silink.properties["out.item.id"])
|
local out_id = tonumber (silink.properties["out.item.id"])
|
||||||
local in_id = tonumber (silink.properties["in.item.id"])
|
local in_id = tonumber (silink.properties["in.item.id"])
|
||||||
if out_id == si.id or in_id == si.id then
|
if out_id == si.id or in_id == si.id then
|
||||||
silink:remove ()
|
silink:remove ()
|
||||||
Log.info (silink, "link removed")
|
Log.info (silink, "... link removed")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function reevaluateSiLinkables ()
|
function rescan()
|
||||||
for si in silinkables_om:iterate() do
|
for si in linkables_om:iterate() do
|
||||||
handleSiLinkable (si)
|
handleLinkable (si)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- if pending_rescan, re-evaluate after sync
|
-- if pending_rescan, re-evaluate after sync
|
||||||
if pending_rescan then
|
if pending_rescan then
|
||||||
pending_rescan = false
|
pending_rescan = false
|
||||||
Core.sync (function (c)
|
Core.sync (function (c)
|
||||||
reevaluateSiLinkables ()
|
rescan()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
default_nodes = Plugin.find("default-nodes-api")
|
default_nodes = Plugin.find("default-nodes-api")
|
||||||
|
|
||||||
metadata_om = ObjectManager {
|
metadata_om = ObjectManager {
|
||||||
Interest {
|
Interest {
|
||||||
type = "metadata",
|
type = "metadata",
|
||||||
Constraint { "metadata.name", "=", "default" },
|
Constraint { "metadata.name", "=", "default" },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
siendpoints_om = ObjectManager { Interest { type = "SiEndpoint" }}
|
|
||||||
silinkables_om = ObjectManager { Interest { type = "SiLinkable",
|
endpoints_om = ObjectManager { Interest { type = "SiEndpoint" } }
|
||||||
-- only handle si-audio-adapter and si-node
|
|
||||||
Constraint {
|
linkables_om = ObjectManager {
|
||||||
"si.factory.name", "c", "si-audio-adapter", "si-node", type = "pw-global" },
|
Interest {
|
||||||
|
type = "SiLinkable",
|
||||||
|
-- only handle si-audio-adapter and si-node
|
||||||
|
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
links_om = ObjectManager {
|
||||||
|
Interest {
|
||||||
|
type = "SiLink",
|
||||||
|
-- only handle links created by this policy
|
||||||
|
Constraint { "is.policy.item.link", "=", true },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
silinks_om = ObjectManager { Interest { type = "SiLink",
|
|
||||||
-- only handle links created by this policy
|
|
||||||
Constraint { "is.policy.item.link", "=", true, type = "pw-global" },
|
|
||||||
} }
|
|
||||||
|
|
||||||
-- listen for default node changes if config.follow is enabled
|
-- listen for default node changes if config.follow is enabled
|
||||||
if config.follow then
|
if config.follow then
|
||||||
default_nodes:connect("changed", function (p)
|
default_nodes:connect("changed", rescan)
|
||||||
reevaluateSiLinkables ()
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- listen for target.node metadata changes if config.move is enabled
|
-- listen for target.node metadata changes if config.move is enabled
|
||||||
@@ -427,22 +380,21 @@ if config.move then
|
|||||||
metadata_om:connect("object-added", function (om, metadata)
|
metadata_om:connect("object-added", function (om, metadata)
|
||||||
metadata:connect("changed", function (m, subject, key, t, value)
|
metadata:connect("changed", function (m, subject, key, t, value)
|
||||||
if key == "target.node" then
|
if key == "target.node" then
|
||||||
reevaluateSiLinkables ()
|
rescan()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
silinkables_om:connect("objects-changed", function (om)
|
linkables_om:connect("objects-changed", function (om)
|
||||||
reevaluateSiLinkables ()
|
rescan()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
silinkables_om:connect("object-removed", function (om, si)
|
linkables_om:connect("object-removed", function (om, si)
|
||||||
unhandleSiLinkable (si)
|
unhandleLinkable (si)
|
||||||
reevaluateSiLinkables ()
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
metadata_om:activate()
|
metadata_om:activate()
|
||||||
siendpoints_om:activate()
|
endpoints_om:activate()
|
||||||
silinkables_om:activate()
|
linkables_om:activate()
|
||||||
silinks_om:activate()
|
links_om:activate()
|
||||||
|
@@ -76,7 +76,8 @@ test_si_audio_adapter_configure_activate (TestFixture * f,
|
|||||||
/* configure */
|
/* configure */
|
||||||
{
|
{
|
||||||
WpProperties *props = wp_properties_new_empty ();
|
WpProperties *props = wp_properties_new_empty ();
|
||||||
wp_properties_setf (props, "node", "%p", node);
|
wp_properties_setf (props, "item.node", "%p", node);
|
||||||
|
wp_properties_set (props, "media.class", "Audio/Source");
|
||||||
g_assert_true (wp_session_item_configure (adapter, props));
|
g_assert_true (wp_session_item_configure (adapter, props));
|
||||||
g_assert_true (wp_session_item_is_configured (adapter));
|
g_assert_true (wp_session_item_is_configured (adapter));
|
||||||
}
|
}
|
||||||
@@ -86,25 +87,7 @@ test_si_audio_adapter_configure_activate (TestFixture * f,
|
|||||||
const gchar *str = NULL;
|
const gchar *str = NULL;
|
||||||
g_autoptr (WpProperties) props = wp_session_item_get_properties (adapter);
|
g_autoptr (WpProperties) props = wp_session_item_get_properties (adapter);
|
||||||
g_assert_nonnull (props);
|
g_assert_nonnull (props);
|
||||||
str = wp_properties_get (props, "name");
|
str = wp_properties_get (props, "item.factory.name");
|
||||||
g_assert_nonnull (str);
|
|
||||||
g_assert_cmpstr ("audiotestsrc.adapter", ==, str);
|
|
||||||
str = wp_properties_get (props, "media.class");
|
|
||||||
g_assert_nonnull (str);
|
|
||||||
g_assert_cmpstr ("Audio/Source", ==, str);
|
|
||||||
str = wp_properties_get (props, "direction");
|
|
||||||
g_assert_nonnull (str);
|
|
||||||
g_assert_cmpstr ("1", ==, str);
|
|
||||||
str = wp_properties_get (props, "enable.control.port");
|
|
||||||
g_assert_nonnull (str);
|
|
||||||
g_assert_cmpstr ("0", ==, str);
|
|
||||||
str = wp_properties_get (props, "enable.monitor");
|
|
||||||
g_assert_nonnull (str);
|
|
||||||
g_assert_cmpstr ("0", ==, str);
|
|
||||||
str = wp_properties_get (props, "is.device");
|
|
||||||
g_assert_nonnull (str);
|
|
||||||
g_assert_cmpstr ("1", ==, str);
|
|
||||||
str = wp_properties_get (props, "si.factory.name");
|
|
||||||
g_assert_nonnull (str);
|
g_assert_nonnull (str);
|
||||||
g_assert_cmpstr ("si-audio-adapter", ==, str);
|
g_assert_cmpstr ("si-audio-adapter", ==, str);
|
||||||
}
|
}
|
||||||
|
@@ -79,7 +79,7 @@ test_si_audio_endpoint_configure_activate (TestFixture * f,
|
|||||||
str = wp_properties_get (props, "direction");
|
str = wp_properties_get (props, "direction");
|
||||||
g_assert_nonnull (str);
|
g_assert_nonnull (str);
|
||||||
g_assert_cmpstr ("1", ==, str);
|
g_assert_cmpstr ("1", ==, str);
|
||||||
str = wp_properties_get (props, "si.factory.name");
|
str = wp_properties_get (props, "item.factory.name");
|
||||||
g_assert_nonnull (str);
|
g_assert_nonnull (str);
|
||||||
g_assert_cmpstr ("si-audio-endpoint", ==, str);
|
g_assert_cmpstr ("si-audio-endpoint", ==, str);
|
||||||
}
|
}
|
||||||
|
@@ -89,7 +89,7 @@ test_si_node_configure_activate (TestFixture * f, gconstpointer user_data)
|
|||||||
|
|
||||||
{
|
{
|
||||||
WpProperties *props = wp_properties_new_empty ();
|
WpProperties *props = wp_properties_new_empty ();
|
||||||
wp_properties_setf (props, "node", "%p", node);
|
wp_properties_setf (props, "item.node", "%p", node);
|
||||||
wp_properties_set (props, "media.class", data->media_class);
|
wp_properties_set (props, "media.class", data->media_class);
|
||||||
g_assert_true (wp_session_item_configure (item, props));
|
g_assert_true (wp_session_item_configure (item, props));
|
||||||
g_assert_true (wp_session_item_is_configured (item));
|
g_assert_true (wp_session_item_is_configured (item));
|
||||||
@@ -99,16 +99,10 @@ test_si_node_configure_activate (TestFixture * f, gconstpointer user_data)
|
|||||||
const gchar *str = NULL;
|
const gchar *str = NULL;
|
||||||
g_autoptr (WpProperties) props = wp_session_item_get_properties (item);
|
g_autoptr (WpProperties) props = wp_session_item_get_properties (item);
|
||||||
g_assert_nonnull (props);
|
g_assert_nonnull (props);
|
||||||
str = wp_properties_get (props, "name");
|
|
||||||
g_assert_nonnull (str);
|
|
||||||
g_assert_cmpstr (data->name, ==, str);
|
|
||||||
str = wp_properties_get (props, "media.class");
|
str = wp_properties_get (props, "media.class");
|
||||||
g_assert_nonnull (str);
|
g_assert_nonnull (str);
|
||||||
g_assert_cmpstr (data->expected_media_class, ==, str);
|
g_assert_cmpstr (data->expected_media_class, ==, str);
|
||||||
str = wp_properties_get (props, "direction");
|
str = wp_properties_get (props, "item.factory.name");
|
||||||
g_assert_nonnull (str);
|
|
||||||
g_assert_cmpstr (data->expected_direction == 0 ? "0" : "1", ==, str);
|
|
||||||
str = wp_properties_get (props, "si.factory.name");
|
|
||||||
g_assert_nonnull (str);
|
g_assert_nonnull (str);
|
||||||
g_assert_cmpstr ("si-node", ==, str);
|
g_assert_cmpstr ("si-node", ==, str);
|
||||||
}
|
}
|
||||||
@@ -132,7 +126,8 @@ test_si_node_configure_activate (TestFixture * f, gconstpointer user_data)
|
|||||||
{
|
{
|
||||||
guint32 node_id, port_id, channel;
|
guint32 node_id, port_id, channel;
|
||||||
g_autoptr (GVariant) v =
|
g_autoptr (GVariant) v =
|
||||||
wp_si_linkable_get_ports (WP_SI_LINKABLE (item), NULL);
|
wp_si_linkable_get_ports (WP_SI_LINKABLE (item),
|
||||||
|
(data->expected_direction == WP_DIRECTION_INPUT) ? "input" : "output");
|
||||||
|
|
||||||
g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE ("a(uuu)")));
|
g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE ("a(uuu)")));
|
||||||
g_assert_cmpint (g_variant_n_children (v), ==, 1);
|
g_assert_cmpint (g_variant_n_children (v), ==, 1);
|
||||||
@@ -165,16 +160,10 @@ test_si_node_configure_activate (TestFixture * f, gconstpointer user_data)
|
|||||||
const gchar *str = NULL;
|
const gchar *str = NULL;
|
||||||
g_autoptr (WpProperties) props = wp_session_item_get_properties (item);
|
g_autoptr (WpProperties) props = wp_session_item_get_properties (item);
|
||||||
g_assert_nonnull (props);
|
g_assert_nonnull (props);
|
||||||
str = wp_properties_get (props, "name");
|
|
||||||
g_assert_nonnull (str);
|
|
||||||
g_assert_cmpstr (data->name, ==, str);
|
|
||||||
str = wp_properties_get (props, "media.class");
|
str = wp_properties_get (props, "media.class");
|
||||||
g_assert_nonnull (str);
|
g_assert_nonnull (str);
|
||||||
g_assert_cmpstr (data->expected_media_class, ==, str);
|
g_assert_cmpstr (data->expected_media_class, ==, str);
|
||||||
str = wp_properties_get (props, "direction");
|
str = wp_properties_get (props, "item.factory.name");
|
||||||
g_assert_nonnull (str);
|
|
||||||
g_assert_cmpstr (data->expected_direction == 0 ? "0" : "1", ==, str);
|
|
||||||
str = wp_properties_get (props, "si.factory.name");
|
|
||||||
g_assert_nonnull (str);
|
g_assert_nonnull (str);
|
||||||
g_assert_cmpstr ("si-node", ==, str);
|
g_assert_cmpstr ("si-node", ==, str);
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,8 @@ typedef struct {
|
|||||||
} TestFixture;
|
} TestFixture;
|
||||||
|
|
||||||
static WpSessionItem *
|
static WpSessionItem *
|
||||||
load_node (TestFixture * f, const gchar * factory, const gchar * media_class)
|
load_node (TestFixture * f, const gchar * factory, const gchar * media_class,
|
||||||
|
const gchar * type)
|
||||||
{
|
{
|
||||||
g_autoptr (WpNode) node = NULL;
|
g_autoptr (WpNode) node = NULL;
|
||||||
g_autoptr (WpSessionItem) adapter = NULL;
|
g_autoptr (WpSessionItem) adapter = NULL;
|
||||||
@@ -45,8 +46,9 @@ load_node (TestFixture * f, const gchar * factory, const gchar * media_class)
|
|||||||
/* configure */
|
/* configure */
|
||||||
{
|
{
|
||||||
WpProperties *props = wp_properties_new_empty ();
|
WpProperties *props = wp_properties_new_empty ();
|
||||||
wp_properties_setf (props, "node", "%p", node);
|
wp_properties_setf (props, "item.node", "%p", node);
|
||||||
wp_properties_set (props, "media.class", media_class);
|
wp_properties_set (props, "media.class", media_class);
|
||||||
|
wp_properties_set (props, "item.node.type", type);
|
||||||
g_assert_true (wp_session_item_configure (adapter, props));
|
g_assert_true (wp_session_item_configure (adapter, props));
|
||||||
g_assert_true (wp_session_item_is_configured (adapter));
|
g_assert_true (wp_session_item_is_configured (adapter));
|
||||||
}
|
}
|
||||||
@@ -92,9 +94,9 @@ test_si_standard_link_setup (TestFixture * f, gconstpointer user_data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (test_is_spa_lib_installed (&f->base, "audiotestsrc"))
|
if (test_is_spa_lib_installed (&f->base, "audiotestsrc"))
|
||||||
f->src_item = load_node (f, "audiotestsrc", "Stream/Output/Audio");
|
f->src_item = load_node (f, "audiotestsrc", "Stream/Output/Audio", "stream");
|
||||||
if (test_is_spa_lib_installed (&f->base, "support.null-audio-sink"))
|
if (test_is_spa_lib_installed (&f->base, "support.null-audio-sink"))
|
||||||
f->sink_item = load_node (f, "support.null-audio-sink", "Audio/Sink");
|
f->sink_item = load_node (f, "support.null-audio-sink", "Audio/Sink", "device");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -131,6 +133,8 @@ test_si_standard_link_main (TestFixture * f, gconstpointer user_data)
|
|||||||
g_autoptr (WpProperties) props = wp_properties_new_empty ();
|
g_autoptr (WpProperties) props = wp_properties_new_empty ();
|
||||||
wp_properties_setf (props, "out.item", "%p", f->src_item);
|
wp_properties_setf (props, "out.item", "%p", f->src_item);
|
||||||
wp_properties_setf (props, "in.item", "%p", f->sink_item);
|
wp_properties_setf (props, "in.item", "%p", f->sink_item);
|
||||||
|
wp_properties_set (props, "out.item.port.context", "output");
|
||||||
|
wp_properties_set (props, "in.item.port.context", "input");
|
||||||
g_assert_true (wp_session_item_configure (link, g_steal_pointer (&props)));
|
g_assert_true (wp_session_item_configure (link, g_steal_pointer (&props)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user