wireplumber/lib/wp/settings.c
George Kiagiadakis 4736d56557 log: implement a log topics system, like pipewire
The intention is to make checks for enabled log topics faster.

Every topic has its own structure that is statically defined in the file
where the logs are printed from. The structure is initialized transparently
when it is first used and it contains all the log level flags for the levels
that this topic should print messages. It is then checked on the wp_log()
macro before printing the message.

Topics from SPA/PipeWire are also handled natively, so messages are printed
directly without checking if the topic is enabled, since the PipeWire and SPA
macros do the checking themselves.

Messages coming from GLib are checked inside the handler.

An internal WpLogFields object is used to manage the state of each log
message, populating all the fields appropriately from the place they
are coming from (wp_log, spa_log, glib log), formatting the message and
then printing it. For printing to the journald, we still use the glib
message handler, converting all the needed fields to GLogField on demand.
That message handler does not do any checks for the topic or the level, so
we can just call it to send the message.
2023-05-16 20:42:28 +03:00

426 lines
12 KiB
C

/* WirePlumber
*
* Copyright © 2022 Collabora Ltd.
* @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "core.h"
#include "settings.h"
#include "metadata.h"
#include "log.h"
#include "object-manager.h"
#include "private/registry.h"
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-settings")
/*! \defgroup wpsettings WpSettings */
/*!
* \struct WpSettings
*
* WpSettings loads and parses the "sm-settings" (default value) metadata, which
* contains wireplumber settings. It provides APIs to its clients (modules, lua
* scripts etc) to access and change them.
*
* Being a WpObject subclass, the settings inherits WpObject's activation
* system.
*/
struct _WpSettings
{
WpObject parent;
WpProperties *settings;
/* element-type: Callback* */
GPtrArray *callbacks;
gchar *metadata_name;
WpObjectManager *metadata_om;
};
typedef struct
{
GClosure *closure;
gchar *pattern;
} Callback;
enum {
PROP_0,
PROP_METADATA_NAME,
PROP_PROPERTIES,
};
G_DEFINE_TYPE (WpSettings, wp_settings, WP_TYPE_OBJECT)
static void
wp_settings_init (WpSettings * self)
{
}
/*!
* \brief Subscribes callback for a given setting pattern(a glob-style pattern
* matched using g_pattern_match_simple), this allows clients to look
* for any changes made in settings through metadata.
*
* \ingroup wpsettings
* \param self the settings object
* \param pattern name of the pattern to match the settings with
* \param callback (scope async): the callback triggered when the settings
* change.
* \param user_data data to pass to \a callback
* \returns the subscription ID (always greater than 0 for successful
* subscriptions)
*/
guintptr
wp_settings_subscribe (WpSettings *self,
const gchar *pattern, WpSettingsChangedCallback callback,
gpointer user_data)
{
return wp_settings_subscribe_closure (self, pattern,
g_cclosure_new (G_CALLBACK (callback), user_data, NULL));
}
/*!
* \brief Subscribes callback for a given setting pattern(a glob-style pattern
* matched using g_pattern_match_simple), this allows clients to look
* for any changes made in settings through metadata.
*
* \ingroup wpsettings
* \param self the settings object
* \param pattern name of the pattern to match the settings with
* \param closure (nullable): a GAsyncReadyCallback wrapped in a GClosure
* \returns the subscription ID (always greater than 0 for success)
*/
guintptr
wp_settings_subscribe_closure (WpSettings *self, const gchar *pattern,
GClosure *closure)
{
g_return_val_if_fail (WP_IS_SETTINGS (self), 0);
g_return_val_if_fail (pattern, 0);
g_return_val_if_fail (closure, 0);
Callback *cb = g_slice_new0 (Callback);
g_return_val_if_fail (cb, 0);
cb->closure = g_closure_ref (closure);
g_closure_sink (closure);
if (G_CLOSURE_NEEDS_MARSHAL (closure))
g_closure_set_marshal (closure, g_cclosure_marshal_generic);
cb->pattern = g_strdup (pattern);
g_ptr_array_add (self->callbacks, cb);
wp_debug_object (self, "callback(%p) subscribed for pattern(%s)",
(void *) cb, pattern);
return (guintptr) cb;
}
/*!
* \brief Unsubscribes callback for a given subscription_id.
*
* \ingroup wpsettings
* \param self the settings object
* \param subscription_id identifies the callback
* \returns TRUE if success, FALSE otherwise
*/
gboolean
wp_settings_unsubscribe (WpSettings *self, guintptr subscription_id)
{
gboolean ret = FALSE;
g_return_val_if_fail (WP_IS_SETTINGS (self), FALSE);
g_return_val_if_fail (subscription_id, FALSE);
Callback *cb = (Callback *) subscription_id;
ret = g_ptr_array_remove (self->callbacks, cb);
wp_debug_object (self, "callback(%p) unsubscription %s", (void *) cb,
(ret)? "succeeded": "failed");
return ret;
}
/*!
* \brief Gets the WpSpaJson of a setting
* \ingroup wpsettings
* \param self the settings object
* \param setting name of the setting
* \returns (transfer full) (nullable): The WpSpaJson of the setting, or NULL
* if the setting does not exist
*/
WpSpaJson *
wp_settings_get (WpSettings *self, const gchar *setting)
{
const gchar *value;
g_return_val_if_fail (WP_IS_SETTINGS (self), NULL);
g_return_val_if_fail (setting, NULL);
if (!(wp_object_get_active_features (WP_OBJECT (self)) &
WP_OBJECT_FEATURES_ALL))
return NULL;
value = wp_properties_get (self->settings, setting);
return value ? wp_spa_json_new_from_string (value) : NULL;
}
enum {
STEP_LOAD = WP_TRANSITION_STEP_CUSTOM_START,
};
static WpObjectFeatures
wp_settings_get_supported_features (WpObject * self)
{
return WP_SETTINGS_LOADED;
}
static guint
wp_settings_activate_get_next_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
g_return_val_if_fail (missing == WP_SETTINGS_LOADED,
WP_TRANSITION_STEP_ERROR);
return STEP_LOAD;
}
static gboolean
check_metadata_name (gpointer g_object, gpointer metadata_name)
{
if (!WP_IS_SETTINGS(g_object))
return FALSE;
g_auto (GValue) value = G_VALUE_INIT;
g_object_get_property (G_OBJECT(g_object), "metadata-name", &value);
return g_str_equal (g_value_get_string (&value), (gchar *)metadata_name);
}
/*!
* \brief Returns the WpSettings instance that is associated with the
* given core.
*
* This method will also create the instance and register it with the core
* if it had not been created before.
*
* \ingroup wpsettings
* \param core the core
* \param metadata_name (nullable): the name of the metadata with which this
* object is associated. `sm-settings` is the default value picked if
* NULL is supplied.
* \returns (transfer full): the WpSettings instance
*/
WpSettings *
wp_settings_get_instance (WpCore *core, const gchar *metadata_name)
{
WpRegistry *registry = wp_core_get_registry (core);
const gchar *name = (metadata_name ? metadata_name : "sm-settings") ;
WpSettings *settings = wp_registry_find_object (registry,
(GEqualFunc) check_metadata_name, name);
if (G_UNLIKELY (!settings)) {
settings = g_object_new (WP_TYPE_SETTINGS,
"core", core,
"metadata-name", name,
NULL);
wp_registry_register_object (registry, g_object_ref (settings));
wp_info_object (settings, "created wpsettings object for metadata"
" name \"%s\"", name);
}
return settings;
}
static void
on_metadata_changed (WpMetadata *m, guint32 subject,
const gchar *setting, const gchar *type, const gchar *new_value, gpointer d)
{
WpSettings *self = WP_SETTINGS(d);
const gchar *old_value = NULL;
/* Only handle JSON metadata values */
if (!g_str_equal (type, "Spa:String:JSON"))
return;
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);
for (guint i = 0; i < self->callbacks->len; i++) {
Callback *cb = g_ptr_array_index (self->callbacks, i);
if (g_pattern_match_simple (cb->pattern, setting)) {
g_autoptr (WpSpaJson) json = NULL;
GValue values[3] = { G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT };
g_value_init (&values[0], G_TYPE_OBJECT);
g_value_init (&values[1], G_TYPE_STRING);
g_value_init (&values[2], WP_TYPE_SPA_JSON);
g_value_set_object (&values[0], self);
g_value_set_string (&values[1], setting);
json = new_value ? wp_spa_json_new_from_string (new_value) : NULL;
g_value_set_boxed (&values[2], json);
g_closure_invoke (cb->closure, NULL, 3, values, NULL);
g_value_unset (&values[0]);
g_value_unset (&values[1]);
g_value_unset (&values[2]);
wp_debug_object (self, "triggered callback(%p)", cb);
}
}
}
static void
on_metadata_added (WpObjectManager *om, WpMetadata *m, gpointer d)
{
WpTransition * transition = WP_TRANSITION (d);
WpSettings * self = wp_transition_get_source_object (transition);
g_autoptr (WpIterator) it = wp_metadata_new_iterator (WP_METADATA (m), 0);
g_auto (GValue) val = G_VALUE_INIT;
/* Handle the changed signal */
g_signal_connect_object (m, "changed", G_CALLBACK (on_metadata_changed),
self, 0);
/* traverse through all settings */
for (; wp_iterator_next (it, &val); g_value_unset (&val)) {
const gchar *setting, *value;
wp_metadata_iterator_item_extract (&val, NULL, &setting, NULL, &value);
wp_properties_set (self->settings, setting, value);
}
wp_info_object (self, "loaded %d settings and from metadata \"%s\"",
wp_properties_get_count (self->settings),
self->metadata_name);
wp_object_update_features (WP_OBJECT (self), WP_SETTINGS_LOADED, 0);
}
static void
callback_unref (Callback * self)
{
g_free (self->pattern);
g_clear_pointer (&self->closure, g_closure_unref);
g_slice_free (Callback, self);
}
static void
wp_settings_activate_execute_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
WpSettings * self = WP_SETTINGS (object);
g_autoptr (WpCore) core = wp_object_get_core (object);
switch (step) {
case STEP_LOAD: {
self->settings = wp_properties_new_empty ();
self->callbacks = g_ptr_array_new_with_free_func
((GDestroyNotify) callback_unref);
self->metadata_om = wp_object_manager_new ();
wp_object_manager_add_interest (self->metadata_om, WP_TYPE_METADATA,
WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "metadata.name", "=s",
self->metadata_name, NULL);
wp_object_manager_request_object_features (self->metadata_om,
WP_TYPE_METADATA, WP_OBJECT_FEATURES_ALL);
g_signal_connect_object (self->metadata_om, "object-added",
G_CALLBACK (on_metadata_added), transition, 0);
wp_core_install_object_manager (core, self->metadata_om);
wp_info_object (self, "looking for metadata object named %s",
self->metadata_name);
break;
}
case WP_TRANSITION_STEP_ERROR:
break;
default:
g_assert_not_reached ();
}
}
static void
wp_settings_deactivate (WpObject * object, WpObjectFeatures features)
{
WpSettings *self = WP_SETTINGS (object);
wp_debug_object (self, "%s", self->metadata_name);
g_free (self->metadata_name);
g_clear_object (&self->metadata_om);
g_clear_pointer (&self->callbacks, g_ptr_array_unref);
g_clear_pointer (&self->settings, wp_properties_unref);
wp_object_update_features (WP_OBJECT (self), 0, WP_OBJECT_FEATURES_ALL);
}
static void
wp_settings_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpSettings *self = WP_SETTINGS (object);
switch (property_id) {
case PROP_METADATA_NAME:
self->metadata_name = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_settings_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpSettings *self = WP_SETTINGS (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_class_init (WpSettingsClass * klass)
{
GObjectClass * object_class = (GObjectClass *) klass;
WpObjectClass * wpobject_class = (WpObjectClass *) klass;
object_class->set_property = wp_settings_set_property;
object_class->get_property = wp_settings_get_property;
wpobject_class->activate_get_next_step = wp_settings_activate_get_next_step;
wpobject_class->activate_execute_step = wp_settings_activate_execute_step;
wpobject_class->deactivate = wp_settings_deactivate;
wpobject_class->get_supported_features = wp_settings_get_supported_features;
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));
}