mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2025-12-20 05:20:05 +01:00
conf: refactor component loading to use a dependency system
Each component can now list required and optional dependencies, using the component feature names to match other components. In addition, each component feature can be declared as required, optional or disabled, making optional components easier to deal with. The component flags (ifexists, nofail) have been removed. Using virtual components, this system also allows easier customization of which components should be loaded for a specific configuration, without requiring the user to copy the list of components and edit it. Also bump the required glib version to 2.68 for g_assert_cmpstrv()
This commit is contained in:
parent
9c2d25b985
commit
e738076cb0
6 changed files with 849 additions and 323 deletions
|
|
@ -14,137 +14,152 @@ WP_DEFINE_LOCAL_LOG_TOPIC ("wp-internal-comp-loader")
|
|||
|
||||
/*** ComponentData ***/
|
||||
|
||||
enum
|
||||
{
|
||||
NO_FAIL = 0x1,
|
||||
IF_EXISTS = 0x2
|
||||
};
|
||||
typedef enum {
|
||||
FEATURE_STATE_DISABLED,
|
||||
FEATURE_STATE_OPTIONAL,
|
||||
FEATURE_STATE_REQUIRED
|
||||
} FeatureState;
|
||||
|
||||
typedef struct _ComponentData ComponentData;
|
||||
struct _ComponentData
|
||||
{
|
||||
grefcount ref;
|
||||
/* an identifier for this component that is understandable by the end user */
|
||||
gchar *printable_id;
|
||||
/* the provided feature name (points to same storage as the id) or NULL */
|
||||
gchar *provides;
|
||||
/* the original state of the feature (required / optional / disabled) */
|
||||
FeatureState state;
|
||||
|
||||
/* other fields extracted as-is from the json description */
|
||||
gchar *name;
|
||||
gchar *type;
|
||||
gint priority;
|
||||
gint flags;
|
||||
WpSpaJson *deps;
|
||||
WpSpaJson *arguments;
|
||||
GPtrArray *requires; /* value-type: string (owned) */
|
||||
GPtrArray *wants; /* value-type: string (owned) */
|
||||
|
||||
/* TRUE when the component is in the final sorted list */
|
||||
gboolean visited;
|
||||
/* one of the components that requires this one with a strong
|
||||
dependency chain (i.e. there is a required component that requires
|
||||
this one, directly or indirectly) */
|
||||
ComponentData *required_by;
|
||||
};
|
||||
typedef struct _ComponentData ComponentData;
|
||||
|
||||
static gint
|
||||
component_cmp_func (const ComponentData *a, const ComponentData *b)
|
||||
{
|
||||
return b->priority - a->priority;
|
||||
}
|
||||
static void component_data_free (ComponentData * self);
|
||||
|
||||
static gint
|
||||
component_equal_func (const ComponentData *a, ComponentData * b)
|
||||
static ComponentData *
|
||||
component_data_ref (ComponentData *self)
|
||||
{
|
||||
return
|
||||
g_str_equal (a->name, b->name) && g_str_equal (a->type, b->type) ? 0 : 1;
|
||||
g_ref_count_inc (&self->ref);
|
||||
return self;
|
||||
}
|
||||
|
||||
static void
|
||||
component_data_free (ComponentData *self)
|
||||
component_data_unref (ComponentData *self)
|
||||
{
|
||||
g_clear_pointer (&self->name, g_free);
|
||||
g_clear_pointer (&self->type, g_free);
|
||||
g_clear_pointer (&self->deps, wp_spa_json_unref);
|
||||
g_slice_free (ComponentData, self);
|
||||
if (self && g_ref_count_dec (&self->ref))
|
||||
component_data_free (self);
|
||||
}
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (ComponentData, component_data_free)
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (ComponentData, component_data_unref)
|
||||
|
||||
/*** components parser ***/
|
||||
|
||||
static gint
|
||||
pick_default_component_priority (const char *type)
|
||||
static FeatureState
|
||||
get_feature_state (WpProperties * dict, const gchar * feature)
|
||||
{
|
||||
if (g_str_equal (type, "module"))
|
||||
/* regular module default priority */
|
||||
return 110;
|
||||
else if (g_str_equal (type, "script/lua"))
|
||||
/* Lua Script default priority */
|
||||
return 100;
|
||||
const gchar *value = wp_properties_get (dict, feature);
|
||||
|
||||
return 100;
|
||||
if (!value || g_str_equal (value, "optional"))
|
||||
return FEATURE_STATE_OPTIONAL;
|
||||
else if (g_str_equal (value, "required"))
|
||||
return FEATURE_STATE_REQUIRED;
|
||||
else if (g_str_equal (value, "disabled"))
|
||||
return FEATURE_STATE_DISABLED;
|
||||
else {
|
||||
wp_warning ("invalid feature state '%s' specified in configuration for '%s'",
|
||||
value, feature);
|
||||
wp_warning ("considering '%s' to be optional", feature);
|
||||
return FEATURE_STATE_OPTIONAL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
json_to_components_list (GList **list, WpSpaJson *json)
|
||||
static ComponentData *
|
||||
component_data_new_from_json (WpSpaJson * json, WpProperties * features,
|
||||
GError ** error)
|
||||
{
|
||||
g_autoptr (WpIterator) it = NULL;
|
||||
g_auto (GValue) item = G_VALUE_INIT;
|
||||
g_autoptr (ComponentData) comp = NULL;
|
||||
g_autoptr (WpSpaJson) deps = NULL;
|
||||
|
||||
it = wp_spa_json_new_iterator (json);
|
||||
for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
|
||||
WpSpaJson *cjson = g_value_get_boxed (&item);
|
||||
g_autoptr (ComponentData) comp = g_slice_new0 (ComponentData);
|
||||
g_autoptr (WpSpaJson) deps = NULL;
|
||||
g_autoptr (WpSpaJson) flags = NULL;
|
||||
if (!wp_spa_json_is_object (json)) {
|
||||
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
||||
"expected JSON object instead of: %.*s", (int) wp_spa_json_get_size (json),
|
||||
wp_spa_json_get_data (json));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Parse name and type (mandatory) */
|
||||
if (!wp_spa_json_is_object (cjson) ||
|
||||
!wp_spa_json_object_get (cjson,
|
||||
"name", "s", &comp->name,
|
||||
"type", "s", &comp->type,
|
||||
NULL)) {
|
||||
wp_warning ("component must have both a 'name' and a 'type'");
|
||||
continue;
|
||||
}
|
||||
comp = g_new0 (ComponentData, 1);
|
||||
g_ref_count_init (&comp->ref);
|
||||
comp->requires = g_ptr_array_new_with_free_func (g_free);
|
||||
comp->wants = g_ptr_array_new_with_free_func (g_free);
|
||||
|
||||
/* Parse priority (optional) */
|
||||
if (!wp_spa_json_object_get (cjson, "priority", "i", &comp->priority,
|
||||
NULL))
|
||||
comp->priority = pick_default_component_priority (comp->type);
|
||||
if (!wp_spa_json_object_get (json, "type", "s", &comp->type, NULL)) {
|
||||
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
||||
"component 'type' is required at: %.*s", (int) wp_spa_json_get_size (json),
|
||||
wp_spa_json_get_data (json));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Parse deps (optional) */
|
||||
if (wp_spa_json_object_get (cjson, "deps", "J", &deps, NULL)) {
|
||||
if (wp_spa_json_is_array (deps)) {
|
||||
comp->deps = g_steal_pointer (&deps);
|
||||
} else {
|
||||
wp_warning ("skipping component %s as its 'deps' is not a JSON array",
|
||||
comp->name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
wp_spa_json_object_get (json, "name", "s", &comp->name, NULL);
|
||||
wp_spa_json_object_get (json, "arguments", "J", &comp->arguments, NULL);
|
||||
|
||||
/* Parse flags (optional) */
|
||||
if (wp_spa_json_object_get (cjson, "flags", "J", &flags, NULL)) {
|
||||
if (flags && wp_spa_json_is_array (flags)) {
|
||||
g_autoptr (WpIterator) it = wp_spa_json_new_iterator (flags);
|
||||
g_auto (GValue) item = G_VALUE_INIT;
|
||||
|
||||
for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
|
||||
WpSpaJson *flag = g_value_get_boxed (&item);
|
||||
g_autofree gchar *flag_str = wp_spa_json_parse_string (flag);
|
||||
|
||||
if (g_str_equal (flag_str, "ifexists"))
|
||||
comp->flags |= IF_EXISTS;
|
||||
else if (g_str_equal (flag_str, "nofail"))
|
||||
comp->flags |= NO_FAIL;
|
||||
else
|
||||
wp_warning ("flag '%s' is not valid for component '%s'", flag_str,
|
||||
comp->name);
|
||||
}
|
||||
} else {
|
||||
wp_warning ("skipping component %s as its 'flags' is not a JSON array",
|
||||
comp->name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert component into the list if it does not exist */
|
||||
if (!g_list_find_custom (*list, comp,
|
||||
(GCompareFunc) component_equal_func)) {
|
||||
wp_trace ("appended component '%s' of type '%s' with priority '%d'",
|
||||
comp->name, comp->type, comp->priority);
|
||||
*list = g_list_insert_sorted (*list, g_steal_pointer (&comp),
|
||||
(GCompareFunc) component_cmp_func);
|
||||
if (wp_spa_json_object_get (json, "provides", "s", &comp->provides, NULL)) {
|
||||
comp->state = get_feature_state (features, comp->provides);
|
||||
if (comp->name) {
|
||||
comp->printable_id =
|
||||
g_strdup_printf ("%s [%s: %s]", comp->provides, comp->type, comp->name);
|
||||
} else {
|
||||
wp_debug ("ignoring component '%s' as it is already defined previously",
|
||||
comp->name);
|
||||
comp->printable_id = g_strdup_printf ("%s [%s]", comp->provides, comp->type);
|
||||
}
|
||||
} else {
|
||||
comp->provides = NULL;
|
||||
comp->state = FEATURE_STATE_REQUIRED;
|
||||
comp->printable_id = g_strdup_printf ("[%s: %s]", comp->type, comp->name);
|
||||
}
|
||||
|
||||
if (wp_spa_json_object_get (json, "requires", "J", &deps, NULL)) {
|
||||
g_autoptr (WpIterator) it = wp_spa_json_new_iterator (deps);
|
||||
g_auto (GValue) item = G_VALUE_INIT;
|
||||
|
||||
for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
|
||||
WpSpaJson *dep = g_value_get_boxed (&item);
|
||||
g_ptr_array_add (comp->requires, wp_spa_json_to_string (dep));
|
||||
}
|
||||
}
|
||||
|
||||
if (wp_spa_json_object_get (json, "wants", "J", &deps, NULL)) {
|
||||
g_autoptr (WpIterator) it = wp_spa_json_new_iterator (deps);
|
||||
g_auto (GValue) item = G_VALUE_INIT;
|
||||
|
||||
for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
|
||||
WpSpaJson *dep = g_value_get_boxed (&item);
|
||||
g_ptr_array_add (comp->wants, wp_spa_json_to_string (dep));
|
||||
}
|
||||
}
|
||||
|
||||
return g_steal_pointer (&comp);
|
||||
}
|
||||
|
||||
static void
|
||||
component_data_free (ComponentData * self)
|
||||
{
|
||||
g_clear_pointer (&self->provides, g_free);
|
||||
g_clear_pointer (&self->printable_id, g_free);
|
||||
g_clear_pointer (&self->name, g_free);
|
||||
g_clear_pointer (&self->type, g_free);
|
||||
g_clear_pointer (&self->arguments, wp_spa_json_unref);
|
||||
g_clear_pointer (&self->requires, g_ptr_array_unref);
|
||||
g_clear_pointer (&self->wants, g_ptr_array_unref);
|
||||
g_free (self);
|
||||
}
|
||||
|
||||
/*** WpComponentArrayLoadTask ***/
|
||||
|
|
@ -152,17 +167,22 @@ json_to_components_list (GList **list, WpSpaJson *json)
|
|||
struct _WpComponentArrayLoadTask
|
||||
{
|
||||
WpTransition parent;
|
||||
/* the input json object */
|
||||
WpSpaJson *json;
|
||||
GList *components;
|
||||
GList *components_iter;
|
||||
/* all components that provide a feature; key: comp->provides, value: comp */
|
||||
GHashTable *feat_components;
|
||||
/* the final sorted list of components to load */
|
||||
GPtrArray *components;
|
||||
/* iterator in the components array above */
|
||||
ComponentData **components_iter;
|
||||
/* the current component being loaded */
|
||||
ComponentData *curr_component;
|
||||
};
|
||||
|
||||
enum {
|
||||
STEP_PARSE = WP_TRANSITION_STEP_CUSTOM_START,
|
||||
STEP_LOAD_NEXT_1,
|
||||
STEP_LOAD_NEXT_2,
|
||||
STEP_CLEANUP,
|
||||
STEP_GET_NEXT,
|
||||
STEP_LOAD_NEXT,
|
||||
};
|
||||
|
||||
G_DECLARE_FINAL_TYPE (WpComponentArrayLoadTask, wp_component_array_load_task,
|
||||
|
|
@ -175,32 +195,6 @@ wp_component_array_load_task_init (WpComponentArrayLoadTask * self)
|
|||
{
|
||||
}
|
||||
|
||||
static gboolean
|
||||
component_meets_dependencies (WpCore *core, ComponentData *comp)
|
||||
{
|
||||
g_autoptr (WpConf) conf = NULL;
|
||||
g_autoptr (WpIterator) it = NULL;
|
||||
g_auto (GValue) item = G_VALUE_INIT;
|
||||
|
||||
if (!comp->deps)
|
||||
return TRUE;
|
||||
|
||||
/* Note that we consider the dependency valid by default if it is not
|
||||
* found in the settings configuration section */
|
||||
conf = wp_conf_get_instance (core);
|
||||
it = wp_spa_json_new_iterator (comp->deps);
|
||||
for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
|
||||
WpSpaJson *dep = g_value_get_boxed (&item);
|
||||
g_autofree gchar *dep_str = wp_spa_json_parse_string (dep);
|
||||
gboolean value = wp_conf_get_value_boolean (conf,
|
||||
"wireplumber.settings", dep_str, TRUE);
|
||||
if (!value)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static guint
|
||||
wp_component_array_load_task_get_next_step (WpTransition * transition, guint step)
|
||||
{
|
||||
|
|
@ -208,17 +202,164 @@ wp_component_array_load_task_get_next_step (WpTransition * transition, guint ste
|
|||
|
||||
switch (step) {
|
||||
case WP_TRANSITION_STEP_NONE: return STEP_PARSE;
|
||||
case STEP_PARSE: return STEP_LOAD_NEXT_1;
|
||||
case STEP_LOAD_NEXT_1:
|
||||
return (self->components_iter) ? STEP_LOAD_NEXT_2 : STEP_CLEANUP;
|
||||
case STEP_LOAD_NEXT_2:
|
||||
return (self->components_iter) ? STEP_LOAD_NEXT_1 : STEP_CLEANUP;
|
||||
case STEP_CLEANUP: return WP_TRANSITION_STEP_NONE;
|
||||
case STEP_PARSE: return STEP_GET_NEXT;
|
||||
case STEP_GET_NEXT:
|
||||
return (self->curr_component) ? STEP_LOAD_NEXT : WP_TRANSITION_STEP_NONE;
|
||||
case STEP_LOAD_NEXT: return STEP_GET_NEXT;
|
||||
default:
|
||||
g_return_val_if_reached (WP_TRANSITION_STEP_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
static gchar *
|
||||
print_dep_chain (ComponentData *comp)
|
||||
{
|
||||
GString *str = g_string_new (NULL);
|
||||
|
||||
while (comp->required_by) {
|
||||
comp = comp->required_by;
|
||||
g_string_prepend (str, comp->printable_id);
|
||||
if (comp->required_by)
|
||||
g_string_prepend (str, " -> ");
|
||||
}
|
||||
return g_string_free (str, FALSE);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
add_component (ComponentData * comp, gboolean strongly_required,
|
||||
WpComponentArrayLoadTask * self, GError ** error)
|
||||
{
|
||||
if (comp->visited || comp->state == FEATURE_STATE_DISABLED)
|
||||
return TRUE;
|
||||
|
||||
comp->visited = TRUE;
|
||||
|
||||
/* recursively visit all the required features */
|
||||
for (guint i = 0; i < comp->requires->len; i++) {
|
||||
const gchar *dependency = g_ptr_array_index (comp->requires, i);
|
||||
ComponentData *req_comp =
|
||||
g_hash_table_lookup (self->feat_components, dependency);
|
||||
if (!req_comp) {
|
||||
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
||||
"no component provides '%s', required by '%s'", dependency,
|
||||
comp->printable_id);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* make a note if there is a strong dependency chain */
|
||||
if (strongly_required && !req_comp->required_by) {
|
||||
if (req_comp->state == FEATURE_STATE_OPTIONAL) {
|
||||
req_comp->required_by = comp;
|
||||
}
|
||||
else if (req_comp->state == FEATURE_STATE_DISABLED) {
|
||||
g_autofree gchar *dep_chain = print_dep_chain (comp);
|
||||
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
||||
"component '%s' is disabled, required by %s",
|
||||
req_comp->printable_id, dep_chain);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!add_component (req_comp, strongly_required, self, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* recursively visit all the optionally wanted features */
|
||||
for (guint i = 0; i < comp->wants->len; i++) {
|
||||
const gchar *dependency = g_ptr_array_index (comp->wants, i);
|
||||
ComponentData *wanted_comp =
|
||||
g_hash_table_lookup (self->feat_components, dependency);
|
||||
if (!wanted_comp) {
|
||||
/* in theory we could ignore this, but it's most likely a typo,
|
||||
so let's be strict about it and let the user correct it */
|
||||
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
||||
"no component provides '%s', wanted by '%s'", dependency,
|
||||
comp->printable_id);
|
||||
return FALSE;
|
||||
}
|
||||
if (!add_component (wanted_comp, FALSE, self, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* append component to the sorted list after all its dependencies */
|
||||
g_ptr_array_add (self->components, component_data_ref (comp));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static WpProperties *
|
||||
conf_get_features_section (WpComponentArrayLoadTask * self)
|
||||
{
|
||||
WpProperties *props = wp_properties_new_empty ();
|
||||
WpCore *core = wp_transition_get_data (WP_TRANSITION (self));
|
||||
g_autoptr (WpConf) conf = wp_conf_get_instance (core);
|
||||
g_autoptr (WpSpaJson) json =
|
||||
wp_conf_get_section (conf, "wireplumber.features", NULL);
|
||||
if (json)
|
||||
wp_properties_update_from_json (props, json);
|
||||
return props;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_components (WpComponentArrayLoadTask * self, GError ** error)
|
||||
{
|
||||
/* all the parsed components that are explicitly required */
|
||||
g_autoptr (GPtrArray) required_components = NULL;
|
||||
g_autoptr (WpProperties) conf = conf_get_features_section (self);
|
||||
g_autoptr (WpIterator) it = NULL;
|
||||
g_auto (GValue) item = G_VALUE_INIT;
|
||||
|
||||
if (!wp_spa_json_is_array (self->json)) {
|
||||
g_set_error (error,
|
||||
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
||||
"components section is not a JSON array");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
self->feat_components = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
NULL, (GDestroyNotify) component_data_unref);
|
||||
self->components = g_ptr_array_new_with_free_func (
|
||||
(GDestroyNotify) component_data_unref);
|
||||
required_components = g_ptr_array_new_with_free_func (
|
||||
(GDestroyNotify) component_data_unref);
|
||||
|
||||
/* first parse each component from its json description */
|
||||
it = wp_spa_json_new_iterator (self->json);
|
||||
for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
|
||||
WpSpaJson *cjson = g_value_get_boxed (&item);
|
||||
GError *e = NULL;
|
||||
g_autoptr (ComponentData) comp = NULL;
|
||||
|
||||
if (!(comp = component_data_new_from_json (cjson, conf, &e))) {
|
||||
g_propagate_error (error, e);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (comp->state == FEATURE_STATE_REQUIRED)
|
||||
g_ptr_array_add (required_components, component_data_ref (comp));
|
||||
|
||||
if (comp->provides)
|
||||
g_hash_table_insert (self->feat_components, comp->provides,
|
||||
component_data_ref (comp));
|
||||
}
|
||||
|
||||
/* topological sorting based on depth-first search */
|
||||
for (guint i = 0; i < required_components->len; i++) {
|
||||
ComponentData *comp = g_ptr_array_index (required_components, i);
|
||||
GError *e = NULL;
|
||||
if (!add_component (comp, TRUE, self, &e)) {
|
||||
g_propagate_error (error, e);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* terminate the array with NULL */
|
||||
g_ptr_array_add (self->components, NULL);
|
||||
|
||||
/* clear feat_components, they are no longer needed */
|
||||
g_clear_pointer (&self->feat_components, g_hash_table_unref);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_component_loaded (WpCore *core, GAsyncResult *res, gpointer data)
|
||||
{
|
||||
|
|
@ -228,26 +369,30 @@ on_component_loaded (WpCore *core, GAsyncResult *res, gpointer data)
|
|||
g_return_if_fail (self->curr_component);
|
||||
|
||||
if (!wp_core_load_component_finish (core, res, &error)) {
|
||||
if (self->curr_component->flags & IF_EXISTS &&
|
||||
error->domain == G_IO_ERROR &&
|
||||
error->code == G_IO_ERROR_NOT_FOUND) {
|
||||
wp_info_object (self, "skipping component '%s' with 'ifexists' flag "
|
||||
"because the file does not exist", self->curr_component->name);
|
||||
goto next;
|
||||
} else if (self->curr_component->flags & NO_FAIL) {
|
||||
wp_info_object (self, "skipping component '%s' with 'nofail' flag "
|
||||
"due to error: %s", self->curr_component->name, error->message);
|
||||
goto next;
|
||||
// if it was required, fail
|
||||
if (self->curr_component->state == FEATURE_STATE_REQUIRED) {
|
||||
wp_transition_return_error (WP_TRANSITION (self), g_error_new (
|
||||
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
||||
"failed to load required component '%s': %s",
|
||||
self->curr_component->printable_id, error->message));
|
||||
return;
|
||||
}
|
||||
// if it was optional, check if strongly_required
|
||||
else if (self->curr_component->state == FEATURE_STATE_OPTIONAL &&
|
||||
self->curr_component->required_by) {
|
||||
g_autofree gchar *dep_chain = print_dep_chain (self->curr_component);
|
||||
wp_transition_return_error (WP_TRANSITION (self), g_error_new (
|
||||
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
||||
"failed to load component '%s' (required by %s): %s",
|
||||
self->curr_component->printable_id, dep_chain, error->message));
|
||||
return;
|
||||
}
|
||||
else {
|
||||
wp_notice_object (core, "optional component '%s' failed to load: %s",
|
||||
self->curr_component->printable_id, error->message);
|
||||
}
|
||||
|
||||
wp_transition_return_error (WP_TRANSITION (self), g_error_new (
|
||||
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
||||
"failed to activate component '%s': %s", self->curr_component->name,
|
||||
error->message));
|
||||
return;
|
||||
}
|
||||
|
||||
next:
|
||||
wp_transition_advance (WP_TRANSITION (self));
|
||||
}
|
||||
|
||||
|
|
@ -255,52 +400,62 @@ static void
|
|||
wp_component_array_load_task_execute_step (WpTransition * transition, guint step)
|
||||
{
|
||||
WpComponentArrayLoadTask *self = WP_COMPONENT_ARRAY_LOAD_TASK (transition);
|
||||
WpCore *core = wp_transition_get_source_object (transition);
|
||||
WpCore *core = wp_transition_get_data(transition);
|
||||
|
||||
switch (step) {
|
||||
case STEP_PARSE:
|
||||
if (!wp_spa_json_is_array (self->json)) {
|
||||
wp_transition_return_error (transition, g_error_new (
|
||||
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
||||
"components section is not a JSON array"));
|
||||
return;
|
||||
case STEP_PARSE: {
|
||||
g_autoptr (GError) error = NULL;
|
||||
if (parse_components (self, &error)) {
|
||||
self->components_iter =
|
||||
(ComponentData **) &g_ptr_array_index (self->components, 0);
|
||||
wp_transition_advance (transition);
|
||||
} else {
|
||||
wp_transition_return_error (transition, error);
|
||||
}
|
||||
|
||||
json_to_components_list (&self->components, self->json);
|
||||
self->components_iter = g_list_first (self->components);
|
||||
break;
|
||||
}
|
||||
case STEP_GET_NEXT:
|
||||
/* get the next enabled component */
|
||||
do {
|
||||
self->curr_component = (ComponentData *) *self->components_iter;
|
||||
self->components_iter++;
|
||||
} while (self->curr_component &&
|
||||
self->curr_component->state == FEATURE_STATE_DISABLED);
|
||||
wp_transition_advance (transition);
|
||||
break;
|
||||
|
||||
case STEP_LOAD_NEXT_1:
|
||||
case STEP_LOAD_NEXT_2:
|
||||
self->curr_component = (ComponentData *) self->components_iter->data;
|
||||
case STEP_LOAD_NEXT: {
|
||||
/* verify that dependencies have been loaded */
|
||||
gboolean dependencies_ok = TRUE;
|
||||
for (guint i = 0; i < self->curr_component->requires->len; i++) {
|
||||
const gchar *dependency =
|
||||
g_ptr_array_index (self->curr_component->requires, i);
|
||||
if (!wp_core_test_feature (core, dependency)) {
|
||||
dependencies_ok = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Advance iterator */
|
||||
self->components_iter = g_list_next (self->components_iter);
|
||||
|
||||
/* Skip component if its dependencies are not met */
|
||||
if (!component_meets_dependencies (core, self->curr_component)) {
|
||||
wp_info_object (self, "... skipping component '%s' as its dependencies "
|
||||
"are not met", self->curr_component->name);
|
||||
if (!dependencies_ok) {
|
||||
/* this component must be optional, because if it wasn't, the dependency
|
||||
failing to load would have caused an error earlier */
|
||||
g_assert (self->curr_component->state == FEATURE_STATE_OPTIONAL);
|
||||
wp_notice_object (core, "skipping component '%s' because some of its "
|
||||
"dependencies were not loaded", self->curr_component->printable_id);
|
||||
wp_transition_advance (transition);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Load the component */
|
||||
wp_debug_object (self,
|
||||
"... loading component '%s' ('%s') with priority '%d' and flags '%x'",
|
||||
self->curr_component->name, self->curr_component->type,
|
||||
self->curr_component->priority, self->curr_component->flags);
|
||||
wp_debug_object (self, "loading component '%s'",
|
||||
self->curr_component->printable_id);
|
||||
wp_core_load_component (core, self->curr_component->name,
|
||||
self->curr_component->type, NULL, NULL, NULL,
|
||||
self->curr_component->type, self->curr_component->arguments,
|
||||
self->curr_component->provides, NULL,
|
||||
(GAsyncReadyCallback) on_component_loaded, self);
|
||||
break;
|
||||
|
||||
case STEP_CLEANUP:
|
||||
}
|
||||
case WP_TRANSITION_STEP_ERROR:
|
||||
g_list_free_full (g_steal_pointer (&self->components),
|
||||
(GDestroyNotify) component_data_free);
|
||||
g_clear_pointer (&self->json, wp_spa_json_unref);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -308,10 +463,26 @@ wp_component_array_load_task_execute_step (WpTransition * transition, guint step
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wp_component_array_load_task_finalize (GObject * object)
|
||||
{
|
||||
WpComponentArrayLoadTask *self = WP_COMPONENT_ARRAY_LOAD_TASK (object);
|
||||
|
||||
g_clear_pointer (&self->feat_components, g_hash_table_unref);
|
||||
g_clear_pointer (&self->components, g_ptr_array_unref);
|
||||
g_clear_pointer (&self->json, wp_spa_json_unref);
|
||||
|
||||
G_OBJECT_CLASS (wp_component_array_load_task_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_component_array_load_task_class_init (WpComponentArrayLoadTaskClass * klass)
|
||||
{
|
||||
GObjectClass * object_class = (GObjectClass *) klass;
|
||||
WpTransitionClass * transition_class = (WpTransitionClass *) klass;
|
||||
|
||||
object_class->finalize = wp_component_array_load_task_finalize;
|
||||
|
||||
transition_class->get_next_step = wp_component_array_load_task_get_next_step;
|
||||
transition_class->execute_step = wp_component_array_load_task_execute_step;
|
||||
}
|
||||
|
|
@ -391,7 +562,9 @@ static gboolean
|
|||
wp_internal_comp_loader_supports_type (WpComponentLoader * cl,
|
||||
const gchar * type)
|
||||
{
|
||||
return g_str_equal (type, "module") || g_str_equal (type, "array");
|
||||
return g_str_equal (type, "module") ||
|
||||
g_str_equal (type, "array") ||
|
||||
g_str_equal (type, "virtual");
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -414,11 +587,18 @@ wp_internal_comp_loader_load (WpComponentLoader * self, WpCore * core,
|
|||
g_task_return_error (task, g_steal_pointer (&error));
|
||||
}
|
||||
else if (g_str_equal (type, "array")) {
|
||||
WpTransition *task = wp_component_array_load_task_new (args, core,
|
||||
WpTransition *task = wp_component_array_load_task_new (args, self,
|
||||
cancellable, callback, data);
|
||||
wp_transition_set_data (task, g_object_ref (core), g_object_unref);
|
||||
wp_transition_set_source_tag (task, wp_internal_comp_loader_load);
|
||||
wp_transition_advance (task);
|
||||
}
|
||||
else if (g_str_equal (type, "virtual")) {
|
||||
/* dummy task, return immediately */
|
||||
g_autoptr (GTask) task = g_task_new (self, cancellable, callback, data);
|
||||
g_task_set_source_tag (task, wp_internal_comp_loader_load);
|
||||
g_task_return_pointer (task, NULL, NULL);
|
||||
}
|
||||
else {
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ pipewire_data_dir = get_option('prefix') / get_option('datadir') / 'pipewire'
|
|||
|
||||
cc = meson.get_compiler('c')
|
||||
|
||||
glib_req_version = '>= 2.62'
|
||||
glib_req_version = '>= 2.68'
|
||||
add_project_arguments([
|
||||
'-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_62',
|
||||
'-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_62',
|
||||
'-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_68',
|
||||
'-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_68',
|
||||
], language: 'c'
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -79,178 +79,403 @@ context.modules = [
|
|||
{ name = libpipewire-module-spa-node-factory }
|
||||
]
|
||||
|
||||
wireplumber.features = {
|
||||
## Syntax:
|
||||
## <feature name> = [ required | optional | disabled ]
|
||||
## optional is the default
|
||||
|
||||
support.settings = required
|
||||
hardware.audio = required
|
||||
hardware.bluetooth = required
|
||||
hardware.video-capture = required
|
||||
policy.standard = required
|
||||
#policy.role-priority-system = optional
|
||||
}
|
||||
|
||||
wireplumber.components = [
|
||||
## WirePlumber components to load
|
||||
## name and type are mandatory rest of the tags are optional.
|
||||
## type is mandatory; rest of the tags are optional
|
||||
##
|
||||
## Syntax:
|
||||
## {
|
||||
## name = <component-name>
|
||||
## type = <component-type>
|
||||
## Higher the value higher the priority
|
||||
## priority = <value>
|
||||
## deps = [ <component-dependencies> ]
|
||||
## flags = [ ifexists | nofail ]
|
||||
## arguments = { <json object> }
|
||||
##
|
||||
## # Feature that this component provides
|
||||
## provides = <feature>
|
||||
##
|
||||
## # List of features that must be provided before this component is loaded
|
||||
## requires = [ <features> ]
|
||||
##
|
||||
## # List of features that would offer additional functionality if provided
|
||||
## # but are not strictly required
|
||||
## wants = [ <features> ]
|
||||
## }
|
||||
|
||||
## Settings provider
|
||||
{
|
||||
name = libwireplumber-module-settings,
|
||||
type = module
|
||||
## highest priority
|
||||
priority = 150
|
||||
name = libwireplumber-module-settings, type = module
|
||||
provides = support.settings
|
||||
}
|
||||
|
||||
## The lua scripting engine
|
||||
{
|
||||
name = libwireplumber-module-lua-scripting,
|
||||
type = module
|
||||
priority = 140
|
||||
}
|
||||
|
||||
## Session item factories
|
||||
{
|
||||
name = libwireplumber-module-si-node,
|
||||
type = module
|
||||
## default priority for session item modules
|
||||
priority = 130
|
||||
}
|
||||
|
||||
{
|
||||
name = libwireplumber-module-si-audio-adapter,
|
||||
type = module
|
||||
## default priority for session item modules
|
||||
priority = 130
|
||||
}
|
||||
|
||||
{
|
||||
name = libwireplumber-module-si-standard-link,
|
||||
type = module
|
||||
## default priority for session item modules
|
||||
priority = 130
|
||||
}
|
||||
|
||||
{
|
||||
name = libwireplumber-module-si-audio-virtual,
|
||||
type = module
|
||||
## default priority for session item modules
|
||||
priority = 130
|
||||
}
|
||||
|
||||
## The shared D-Bus connection
|
||||
{
|
||||
name = libwireplumber-module-dbus-connection
|
||||
type = module
|
||||
priority = 120
|
||||
}
|
||||
|
||||
## API to access default nodes from scripts
|
||||
{
|
||||
name = libwireplumber-module-default-nodes-api,
|
||||
type = module
|
||||
## These modules may be needed by other modules, so they assume higher
|
||||
## priority than regular modules.
|
||||
priority = 120
|
||||
}
|
||||
|
||||
## API to access mixer controls, needed for volume ducking
|
||||
{
|
||||
name = libwireplumber-module-mixer-api,
|
||||
type = module
|
||||
## These modules may be needed by other modules, so they assume higher
|
||||
## priority than regular modules.
|
||||
priority = 120
|
||||
name = libwireplumber-module-lua-scripting, type = module
|
||||
provides = support.lua-scripting
|
||||
}
|
||||
|
||||
## Module listening for pipewire objects to push events
|
||||
{
|
||||
name = libwireplumber-module-standard-event-source,
|
||||
type = module
|
||||
## regular module default priority
|
||||
priority = 110
|
||||
name = libwireplumber-module-standard-event-source, type = module
|
||||
provides = support.standard-event-source
|
||||
}
|
||||
|
||||
## The shared D-Bus connection
|
||||
{
|
||||
name = libwireplumber-module-dbus-connection, type = module
|
||||
provides = support.dbus
|
||||
}
|
||||
|
||||
## Module managing the portal permissions
|
||||
{
|
||||
name = libwireplumber-module-portal-permissionstore,
|
||||
type = module,
|
||||
## regular module default priority
|
||||
priority = 110
|
||||
deps = [ access-enable-flatpak-portal ]
|
||||
name = libwireplumber-module-portal-permissionstore, type = module
|
||||
provides = support.portal-permissionstore
|
||||
requires = [ support.dbus ]
|
||||
}
|
||||
|
||||
## Needed for device reservation to work
|
||||
{
|
||||
name = libwireplumber-module-reserve-device,
|
||||
type = module,
|
||||
## regular module default priority
|
||||
priority = 110
|
||||
deps = [ monitor.alsa.reserve ]
|
||||
name = libwireplumber-module-reserve-device, type = module
|
||||
provides = support.reserve-device
|
||||
requires = [ support.dbus ]
|
||||
}
|
||||
|
||||
## Needed for logind to work
|
||||
## logind integration to enable certain functionality only on the active seat
|
||||
{
|
||||
name = libwireplumber-module-logind,
|
||||
type = module,
|
||||
## regular module default priority
|
||||
priority = 110
|
||||
deps = [ monitor.bluetooth.enable-logind ],
|
||||
flags = [ ifexists ]
|
||||
name = libwireplumber-module-logind, type = module
|
||||
provides = support.logind
|
||||
}
|
||||
|
||||
## Needed for MIDI monitor to work
|
||||
## Session item factories
|
||||
{
|
||||
name = libwireplumber-module-file-monitor-api,
|
||||
type = module,
|
||||
## regular module default priority
|
||||
priority = 110
|
||||
deps = [ monitor.alsa.midi.monitoring ]
|
||||
name = libwireplumber-module-si-node, type = module
|
||||
provides = si.node
|
||||
}
|
||||
{
|
||||
name = libwireplumber-module-si-audio-adapter, type = module
|
||||
provides = si.audio-adapter
|
||||
}
|
||||
{
|
||||
name = libwireplumber-module-si-standard-link, type = module
|
||||
provides = si.standard-link
|
||||
}
|
||||
{
|
||||
name = libwireplumber-module-si-audio-virtual, type = module
|
||||
provides = si.audio-virtual
|
||||
}
|
||||
|
||||
## API to access default nodes from scripts
|
||||
{
|
||||
name = libwireplumber-module-default-nodes-api, type = module
|
||||
provides = api.default-nodes
|
||||
}
|
||||
|
||||
## API to access mixer controls
|
||||
{
|
||||
name = libwireplumber-module-mixer-api, type = module
|
||||
provides = api.mixer
|
||||
}
|
||||
|
||||
## API to get notified about file changes
|
||||
{
|
||||
name = libwireplumber-module-file-monitor-api, type = module
|
||||
provides = api.file-monitor
|
||||
}
|
||||
|
||||
## Provide the "default" pw_metadata
|
||||
{ name = metadata.lua, type = script/lua, priority = 110 }
|
||||
{
|
||||
name = metadata.lua, type = script/lua
|
||||
provides = metadata.default
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
|
||||
{ name = monitors/alsa.lua, type = script/lua, priority = 101 }
|
||||
{ name = monitors/bluez.lua, type = script/lua, priority = 101 }
|
||||
{ name = monitors/bluez-midi.lua, type = script/lua, priority = 101 }
|
||||
{ name = monitors/alsa-midi.lua, type = script/lua, priority = 101 }
|
||||
{ name = monitors/v4l2.lua, type = script/lua, priority = 101 }
|
||||
{ name = monitors/libcamera.lua, type = script/lua, priority = 101 }
|
||||
## Device monitors
|
||||
{
|
||||
name = monitors/alsa.lua, type = script/lua
|
||||
provides = monitor.alsa
|
||||
requires = [ support.lua-scripting ]
|
||||
wants = [ support.reserve-device ]
|
||||
}
|
||||
{
|
||||
name = monitors/bluez.lua, type = script/lua
|
||||
provides = monitor.bluez
|
||||
requires = [ support.lua-scripting ]
|
||||
wants = [ support.logind ]
|
||||
}
|
||||
{
|
||||
name = monitors/bluez-midi.lua, type = script/lua
|
||||
provides = monitor.bluez-midi
|
||||
requires = [ support.lua-scripting ]
|
||||
wants = [ support.logind ]
|
||||
}
|
||||
{
|
||||
name = monitors/alsa-midi.lua, type = script/lua
|
||||
provides = monitor.alsa-midi
|
||||
requires = [ support.lua-scripting ]
|
||||
wants = [ api.file-monitor ]
|
||||
}
|
||||
{
|
||||
name = monitors/v4l2.lua, type = script/lua
|
||||
provides = monitor.v4l2
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = monitors/libcamera.lua, type = script/lua
|
||||
provides = monitor.libcamera
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
|
||||
{ name = default-nodes/apply-default-node.lua, type = script/lua, priority = 120 }
|
||||
{ name = default-nodes/find-echo-cancel-default-node.lua, type = script/lua, priority = 120 }
|
||||
{ name = default-nodes/state-default-nodes.lua, type = script/lua, priority = 120 }
|
||||
{ name = default-nodes/find-best-default-node.lua, type = script/lua, priority = 120 }
|
||||
{ name = default-nodes/find-selected-default-node.lua, type = script/lua, priority = 120 }
|
||||
{ name = default-nodes/rescan.lua, type = script/lua, priority = 120 }
|
||||
## Client access configuration hooks
|
||||
{
|
||||
name = client/access-default.lua, type = script/lua
|
||||
provides = script.client.access-default
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = client/access-portal.lua, type = script/lua
|
||||
provides = script.client.access-portal
|
||||
requires = [ support.lua-scripting, support.portal-permissionstore ]
|
||||
}
|
||||
{
|
||||
type = virtual, provides = policy.client.access
|
||||
wants = [ script.client.access-default,
|
||||
script.client.access-portal ]
|
||||
}
|
||||
|
||||
{ name = linking/find-virtual-target.lua, type = script/lua, priority = 100 }
|
||||
{ name = linking/find-defined-target.lua, type = script/lua, priority = 100 }
|
||||
{ name = linking/find-default-target.lua, type = script/lua, priority = 100 }
|
||||
{ name = linking/find-best-target.lua, type = script/lua, priority = 100 }
|
||||
{ name = linking/link-target.lua, type = script/lua, priority = 100 }
|
||||
{ name = linking/prepare-link.lua, type = script/lua, priority = 100 }
|
||||
{ name = linking/filter-forward-format.lua, type = script/lua, priority = 100 }
|
||||
{ name = linking/rescan.lua, type = script/lua, priority = 100 }
|
||||
{ name = linking/move-follow.lua, type = script/lua, priority = 100 }
|
||||
{ name = linking/rescan-virtual-links.lua, type = script/lua, priority = 100 }
|
||||
## Device profile selection hooks
|
||||
{
|
||||
name = device/select-profile.lua, type = script/lua
|
||||
provides = hooks.device.profile.select
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = device/find-best-profile.lua, type = script/lua
|
||||
provides = hooks.device.profile.find-best
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = device/state-profile.lua, type = script/lua
|
||||
provides = hooks.device.profile.state
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = device/apply-profile.lua, type = script/lua
|
||||
provides = hooks.device.profile.apply
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
type = virtual, provides = policy.device.profile
|
||||
requires = [ hooks.device.profile.select,
|
||||
hooks.device.profile.apply ]
|
||||
wants = [ hooks.device.profile.find-best,
|
||||
hooks.device.profile.state ]
|
||||
}
|
||||
|
||||
{ name = device/apply-routes.lua, type = script/lua, priority = 100 }
|
||||
{ name = device/find-best-profile.lua, type = script/lua, priority = 100 }
|
||||
{ name = device/select-routes.lua, type = script/lua, priority = 100 }
|
||||
{ name = device/find-best-routes.lua, type = script/lua, priority = 100 }
|
||||
{ name = device/state-profile.lua, type = script/lua, priority = 100 }
|
||||
{ name = device/state-routes.lua, type = script/lua, priority = 100 }
|
||||
{ name = device/apply-profile.lua, type = script/lua, priority = 100 }
|
||||
{ name = device/select-profile.lua, type = script/lua, priority = 100 }
|
||||
# Device route selection hooks
|
||||
{
|
||||
name = device/select-routes.lua, type = script/lua
|
||||
provides = hooks.device.routes.select
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = device/find-best-routes.lua, type = script/lua
|
||||
provides = hooks.device.routes.find-best
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = device/state-routes.lua, type = script/lua
|
||||
provides = hooks.device.routes.state
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = device/apply-routes.lua, type = script/lua
|
||||
provides = hooks.device.routes.apply
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
type = virtual, provides = policy.device.routes
|
||||
requires = [ hooks.device.routes.select,
|
||||
hooks.device.routes.apply ]
|
||||
wants = [ hooks.device.routes.find-best,
|
||||
hooks.device.routes.state ]
|
||||
}
|
||||
|
||||
{ name = client/access-portal.lua, type = script/lua, priority = 100 }
|
||||
{ name = client/access-default.lua, type = script/lua, priority = 100 }
|
||||
## Default nodes selection hooks
|
||||
{
|
||||
name = default-nodes/rescan.lua, type = script/lua
|
||||
provides = hooks.default-nodes.rescan
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = default-nodes/find-selected-default-node.lua, type = script/lua
|
||||
provides = hooks.default-nodes.find-selected
|
||||
requires = [ support.lua-scripting, metadata.default ]
|
||||
}
|
||||
{
|
||||
name = default-nodes/find-best-default-node.lua, type = script/lua
|
||||
provides = hooks.default-nodes.find-best
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = default-nodes/find-echo-cancel-default-node.lua, type = script/lua
|
||||
provides = hooks.default-nodes.find-echo-cancel
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = default-nodes/state-default-nodes.lua, type = script/lua
|
||||
provides = hooks.default-nodes.state
|
||||
requires = [ support.lua-scripting, metadata.default ]
|
||||
}
|
||||
{
|
||||
name = default-nodes/apply-default-node.lua, type = script/lua,
|
||||
provides = hooks.default-nodes.apply
|
||||
requires = [ support.lua-scripting, metadata.default ]
|
||||
}
|
||||
{
|
||||
type = virtual, provides = policy.default-nodes
|
||||
requires = [ hooks.default-nodes.rescan,
|
||||
hooks.default-nodes.apply ]
|
||||
wants = [ hooks.default-nodes.find-selected,
|
||||
hooks.default-nodes.find-best,
|
||||
hooks.default-nodes.find-echo-cancel,
|
||||
hooks.default-nodes.state ]
|
||||
}
|
||||
|
||||
{ name = node/create-item.lua, type = script/lua, priority = 100 }
|
||||
{ name = node/create-virtual-item.lua, type = script/lua, priority = 100 }
|
||||
{ name = node/suspend-node.lua, type = script/lua, priority = 100 }
|
||||
{ name = node/state-stream.lua, type = script/lua, priority = 100 }
|
||||
## Node configuration hooks
|
||||
{
|
||||
name = node/create-item.lua, type = script/lua
|
||||
provides = hooks.node.create-session-item
|
||||
requires = [ support.lua-scripting, si.audio-adapter, si.node ]
|
||||
}
|
||||
{
|
||||
name = node/suspend-node.lua, type = script/lua
|
||||
provides = hooks.node.suspend
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = node/state-stream.lua, type = script/lua
|
||||
provides = hooks.stream.state
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = linking/filter-forward-format.lua, type = script/lua
|
||||
provides = hooks.loopback.forward-format
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = node/create-virtual-item.lua, type = script/lua
|
||||
provides = script.create-role-items
|
||||
requires = [ support.lua-scripting, si.audio-virtual ]
|
||||
}
|
||||
|
||||
## Linking hooks
|
||||
{
|
||||
name = linking/rescan.lua, type = script/lua
|
||||
provides = hooks.linking.rescan
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = linking/move-follow.lua, type = script/lua
|
||||
provides = hooks.linking.move-follow
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = linking/find-defined-target.lua, type = script/lua
|
||||
provides = hooks.linking.target.find-defined
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = linking/find-default-target.lua, type = script/lua
|
||||
provides = hooks.linking.target.find-default
|
||||
requires = [ support.lua-scripting, api.default-nodes ]
|
||||
}
|
||||
{
|
||||
name = linking/find-best-target.lua, type = script/lua
|
||||
provides = hooks.linking.target.find-best
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
name = linking/prepare-link.lua, type = script/lua
|
||||
provides = hooks.linking.target.prepare-link
|
||||
requires = [ support.lua-scripting, api.default-nodes ]
|
||||
}
|
||||
{
|
||||
name = linking/link-target.lua, type = script/lua
|
||||
provides = hooks.linking.target.link
|
||||
requires = [ support.lua-scripting, si.standard-link ]
|
||||
}
|
||||
{
|
||||
type = virtual, provides = policy.linking.standard
|
||||
requires = [ hooks.linking.rescan,
|
||||
hooks.linking.target.prepare-link,
|
||||
hooks.linking.target.link ]
|
||||
wants = [ hooks.linking.move-follow,
|
||||
hooks.linking.target.find-defined,
|
||||
hooks.linking.target.find-default,
|
||||
hooks.linking.target.find-best ]
|
||||
}
|
||||
|
||||
## Linking: Role-based priority system
|
||||
{
|
||||
name = linking/rescan-virtual-links.lua, type = script/lua
|
||||
provides = hooks.linking.role-priority-system.links.rescan
|
||||
requires = [ support.lua-scripting, api.mixer ]
|
||||
}
|
||||
{
|
||||
name = linking/find-virtual-target.lua, type = script/lua
|
||||
provides = hooks.linking.role-priority-system.target.find
|
||||
requires = [ support.lua-scripting ]
|
||||
}
|
||||
{
|
||||
type = virtual, provides = policy.linking.role-priority-system
|
||||
requires = [ policy.linking.standard,
|
||||
hooks.linking.role-priority-system.links.rescan,
|
||||
hooks.linking.role-priority-system.target.find ]
|
||||
}
|
||||
|
||||
## Load targets
|
||||
{
|
||||
type = virtual, provides = hardware.audio
|
||||
wants = [ monitor.alsa, monitor.alsa-midi ]
|
||||
}
|
||||
{
|
||||
type = virtual, provides = hardware.bluetooth
|
||||
wants = [ monitor.bluez, monitor.bluez-midi ]
|
||||
}
|
||||
{
|
||||
type = virtual, provides = hardware.video-capture
|
||||
wants = [ monitor.v4l2, monitor.libcamera ]
|
||||
}
|
||||
{
|
||||
type = virtual, provides = policy.standard
|
||||
requires = [ policy.client.access
|
||||
policy.device.profile
|
||||
policy.device.routes
|
||||
policy.default-nodes
|
||||
policy.linking.standard
|
||||
hooks.node.create-session-item
|
||||
support.standard-event-source ]
|
||||
wants = [ hooks.node.suspend
|
||||
hooks.stream.state
|
||||
hooks.loopback.forward-format ]
|
||||
}
|
||||
{
|
||||
type = virtual, provides = policy.role-priority-system
|
||||
requires = [ policy.standard,
|
||||
script.create-role-items,
|
||||
policy.linking.role-priority-system ]
|
||||
}
|
||||
]
|
||||
|
||||
wireplumber.settings = {
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ wp_base_test_fixture_teardown (WpBaseTestFixture * self)
|
|||
g_main_context_iteration (self->context, TRUE);
|
||||
|
||||
g_main_context_pop_thread_default (self->context);
|
||||
g_clear_pointer (&self->conf_file, g_free);
|
||||
g_clear_object (&self->client_core);
|
||||
g_clear_object (&self->core);
|
||||
g_clear_pointer (&self->timeout_source, g_source_unref);
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ wp_test_plugin_class_init (WpTestPluginClass * klass)
|
|||
struct _WpTestCompLoader
|
||||
{
|
||||
GObject parent;
|
||||
GPtrArray *history;
|
||||
};
|
||||
|
||||
static void wp_test_comp_loader_iface_init (WpComponentLoaderInterface * iface);
|
||||
|
|
@ -55,11 +56,21 @@ G_DEFINE_TYPE_WITH_CODE (WpTestCompLoader, wp_test_comp_loader,
|
|||
static void
|
||||
wp_test_comp_loader_init (WpTestCompLoader * self)
|
||||
{
|
||||
self->history = g_ptr_array_new_with_free_func (g_free);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_test_comp_loader_finalize (GObject * self)
|
||||
{
|
||||
g_clear_pointer (&WP_TEST_COMP_LOADER (self)->history, g_ptr_array_unref);
|
||||
G_OBJECT_CLASS (wp_test_comp_loader_parent_class)->finalize (self);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_test_comp_loader_class_init (WpTestCompLoaderClass * klass)
|
||||
{
|
||||
GObjectClass *oclass = (GObjectClass *) klass;
|
||||
oclass->finalize = wp_test_comp_loader_finalize;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
|
|
@ -78,6 +89,7 @@ wp_test_comp_loader_load (WpComponentLoader * self, WpCore * core,
|
|||
"name", component,
|
||||
"core", core,
|
||||
NULL);
|
||||
g_ptr_array_add (WP_TEST_COMP_LOADER (self)->history, g_strdup (component));
|
||||
g_task_return_pointer (task, plugin, g_object_unref);
|
||||
}
|
||||
|
||||
|
|
@ -99,14 +111,15 @@ wp_test_comp_loader_iface_init (WpComponentLoaderInterface * iface)
|
|||
|
||||
typedef struct {
|
||||
WpBaseTestFixture base;
|
||||
WpTestCompLoader *loader;
|
||||
} TestFixture;
|
||||
|
||||
static void
|
||||
test_setup (TestFixture *self, gconstpointer user_data)
|
||||
{
|
||||
wp_base_test_fixture_setup (&self->base, 0);
|
||||
wp_core_register_object (self->base.core,
|
||||
g_object_new (WP_TYPE_TEST_COMP_LOADER, NULL));
|
||||
self->loader = g_object_new (WP_TYPE_TEST_COMP_LOADER, NULL);
|
||||
wp_core_register_object (self->base.core, self->loader);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -142,6 +155,47 @@ test_load (TestFixture *f, gconstpointer data)
|
|||
g_assert_true (wp_core_test_feature (f->base.core, "feature.name123"));
|
||||
}
|
||||
|
||||
static void
|
||||
test_dependencies_setup (TestFixture *f, gconstpointer data)
|
||||
{
|
||||
f->base.conf_file =
|
||||
g_strdup_printf ("%s/component-loader.conf", g_getenv ("G_TEST_SRCDIR"));
|
||||
test_setup (f, data);
|
||||
}
|
||||
|
||||
static void
|
||||
test_dependencies (TestFixture *f, gconstpointer data)
|
||||
{
|
||||
g_autoptr (WpConf) conf = wp_conf_get_instance (f->base.core);
|
||||
g_assert_nonnull (conf);
|
||||
|
||||
g_autoptr (WpSpaJson) components = wp_conf_get_section (conf,
|
||||
"wireplumber.components", NULL);
|
||||
g_assert_nonnull (components);
|
||||
|
||||
wp_core_load_component (f->base.core, NULL, "array", components,
|
||||
NULL, NULL, (GAsyncReadyCallback) on_component_loaded, f);
|
||||
g_main_loop_run (f->base.loop);
|
||||
|
||||
// NULL-terminate the array
|
||||
g_ptr_array_add (f->loader->history, NULL);
|
||||
|
||||
/* verify the order of loading the plugins was as expected */
|
||||
const gchar *expected[] = {
|
||||
"five", "one", "six", "two", "three", "four", "seven", NULL };
|
||||
g_assert_cmpstrv (f->loader->history->pdata, expected);
|
||||
|
||||
g_assert_true (wp_core_test_feature (f->base.core, "support.one"));
|
||||
g_assert_true (wp_core_test_feature (f->base.core, "support.two"));
|
||||
g_assert_true (wp_core_test_feature (f->base.core, "support.three"));
|
||||
g_assert_true (wp_core_test_feature (f->base.core, "support.four"));
|
||||
g_assert_true (wp_core_test_feature (f->base.core, "virtual.four"));
|
||||
g_assert_true (wp_core_test_feature (f->base.core, "support.five"));
|
||||
g_assert_true (wp_core_test_feature (f->base.core, "support.six"));
|
||||
g_assert_false (wp_core_test_feature (f->base.core, "support.seven"));
|
||||
g_assert_false (wp_core_test_feature (f->base.core, "support.eight"));
|
||||
}
|
||||
|
||||
gint
|
||||
main (gint argc, gchar *argv[])
|
||||
{
|
||||
|
|
@ -150,6 +204,8 @@ main (gint argc, gchar *argv[])
|
|||
|
||||
g_test_add ("/wp/comploader/load", TestFixture, NULL,
|
||||
test_setup, test_load, test_teardown);
|
||||
g_test_add ("/wp/comploader/dependencies", TestFixture, NULL,
|
||||
test_dependencies_setup, test_dependencies, test_teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
|
|||
64
tests/wp/component-loader.conf
Normal file
64
tests/wp/component-loader.conf
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
context.modules = [
|
||||
{ name = libpipewire-module-protocol-native }
|
||||
]
|
||||
|
||||
wireplumber.features = {
|
||||
virtual.four = required
|
||||
}
|
||||
|
||||
wireplumber.components = [
|
||||
# expected load order:
|
||||
# five, one, six, two, three, four, seven
|
||||
# eight is not loaded - optional feature
|
||||
{
|
||||
name = one
|
||||
type = test
|
||||
provides = support.one
|
||||
}
|
||||
{
|
||||
name = two
|
||||
type = test
|
||||
provides = support.two
|
||||
requires = [ support.one support.six ]
|
||||
}
|
||||
{
|
||||
type = virtual
|
||||
provides = virtual.four
|
||||
requires = [ support.four ]
|
||||
}
|
||||
{
|
||||
name = three
|
||||
type = test
|
||||
provides = support.three
|
||||
wants = [ support.two ]
|
||||
}
|
||||
{
|
||||
name = four
|
||||
type = test
|
||||
provides = support.four
|
||||
requires = [ support.five ]
|
||||
wants = [ support.three ]
|
||||
}
|
||||
{
|
||||
name = five
|
||||
type = test
|
||||
provides = support.five
|
||||
}
|
||||
{
|
||||
name = six
|
||||
type = test
|
||||
provides = support.six
|
||||
requires = [ support.one ]
|
||||
}
|
||||
{
|
||||
name = seven
|
||||
type = test
|
||||
requires = [ support.five ]
|
||||
}
|
||||
{
|
||||
name = eight
|
||||
type = test
|
||||
provides = support.eight
|
||||
requires = [ support.four ]
|
||||
}
|
||||
]
|
||||
Loading…
Add table
Reference in a new issue