diff --git a/lib/wp/settings.h b/lib/wp/settings.h index cf04e453..752064ff 100644 --- a/lib/wp/settings.h +++ b/lib/wp/settings.h @@ -12,6 +12,9 @@ #include "object.h" #include "spa-json.h" +#define WP_SETTINGS_SCHEMA_METADATA_NAME_PREFIX "schema-" +#define WP_SETTINGS_PERSISTENT_METADATA_NAME_PREFIX "persistent-" + G_BEGIN_DECLS /*! diff --git a/modules/module-settings.c b/modules/module-settings.c index 765b5413..3da7b7c8 100644 --- a/modules/module-settings.c +++ b/modules/module-settings.c @@ -30,9 +30,11 @@ struct _WpSettingsPlugin /* Props */ gchar *metadata_name; - gchar *persistent_metadata_name; + gchar *metadata_schema_name; + gchar *metadata_persistent_name; WpImplMetadata *impl_metadata; + WpImplMetadata *schema_impl_metadata; WpImplMetadata *persistent_impl_metadata; WpState *state; WpProperties *persistent_settings; @@ -183,7 +185,7 @@ on_persistent_metadata_activated (WpMetadata * m, GAsyncResult * res, if (!wp_object_activate_finish (WP_OBJECT (m), res, &error)) { g_prefix_error (&error, "Failed to activate \"%s\": " - "Metadata object ", self->metadata_name); + "Metadata object ", self->metadata_persistent_name); wp_transition_return_error (transition, g_steal_pointer (&error)); return; } @@ -201,7 +203,7 @@ on_persistent_metadata_activated (WpMetadata * m, GAsyncResult * res, const gchar *value = wp_properties_item_get_value (pi); wp_debug_object (self, "adding persistent setting to %s metadata: %s = %s", - self->persistent_metadata_name, key, value); + self->metadata_persistent_name, key, value); wp_metadata_set (m, 0, key, "Spa:String:JSON", value); } @@ -219,19 +221,85 @@ on_persistent_metadata_activated (WpMetadata * m, GAsyncResult * res, transition); } +static void +on_schema_metadata_activated (WpMetadata * m, GAsyncResult * res, + gpointer user_data) +{ + WpTransition *transition = WP_TRANSITION (user_data); + WpSettingsPlugin *self = wp_transition_get_source_object (transition); + g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); + g_autoptr (WpConf) conf = wp_conf_get_instance (core); + g_autoptr (GError) error = NULL; + g_autoptr (WpSpaJson) schema_json = NULL; + + if (!wp_object_activate_finish (WP_OBJECT (m), res, &error)) { + wp_transition_return_error (transition, g_error_new ( + WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, + "Failed to activate metadata object %s", self->metadata_schema_name)); + return; + } + + /* Load the schema into metadata if any */ + schema_json = wp_conf_get_section (conf, "wireplumber.settings.schema", NULL); + if (schema_json) { + g_autoptr (WpIterator) it = NULL; + g_auto (GValue) item = G_VALUE_INIT; + + if (!wp_spa_json_is_object (schema_json)) { + wp_transition_return_error (transition, g_error_new ( + WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, + "Settings schema is not a JSON object: %s", + wp_spa_json_get_data (schema_json))); + return; + } + + it = wp_spa_json_new_iterator (schema_json); + while (wp_iterator_next (it, &item)) { + WpSpaJson *j = g_value_get_boxed (&item); + g_autofree gchar *key = wp_spa_json_parse_string (j); + g_autofree gchar *value = NULL; + + g_value_unset (&item); + if (!wp_iterator_next (it, &item)) { + wp_transition_return_error (transition, g_error_new (WP_DOMAIN_LIBRARY, + WP_LIBRARY_ERROR_INVARIANT, "Malformed settings schema")); + return; + } + j = g_value_get_boxed (&item); + value = wp_spa_json_to_string (j); + g_value_unset (&item); + + wp_debug_object (self, "adding schema setting to %s metadata: %s = %s", + self->metadata_schema_name, key, value); + wp_metadata_set (m, 0, key, "Spa:String:JSON", value); + } + } else { + wp_warning_object (self, "settings schema not found in configuration"); + } + + /* create persistent metadata object */ + self->persistent_impl_metadata = wp_impl_metadata_new_full (core, + self->metadata_persistent_name, NULL); + wp_object_activate (WP_OBJECT (self->persistent_impl_metadata), + WP_OBJECT_FEATURES_ALL, + NULL, + (GAsyncReadyCallback)on_persistent_metadata_activated, + transition); +} + static void wp_settings_plugin_enable (WpPlugin * plugin, WpTransition * transition) { WpSettingsPlugin * self = WP_SETTINGS_PLUGIN (plugin); g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin)); - /* create persistent metadata object */ - self->persistent_impl_metadata = wp_impl_metadata_new_full (core, - self->persistent_metadata_name, NULL); - wp_object_activate (WP_OBJECT (self->persistent_impl_metadata), + /* create schema metadata object */ + self->schema_impl_metadata = wp_impl_metadata_new_full (core, + self->metadata_schema_name, NULL); + wp_object_activate (WP_OBJECT (self->schema_impl_metadata), WP_OBJECT_FEATURES_ALL, NULL, - (GAsyncReadyCallback)on_persistent_metadata_activated, + (GAsyncReadyCallback)on_schema_metadata_activated, transition); } @@ -246,7 +314,8 @@ wp_settings_plugin_disable (WpPlugin * plugin) g_clear_object (&self->state); g_clear_pointer (&self->metadata_name, g_free); - g_clear_pointer (&self->persistent_metadata_name, g_free); + g_clear_pointer (&self->metadata_schema_name, g_free); + g_clear_pointer (&self->metadata_persistent_name, g_free); } static void @@ -258,8 +327,10 @@ wp_settings_plugin_set_property (GObject * object, guint property_id, switch (property_id) { case PROP_METADATA_NAME: self->metadata_name = g_value_dup_string (value); - self->persistent_metadata_name = g_strdup_printf ("persistent-%s", - self->metadata_name); + self->metadata_schema_name = g_strdup_printf ( + WP_SETTINGS_SCHEMA_METADATA_NAME_PREFIX "%s", self->metadata_name); + self->metadata_persistent_name = g_strdup_printf ( + WP_SETTINGS_PERSISTENT_METADATA_NAME_PREFIX "%s", self->metadata_name); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); diff --git a/src/config/wireplumber.conf b/src/config/wireplumber.conf index 25e38739..4c9f6d44 100644 --- a/src/config/wireplumber.conf +++ b/src/config/wireplumber.conf @@ -664,3 +664,115 @@ wireplumber.components.rules = [ } } ] + +wireplumber.settings.schema = { + ## Bluetooth + bluetooth.use-persistent-storage = { + description = "Whether to use persistent BT storage or not" + type = "bool" + default = true + } + bluetooth.autoswitch-to-headset-profile = { + description = "Whether to autoswitch to BT headset profile or not" + type = "bool" + default = true + } + + ## Device + device.restore-profile = { + description = "Whether to restore device profile or not" + type = "bool" + default = true + } + device.restore-routes = { + description = "Whether to restore device routes or not" + type = "bool" + default = true + } + device.routes.default-sink-volume = { + description = "The default volume for sink devices" + type = "float" + default = 0.064 + min = 0.0 + max = 1.0 + } + device.routes.default-source-volume = { + description = "The default volume for source devices" + type = "float" + default = 1.0 + min = 0.0 + max = 1.0 + } + + ## Linking + linking.allow-moving-streams = { + description = "Whether to allow metadata to move streams at runtime or not" + type = "bool" + default = true + } + linking.follow-default-target = { + description = "Whether to allow streams follow the default device or not" + type = "bool" + default = true + } + + ## Monitor + monitor.camera-discovery-timeout = { + description = "The camera discovery timeout in milliseconds" + type = "int" + default = 100 + min = 0 + max = 60000 + } + + ## Node + node.features.audio.no-dsp = { + description = "Whether to never convert audio to F32 format or not" + type = "bool" + default = false + } + node.eatures.audio.monitor-ports = { + description = "Whether to enable monitor ports on audio nodes or not" + type = "bool" + default = true + } + node.features.audio.control-port = { + description = "Whether to enable control ports on audio nodes or not" + type = "bool" + default = false + } + node.stream.restore-props = { + description = "Whether to restore properties on stream nodes or not" + type = "bool" + default = true + } + node.stream.restore-target = { + description = "Whether to restore target on stream nodes or not" + type = "bool" + default = true + } + node.default-playback-volume = { + description = "The default volume for playback nodes" + type = "float" + default = 1.0 + min = 0.0 + max = 1.0 + } + node.default-capture-volume = { + description = "The default volume for capture nodes" + type = "float" + default = 1.0 + min = 0.0 + max = 1.0 + } + node.filter.forward-format = { + description = "Whether to forward format on filter nodes or not" + type = "bool" + default = false + } + node.restore-default-targets = { + description = "Whether to restore default targets or not" + type = "bool" + default = true + } +}