Files
wireplumber/modules/module-default-routes.c

833 lines
25 KiB
C

/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author Arnaud Ferraris <arnaud.ferraris@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
#include <spa/param/audio/raw.h>
#include <spa/param/audio/type-info.h>
#include <spa/debug/types.h>
#include <spa/utils/json.h>
#define STATE_NAME "default-routes"
#define SAVE_INTERVAL_MS 1000
G_DEFINE_QUARK (wp-module-default-routes-routes, routes);
/* Signals */
enum
{
SIGNAL_GET_ROUTES,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
G_DECLARE_DERIVABLE_TYPE (WpDefaultRoutes, wp_default_routes, WP,
DEFAULT_ROUTES, WpPlugin)
struct _WpDefaultRoutesClass
{
WpPluginClass parent_class;
void (*get_routes) (WpDefaultRoutes *self, WpPipewireObject *device,
GHashTable **routes);
};
typedef struct _WpDefaultRoutesPrivate WpDefaultRoutesPrivate;
struct _WpDefaultRoutesPrivate
{
WpState *state;
WpProperties *routes;
WpProperties *props;
GSource *routes_timeout;
GSource *props_timeout;
GHashTable *current_routes;
GHashTable *default_routes;
WpObjectManager *devices_om;
};
G_DEFINE_TYPE_WITH_PRIVATE (WpDefaultRoutes, wp_default_routes,
WP_TYPE_PLUGIN)
#define ALLOW_CHARS "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_."
#define MAX_JSON_STRING_LEN 256
static gint
find_device_route (WpPipewireObject *device, const gchar *lookup_name,
gint lookup_device)
{
WpIterator *routes = NULL;
g_auto (GValue) item = G_VALUE_INIT;
routes = g_object_get_qdata (G_OBJECT (device), routes_quark ());
g_return_val_if_fail (routes, -1);
wp_iterator_reset (routes);
for (; wp_iterator_next (routes, &item); g_value_unset (&item)) {
WpSpaPod *pod = g_value_get_boxed (&item);
gint index = 0;
const gchar *name = NULL;
guint size, type, num, i;
gint *devlist = NULL;
/* Parse */
if (!wp_spa_pod_get_object (pod, NULL,
"index", "i", &index,
"name", "s", &name,
"devices", "a", &size, &type, &num, &devlist,
NULL)) {
continue;
}
for (i = 0; i < num; i++) {
if (devlist[i] == lookup_device && g_strcmp0 (name, lookup_name) == 0)
return index;
}
}
return -1;
}
static gboolean
timeout_save_properties_cb (WpDefaultRoutes *self)
{
WpDefaultRoutesPrivate *priv = wp_default_routes_get_instance_private (self);
if (!wp_state_save (priv->state, "properties", priv->props))
wp_warning_object (self, "could not save properties");
return G_SOURCE_REMOVE;
}
static void
timeout_save_properties (WpDefaultRoutes *self, guint ms)
{
WpDefaultRoutesPrivate *priv = wp_default_routes_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->props);
/* Clear the current timeout callback */
if (priv->props_timeout)
g_source_destroy (priv->props_timeout);
g_clear_pointer (&priv->props_timeout, g_source_unref);
/* Add the timeout callback */
wp_core_timeout_add_closure (core, &priv->props_timeout, ms,
g_cclosure_new_object (G_CALLBACK (timeout_save_properties_cb),
G_OBJECT (self)));
}
static uint32_t
channel_from_name (const char *name)
{
for (gint i = 0; spa_type_audio_channel[i].name; i++) {
if (g_strcmp0 (name, spa_debug_type_short_name (spa_type_audio_channel[i].name)) == 0)
return spa_type_audio_channel[i].type;
}
return SPA_AUDIO_CHANNEL_UNKNOWN;
}
static const char *
channel_to_name(guint channel)
{
for (gint i = 0; spa_type_audio_channel[i].name; i++) {
if (spa_type_audio_channel[i].type == channel)
return spa_debug_type_short_name (spa_type_audio_channel[i].name);
}
return "UNK";
}
static GArray *
parse_channel_volumes(const gchar *chvol_str)
{
struct spa_json array;
struct spa_json item;
GArray *values = NULL;
float val;
spa_json_init (&array, chvol_str, strlen (chvol_str));
if (spa_json_enter_array (&array, &item) <= 0)
return NULL;
values = g_array_new (FALSE, FALSE, sizeof (float));
while (spa_json_get_float (&item, &val) > 0)
g_array_append_val (values, val);
return values;
}
static GPtrArray *
parse_channel_map (const gchar *chmap_str)
{
struct spa_json array;
struct spa_json item;
GPtrArray *values;
char val[MAX_JSON_STRING_LEN];
spa_json_init (&array, chmap_str, strlen (chmap_str));
if (spa_json_enter_array (&array, &item) <= 0)
return NULL;
values = g_ptr_array_new ();
g_ptr_array_set_free_func (values, g_free);
while (spa_json_get_string (&item, val, MAX_JSON_STRING_LEN) > 0)
g_ptr_array_add (values, g_strdup (val));
return values;
}
static void
apply_routes_properties (WpDefaultRoutes *self, WpPipewireObject *device,
const gchar *name, WpDirection direction, gint device_id)
{
WpDefaultRoutesPrivate *priv = wp_default_routes_get_instance_private (self);
g_autoptr (WpSpaPodBuilder) builder =
wp_spa_pod_builder_new_object ("Spa:Pod:Object:Param:Props", "Route");
g_autoptr (WpSpaPod) props = NULL;
const gchar *dev_name = NULL;
const gchar *prop_value = NULL;
gchar *prop_name = NULL;
gchar *routes_name = NULL;
gchar *dir_str = direction == WP_DIRECTION_INPUT ? "input" : "output";
gint index;
g_return_if_fail (priv->props);
index = find_device_route (device, name, device_id);
if (index < 0) {
wp_info_object (self, "route '%s' (%d) is not valid", name, index);
return;
}
/* Get the device name */
dev_name = wp_pipewire_object_get_property (device, PW_KEY_DEVICE_NAME);
g_return_if_fail (dev_name);
/*
* Property keys cannot contain spaces or brackets,
* make sure we use a valid string
*/
routes_name = g_strcanon (g_strdup (name), ALLOW_CHARS, '_');
prop_name = g_strdup_printf ("%s:%s:%s:%s", dev_name, dir_str, routes_name,
"volume");
prop_value = wp_properties_get (priv->props, prop_name);
g_free (prop_name);
if (prop_value) {
wp_spa_pod_builder_add_property (builder, "volume");
wp_spa_pod_builder_add_float (builder, strtof (prop_value, NULL));
}
prop_name = g_strdup_printf ("%s:%s:%s:%s", dev_name, dir_str, routes_name,
"mute");
prop_value = wp_properties_get (priv->props, prop_name);
g_free (prop_name);
if (prop_value) {
wp_spa_pod_builder_add_property (builder, "mute");
wp_spa_pod_builder_add_boolean (builder,
g_strcmp0 (prop_value, "true") == 0 ? TRUE : FALSE);
}
prop_name = g_strdup_printf ("%s:%s:%s:%s", dev_name, dir_str, routes_name,
"channelVolumes");
prop_value = wp_properties_get (priv->props, prop_name);
g_free (prop_name);
if (prop_value) {
g_autoptr (GArray) values = parse_channel_volumes (prop_value);
if (values) {
g_autoptr (WpSpaPodBuilder) chvol_builder = wp_spa_pod_builder_new_array ();
/* Build the channelVolumes array spa pod */
for (guint i = 0; i < values->len; i++) {
wp_spa_pod_builder_add_float (chvol_builder,
g_array_index (values, float, i));
}
/* Add the channelVolumes property */
wp_spa_pod_builder_add_property (builder, "channelVolumes");
g_autoptr (WpSpaPod) chvol = wp_spa_pod_builder_end (chvol_builder);
wp_spa_pod_builder_add_pod (builder, chvol);
}
}
prop_name = g_strdup_printf ("%s:%s:%s:%s", dev_name, dir_str, routes_name,
"channelMap");
prop_value = wp_properties_get (priv->props, prop_name);
g_free (prop_name);
if (prop_value) {
g_autoptr (GPtrArray) values = parse_channel_map (prop_value);
if (values) {
g_autoptr (WpSpaPodBuilder) chmap_builder = wp_spa_pod_builder_new_array ();
/* Build the channelMap array spa pod */
for (guint i = 0; i < values->len; i++) {
gchar *channel = g_ptr_array_index (values, i);
wp_spa_pod_builder_add_id (chmap_builder, channel_from_name (channel));
}
/* Add the channelMap property */
wp_spa_pod_builder_add_property (builder, "channelMap");
g_autoptr (WpSpaPod) chmap = wp_spa_pod_builder_end (chmap_builder);
wp_spa_pod_builder_add_pod (builder, chmap);
}
}
props = wp_spa_pod_builder_end (builder);
wp_pipewire_object_set_param (device, "Route", 0,
wp_spa_pod_new_object (
"Spa:Pod:Object:Param:Route", "Route",
"index", "i", index,
"device", "i", device_id,
"props", "O", props,
NULL));
wp_info_object (self, "properties set for route %d on " WP_OBJECT_FORMAT,
index, WP_OBJECT_ARGS (device));
}
static void
save_routes_properties (WpDefaultRoutes *self, WpPipewireObject *device,
const gchar *name, WpDirection direction, WpSpaPod *properties)
{
WpDefaultRoutesPrivate *priv = wp_default_routes_get_instance_private (self);
const gchar *dev_name = NULL;
gchar *routes_name = NULL;
gchar *prop_name = NULL;
gchar *prop_value = NULL;
g_autoptr (WpIterator) props = NULL;
g_auto (GValue) item = G_VALUE_INIT;
gchar *dir_str = direction == WP_DIRECTION_INPUT ? "input" : "output";
g_return_if_fail (properties);
g_return_if_fail (priv->props);
/* Get the device name */
dev_name = wp_pipewire_object_get_property (device, PW_KEY_DEVICE_NAME);
g_return_if_fail (dev_name);
routes_name = g_strcanon (g_strdup (name), ALLOW_CHARS, '_');
props = wp_spa_pod_new_iterator (properties);
for (; wp_iterator_next (props, &item); g_value_unset (&item)) {
WpSpaPod *pod = g_value_get_boxed (&item);
const char *p_key = NULL;
g_autoptr (WpSpaPod) p_val = NULL;
wp_spa_pod_get_property (pod, &p_key, &p_val);
prop_name = g_strdup_printf ("%s:%s:%s:%s", dev_name, dir_str,
routes_name, p_key);
/* volume */
if (g_strcmp0 (p_key, "volume") == 0) {
float vol = 0.0f;
wp_spa_pod_get_float (p_val, &vol);
prop_value = g_strdup_printf ("%f", vol);
}
/* mute */
else if (g_strcmp0 (p_key, "mute") == 0) {
gboolean b = FALSE;
wp_spa_pod_get_boolean (p_val, &b);
prop_value = g_strdup (b ? "true" : "false");
}
/* channelVolumes */
else if (g_strcmp0 (p_key, "channelVolumes") == 0) {
g_autoptr (WpIterator) it2 = wp_spa_pod_new_iterator (p_val);
g_auto (GValue) item2 = G_VALUE_INIT;
guint i = 0, n_vols = 0;
float vols[SPA_AUDIO_MAX_CHANNELS];
for (; wp_iterator_next (it2, &item2); g_value_unset (&item2)) {
float *vol = (float *) g_value_get_pointer (&item2);
vols[n_vols] = *vol;
n_vols++;
}
if (n_vols > 0) {
size_t size;
FILE *f;
f = open_memstream (&prop_value, &size);
fprintf (f, "[ ");
for (i = 0; i < n_vols; i++)
fprintf (f, "%s%f", i > 0 ? ", " : "", vols[i]);
fprintf (f, " ]");
fclose (f);
}
}
/* channelMap */
else if (g_strcmp0 (p_key, "channelMap") == 0) {
g_autoptr (WpIterator) it2 = wp_spa_pod_new_iterator (p_val);
g_auto (GValue) item2 = G_VALUE_INIT;
guint i = 0, n_vals = 0;
guint vals[SPA_AUDIO_MAX_CHANNELS];
for (; wp_iterator_next (it2, &item2); g_value_unset (&item2)) {
guint *val = (guint *) g_value_get_pointer (&item2);
vals[n_vals] = *val;
n_vals++;
}
if (n_vals > 0) {
size_t size;
FILE *f;
f = open_memstream (&prop_value, &size);
fprintf (f, "[ ");
for (i = 0; i < n_vals; i++)
fprintf (f, "%s\"%s\"", i > 0 ? ", " : "", channel_to_name (vals[i]));
fprintf (f, " ]");
fclose (f);
}
}
if (prop_value) {
wp_properties_set (priv->props, prop_name, prop_value);
g_free (prop_value);
}
if (prop_name)
g_free (prop_name);
prop_name = prop_value = NULL;
}
g_free (routes_name);
timeout_save_properties (self, SAVE_INTERVAL_MS);
}
static gchar *
serialize_routes (GHashTable *routes)
{
GHashTableIter iter;
const gchar *key;
gpointer value;
gchar *ptr;
FILE *f;
size_t size;
gboolean first = TRUE;
g_return_val_if_fail (routes, NULL);
g_return_val_if_fail (g_hash_table_size (routes), NULL);
f = open_memstream (&ptr, &size);
g_return_val_if_fail (f, NULL);
/* Routes are stored in a JSON array */
fprintf (f, "[ ");
g_hash_table_iter_init (&iter, routes);
while (g_hash_table_iter_next (&iter, (gpointer *) &key, &value)) {
if (!first)
fprintf (f, ", ");
/* Each route is stored as a JSON object with "name" and "device" attributes */
fprintf (f, "{ \"name\": \"%s\", \"device\": %d }", key, GPOINTER_TO_INT (value));
first = FALSE;
}
fprintf (f, " ]");
fclose (f);
return ptr;
}
static GHashTable *
parse_routes (const gchar *routes_str)
{
struct spa_json array;
struct spa_json item;
struct spa_json object;
GHashTable *routes;
spa_json_init (&array, routes_str, strlen (routes_str));
if (spa_json_enter_array (&array, &item) <= 0)
return NULL;
routes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
while (spa_json_enter_object (&item, &object) > 0) {
const char *prop;
char name[MAX_JSON_STRING_LEN];
int dev_id, res;
dev_id = -1;
name[0] = 0;
while ((res = spa_json_next (&object, &prop)) > 0) {
if (strncmp (prop, "\"name\"", res) == 0)
res = spa_json_get_string (&object, name, MAX_JSON_STRING_LEN);
else if (strncmp (prop, "\"device\"", res) == 0)
res = spa_json_get_int (&object, &dev_id);
if (res <= 0) {
g_critical ("unable to parse route");
g_hash_table_destroy (routes);
return NULL;
}
}
if (dev_id >= 0 && strlen(name) > 0)
g_hash_table_insert (routes, g_strdup(name), GINT_TO_POINTER (dev_id));
}
return routes;
}
static void
hash_table_copy_elements (gpointer key, gpointer value, gpointer data)
{
GHashTable *ht = data;
gchar *route = key;
g_hash_table_insert (ht, g_strdup(route), value);
}
static gboolean
timeout_save_routes_cb (WpDefaultRoutes *self)
{
WpDefaultRoutesPrivate *priv = wp_default_routes_get_instance_private (self);
WpPipewireObject *device;
GHashTableIter iter;
GHashTable *ht;
/*
* `default_routes` is the reference list, used when other modules require
* the default routes for a given device.
* `current_routes` is a working copy, which is copied into `default_routes`
* when saving to disk.
*/
g_hash_table_remove_all (priv->default_routes);
g_hash_table_iter_init (&iter, priv->current_routes);
while (g_hash_table_iter_next (&iter, (gpointer*) &device, (gpointer*) &ht)) {
const gchar *dev_name;
gchar *routes;
GHashTable *copy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
NULL);
dev_name = wp_pipewire_object_get_property (device, PW_KEY_DEVICE_NAME);
if (!dev_name)
continue;
routes = serialize_routes (ht);
if (routes) {
wp_properties_set (priv->routes, dev_name, routes);
g_free (routes);
}
g_hash_table_foreach (ht, hash_table_copy_elements, copy);
g_hash_table_insert (priv->default_routes, device, copy);
}
if (!wp_state_save (priv->state, "routes", priv->routes))
wp_warning_object (self, "could not save routes");
return G_SOURCE_REMOVE;
}
static void
timeout_save_routes (WpDefaultRoutes *self, guint ms)
{
WpDefaultRoutesPrivate *priv = wp_default_routes_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->routes);
/* Clear the current timeout callback */
if (priv->routes_timeout)
g_source_destroy (priv->routes_timeout);
g_clear_pointer (&priv->routes_timeout, g_source_unref);
/* Add the timeout callback */
wp_core_timeout_add_closure (core, &priv->routes_timeout, ms,
g_cclosure_new_object (G_CALLBACK (timeout_save_routes_cb),
G_OBJECT (self)));
}
static void
wp_default_routes_get_routes (WpDefaultRoutes *self, WpPipewireObject *device,
GHashTable **routes)
{
WpDefaultRoutesPrivate *priv = wp_default_routes_get_instance_private (self);
g_return_if_fail (device);
g_return_if_fail (routes);
g_return_if_fail (priv->default_routes);
*routes = g_hash_table_lookup (priv->default_routes, device);
}
static void
update_routes (WpDefaultRoutes *self, WpPipewireObject *device,
GHashTable *new_routes)
{
WpDefaultRoutesPrivate *priv = wp_default_routes_get_instance_private (self);
GHashTable *curr_routes;
GHashTableIter iter;
g_return_if_fail (new_routes);
g_return_if_fail (priv->routes);
curr_routes = g_hash_table_lookup (priv->current_routes, device);
/* Check if the new routes are the same as the current ones */
if (curr_routes) {
if (g_hash_table_size (curr_routes) == g_hash_table_size (new_routes)) {
gboolean identical = TRUE;
const gchar *key;
gpointer value;
gint index;
g_hash_table_iter_init (&iter, new_routes);
while (g_hash_table_iter_next (&iter, (gpointer*) &key, &value)) {
/* Make sure the route is valid */
index = find_device_route (device, key, GPOINTER_TO_INT (value));
if (index < 0) {
wp_info_object (self, "route '%s' (%d) is not valid", key, index);
return;
}
if (!g_hash_table_contains (curr_routes, key)) {
identical = FALSE;
break;
}
}
if (identical)
return;
}
}
/* Otherwise update the route and add timeout save callback */
g_hash_table_insert (priv->current_routes, device, new_routes);
timeout_save_routes (self, SAVE_INTERVAL_MS);
}
static void
on_device_routes_notified (WpPipewireObject *device, GAsyncResult *res,
WpDefaultRoutes *self)
{
WpDefaultRoutesPrivate *priv = wp_default_routes_get_instance_private (self);
g_autoptr (WpIterator) routes = NULL;
g_autoptr (GError) error = NULL;
g_auto (GValue) item = G_VALUE_INIT;
const gchar *name = NULL;
gint device_id, direction = 0;
GHashTable *new_routes;
GHashTable *ht;
/* Finish */
routes = wp_pipewire_object_enum_params_finish (device, res, &error);
if (error) {
wp_warning_object (self, "failed to get current route on device: %s",
error->message);
return;
}
new_routes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
if (!new_routes) {
wp_warning_object (self, "failed to allocate new hash table");
return;
}
ht = g_hash_table_lookup (priv->current_routes, device);
for (; wp_iterator_next (routes, &item); g_value_unset (&item)) {
/* Parse the route */
WpSpaPod *pod = g_value_get_boxed (&item);
WpSpaPod *props;
if (!wp_spa_pod_get_object (pod, NULL,
"direction", "I", &direction,
"device", "i", &device_id,
"name", "s", &name,
"props", "P", &props,
NULL)) {
wp_warning_object (self, "failed to parse current route");
continue;
}
if (ht) {
if (g_hash_table_contains (ht, name))
save_routes_properties (self, device, name, direction, props);
else if (g_hash_table_size (ht) > 0)
/*
* Apply route properties only once we have filled the `current_routes`
* table for this device. This prevents race conditions when a device
* appears.
*/
apply_routes_properties (self, device, name, direction, device_id);
}
g_hash_table_insert (new_routes, g_strdup (name),
GINT_TO_POINTER (device_id));
}
/* Update the routes list */
update_routes (self, device, new_routes);
}
static void
on_device_param_info_notified (WpPipewireObject * device, GParamSpec * param,
WpDefaultRoutes *self)
{
/* Check the route every time the params have changed */
wp_pipewire_object_enum_params (device, "Route", NULL, NULL,
(GAsyncReadyCallback) on_device_routes_notified, self);
}
static void
on_device_added (WpObjectManager *om, WpPipewireObject *device, gpointer d)
{
WpDefaultRoutes *self = WP_DEFAULT_ROUTES (d);
WpDefaultRoutesPrivate *priv = wp_default_routes_get_instance_private (self);
g_autoptr (WpIterator) it_routes = NULL;
const gchar *dev_name = NULL;
const gchar *routes = NULL;
GHashTable *ht;
wp_debug_object (self, "device " WP_OBJECT_FORMAT " added",
WP_OBJECT_ARGS (device));
/* Enum available routes */
it_routes = wp_pipewire_object_enum_params_sync (device, "EnumRoute", NULL);
if (!it_routes)
return;
/* Keep a reference of the routes in the device object */
g_object_set_qdata_full (G_OBJECT (device), routes_quark (),
g_steal_pointer (&it_routes), (GDestroyNotify) wp_iterator_unref);
/* Watch for param info changes */
g_signal_connect_object (device, "notify::param-info",
G_CALLBACK (on_device_param_info_notified), self, 0);
/* Load default routes for device */
dev_name = wp_pipewire_object_get_property (device, PW_KEY_DEVICE_NAME);
g_return_if_fail (dev_name);
routes = wp_properties_get (priv->routes, dev_name);
if (!routes)
return;
ht = parse_routes (routes);
g_return_if_fail (ht);
g_hash_table_insert (priv->default_routes, device, ht);
}
static void
on_device_removed (WpObjectManager *om, WpPipewireObject *device, gpointer d)
{
WpDefaultRoutes *self = WP_DEFAULT_ROUTES (d);
WpDefaultRoutesPrivate *priv = wp_default_routes_get_instance_private (self);
wp_debug_object (self, "device " WP_OBJECT_FORMAT " removed",
WP_OBJECT_ARGS (device));
g_hash_table_remove (priv->current_routes, device);
g_hash_table_remove (priv->default_routes, device);
}
static void
wp_default_routes_enable (WpPlugin * plugin, WpTransition * transition)
{
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
WpDefaultRoutes *self = WP_DEFAULT_ROUTES (plugin);
WpDefaultRoutesPrivate *priv = wp_default_routes_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);
g_signal_connect_object (priv->devices_om, "object-removed",
G_CALLBACK (on_device_removed), 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_routes_disable (WpPlugin * plugin)
{
WpDefaultRoutes *self = WP_DEFAULT_ROUTES (plugin);
WpDefaultRoutesPrivate *priv = wp_default_routes_get_instance_private (self);
g_clear_object (&priv->devices_om);
}
static void
wp_default_routes_finalize (GObject * object)
{
WpDefaultRoutes *self = WP_DEFAULT_ROUTES (object);
WpDefaultRoutesPrivate *priv = wp_default_routes_get_instance_private (self);
/* Clear the current timeout callback */
if (priv->props_timeout)
g_source_destroy (priv->props_timeout);
g_clear_pointer (&priv->props_timeout, g_source_unref);
if (priv->routes_timeout)
g_source_destroy (priv->routes_timeout);
g_clear_pointer (&priv->routes_timeout, g_source_unref);
g_hash_table_destroy (priv->current_routes);
g_hash_table_destroy (priv->default_routes);
g_clear_pointer (&priv->routes, wp_properties_unref);
g_clear_object (&priv->state);
}
static void
wp_default_routes_init (WpDefaultRoutes * self)
{
WpDefaultRoutesPrivate *priv = wp_default_routes_get_instance_private (self);
priv->state = wp_state_new (STATE_NAME);
wp_debug_object (self, "module default route loaded");
priv->current_routes = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, (GDestroyNotify) g_hash_table_destroy);
priv->default_routes = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, (GDestroyNotify) g_hash_table_destroy);
/* Load the saved routes */
priv->routes = wp_state_load (priv->state, "routes");
if (!priv->routes) {
wp_warning_object (self, "could not load routes");
}
/* Load the saved properties */
priv->props = wp_state_load (priv->state, "properties");
if (!priv->props) {
wp_warning_object (self, "could not load properties");
}
}
static void
wp_default_routes_class_init (WpDefaultRoutesClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpPluginClass *plugin_class = (WpPluginClass *) klass;
object_class->finalize = wp_default_routes_finalize;
plugin_class->enable = wp_default_routes_enable;
plugin_class->disable = wp_default_routes_disable;
klass->get_routes = wp_default_routes_get_routes;
/* Signals */
signals[SIGNAL_GET_ROUTES] = g_signal_new ("get-routes",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (WpDefaultRoutesClass, get_routes), 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_routes_get_type (),
"name", STATE_NAME,
"core", core,
NULL));
return TRUE;
}