Files
wireplumber/modules/module-default-profile.c
George Kiagiadakis 38f7483793 state: remove support for groups and propagate save errors
There is no real use for groups in our API. Just use the name of
the file as the default group and be done with it...
Storing multiple groups with this API is problematic because it
forces flushing the file to disk multiple times, one for each group,
and it's just more performant if we use a prefix in the keys
to implement some form of logical separation.

This commit also makes the GKeyFile a temporary object. As we
always load the file from the file system in _load()
and we always replace its contents with a new dictionary in _save(),
there is no point in keeping the keyfile's internal data structures
stored in memory.

Save errors are now also propagated to adhere to the programming
practices of GObject
2021-06-04 18:36:19 +03:00

330 lines
9.2 KiB
C

/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
#define STATE_NAME "default-profile"
#define SAVE_INTERVAL_MS 1000
G_DEFINE_QUARK (wp-module-default-profile-profiles, profiles);
/* Signals */
enum
{
SIGNAL_GET_PROFILE,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
G_DECLARE_DERIVABLE_TYPE (WpDefaultProfile, wp_default_profile, WP,
DEFAULT_PROFILE, WpPlugin)
struct _WpDefaultProfileClass
{
WpPluginClass parent_class;
void (*get_profile) (WpDefaultProfile *self, WpPipewireObject *device,
const char **curr_profile);
};
typedef struct _WpDefaultProfilePrivate WpDefaultProfilePrivate;
struct _WpDefaultProfilePrivate
{
WpState *state;
WpProperties *profiles;
GSource *timeout_source;
WpObjectManager *devices_om;
};
G_DEFINE_TYPE_WITH_PRIVATE (WpDefaultProfile, wp_default_profile,
WP_TYPE_PLUGIN)
static gint
find_device_profile (WpPipewireObject *device, const gchar *lookup_name)
{
WpIterator *profiles = NULL;
g_auto (GValue) item = G_VALUE_INIT;
profiles = g_object_get_qdata (G_OBJECT (device), profiles_quark ());
g_return_val_if_fail (profiles, -1);
wp_iterator_reset (profiles);
for (; wp_iterator_next (profiles, &item); g_value_unset (&item)) {
WpSpaPod *pod = g_value_get_boxed (&item);
gint index = 0;
const gchar *name = NULL;
/* Parse */
if (!wp_spa_pod_get_object (pod, NULL,
"index", "i", &index,
"name", "s", &name,
NULL)) {
continue;
}
if (g_strcmp0 (name, lookup_name) == 0)
return index;
}
return -1;
}
static gboolean
timeout_save_callback (WpDefaultProfile *self)
{
WpDefaultProfilePrivate *priv =
wp_default_profile_get_instance_private (self);
g_autoptr (GError) error = NULL;
if (!wp_state_save (priv->state, priv->profiles, &error))
wp_warning_object (self, "%s", error->message);
return G_SOURCE_REMOVE;
}
static void
timeout_save_profiles (WpDefaultProfile *self, guint ms)
{
WpDefaultProfilePrivate *priv =
wp_default_profile_get_instance_private (self);
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
g_return_if_fail (core);
g_return_if_fail (priv->profiles);
/* Clear the current timeout callback */
if (priv->timeout_source)
g_source_destroy (priv->timeout_source);
g_clear_pointer (&priv->timeout_source, g_source_unref);
/* Add the timeout callback */
wp_core_timeout_add_closure (core, &priv->timeout_source, ms,
g_cclosure_new_object (G_CALLBACK (timeout_save_callback),
G_OBJECT (self)));
}
static void
wp_default_profile_get_profile (WpDefaultProfile *self,
WpPipewireObject *device, const gchar **curr_profile)
{
WpDefaultProfilePrivate *priv =
wp_default_profile_get_instance_private (self);
const gchar *dev_name = NULL;
g_return_if_fail (device);
g_return_if_fail (curr_profile);
g_return_if_fail (priv->profiles);
/* Get the device name */
dev_name = wp_pipewire_object_get_property (device, PW_KEY_DEVICE_NAME);
g_return_if_fail (dev_name);
/* Get the profile */
*curr_profile = wp_properties_get (priv->profiles, dev_name);
}
static void
update_profile (WpDefaultProfile *self, WpPipewireObject *device,
const gchar *new_profile)
{
WpDefaultProfilePrivate *priv =
wp_default_profile_get_instance_private (self);
const gchar *dev_name, *curr_profile = NULL;
gint index;
g_return_if_fail (new_profile);
g_return_if_fail (priv->profiles);
/* Get the device name */
dev_name = wp_pipewire_object_get_property (device, PW_KEY_DEVICE_NAME);
g_return_if_fail (dev_name);
/* Check if the new profile is the same as the current one */
curr_profile = wp_properties_get (priv->profiles, dev_name);
if (curr_profile && g_strcmp0 (curr_profile, new_profile) == 0)
return;
/* Make sure the profile is valid */
index = find_device_profile (device, new_profile);
if (index < 0) {
wp_info_object (self, "profile '%s' (%d) is not valid on device '%s'",
new_profile, index, dev_name);
return;
}
/* Otherwise update the profile and add timeout save callback */
wp_properties_set (priv->profiles, dev_name, new_profile);
timeout_save_profiles (self, SAVE_INTERVAL_MS);
wp_info_object (self, "updated profile '%s' (%d) on device '%s'", new_profile,
index, dev_name);
}
static void
on_device_profile_notified (WpPipewireObject *device, GAsyncResult *res,
WpDefaultProfile *self)
{
g_autoptr (WpIterator) profiles = NULL;
g_autoptr (GError) error = NULL;
g_auto (GValue) item = G_VALUE_INIT;
const gchar *name = NULL;
gint index = 0;
/* Finish */
profiles = wp_pipewire_object_enum_params_finish (device, res, &error);
if (error) {
wp_warning_object (self, "failed to get current profile on device: %s",
error->message);
return;
}
/* Ignore empty profile notifications */
if (!wp_iterator_next (profiles, &item))
return;
/* Parse the profile */
WpSpaPod *pod = g_value_get_boxed (&item);
if (!wp_spa_pod_get_object (pod, NULL,
"index", "i", &index,
"name", "s", &name,
NULL)) {
wp_warning_object (self, "failed to parse current profile");
return;
}
g_value_unset (&item);
/* Update the profile */
update_profile (self, device, name);
}
static void
on_device_param_info_notified (WpPipewireObject * device, GParamSpec * param,
WpDefaultProfile *self)
{
/* Check the profile every time the params have changed */
wp_pipewire_object_enum_params (device, "Profile", NULL, NULL,
(GAsyncReadyCallback) on_device_profile_notified, self);
}
static void
on_device_added (WpObjectManager *om, WpPipewireObject *proxy, gpointer d)
{
WpDefaultProfile *self = WP_DEFAULT_PROFILE (d);
g_autoptr (WpIterator) profiles = NULL;
wp_debug_object (self, "device " WP_OBJECT_FORMAT " added",
WP_OBJECT_ARGS (proxy));
/* Enum available profiles */
profiles = wp_pipewire_object_enum_params_sync (proxy, "EnumProfile", NULL);
if (!profiles)
return;
/* Keep a reference of the profiles in the device object */
g_object_set_qdata_full (G_OBJECT (proxy), profiles_quark (),
g_steal_pointer (&profiles), (GDestroyNotify) wp_iterator_unref);
/* Watch for param info changes */
g_signal_connect_object (proxy, "notify::param-info",
G_CALLBACK (on_device_param_info_notified), self, 0);
}
static void
wp_default_profile_enable (WpPlugin * plugin, WpTransition * transition)
{
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
WpDefaultProfile *self = WP_DEFAULT_PROFILE (plugin);
WpDefaultProfilePrivate *priv =
wp_default_profile_get_instance_private (self);
/* Create the devices object manager */
priv->devices_om = wp_object_manager_new ();
wp_object_manager_add_interest (priv->devices_om, WP_TYPE_DEVICE, NULL);
wp_object_manager_request_object_features (priv->devices_om,
WP_TYPE_DEVICE, WP_PIPEWIRE_OBJECT_FEATURES_ALL);
g_signal_connect_object (priv->devices_om, "object-added",
G_CALLBACK (on_device_added), self, 0);
wp_core_install_object_manager (core, priv->devices_om);
wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}
static void
wp_default_profile_disable (WpPlugin * plugin)
{
WpDefaultProfile *self = WP_DEFAULT_PROFILE (plugin);
WpDefaultProfilePrivate *priv =
wp_default_profile_get_instance_private (self);
g_clear_object (&priv->devices_om);
}
static void
wp_default_profile_finalize (GObject * object)
{
WpDefaultProfile *self = WP_DEFAULT_PROFILE (object);
WpDefaultProfilePrivate *priv =
wp_default_profile_get_instance_private (self);
/* Clear the current timeout callback */
if (priv->timeout_source)
g_source_destroy (priv->timeout_source);
g_clear_pointer (&priv->timeout_source, g_source_unref);
g_clear_pointer (&priv->profiles, wp_properties_unref);
g_clear_object (&priv->state);
G_OBJECT_CLASS (wp_default_profile_parent_class)->finalize (object);
}
static void
wp_default_profile_init (WpDefaultProfile * self)
{
WpDefaultProfilePrivate *priv =
wp_default_profile_get_instance_private (self);
priv->state = wp_state_new (STATE_NAME);
/* Load the saved profiles */
priv->profiles = wp_state_load (priv->state);
}
static void
wp_default_profile_class_init (WpDefaultProfileClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpPluginClass *plugin_class = (WpPluginClass *) klass;
object_class->finalize = wp_default_profile_finalize;
plugin_class->enable = wp_default_profile_enable;
plugin_class->disable = wp_default_profile_disable;
klass->get_profile = wp_default_profile_get_profile;
/* Signals */
signals[SIGNAL_GET_PROFILE] = g_signal_new ("get-profile",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (WpDefaultProfileClass, get_profile), NULL, NULL,
NULL, G_TYPE_NONE, 2, WP_TYPE_DEVICE, G_TYPE_POINTER);
}
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
wp_plugin_register (g_object_new (wp_default_profile_get_type (),
"name", STATE_NAME,
"core", core,
NULL));
return TRUE;
}