
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
330 lines
9.2 KiB
C
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;
|
|
}
|