2023-05-26 13:49:40 +03:00
|
|
|
/* WirePlumber
|
|
|
|
|
*
|
|
|
|
|
* Copyright © 2023 Collabora Ltd.
|
|
|
|
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: MIT
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "internal-comp-loader.h"
|
|
|
|
|
#include "wp.h"
|
|
|
|
|
#include "registry.h"
|
|
|
|
|
|
2023-11-14 15:34:11 +02:00
|
|
|
#include <pipewire/impl.h>
|
|
|
|
|
|
2023-05-26 13:49:40 +03:00
|
|
|
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-internal-comp-loader")
|
|
|
|
|
|
2023-05-29 15:48:39 +03:00
|
|
|
/*** ComponentData ***/
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
typedef enum {
|
|
|
|
|
FEATURE_STATE_DISABLED,
|
|
|
|
|
FEATURE_STATE_OPTIONAL,
|
|
|
|
|
FEATURE_STATE_REQUIRED
|
|
|
|
|
} FeatureState;
|
2023-05-29 15:48:39 +03:00
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
typedef struct _ComponentData ComponentData;
|
2023-05-29 15:48:39 +03:00
|
|
|
struct _ComponentData
|
|
|
|
|
{
|
2023-06-15 21:27:13 +03:00
|
|
|
grefcount ref;
|
|
|
|
|
/* an identifier for this component that is understandable by the end user */
|
|
|
|
|
gchar *printable_id;
|
2025-02-13 16:06:29 +02:00
|
|
|
/* the provided feature name */
|
2023-06-15 21:27:13 +03:00
|
|
|
gchar *provides;
|
|
|
|
|
/* the original state of the feature (required / optional / disabled) */
|
|
|
|
|
FeatureState state;
|
|
|
|
|
|
|
|
|
|
/* other fields extracted as-is from the json description */
|
2023-05-29 15:48:39 +03:00
|
|
|
gchar *name;
|
|
|
|
|
gchar *type;
|
2023-06-15 21:27:13 +03:00
|
|
|
WpSpaJson *arguments;
|
|
|
|
|
GPtrArray *requires; /* value-type: string (owned) */
|
|
|
|
|
GPtrArray *wants; /* value-type: string (owned) */
|
internal-comp-loader: implement before/after dependencies for components
In some cases, requires/wants dependencies are not enough. As we saw in
!617, the m-standard-event-source module needs to be loaded after all
the hooks, otherwise there may be missed events that the hook was
supposed to "catch", but they were delivered before the hook was actually
loaded. In a similar fashion, we have in purpose put all the "monitor"
components at the every end of the array because if we load them earlier,
they will create devices and nodes before all the hooks are in place to
react.
While in standard configuration we can work this around, in extended
user configurations with custom components, it is impossible to do this
without overriding the entire components array.
To fix this properly, introduce before/after dependencies. They work in
a similar fashion as they work with event hooks. They do not implicitly
"pull" any components to be loaded, but they affect the ordering if the
mentioned components are indeed present.
Note that for backwards compatibility reasons and unlike systemd units,
the "requires"/"wants" targets imply an "after" dependency on them.
Fixes: #600
2024-08-31 20:33:42 +03:00
|
|
|
GPtrArray *before; /* value-type: string (owned) */
|
|
|
|
|
GPtrArray *after; /* value-type: string (owned) */
|
2023-06-15 21:27:13 +03:00
|
|
|
|
|
|
|
|
/* 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;
|
2023-05-29 15:48:39 +03:00
|
|
|
};
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
static void component_data_free (ComponentData * self);
|
2023-05-29 15:48:39 +03:00
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
static ComponentData *
|
|
|
|
|
component_data_ref (ComponentData *self)
|
2023-05-29 15:48:39 +03:00
|
|
|
{
|
2023-06-15 21:27:13 +03:00
|
|
|
g_ref_count_inc (&self->ref);
|
|
|
|
|
return self;
|
2023-05-29 15:48:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2023-06-15 21:27:13 +03:00
|
|
|
component_data_unref (ComponentData *self)
|
2023-05-29 15:48:39 +03:00
|
|
|
{
|
2023-06-15 21:27:13 +03:00
|
|
|
if (self && g_ref_count_dec (&self->ref))
|
|
|
|
|
component_data_free (self);
|
2023-05-29 15:48:39 +03:00
|
|
|
}
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC (ComponentData, component_data_unref)
|
2023-05-29 15:48:39 +03:00
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
static FeatureState
|
|
|
|
|
get_feature_state (WpProperties * dict, const gchar * feature)
|
2023-05-29 15:48:39 +03:00
|
|
|
{
|
2023-06-15 21:27:13 +03:00
|
|
|
const gchar *value = wp_properties_get (dict, feature);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2023-05-29 15:48:39 +03:00
|
|
|
}
|
|
|
|
|
|
2023-11-14 12:42:32 +02:00
|
|
|
static gboolean
|
|
|
|
|
component_rule_match_cb (gpointer data, const gchar * action, WpSpaJson * value,
|
|
|
|
|
GError ** error)
|
|
|
|
|
{
|
|
|
|
|
WpProperties *props = data;
|
|
|
|
|
g_autoptr (WpIterator) it = NULL;
|
|
|
|
|
g_auto (GValue) item = G_VALUE_INIT;
|
|
|
|
|
gboolean merge;
|
|
|
|
|
|
|
|
|
|
if (!wp_spa_json_is_object (value)) {
|
|
|
|
|
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
|
|
|
|
"expected JSON object instead of: %.*s", (int) wp_spa_json_get_size (value),
|
|
|
|
|
wp_spa_json_get_data (value));
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (g_str_equal (action, "merge")) {
|
|
|
|
|
merge = TRUE;
|
|
|
|
|
} else if (g_str_equal (action, "override")) {
|
|
|
|
|
merge = FALSE;
|
|
|
|
|
} else {
|
|
|
|
|
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
|
|
|
|
"invalid action '%s' in component rules", action);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it = wp_spa_json_new_iterator (value);
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
g_autofree gchar *key = NULL;
|
|
|
|
|
g_autofree gchar *val = NULL;
|
|
|
|
|
const gchar *old_val = NULL;
|
|
|
|
|
|
|
|
|
|
/* extract key */
|
|
|
|
|
if (!wp_iterator_next (it, &item))
|
|
|
|
|
break;
|
|
|
|
|
key = wp_spa_json_to_string (g_value_get_boxed (&item));
|
|
|
|
|
g_value_unset (&item);
|
|
|
|
|
|
|
|
|
|
/* extract value */
|
|
|
|
|
if (!wp_iterator_next (it, &item)) {
|
|
|
|
|
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
|
|
|
|
"expected value for key '%s' in component rules", key);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
val = wp_spa_json_to_string (g_value_get_boxed (&item));
|
|
|
|
|
g_value_unset (&item);
|
|
|
|
|
|
|
|
|
|
old_val = wp_properties_get (props, key);
|
|
|
|
|
|
|
|
|
|
/* override if not merging or if the value is not a container */
|
|
|
|
|
if (!merge || !old_val || (*old_val != '[' && *old_val != '{')) {
|
|
|
|
|
wp_properties_set (props, key, val);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
g_autoptr (WpSpaJson) old_json = NULL;
|
|
|
|
|
g_autoptr (WpSpaJson) new_json = NULL;
|
|
|
|
|
g_autoptr (WpSpaJson) merged_json = NULL;
|
|
|
|
|
|
|
|
|
|
old_json = wp_spa_json_new_wrap_string (old_val);
|
|
|
|
|
new_json = wp_spa_json_new_wrap_string (val);
|
|
|
|
|
merged_json = wp_json_utils_merge_containers (old_json, new_json);
|
|
|
|
|
wp_properties_set (props, key,
|
|
|
|
|
merged_json ? wp_spa_json_get_data (merged_json) : val);
|
|
|
|
|
}
|
|
|
|
|
} while (TRUE);
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
static ComponentData *
|
|
|
|
|
component_data_new_from_json (WpSpaJson * json, WpProperties * features,
|
2023-11-14 12:42:32 +02:00
|
|
|
WpSpaJson * rules, GError ** error)
|
2023-05-29 15:48:39 +03:00
|
|
|
{
|
2023-06-15 21:27:13 +03:00
|
|
|
g_autoptr (ComponentData) comp = NULL;
|
2023-11-14 12:42:32 +02:00
|
|
|
g_autoptr (WpProperties) props = NULL;
|
|
|
|
|
const gchar *str;
|
2023-05-29 15:48:39 +03:00
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
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;
|
|
|
|
|
}
|
2023-05-29 15:48:39 +03:00
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
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);
|
internal-comp-loader: implement before/after dependencies for components
In some cases, requires/wants dependencies are not enough. As we saw in
!617, the m-standard-event-source module needs to be loaded after all
the hooks, otherwise there may be missed events that the hook was
supposed to "catch", but they were delivered before the hook was actually
loaded. In a similar fashion, we have in purpose put all the "monitor"
components at the every end of the array because if we load them earlier,
they will create devices and nodes before all the hooks are in place to
react.
While in standard configuration we can work this around, in extended
user configurations with custom components, it is impossible to do this
without overriding the entire components array.
To fix this properly, introduce before/after dependencies. They work in
a similar fashion as they work with event hooks. They do not implicitly
"pull" any components to be loaded, but they affect the ordering if the
mentioned components are indeed present.
Note that for backwards compatibility reasons and unlike systemd units,
the "requires"/"wants" targets imply an "after" dependency on them.
Fixes: #600
2024-08-31 20:33:42 +03:00
|
|
|
comp->before = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
|
comp->after = g_ptr_array_new_with_free_func (g_free);
|
2023-06-15 21:27:13 +03:00
|
|
|
|
2023-11-14 12:42:32 +02:00
|
|
|
props = wp_properties_new_json (json);
|
|
|
|
|
if (rules && !wp_json_utils_match_rules (rules, props, component_rule_match_cb,
|
|
|
|
|
props, error))
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
if (!(comp->type = g_strdup (wp_properties_get (props, "type")))) {
|
2023-06-15 21:27:13 +03:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-14 12:42:32 +02:00
|
|
|
comp->name = g_strdup (wp_properties_get (props, "name"));
|
|
|
|
|
str = wp_properties_get (props, "arguments");
|
|
|
|
|
comp->arguments = str ? wp_spa_json_new_from_string (str) : NULL;
|
2023-06-15 21:27:13 +03:00
|
|
|
|
2023-11-14 12:42:32 +02:00
|
|
|
if ((str = wp_properties_get (props, "provides"))) {
|
|
|
|
|
comp->provides = g_strdup (str);
|
2023-06-15 21:27:13 +03:00
|
|
|
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 {
|
|
|
|
|
comp->printable_id = g_strdup_printf ("%s [%s]", comp->provides, comp->type);
|
2023-05-29 15:48:39 +03:00
|
|
|
}
|
2023-06-15 21:27:13 +03:00
|
|
|
} else {
|
2025-02-13 16:06:29 +02:00
|
|
|
comp->provides = g_strdup_printf ("__anonymous_%p", comp);
|
2023-06-15 21:27:13 +03:00
|
|
|
comp->state = FEATURE_STATE_REQUIRED;
|
|
|
|
|
comp->printable_id = g_strdup_printf ("[%s: %s]", comp->type, comp->name);
|
|
|
|
|
}
|
2023-05-29 15:48:39 +03:00
|
|
|
|
2023-11-14 12:42:32 +02:00
|
|
|
if ((str = wp_properties_get (props, "requires"))) {
|
|
|
|
|
g_autoptr (WpSpaJson) comp_reqs = wp_spa_json_new_wrap_string (str);
|
internal-comp-loader: fix `WpSpaJson` memory leak
Previously, the `deps` variable was reused for parsing
the required and wanted dependencies of a component,
which lead to the old value allocated here:
if (wp_spa_json_object_get (json, "requires", "J", &deps, NULL)) {
being leaked when a bit later
if (wp_spa_json_object_get (json, "wants", "J", &deps, NULL)) {
succeeded.
Fix that by using two separate variables.
2023-10-28 01:30:16 +02:00
|
|
|
g_autoptr (WpIterator) it = wp_spa_json_new_iterator (comp_reqs);
|
2023-06-15 21:27:13 +03:00
|
|
|
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));
|
2023-05-29 15:48:39 +03:00
|
|
|
}
|
2023-06-15 21:27:13 +03:00
|
|
|
}
|
2023-05-29 15:48:39 +03:00
|
|
|
|
2023-11-14 12:42:32 +02:00
|
|
|
if ((str = wp_properties_get (props, "wants"))) {
|
|
|
|
|
g_autoptr (WpSpaJson) comp_wants = wp_spa_json_new_wrap_string (str);
|
internal-comp-loader: fix `WpSpaJson` memory leak
Previously, the `deps` variable was reused for parsing
the required and wanted dependencies of a component,
which lead to the old value allocated here:
if (wp_spa_json_object_get (json, "requires", "J", &deps, NULL)) {
being leaked when a bit later
if (wp_spa_json_object_get (json, "wants", "J", &deps, NULL)) {
succeeded.
Fix that by using two separate variables.
2023-10-28 01:30:16 +02:00
|
|
|
g_autoptr (WpIterator) it = wp_spa_json_new_iterator (comp_wants);
|
2023-06-15 21:27:13 +03:00
|
|
|
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));
|
2023-05-29 15:48:39 +03:00
|
|
|
}
|
|
|
|
|
}
|
2023-06-15 21:27:13 +03:00
|
|
|
|
internal-comp-loader: implement before/after dependencies for components
In some cases, requires/wants dependencies are not enough. As we saw in
!617, the m-standard-event-source module needs to be loaded after all
the hooks, otherwise there may be missed events that the hook was
supposed to "catch", but they were delivered before the hook was actually
loaded. In a similar fashion, we have in purpose put all the "monitor"
components at the every end of the array because if we load them earlier,
they will create devices and nodes before all the hooks are in place to
react.
While in standard configuration we can work this around, in extended
user configurations with custom components, it is impossible to do this
without overriding the entire components array.
To fix this properly, introduce before/after dependencies. They work in
a similar fashion as they work with event hooks. They do not implicitly
"pull" any components to be loaded, but they affect the ordering if the
mentioned components are indeed present.
Note that for backwards compatibility reasons and unlike systemd units,
the "requires"/"wants" targets imply an "after" dependency on them.
Fixes: #600
2024-08-31 20:33:42 +03:00
|
|
|
if ((str = wp_properties_get (props, "before"))) {
|
|
|
|
|
g_autoptr (WpSpaJson) comp_before = wp_spa_json_new_wrap_string (str);
|
|
|
|
|
g_autoptr (WpIterator) it = wp_spa_json_new_iterator (comp_before);
|
|
|
|
|
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->before, wp_spa_json_to_string (dep));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((str = wp_properties_get (props, "after"))) {
|
|
|
|
|
g_autoptr (WpSpaJson) comp_after = wp_spa_json_new_wrap_string (str);
|
|
|
|
|
g_autoptr (WpIterator) it = wp_spa_json_new_iterator (comp_after);
|
|
|
|
|
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->after, wp_spa_json_to_string (dep));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
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);
|
internal-comp-loader: implement before/after dependencies for components
In some cases, requires/wants dependencies are not enough. As we saw in
!617, the m-standard-event-source module needs to be loaded after all
the hooks, otherwise there may be missed events that the hook was
supposed to "catch", but they were delivered before the hook was actually
loaded. In a similar fashion, we have in purpose put all the "monitor"
components at the every end of the array because if we load them earlier,
they will create devices and nodes before all the hooks are in place to
react.
While in standard configuration we can work this around, in extended
user configurations with custom components, it is impossible to do this
without overriding the entire components array.
To fix this properly, introduce before/after dependencies. They work in
a similar fashion as they work with event hooks. They do not implicitly
"pull" any components to be loaded, but they affect the ordering if the
mentioned components are indeed present.
Note that for backwards compatibility reasons and unlike systemd units,
the "requires"/"wants" targets imply an "after" dependency on them.
Fixes: #600
2024-08-31 20:33:42 +03:00
|
|
|
g_clear_pointer (&self->before, g_ptr_array_unref);
|
|
|
|
|
g_clear_pointer (&self->after, g_ptr_array_unref);
|
2023-06-15 21:27:13 +03:00
|
|
|
g_free (self);
|
2023-05-29 15:48:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** WpComponentArrayLoadTask ***/
|
|
|
|
|
|
|
|
|
|
struct _WpComponentArrayLoadTask
|
|
|
|
|
{
|
|
|
|
|
WpTransition parent;
|
2023-06-15 21:27:13 +03:00
|
|
|
/* the input json object */
|
2023-05-29 15:48:39 +03:00
|
|
|
WpSpaJson *json;
|
2023-06-23 20:15:26 +03:00
|
|
|
/* the features profile */
|
|
|
|
|
WpProperties *profile;
|
2023-11-14 12:42:32 +02:00
|
|
|
/* the rules to apply on each component description */
|
|
|
|
|
WpSpaJson *rules;
|
2023-06-15 21:27:13 +03:00
|
|
|
/* 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 */
|
2023-05-29 15:48:39 +03:00
|
|
|
ComponentData *curr_component;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
STEP_PARSE = WP_TRANSITION_STEP_CUSTOM_START,
|
2023-06-15 21:27:13 +03:00
|
|
|
STEP_GET_NEXT,
|
|
|
|
|
STEP_LOAD_NEXT,
|
2023-05-29 15:48:39 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
G_DECLARE_FINAL_TYPE (WpComponentArrayLoadTask, wp_component_array_load_task,
|
|
|
|
|
WP, COMPONENT_ARRAY_LOAD_TASK, WpTransition)
|
|
|
|
|
G_DEFINE_TYPE (WpComponentArrayLoadTask, wp_component_array_load_task,
|
|
|
|
|
WP_TYPE_TRANSITION)
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_component_array_load_task_init (WpComponentArrayLoadTask * self)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
static guint
|
|
|
|
|
wp_component_array_load_task_get_next_step (WpTransition * transition, guint step)
|
2023-05-29 15:48:39 +03:00
|
|
|
{
|
2023-06-15 21:27:13 +03:00
|
|
|
WpComponentArrayLoadTask *self = WP_COMPONENT_ARRAY_LOAD_TASK (transition);
|
2023-05-29 15:48:39 +03:00
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
switch (step) {
|
|
|
|
|
case WP_TRANSITION_STEP_NONE: return STEP_PARSE;
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
internal-comp-loader: implement before/after dependencies for components
In some cases, requires/wants dependencies are not enough. As we saw in
!617, the m-standard-event-source module needs to be loaded after all
the hooks, otherwise there may be missed events that the hook was
supposed to "catch", but they were delivered before the hook was actually
loaded. In a similar fashion, we have in purpose put all the "monitor"
components at the every end of the array because if we load them earlier,
they will create devices and nodes before all the hooks are in place to
react.
While in standard configuration we can work this around, in extended
user configurations with custom components, it is impossible to do this
without overriding the entire components array.
To fix this properly, introduce before/after dependencies. They work in
a similar fashion as they work with event hooks. They do not implicitly
"pull" any components to be loaded, but they affect the ordering if the
mentioned components are indeed present.
Note that for backwards compatibility reasons and unlike systemd units,
the "requires"/"wants" targets imply an "after" dependency on them.
Fixes: #600
2024-08-31 20:33:42 +03:00
|
|
|
static gboolean
|
|
|
|
|
component_equals (const ComponentData * comp, const gchar * provides)
|
|
|
|
|
{
|
2025-02-13 16:06:29 +02:00
|
|
|
return g_str_equal (provides, comp->provides);
|
internal-comp-loader: implement before/after dependencies for components
In some cases, requires/wants dependencies are not enough. As we saw in
!617, the m-standard-event-source module needs to be loaded after all
the hooks, otherwise there may be missed events that the hook was
supposed to "catch", but they were delivered before the hook was actually
loaded. In a similar fashion, we have in purpose put all the "monitor"
components at the every end of the array because if we load them earlier,
they will create devices and nodes before all the hooks are in place to
react.
While in standard configuration we can work this around, in extended
user configurations with custom components, it is impossible to do this
without overriding the entire components array.
To fix this properly, introduce before/after dependencies. They work in
a similar fashion as they work with event hooks. They do not implicitly
"pull" any components to be loaded, but they affect the ordering if the
mentioned components are indeed present.
Note that for backwards compatibility reasons and unlike systemd units,
the "requires"/"wants" targets imply an "after" dependency on them.
Fixes: #600
2024-08-31 20:33:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline gboolean
|
|
|
|
|
component_exists_in (const gchar *comp_provides, GPtrArray *list)
|
|
|
|
|
{
|
|
|
|
|
return g_ptr_array_find_with_equal_func (list, comp_provides,
|
|
|
|
|
(GEqualFunc) component_equals, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
sort_components_before_after (WpComponentArrayLoadTask * self, GError ** error)
|
|
|
|
|
{
|
|
|
|
|
g_autoptr (GPtrArray) remaining = g_ptr_array_new_with_free_func (
|
|
|
|
|
(GDestroyNotify) component_data_unref);
|
|
|
|
|
g_autoptr (GPtrArray) result = g_ptr_array_new_with_free_func (
|
|
|
|
|
(GDestroyNotify) component_data_unref);
|
|
|
|
|
|
|
|
|
|
for (guint i = 0; i < self->components->len; i++) {
|
|
|
|
|
ComponentData *comp = g_ptr_array_index (self->components, i);
|
|
|
|
|
|
|
|
|
|
/* implicitly add all "requires" and "wants" as "after" dependencies */
|
|
|
|
|
g_ptr_array_extend (comp->after, comp->requires, (GCopyFunc) g_strdup, NULL);
|
|
|
|
|
g_ptr_array_extend (comp->after, comp->wants, (GCopyFunc) g_strdup, NULL);
|
|
|
|
|
|
|
|
|
|
/* convert "before" dependencies into "after" dependencies */
|
|
|
|
|
for (guint j = 0; j < comp->before->len; j++) {
|
|
|
|
|
gchar *target_provides = g_ptr_array_index (comp->before, j);
|
|
|
|
|
for (guint k = 0; k < self->components->len; k++) {
|
|
|
|
|
ComponentData *target = g_ptr_array_index (self->components, k);
|
2025-02-13 16:06:29 +02:00
|
|
|
if (g_str_equal (target_provides, target->provides)) {
|
internal-comp-loader: implement before/after dependencies for components
In some cases, requires/wants dependencies are not enough. As we saw in
!617, the m-standard-event-source module needs to be loaded after all
the hooks, otherwise there may be missed events that the hook was
supposed to "catch", but they were delivered before the hook was actually
loaded. In a similar fashion, we have in purpose put all the "monitor"
components at the every end of the array because if we load them earlier,
they will create devices and nodes before all the hooks are in place to
react.
While in standard configuration we can work this around, in extended
user configurations with custom components, it is impossible to do this
without overriding the entire components array.
To fix this properly, introduce before/after dependencies. They work in
a similar fashion as they work with event hooks. They do not implicitly
"pull" any components to be loaded, but they affect the ordering if the
mentioned components are indeed present.
Note that for backwards compatibility reasons and unlike systemd units,
the "requires"/"wants" targets imply an "after" dependency on them.
Fixes: #600
2024-08-31 20:33:42 +03:00
|
|
|
g_ptr_array_insert (target->after, -1, g_strdup (comp->provides));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* sort */
|
|
|
|
|
while (self->components->len > 0) {
|
|
|
|
|
gboolean made_progress = FALSE;
|
|
|
|
|
|
|
|
|
|
/* examine each component to see if its dependencies are satisfied in the
|
|
|
|
|
result list; if yes, then append it to the result too */
|
|
|
|
|
while (self->components->len > 0) {
|
|
|
|
|
ComponentData *comp = g_ptr_array_steal_index (self->components, 0);
|
|
|
|
|
guint deps_satisfied = 0;
|
|
|
|
|
|
|
|
|
|
wp_trace_object (self, "examining: %s", comp->printable_id);
|
|
|
|
|
|
|
|
|
|
for (guint i = 0; i < comp->after->len; i++) {
|
|
|
|
|
const gchar *dep = g_ptr_array_index (comp->after, i);
|
|
|
|
|
/* if the dependency is already in the sorted result list or if
|
|
|
|
|
it doesn't exist at all, we consider it satisfied */
|
|
|
|
|
if (component_exists_in (dep, result) ||
|
|
|
|
|
!(component_exists_in (dep, self->components) ||
|
|
|
|
|
component_exists_in (dep, remaining))) {
|
|
|
|
|
deps_satisfied++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wp_trace_object (self, "depends: %s, satisfied: %u/%u",
|
|
|
|
|
dep, deps_satisfied, comp->after->len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (deps_satisfied == comp->after->len) {
|
|
|
|
|
wp_trace_object (self, "sorted: %s", comp->printable_id);
|
|
|
|
|
|
|
|
|
|
g_ptr_array_add (result, comp);
|
|
|
|
|
made_progress = TRUE;
|
|
|
|
|
} else {
|
|
|
|
|
g_ptr_array_add (remaining, comp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (made_progress) {
|
|
|
|
|
/* run again with the remaining components */
|
|
|
|
|
g_ptr_array_extend_and_steal (self->components, g_ptr_array_ref (remaining));
|
|
|
|
|
}
|
|
|
|
|
else if (remaining->len > 0) {
|
|
|
|
|
/* if we did not make any progress towards growing the result list,
|
|
|
|
|
it means the dependencies cannot be satisfied because of circles */
|
|
|
|
|
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
|
|
|
|
"detected circular before/after dependencies in the components!");
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* transfer the result array back to self->components */
|
|
|
|
|
g_ptr_array_extend_and_steal (self->components, g_steal_pointer (&result));
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
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)
|
2023-05-29 15:48:39 +03:00
|
|
|
return TRUE;
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
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))
|
2023-05-29 15:48:39 +03:00
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
/* 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));
|
2023-05-29 15:48:39 +03:00
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
static gboolean
|
|
|
|
|
parse_components (WpComponentArrayLoadTask * self, GError ** error)
|
|
|
|
|
{
|
|
|
|
|
/* all the parsed components that are explicitly required */
|
|
|
|
|
g_autoptr (GPtrArray) required_components = NULL;
|
|
|
|
|
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;
|
|
|
|
|
|
2023-11-14 12:42:32 +02:00
|
|
|
if (!(comp = component_data_new_from_json (cjson, self->profile, self->rules, &e))) {
|
2023-06-15 21:27:13 +03:00
|
|
|
g_propagate_error (error, e);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (comp->state == FEATURE_STATE_REQUIRED)
|
|
|
|
|
g_ptr_array_add (required_components, component_data_ref (comp));
|
|
|
|
|
|
2025-02-13 16:06:29 +02:00
|
|
|
g_hash_table_insert (self->feat_components, comp->provides,
|
|
|
|
|
component_data_ref (comp));
|
2023-05-29 15:48:39 +03:00
|
|
|
}
|
2023-06-15 21:27:13 +03:00
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
internal-comp-loader: implement before/after dependencies for components
In some cases, requires/wants dependencies are not enough. As we saw in
!617, the m-standard-event-source module needs to be loaded after all
the hooks, otherwise there may be missed events that the hook was
supposed to "catch", but they were delivered before the hook was actually
loaded. In a similar fashion, we have in purpose put all the "monitor"
components at the every end of the array because if we load them earlier,
they will create devices and nodes before all the hooks are in place to
react.
While in standard configuration we can work this around, in extended
user configurations with custom components, it is impossible to do this
without overriding the entire components array.
To fix this properly, introduce before/after dependencies. They work in
a similar fashion as they work with event hooks. They do not implicitly
"pull" any components to be loaded, but they affect the ordering if the
mentioned components are indeed present.
Note that for backwards compatibility reasons and unlike systemd units,
the "requires"/"wants" targets imply an "after" dependency on them.
Fixes: #600
2024-08-31 20:33:42 +03:00
|
|
|
/* sort again, taking into account before/after dependencies */
|
|
|
|
|
if (!sort_components_before_after (self, error))
|
|
|
|
|
return FALSE;
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
/* 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;
|
2023-05-29 15:48:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
on_component_loaded (WpCore *core, GAsyncResult *res, gpointer data)
|
|
|
|
|
{
|
|
|
|
|
WpComponentArrayLoadTask *self = WP_COMPONENT_ARRAY_LOAD_TASK (data);
|
|
|
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (self->curr_component);
|
|
|
|
|
|
|
|
|
|
if (!wp_core_load_component_finish (core, res, &error)) {
|
2023-06-15 21:27:13 +03:00
|
|
|
// 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);
|
2023-05-29 15:48:39 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wp_transition_advance (WP_TRANSITION (self));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_component_array_load_task_execute_step (WpTransition * transition, guint step)
|
|
|
|
|
{
|
|
|
|
|
WpComponentArrayLoadTask *self = WP_COMPONENT_ARRAY_LOAD_TASK (transition);
|
2023-06-15 21:27:13 +03:00
|
|
|
WpCore *core = wp_transition_get_data(transition);
|
2023-05-29 15:48:39 +03:00
|
|
|
|
|
|
|
|
switch (step) {
|
2023-06-15 21:27:13 +03:00
|
|
|
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 {
|
2023-06-20 17:03:51 +03:00
|
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
2023-05-29 15:48:39 +03:00
|
|
|
}
|
2023-06-15 21:27:13 +03:00
|
|
|
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);
|
2023-05-29 15:48:39 +03:00
|
|
|
wp_transition_advance (transition);
|
|
|
|
|
break;
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-29 15:48:39 +03:00
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
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);
|
2023-05-29 15:48:39 +03:00
|
|
|
wp_transition_advance (transition);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Load the component */
|
2023-06-15 21:27:13 +03:00
|
|
|
wp_debug_object (self, "loading component '%s'",
|
|
|
|
|
self->curr_component->printable_id);
|
2023-05-29 15:48:39 +03:00
|
|
|
wp_core_load_component (core, self->curr_component->name,
|
2023-06-15 21:27:13 +03:00
|
|
|
self->curr_component->type, self->curr_component->arguments,
|
|
|
|
|
self->curr_component->provides, NULL,
|
2023-05-29 15:48:39 +03:00
|
|
|
(GAsyncReadyCallback) on_component_loaded, self);
|
|
|
|
|
break;
|
2023-06-15 21:27:13 +03:00
|
|
|
}
|
2023-05-29 15:48:39 +03:00
|
|
|
case WP_TRANSITION_STEP_ERROR:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
g_assert_not_reached ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-15 21:27:13 +03:00
|
|
|
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);
|
2023-06-23 20:15:26 +03:00
|
|
|
g_clear_pointer (&self->profile, wp_properties_unref);
|
2023-11-14 12:42:32 +02:00
|
|
|
g_clear_pointer (&self->rules, wp_spa_json_unref);
|
2023-06-15 21:27:13 +03:00
|
|
|
g_clear_pointer (&self->json, wp_spa_json_unref);
|
|
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (wp_component_array_load_task_parent_class)->finalize (object);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-29 15:48:39 +03:00
|
|
|
static void
|
|
|
|
|
wp_component_array_load_task_class_init (WpComponentArrayLoadTaskClass * klass)
|
|
|
|
|
{
|
2023-06-15 21:27:13 +03:00
|
|
|
GObjectClass * object_class = (GObjectClass *) klass;
|
2023-05-29 15:48:39 +03:00
|
|
|
WpTransitionClass * transition_class = (WpTransitionClass *) klass;
|
2023-06-15 21:27:13 +03:00
|
|
|
|
|
|
|
|
object_class->finalize = wp_component_array_load_task_finalize;
|
|
|
|
|
|
2023-05-29 15:48:39 +03:00
|
|
|
transition_class->get_next_step = wp_component_array_load_task_get_next_step;
|
|
|
|
|
transition_class->execute_step = wp_component_array_load_task_execute_step;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static WpTransition *
|
2023-06-23 20:15:26 +03:00
|
|
|
wp_component_array_load_task_new (WpSpaJson * json, WpProperties * profile,
|
2023-11-14 12:42:32 +02:00
|
|
|
WpSpaJson * rules, gpointer source_object, GCancellable * cancellable,
|
2023-05-29 15:48:39 +03:00
|
|
|
GAsyncReadyCallback callback, gpointer callback_data)
|
|
|
|
|
{
|
|
|
|
|
WpTransition *t = wp_transition_new (wp_component_array_load_task_get_type (),
|
|
|
|
|
source_object, cancellable, callback, callback_data);
|
|
|
|
|
WpComponentArrayLoadTask *task = WP_COMPONENT_ARRAY_LOAD_TASK (t);
|
|
|
|
|
task->json = wp_spa_json_ref (json);
|
2023-06-23 20:15:26 +03:00
|
|
|
task->profile = wp_properties_ref (profile);
|
2023-11-14 12:42:32 +02:00
|
|
|
task->rules = rules ? wp_spa_json_ref (rules) : NULL;
|
2023-05-29 15:48:39 +03:00
|
|
|
return t;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 19:35:23 +03:00
|
|
|
/*** built-in components ***/
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
ensure_no_media_session_om_installed (WpObjectManager * om, GTask * task)
|
|
|
|
|
{
|
|
|
|
|
if (wp_object_manager_get_n_objects (om) > 0) {
|
|
|
|
|
g_task_return_new_error (task,
|
|
|
|
|
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
|
|
|
"pipewire-media-session appears to be running; "
|
|
|
|
|
"please stop it before starting wireplumber");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
g_task_return_pointer (task, NULL, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
ensure_no_media_session_task_idle (GTask * task)
|
|
|
|
|
{
|
|
|
|
|
/* removing this idle source will cause the task to be destroyed */
|
|
|
|
|
return g_task_get_completed (task) ? G_SOURCE_REMOVE : G_SOURCE_CONTINUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2024-01-26 11:34:08 +02:00
|
|
|
ensure_no_media_session (GTask * task, WpCore * core, WpSpaJson * args)
|
2023-06-21 19:35:23 +03:00
|
|
|
{
|
|
|
|
|
WpObjectManager *om = wp_object_manager_new ();
|
|
|
|
|
|
|
|
|
|
wp_info_object (core, "checking if pipewire-media-session is running...");
|
|
|
|
|
|
|
|
|
|
/* make the object manager owned by the task and the task owned by the core;
|
|
|
|
|
use an idle callback to test when it is ok to unref the task */
|
|
|
|
|
g_task_set_task_data (task, om, g_object_unref);
|
|
|
|
|
wp_core_idle_add (core, NULL, (GSourceFunc) ensure_no_media_session_task_idle,
|
|
|
|
|
g_object_ref (task), g_object_unref);
|
|
|
|
|
|
|
|
|
|
wp_object_manager_add_interest (om, WP_TYPE_CLIENT,
|
|
|
|
|
WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY,
|
|
|
|
|
"application.name", "=s", "pipewire-media-session", NULL);
|
|
|
|
|
g_signal_connect_object (om, "installed",
|
|
|
|
|
G_CALLBACK (ensure_no_media_session_om_installed), task, 0);
|
|
|
|
|
wp_core_install_object_manager (core, om);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2024-01-26 11:34:08 +02:00
|
|
|
load_export_core (GTask * task, WpCore * core, WpSpaJson * args)
|
2023-06-21 19:35:23 +03:00
|
|
|
{
|
|
|
|
|
g_autofree gchar *export_core_name = NULL;
|
|
|
|
|
g_autoptr (WpCore) export_core = NULL;
|
|
|
|
|
g_autoptr (WpProperties) props = wp_core_get_properties (core);
|
|
|
|
|
const gchar *str = NULL;
|
|
|
|
|
|
|
|
|
|
wp_info_object (core, "connecting export core to pipewire...");
|
|
|
|
|
|
|
|
|
|
str = wp_properties_get (props, PW_KEY_APP_NAME);
|
|
|
|
|
export_core_name =
|
|
|
|
|
g_strdup_printf ("%s [export]", str ? str : "WirePlumber");
|
|
|
|
|
|
|
|
|
|
export_core = wp_core_clone (core);
|
|
|
|
|
wp_core_update_properties (export_core, wp_properties_new (
|
|
|
|
|
PW_KEY_APP_NAME, export_core_name,
|
|
|
|
|
"wireplumber.export-core", "true",
|
|
|
|
|
NULL));
|
|
|
|
|
|
|
|
|
|
g_task_return_pointer (task, g_steal_pointer (&export_core), g_object_unref);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-26 11:34:08 +02:00
|
|
|
static void
|
|
|
|
|
load_settings_instance (GTask * task, WpCore * core, WpSpaJson * args)
|
|
|
|
|
{
|
|
|
|
|
g_autofree gchar *metadata_name = NULL;
|
|
|
|
|
if (args)
|
|
|
|
|
wp_spa_json_object_get (args, "metadata.name", "s", &metadata_name, NULL);
|
|
|
|
|
|
2024-02-10 17:48:23 +02:00
|
|
|
wp_info_object (core, "loading settings instance '%s'...",
|
|
|
|
|
metadata_name ? metadata_name : "(default: sm-settings)");
|
2024-01-26 11:34:08 +02:00
|
|
|
|
2024-02-10 17:48:23 +02:00
|
|
|
WpSettings *settings = wp_settings_new (core, metadata_name);
|
|
|
|
|
g_task_return_pointer (task, settings, g_object_unref);
|
2024-01-26 11:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
2023-06-21 19:35:23 +03:00
|
|
|
static const struct {
|
|
|
|
|
const gchar * name;
|
2024-01-26 11:34:08 +02:00
|
|
|
void (*load) (GTask *, WpCore *, WpSpaJson *);
|
2023-06-21 19:35:23 +03:00
|
|
|
} builtin_components[] = {
|
|
|
|
|
{ "ensure-no-media-session", ensure_no_media_session },
|
|
|
|
|
{ "export-core", load_export_core },
|
2024-01-26 11:34:08 +02:00
|
|
|
{ "settings-instance", load_settings_instance },
|
2023-06-21 19:35:23 +03:00
|
|
|
};
|
|
|
|
|
|
2023-05-29 15:48:39 +03:00
|
|
|
/*** WpInternalCompLoader ***/
|
|
|
|
|
|
2023-05-26 13:49:40 +03:00
|
|
|
struct _WpInternalCompLoader
|
|
|
|
|
{
|
|
|
|
|
GObject parent;
|
|
|
|
|
};
|
|
|
|
|
static void wp_internal_comp_loader_iface_init (WpComponentLoaderInterface * iface);
|
|
|
|
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (WpInternalCompLoader, wp_internal_comp_loader,
|
|
|
|
|
G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (
|
|
|
|
|
WP_TYPE_COMPONENT_LOADER,
|
|
|
|
|
wp_internal_comp_loader_iface_init))
|
|
|
|
|
|
|
|
|
|
#define WP_MODULE_INIT_SYMBOL "wireplumber__module_init"
|
|
|
|
|
typedef GObject * (*WpModuleInitFunc) (WpCore *, WpSpaJson *, GError **);
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_internal_comp_loader_init (WpInternalCompLoader * self)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_internal_comp_loader_class_init (WpInternalCompLoaderClass * klass)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GObject *
|
|
|
|
|
load_module (WpCore * core, const gchar * module_name, WpSpaJson * args,
|
|
|
|
|
GError ** error)
|
|
|
|
|
{
|
|
|
|
|
g_autofree gchar *module_path = NULL;
|
|
|
|
|
GModule *gmodule;
|
|
|
|
|
gpointer module_init;
|
|
|
|
|
|
2024-02-27 11:59:33 +02:00
|
|
|
module_path = wp_base_dirs_find_file (WP_BASE_DIRS_MODULE, NULL, module_name);
|
|
|
|
|
if (!module_path) {
|
|
|
|
|
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
|
|
|
"Failed to locate module %s", module_name);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2023-05-26 13:49:40 +03:00
|
|
|
|
2023-05-26 21:22:48 +03:00
|
|
|
wp_trace_object (core, "loading %s from %s", module_name, module_path);
|
2023-05-26 13:49:40 +03:00
|
|
|
|
|
|
|
|
gmodule = g_module_open (module_path, G_MODULE_BIND_LOCAL);
|
|
|
|
|
if (!gmodule) {
|
|
|
|
|
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
|
|
|
"Failed to open %s: %s", module_path, g_module_error ());
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!g_module_symbol (gmodule, WP_MODULE_INIT_SYMBOL, &module_init)) {
|
|
|
|
|
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
|
|
|
"Failed to locate symbol " WP_MODULE_INIT_SYMBOL " in %s",
|
|
|
|
|
module_path);
|
|
|
|
|
g_module_close (gmodule);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ((WpModuleInitFunc) module_init) (core, args, error);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-02 17:00:53 +03:00
|
|
|
static gboolean
|
|
|
|
|
parse_profile_description (WpProperties * profile, WpSpaJson * all_profiles_j,
|
|
|
|
|
const gchar * profile_name, GPtrArray * inherited_set, GError ** error)
|
|
|
|
|
{
|
|
|
|
|
g_autoptr (WpSpaJson) profile_j = NULL;
|
|
|
|
|
g_autoptr (WpSpaJson) inherits_j = NULL;
|
|
|
|
|
|
|
|
|
|
if (!all_profiles_j) {
|
|
|
|
|
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
|
|
|
|
"wireplumber.profiles section does not exist in the configuration");
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!wp_spa_json_is_object (all_profiles_j)) {
|
|
|
|
|
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
|
|
|
|
"wireplumber.profiles section is not an object");
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!wp_spa_json_object_get (all_profiles_j, profile_name, "J", &profile_j, NULL)) {
|
|
|
|
|
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
|
|
|
|
"profile '%s' not found in the configuration", profile_name);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!wp_spa_json_is_object (profile_j)) {
|
|
|
|
|
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
|
|
|
|
"profile description of '%s' is not an object", profile_name);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* mark as inherited */
|
|
|
|
|
g_ptr_array_add (inherited_set, g_strdup (profile_name));
|
|
|
|
|
|
|
|
|
|
if (wp_spa_json_object_get (profile_j, "inherits", "J", &inherits_j, NULL) &&
|
|
|
|
|
wp_spa_json_is_array (inherits_j)) {
|
|
|
|
|
g_autoptr (WpIterator) it = wp_spa_json_new_iterator (inherits_j);
|
|
|
|
|
g_auto (GValue) item = G_VALUE_INIT;
|
|
|
|
|
|
|
|
|
|
for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
|
|
|
|
|
WpSpaJson *inherited_j = g_value_get_boxed (&item);
|
|
|
|
|
g_autofree gchar *inherited_profile = wp_spa_json_to_string (inherited_j);
|
|
|
|
|
|
|
|
|
|
/* skip if already inherited - avoid loops */
|
|
|
|
|
if (g_ptr_array_find_with_equal_func (inherited_set, inherited_profile,
|
|
|
|
|
g_str_equal, NULL))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (!parse_profile_description (profile, all_profiles_j, inherited_profile,
|
|
|
|
|
inherited_set, error))
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wp_properties_update_from_json (profile, profile_j);
|
|
|
|
|
wp_properties_set (profile, "inherits", NULL);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-26 13:49:40 +03:00
|
|
|
static gboolean
|
|
|
|
|
wp_internal_comp_loader_supports_type (WpComponentLoader * cl,
|
|
|
|
|
const gchar * type)
|
|
|
|
|
{
|
2023-06-15 21:27:13 +03:00
|
|
|
return g_str_equal (type, "module") ||
|
2023-11-14 15:34:11 +02:00
|
|
|
g_str_equal (type, "pw-module") ||
|
2023-06-21 19:35:23 +03:00
|
|
|
g_str_equal (type, "virtual") ||
|
2023-06-23 20:15:26 +03:00
|
|
|
g_str_equal (type, "built-in") ||
|
|
|
|
|
g_str_equal (type, "profile") ||
|
|
|
|
|
g_str_equal (type, "array");
|
2023-05-26 13:49:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_internal_comp_loader_load (WpComponentLoader * self, WpCore * core,
|
|
|
|
|
const gchar * component, const gchar * type, WpSpaJson * args,
|
|
|
|
|
GCancellable * cancellable, GAsyncReadyCallback callback, gpointer data)
|
|
|
|
|
{
|
2023-06-23 20:15:26 +03:00
|
|
|
if (g_str_equal (type, "profile") || g_str_equal (type, "array")) {
|
|
|
|
|
WpTransition *task = NULL;
|
|
|
|
|
g_autoptr (WpSpaJson) components = NULL;
|
2023-11-14 12:42:32 +02:00
|
|
|
g_autoptr (WpSpaJson) rules = NULL;
|
2023-06-23 20:15:26 +03:00
|
|
|
g_autoptr (WpProperties) profile = wp_properties_new_empty ();
|
|
|
|
|
|
|
|
|
|
if (g_str_equal (type, "profile")) {
|
|
|
|
|
/* component name is the profile name;
|
|
|
|
|
component list and profile features are loaded from config */
|
conf: refactor configuration loading
Changes:
- Configuration files are no longer located by libpipewire,
which allows us to control the paths that are being looked up.
This is a requirement for installations where pipewire and
wireplumber are built using different prefixes, in which case
the configuration files of wireplumber end up being installed in
a place that libpipewire doesn't look into...
- The location of conf files is now again $prefix/share/wireplumber,
/etc/wireplumber and $XDG_CONFIG_HOME/wireplumber, instead of using
the pipewire directories. Also, since the previous commits, we now
also support $XDG_CONFIG_DIRS/wireplumber (typically /etc/xdg/wireplumber)
and $XDG_DATA_DIRS/wireplumber for system-wide configuration.
- Since libpipewire doesn't expose the parser, we now also do the
parsing of sections ourselves. This has the advantage that we can
optimize it a bit for our use case.
- The WpConf API has changed to not be a singleton and it is a
property of WpCore instead. The configuration is now expected
to be opened before the core is created, which allows the caller
to identify configuration errors in advance. By not being a singleton,
we can also reuse the WpConf API to open other SPA-JSON files.
- WpConf also now has a lazy loading mechanism. The configuration
files are mmap'ed and the various sections are located in advance,
but not parsed until they are actually requested. Also, the sections
are not copied in memory, unlike what happens in libpipewire. They
are only copied when merging is needed.
- WpCore now disables loading of a configuration file in pw_context,
if a WpConf is provided. This is to have complete control here.
The 'context.spa-libs' and 'context.modules' sections are still
loaded, but we load them in WpConf and pass them down to pw_context
for parsing. If a WpConf is not provided, pw_context is left to load
the default configuration file (client.conf normally).
2024-02-28 12:11:38 +02:00
|
|
|
g_autoptr (WpConf) conf = wp_core_get_conf (core);
|
2024-09-02 17:00:53 +03:00
|
|
|
g_autoptr (GPtrArray) inherited_set = g_ptr_array_new_with_free_func (g_free);
|
2024-02-28 18:58:57 +02:00
|
|
|
g_autoptr (WpSpaJson) all_profiles_j = NULL;
|
2024-09-02 17:00:53 +03:00
|
|
|
g_autoptr (GError) error = NULL;
|
2024-02-28 18:58:57 +02:00
|
|
|
const gchar *profile_name = component;
|
2023-11-14 12:42:32 +02:00
|
|
|
|
2025-02-21 09:24:17 +00:00
|
|
|
wp_info ("Loading profile '%s'", profile_name);
|
2024-09-03 11:32:54 +03:00
|
|
|
|
2024-02-28 18:58:57 +02:00
|
|
|
all_profiles_j = wp_conf_get_section (conf, "wireplumber.profiles");
|
|
|
|
|
|
2024-09-02 17:00:53 +03:00
|
|
|
if (!parse_profile_description (profile, all_profiles_j, profile_name,
|
|
|
|
|
inherited_set, &error)) {
|
2023-12-18 17:58:09 +02:00
|
|
|
g_autoptr (GTask) task = g_task_new (self, cancellable, callback, data);
|
|
|
|
|
g_task_set_source_tag (task, wp_internal_comp_loader_load);
|
2024-09-02 17:00:53 +03:00
|
|
|
g_task_return_error (G_TASK (task), g_steal_pointer (&error));
|
2023-12-18 17:58:09 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-28 18:58:57 +02:00
|
|
|
components = wp_conf_get_section (conf, "wireplumber.components");
|
|
|
|
|
rules = wp_conf_get_section (conf, "wireplumber.components.rules");
|
2023-11-14 12:42:32 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2023-06-23 20:15:26 +03:00
|
|
|
/* component list is retrieved from args; profile features are empty */
|
|
|
|
|
components = wp_spa_json_ref (args);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-14 12:42:32 +02:00
|
|
|
task = wp_component_array_load_task_new (components, profile, rules, self,
|
2023-05-29 15:48:39 +03:00
|
|
|
cancellable, callback, data);
|
2023-06-15 21:27:13 +03:00
|
|
|
wp_transition_set_data (task, g_object_ref (core), g_object_unref);
|
2023-05-29 15:48:39 +03:00
|
|
|
wp_transition_set_source_tag (task, wp_internal_comp_loader_load);
|
|
|
|
|
wp_transition_advance (task);
|
|
|
|
|
}
|
2023-06-21 19:35:23 +03:00
|
|
|
else {
|
2023-06-15 21:27:13 +03:00
|
|
|
g_autoptr (GTask) task = g_task_new (self, cancellable, callback, data);
|
|
|
|
|
g_task_set_source_tag (task, wp_internal_comp_loader_load);
|
2023-06-21 19:35:23 +03:00
|
|
|
|
|
|
|
|
if (g_str_equal (type, "module")) {
|
|
|
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
|
g_autoptr (GObject) o = NULL;
|
|
|
|
|
|
|
|
|
|
o = load_module (core, component, args, &error);
|
|
|
|
|
if (o)
|
|
|
|
|
g_task_return_pointer (task, g_steal_pointer (&o), g_object_unref);
|
|
|
|
|
else
|
|
|
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
|
|
|
}
|
2023-11-14 15:34:11 +02:00
|
|
|
else if (g_str_equal (type, "pw-module")) {
|
|
|
|
|
if (!pw_context_load_module (wp_core_get_pw_context (core), component,
|
|
|
|
|
args ? wp_spa_json_get_data (args) : NULL, NULL)) {
|
|
|
|
|
g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
|
|
|
|
|
WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
|
|
|
"Failed to load pipewire module %s: %s", component, strerror (errno));
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
g_task_return_pointer (task, NULL, NULL);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-21 19:35:23 +03:00
|
|
|
else if (g_str_equal (type, "virtual")) {
|
|
|
|
|
g_task_return_pointer (task, NULL, NULL);
|
|
|
|
|
}
|
|
|
|
|
else if (g_str_equal (type, "built-in")) {
|
|
|
|
|
for (guint i = 0; i < G_N_ELEMENTS (builtin_components); i++) {
|
|
|
|
|
if (g_str_equal (component, builtin_components[i].name)) {
|
2024-01-26 11:34:08 +02:00
|
|
|
builtin_components[i].load (task, core, args);
|
2023-06-21 19:35:23 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
|
|
|
|
|
WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
|
|
|
|
"invalid 'built-in' component: %s", component);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
g_assert_not_reached ();
|
|
|
|
|
}
|
2023-05-29 15:48:39 +03:00
|
|
|
}
|
2023-05-26 13:49:40 +03:00
|
|
|
}
|
|
|
|
|
|
2023-05-26 19:21:51 +03:00
|
|
|
static GObject *
|
|
|
|
|
wp_internal_comp_loader_load_finish (WpComponentLoader * self,
|
|
|
|
|
GAsyncResult * res, GError ** error)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (
|
|
|
|
|
g_async_result_is_tagged (res, wp_internal_comp_loader_load), NULL);
|
|
|
|
|
|
2023-05-29 15:48:39 +03:00
|
|
|
if (G_IS_TASK (res))
|
|
|
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
|
|
|
else {
|
|
|
|
|
wp_transition_finish (res, error);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2023-05-26 19:21:51 +03:00
|
|
|
}
|
|
|
|
|
|
2023-05-26 13:49:40 +03:00
|
|
|
static void
|
|
|
|
|
wp_internal_comp_loader_iface_init (WpComponentLoaderInterface * iface)
|
|
|
|
|
{
|
|
|
|
|
iface->supports_type = wp_internal_comp_loader_supports_type;
|
|
|
|
|
iface->load = wp_internal_comp_loader_load;
|
2023-05-26 19:21:51 +03:00
|
|
|
iface->load_finish = wp_internal_comp_loader_load_finish;
|
2023-05-26 13:49:40 +03:00
|
|
|
}
|