/* WirePlumber * * Copyright © 2022 Collabora Ltd. * @author Ashok Sidipotu * * SPDX-License-Identifier: MIT */ #include #include #include #include /* * This module parses the "wireplumber.settings" section from the .conf file. * * Creates "sm-settings"(default) metadata and pushes the settings to it. * Looks out for changes done in the metadata via the pw-metadata interface. * * If persistent settings is enabled stores the settings in a state file * and retains the settings from there on subsequent reboots ignoring the * contents of .conf file. */ struct _WpSettingsPlugin { WpPlugin parent; gchar *metadata_name; WpImplMetadata *impl_metadata; WpProperties *settings; WpState *state; GSource *timeout_source; guint save_interval_ms; gboolean use_persistent_storage; }; enum { PROP_0, PROP_METADATA_NAME, PROP_PROPERTIES, }; G_DECLARE_FINAL_TYPE (WpSettingsPlugin, wp_settings_plugin, WP, SETTINGS_PLUGIN, WpPlugin) G_DEFINE_TYPE (WpSettingsPlugin, wp_settings_plugin, WP_TYPE_PLUGIN) #define NAME "sm-settings" #define PERSISTENT_SETTING "persistent.settings" static void wp_settings_plugin_init (WpSettingsPlugin * self) { } static gboolean timeout_save_state_callback (WpSettingsPlugin *self) { g_autoptr (GError) error = NULL; if (!self->state) self->state = wp_state_new (NAME); if (!wp_state_save (self->state, self->settings, &error)) wp_warning_object (self, "%s", error->message); g_clear_pointer (&self->timeout_source, g_source_unref); return G_SOURCE_REMOVE; } static void timer_start (WpSettingsPlugin *self) { if (!self->timeout_source && self->use_persistent_storage) { g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); g_return_if_fail (core); /* Add the timeout callback */ wp_core_timeout_add_closure (core, &self->timeout_source, self->save_interval_ms, g_cclosure_new_object ( G_CALLBACK (timeout_save_state_callback), G_OBJECT (self))); } } static gboolean settings_available_in_state_file (WpSettingsPlugin * self) { g_autoptr (WpState) state = wp_state_new (NAME); g_autoptr (WpProperties) settings = wp_state_load (state); guint count = wp_properties_get_count(settings); if (count > 0) { wp_info_object (self, "%d settings are available in state file", count); self->state = g_steal_pointer (&state); self->use_persistent_storage = true; return true; } else wp_info_object (self, "no settings are available in state file"); return false; } static void on_metadata_changed (WpMetadata *m, guint32 subject, const gchar *setting, const gchar *type, const gchar *new_value, gpointer d) { WpSettingsPlugin *self = WP_SETTINGS_PLUGIN(d); const gchar *old_value = wp_properties_get (self->settings, setting); if (!old_value) { wp_info_object (self, "new setting defined \"%s\" = \"%s\"", setting, new_value); } else { wp_info_object (self, "setting \"%s\" new_value changed from \"%s\" ->" " \"%s\"", setting, old_value, new_value); } wp_properties_set (self->settings, setting, new_value); /* update the state */ timer_start (self); } struct data { WpTransition *transition; int count; WpProperties *settings; }; static int do_parse_settings (void *data, const char *location, const char *section, const char *str, size_t len) { struct data *d = data; WpTransition *transition = d->transition; WpSettingsPlugin *self = wp_transition_get_source_object (transition); g_autoptr (WpSpaJson) json = wp_spa_json_new_from_stringn (str, len); g_autoptr (WpIterator) iter = wp_spa_json_new_iterator (json); g_auto (GValue) item = G_VALUE_INIT; if (!wp_spa_json_is_object (json)) { /* section has to be a JSON object element. */ wp_transition_return_error (transition, g_error_new ( WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, "failed to parse the section")); return -EINVAL; } while (wp_iterator_next (iter, &item)) { WpSpaJson *j = g_value_get_boxed (&item); g_autofree gchar *name = wp_spa_json_parse_string (j); g_autofree gchar *value = NULL; int len = 0; g_value_unset (&item); if (!wp_iterator_next (iter, &item)) { wp_warning ("It is likely that the JSON syntax is incorrect," " check key value pair formatting"); break; } j = g_value_get_boxed (&item); value = wp_spa_json_parse_string (j); len = wp_spa_json_get_size (j); g_value_unset (&item); if (name && value) { wp_debug_object (self, "%s(%d) = %s", name, len, value); wp_properties_set (d->settings, name, value); if (g_str_equal (name, PERSISTENT_SETTING) && spa_atob (value)) { self->use_persistent_storage = true; wp_info_object (self, "persistent settings enabled"); } d->count++; } } wp_info_object (self, "parsed %d settings & rules from conf file", d->count); return 0; } static int do_parse_endpoints (void *data, const char *location, const char *section, const char *str, size_t len) { return do_parse_settings (data, location, section, str, len); } static void on_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 (GError) error = NULL; g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); struct pw_context *pw_ctx = wp_core_get_pw_context (core); g_autoptr (WpProperties) settings = wp_properties_new_empty(); struct data data = { .transition = transition, .settings = settings }; g_autoptr (WpIterator) it = NULL; g_auto (GValue) item = G_VALUE_INIT; if (!wp_object_activate_finish (WP_OBJECT (m), res, &error)) { g_clear_object (&self->impl_metadata); g_prefix_error (&error, "Failed to activate \"%s\": " "Metadata object ", self->metadata_name); wp_transition_return_error (transition, g_steal_pointer (&error)); return; } wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0); if (pw_context_conf_section_for_each (pw_ctx, "wireplumber.settings", do_parse_settings, &data) < 0) return; if (data.count == 0) { /* * either the "wireplumber.settings" is not found or not defined as a * valid JSON object element. */ wp_transition_return_error (transition, g_error_new ( WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, "No settings present in the context conf file: settings " "are not loaded")); return; } if (pw_context_conf_section_for_each (pw_ctx, "wireplumber.endpoints", do_parse_endpoints, &data) < 0) return; if (data.count == 0) { /* * either the "wireplumber.endpoints" is not found or not defined as a * valid JSON object element. */ wp_transition_return_error (transition, g_error_new ( WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, "No endpoints present in the context conf file: endpoints " "are not loaded")); return; } if (!self->use_persistent_storage) { /* use parsed settings from .conf file */ self->settings = g_steal_pointer (&settings); wp_info_object(self, "use settings from .conf file"); if (settings_available_in_state_file (self)) { wp_info_object (self, "persistant storage is disabled clear the" " settings in the state file"); wp_state_clear (self->state); g_clear_object (&self->state); } } else if (self->use_persistent_storage) { /* consider using settings from state file */ if (settings_available_in_state_file (self)) { self->settings = wp_state_load (self->state); wp_info_object (self, "persistant storage enabled and settings are found" " in state file, use them"); } else { wp_info_object (self, "persistant storage enabled but settings are" " not found in state file so load from .conf file"); self->settings = g_steal_pointer (&settings); /* save state after time out */ timer_start (self); } } for (it = wp_properties_new_iterator (self->settings); wp_iterator_next (it, &item); g_value_unset (&item)) { WpPropertiesItem *pi = g_value_get_boxed (&item); const gchar *setting = wp_properties_item_get_key (pi); const gchar *value = wp_properties_item_get_value (pi); wp_debug_object (self, "%s(%lu) = %s", setting, strlen(value), value); wp_metadata_set (m, 0, setting, "Spa:String:JSON", value); } wp_info_object (self, "loaded settings(%d) to \"%s\" metadata", wp_properties_get_count (self->settings), self->metadata_name); /* monitor changes in metadata. */ g_signal_connect_object (m, "changed", G_CALLBACK (on_metadata_changed), self, 0); } 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)); g_return_if_fail (core); self->use_persistent_storage = false; /* create metadata object */ self->impl_metadata = wp_impl_metadata_new_full (core, self->metadata_name, NULL); wp_object_activate (WP_OBJECT (self->impl_metadata), WP_OBJECT_FEATURES_ALL, NULL, (GAsyncReadyCallback)on_metadata_activated, transition); } static void wp_settings_plugin_disable (WpPlugin * plugin) { WpSettingsPlugin * self = WP_SETTINGS_PLUGIN (plugin); if (self->timeout_source) g_source_destroy (self->timeout_source); g_clear_pointer (&self->timeout_source, g_source_unref); g_clear_pointer (&self->settings, wp_properties_unref); g_clear_object (&self->impl_metadata); g_clear_object (&self->state); g_free (self->metadata_name); } static void wp_settings_plugin_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { WpSettingsPlugin *self = WP_SETTINGS_PLUGIN (object); switch (property_id) { case PROP_METADATA_NAME: self->metadata_name = g_strdup (g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void wp_settings_plugin_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { WpSettingsPlugin *self = WP_SETTINGS_PLUGIN (object); switch (property_id) { case PROP_METADATA_NAME: g_value_set_string (value, self->metadata_name); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void wp_settings_plugin_class_init (WpSettingsPluginClass * klass) { WpPluginClass *plugin_class = (WpPluginClass *) klass; GObjectClass *object_class = (GObjectClass *) klass; plugin_class->enable = wp_settings_plugin_enable; plugin_class->disable = wp_settings_plugin_disable; object_class->set_property = wp_settings_plugin_set_property; object_class->get_property = wp_settings_plugin_get_property; g_object_class_install_property (object_class, PROP_METADATA_NAME, g_param_spec_string ("metadata-name", "metadata-name", "The metadata object to look after", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } WP_PLUGIN_EXPORT gboolean wireplumber__module_init (WpCore * core, GVariant * args, GError ** error) { const gchar *metadata_name = "sm-settings"; if (args) { metadata_name = g_variant_get_string(args, NULL); } wp_plugin_register (g_object_new (wp_settings_plugin_get_type (), "name", "settings", "core", core, "metadata-name", metadata_name, NULL)); return TRUE; }