wireplumber/modules/module-default-nodes.c
Ashok Sidipotu 6e31b47c28 event-dispatcher: Add an enumeration of default event hook priorities
This scheme provides for an orderly execution of hooks as the priorities
are controlled from one single place. Enumeration is defined in such a
way that new items can be added easily.

All the event hooks are changed to get the priorities from this
enumeration.
2023-04-17 07:47:09 -04:00

772 lines
26 KiB
C

/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <wp/wp.h>
#include <errno.h>
#include <pipewire/pipewire.h>
#include <pipewire/keys.h>
#include "module-default-nodes/common.h"
#define NAME "default-nodes"
#define DEFAULT_SAVE_INTERVAL_MS 1000
#define DEFAULT_USE_PERSISTENT_STORAGE TRUE
#define DEFAULT_AUTO_ECHO_CANCEL TRUE
#define DEFAULT_ECHO_CANCEL_SINK_NAME "echo-cancel-sink"
#define DEFAULT_ECHO_CANCEL_SOURCE_NAME "echo-cancel-source"
#define N_PREV_CONFIGS 16
enum {
PROP_0,
PROP_SAVE_INTERVAL_MS,
PROP_USE_PERSISTENT_STORAGE,
PROP_AUTO_ECHO_CANCEL,
PROP_ECHO_CANCEL_SINK_NAME,
PROP_ECHO_CANCEL_SOURCE_NAME,
};
/*
* Module maintains the default devices to be used for a given media class. The
* module looks for changes in user preference and the changes in devices(when
* new devices like headsets, BT devices, HDMI etc are plugged in). User
* preference can be expressed via pavuctrl, gnome settings or metadata etc.
* These apps typically update the
* default.configured.*(default-configured-nodes) keys.
*/
/*
* settings file: device-settings.conf
*/
typedef struct _WpDefaultNode WpDefaultNode;
struct _WpDefaultNode
{
gchar *value;
gchar *config_value;
gchar *prev_config_value[N_PREV_CONFIGS];
};
struct _WpDefaultNodes
{
WpPlugin parent;
WpState *state;
WpDefaultNode defaults[N_DEFAULT_NODES];
WpObjectManager *metadata_om;
WpObjectManager *rescan_om;
GSource *timeout_source;
/* properties */
guint save_interval_ms;
gboolean use_persistent_storage;
gboolean auto_echo_cancel;
gchar *echo_cancel_names[2];
};
G_DECLARE_FINAL_TYPE (WpDefaultNodes, wp_default_nodes,
WP, DEFAULT_NODES, WpPlugin)
G_DEFINE_TYPE (WpDefaultNodes, wp_default_nodes, WP_TYPE_PLUGIN)
static void
wp_default_nodes_init (WpDefaultNodes * self)
{
}
static void
update_prev_config_values (WpDefaultNode *def)
{
gint pos = N_PREV_CONFIGS - 1;
if (!def->config_value)
return;
/* Find if the current configured value is already in the stack */
for (gint i = 0; i < N_PREV_CONFIGS; ++i) {
if (!g_strcmp0(def->config_value, def->prev_config_value[i])) {
pos = i;
break;
}
}
if (pos == 0)
return;
/* Insert on top position */
g_clear_pointer (&def->prev_config_value[pos], g_free);
for (gint i = pos; i > 0; --i)
def->prev_config_value[i] = def->prev_config_value[i-1];
def->prev_config_value[0] = g_strdup(def->config_value);
}
static void
load_state (WpDefaultNodes * self)
{
g_autoptr (WpProperties) props = wp_state_load (self->state);
for (gint i = 0; i < N_DEFAULT_NODES; i++) {
const gchar *value = wp_properties_get (props, DEFAULT_CONFIG_KEY[i]);
self->defaults[i].config_value = g_strdup (value);
for (gint j = 0; j < N_PREV_CONFIGS; ++j) {
g_autofree gchar *key = g_strdup_printf("%s.%d", DEFAULT_CONFIG_KEY[i], j);
value = wp_properties_get (props, key);
self->defaults[i].prev_config_value[j] = g_strdup(value);
}
}
}
static gboolean
timeout_save_state_callback (WpDefaultNodes *self)
{
g_autoptr (WpProperties) props = wp_properties_new_empty ();
g_autoptr (GError) error = NULL;
for (gint i = 0; i < N_DEFAULT_NODES; i++) {
if (self->defaults[i].config_value)
wp_properties_set (props, DEFAULT_CONFIG_KEY[i],
self->defaults[i].config_value);
for (gint j = 0; j < N_PREV_CONFIGS; ++j) {
g_autofree gchar *key = g_strdup_printf("%s.%d", DEFAULT_CONFIG_KEY[i], j);
wp_properties_set (props, key, self->defaults[i].prev_config_value[j]);
}
}
if (!wp_state_save (self->state, props, &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 (WpEvent *event, gpointer d)
{
WpDefaultNodes * self = WP_DEFAULT_NODES (d);
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
node_has_available_routes (WpDefaultNodes * self, WpNode *node)
{
const gchar *dev_id_str = wp_pipewire_object_get_property (
WP_PIPEWIRE_OBJECT (node), PW_KEY_DEVICE_ID);
const gchar *cpd_str = wp_pipewire_object_get_property (
WP_PIPEWIRE_OBJECT (node), "card.profile.device");
gint dev_id = dev_id_str ? atoi (dev_id_str) : -1;
gint cpd = cpd_str ? atoi (cpd_str) : -1;
g_autoptr (WpDevice) device = NULL;
gint found = 0;
if (dev_id == -1 || cpd == -1)
return TRUE;
/* Get the device */
device = wp_object_manager_lookup (self->rescan_om, WP_TYPE_DEVICE,
WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=i", dev_id, NULL);
if (!device)
return TRUE;
/* Check if the current device route supports the node card device profile */
{
g_autoptr (WpIterator) routes = NULL;
g_auto (GValue) val = G_VALUE_INIT;
routes = wp_pipewire_object_enum_params_sync (WP_PIPEWIRE_OBJECT (device),
"Route", NULL);
for (; wp_iterator_next (routes, &val); g_value_unset (&val)) {
WpSpaPod *route = g_value_get_boxed (&val);
gint route_device = -1;
guint32 route_avail = SPA_PARAM_AVAILABILITY_unknown;
if (!wp_spa_pod_get_object (route, NULL,
"device", "i", &route_device,
"available", "?I", &route_avail,
NULL))
continue;
if (route_device != cpd)
continue;
if (route_avail == SPA_PARAM_AVAILABILITY_no)
return FALSE;
return TRUE;
}
}
/* Check if available routes support the node card device profile */
{
g_autoptr (WpIterator) routes = NULL;
g_auto (GValue) val = G_VALUE_INIT;
routes = wp_pipewire_object_enum_params_sync (WP_PIPEWIRE_OBJECT (device),
"EnumRoute", NULL);
for (; wp_iterator_next (routes, &val); g_value_unset (&val)) {
WpSpaPod *route = g_value_get_boxed (&val);
guint32 route_avail = SPA_PARAM_AVAILABILITY_unknown;
g_autoptr (WpSpaPod) route_devices = NULL;
if (!wp_spa_pod_get_object (route, NULL,
"available", "?I", &route_avail,
"devices", "?P", &route_devices,
NULL))
continue;
{
g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (route_devices);
g_auto (GValue) v = G_VALUE_INIT;
for (; wp_iterator_next (it, &v); g_value_unset (&v)) {
gint32 *d = (gint32 *)g_value_get_pointer (&v);
if (d && *d == cpd) {
found++;
if (route_avail != SPA_PARAM_AVAILABILITY_no)
return TRUE;
}
}
}
}
}
/* The node is part of a profile without routes so we assume it
* is available. This can happen for Pro Audio profiles */
if (found == 0)
return TRUE;
return FALSE;
}
static gboolean
is_echo_cancel_node (WpDefaultNodes * self, WpNode *node, WpDirection direction)
{
const gchar *name = wp_pipewire_object_get_property (
WP_PIPEWIRE_OBJECT (node), PW_KEY_NODE_NAME);
const gchar *virtual_str = wp_pipewire_object_get_property (
WP_PIPEWIRE_OBJECT (node), PW_KEY_NODE_VIRTUAL);
gboolean virtual = virtual_str && pw_properties_parse_bool (virtual_str);
if (!name || !virtual)
return FALSE;
return g_strcmp0 (name, self->echo_cancel_names[direction]) == 0;
}
static WpNode *
find_best_media_class_node (WpDefaultNodes * self, const gchar *media_class,
const WpDefaultNode *def, WpDirection direction, gint *priority)
{
g_autoptr (WpIterator) it = NULL;
g_auto (GValue) val = G_VALUE_INIT;
gint highest_prio = 0;
WpNode *res = NULL;
g_return_val_if_fail (media_class, NULL);
it = wp_object_manager_new_filtered_iterator (self->rescan_om, WP_TYPE_NODE,
WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "=s", media_class,
NULL);
for (; wp_iterator_next (it, &val); g_value_unset (&val)) {
WpNode *node = g_value_get_object (&val);
g_autoptr (WpPort) port = wp_object_manager_lookup (self->rescan_om,
WP_TYPE_PORT, WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_NODE_ID,
"=u", wp_proxy_get_bound_id (WP_PROXY (node)),
WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_PORT_DIRECTION,
"=s", direction == WP_DIRECTION_INPUT ? "in" : "out",
NULL);
if (port) {
const gchar *name = wp_pipewire_object_get_property (
WP_PIPEWIRE_OBJECT (node), PW_KEY_NODE_NAME);
const gchar *prio_str = wp_pipewire_object_get_property (
WP_PIPEWIRE_OBJECT (node), PW_KEY_PRIORITY_SESSION);
gint prio = prio_str ? atoi (prio_str) : -1;
if (!node_has_available_routes (self, node))
continue;
if (self->auto_echo_cancel && is_echo_cancel_node (self, node, direction))
prio += 10000;
if (name && def->config_value && g_strcmp0 (name, def->config_value) == 0) {
prio += 20000 * (N_PREV_CONFIGS + 1);
} else if (name) {
for (gint i = 0; i < N_PREV_CONFIGS; ++i) {
if (!def->prev_config_value[i])
continue;
/* Match by name */
if (g_strcmp0 (name, def->prev_config_value[i]) == 0) {
prio += (N_PREV_CONFIGS - i) * 20000;
break;
}
}
}
if (prio > highest_prio || res == NULL) {
highest_prio = prio;
res = node;
}
}
}
if (priority)
*priority = highest_prio;
return res;
}
static WpNode *
find_best_media_classes_node (WpDefaultNodes * self,
const gchar **media_classes, const WpDefaultNode *def, WpDirection direction)
{
gint highest_prio = -1;
WpNode *res = NULL;
for (guint i = 0; media_classes[i]; i++) {
gint prio = -1;
WpNode *node = find_best_media_class_node (self, media_classes[i],
def, direction, &prio);
if (node && (!res || prio > highest_prio)) {
highest_prio = prio;
res = node;
}
}
return res;
}
static WpNode *
find_best_node (WpDefaultNodes * self, gint node_t)
{
const WpDefaultNode *def = &self->defaults[node_t];
switch (node_t) {
case AUDIO_SINK: {
const gchar *media_classes[] = {
"Audio/Sink",
"Audio/Duplex",
NULL};
return find_best_media_classes_node (self, media_classes, def,
WP_DIRECTION_INPUT);
}
case AUDIO_SOURCE: {
const gchar *media_classes[] = {
"Audio/Source",
"Audio/Source/Virtual",
"Audio/Duplex",
"Audio/Sink",
NULL};
return find_best_media_classes_node (self, media_classes, def,
WP_DIRECTION_OUTPUT);
}
case VIDEO_SOURCE: {
const gchar *media_classes[] = {
"Video/Source",
"Video/Source/Virtual",
NULL};
return find_best_media_classes_node (self, media_classes, def,
WP_DIRECTION_OUTPUT);
}
default:
break;
}
return NULL;
}
static void
reevaluate_default_node (WpDefaultNodes * self, WpMetadata *m, gint node_t)
{
WpNode *node = NULL;
const gchar *node_name = NULL;
node = find_best_node (self, node_t);
if (node)
node_name = wp_pipewire_object_get_property (WP_PIPEWIRE_OBJECT (node),
PW_KEY_NODE_NAME);
/* store it in the metadata if it was changed */
if (node && node_name &&
g_strcmp0 (node_name, self->defaults[node_t].value) != 0)
{
g_autoptr (WpSpaJson) json = NULL;
g_free (self->defaults[node_t].value);
self->defaults[node_t].value = g_strdup (node_name);
wp_info_object (self, "set default node for %s: %s",
NODE_TYPE_STR[node_t], node_name);
json = wp_spa_json_new_object ("name", "s", node_name, NULL);
wp_metadata_set (m, 0, DEFAULT_KEY[node_t], "Spa:String:JSON",
wp_spa_json_get_data (json));
} else if (!node && self->defaults[node_t].value) {
g_clear_pointer (&self->defaults[node_t].value, g_free);
wp_info_object (self, "unset default node for %s", NODE_TYPE_STR[node_t]);
wp_metadata_set (m, 0, DEFAULT_KEY[node_t], NULL, NULL);
}
}
static void
sync_rescan (WpCore * core, GAsyncResult * res, WpDefaultNodes * self)
{
g_autoptr (WpMetadata) metadata = NULL;
g_autoptr (GError) error = NULL;
if (!wp_core_sync_finish (core, res, &error)) {
wp_warning_object (self, "core sync error: %s", error->message);
return;
}
/* Get the metadata */
metadata = wp_object_manager_lookup (self->metadata_om, WP_TYPE_METADATA,
NULL);
if (!metadata)
return;
wp_trace_object (self, "re-evaluating defaults");
reevaluate_default_node (self, metadata, AUDIO_SINK);
reevaluate_default_node (self, metadata, AUDIO_SOURCE);
reevaluate_default_node (self, metadata, VIDEO_SOURCE);
}
static void
schedule_rescan (WpEvent *event, gpointer d)
{
WpDefaultNodes * self = WP_DEFAULT_NODES (d);
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
g_return_if_fail (core);
wp_debug_object (self, "scheduling default nodes rescan");
// Event-Stack TBD: do we need to retain this behavior? or push this as a
// event & hook pair on to event stack
wp_core_sync_closure (core, NULL, g_cclosure_new_object (
G_CALLBACK (sync_rescan), G_OBJECT (self)));
}
static void
on_metadata_changed (WpEvent *event, gpointer d)
{
WpDefaultNodes * self = WP_DEFAULT_NODES (d);
gint node_t = -1;
g_autoptr (GObject) subject = wp_event_get_subject (event);
WpMetadata *m = WP_METADATA (subject);
g_autoptr (WpProperties) p = wp_event_get_properties (event);
guint32 subject_id = atoi (wp_properties_get (p, "event.subject.id"));
const gchar *key = wp_properties_get (p, "event.subject.key");
const gchar *type = wp_properties_get (p, "event.subject.spa_type");
const gchar *value = wp_properties_get (p, "event.subject.value");
if (subject_id == 0) {
for (gint i = 0; i < N_DEFAULT_NODES; i++) {
if (!g_strcmp0 (key, DEFAULT_CONFIG_KEY[i])) {
node_t = i;
break;
}
}
}
if (node_t != -1) {
g_clear_pointer (&self->defaults[node_t].config_value, g_free);
if (value && !g_strcmp0 (type, "Spa:String:JSON")) {
g_autoptr (WpSpaJson) json = wp_spa_json_new_from_string (value);
g_autofree gchar *name = NULL;
if (wp_spa_json_object_get (json, "name", "s", &name, NULL))
self->defaults[node_t].config_value = g_strdup (name);
}
update_prev_config_values (&self->defaults[node_t]);
wp_debug_object (m, "changed '%s' -> '%s'", key,
self->defaults[node_t].config_value);
/* schedule rescan */
schedule_rescan (self);
/* Save state after specific interval */
timer_start (self);
}
}
static void
on_metadata_added (WpEvent *event, gpointer d)
{
WpDefaultNodes * self = WP_DEFAULT_NODES (d);
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
g_return_if_fail (core);
g_autoptr (GObject) subject = wp_event_get_subject (event);
WpMetadata *metadata = WP_METADATA (subject);
for (gint i = 0; i < N_DEFAULT_NODES; i++) {
if (self->defaults[i].config_value) {
g_autoptr (WpSpaJson) json = wp_spa_json_new_object (
"name", "s", self->defaults[i].config_value, NULL);
wp_metadata_set (metadata, 0, DEFAULT_CONFIG_KEY[i], "Spa:String:JSON",
wp_spa_json_get_data (json));
}
}
}
static void
wp_default_nodes_enable (WpPlugin * plugin, WpTransition * transition)
{
WpDefaultNodes * self = WP_DEFAULT_NODES (plugin);
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
g_return_if_fail (core);
g_autoptr (WpEventDispatcher) dispatcher =
wp_event_dispatcher_get_instance (core);
g_autoptr (WpEventHook) hook = NULL;
g_return_if_fail (dispatcher);
/* default metadata added */
hook = wp_simple_event_hook_new ("default-nodes",
WP_EVENT_HOOK_DEFAULT_PRIORITY_DEFAULT_METADATA_ADDED_DEFAULT_NODES,
WP_EVENT_HOOK_EXEC_TYPE_ON_EVENT,
g_cclosure_new ((GCallback) on_metadata_added, self, NULL));
wp_interest_event_hook_add_interest (WP_INTEREST_EVENT_HOOK (hook),
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.type", "=s", "object-added",
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.subject.type", "=s", "metadata",
WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "metadata.name", "=s", "default",
NULL);
wp_event_dispatcher_register_hook (dispatcher, hook);
g_clear_object(&hook);
/* default metadata changed */
hook = wp_simple_event_hook_new ("default-nodes",
WP_EVENT_HOOK_DEFAULT_PRIORITY_DEFAULT_METADATA_CHANGED_DEFAULT_NODES,
WP_EVENT_HOOK_EXEC_TYPE_ON_EVENT,
g_cclosure_new ((GCallback) on_metadata_changed, self, NULL));
wp_interest_event_hook_add_interest (WP_INTEREST_EVENT_HOOK (hook),
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.type", "=s", "object-changed",
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.subject.type", "=s", "metadata",
WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "metadata.name", "=s", "default",
NULL);
wp_event_dispatcher_register_hook (dispatcher, hook);
g_clear_object(&hook);
/* register rescan hook as an after event */
hook = wp_simple_event_hook_new("rescan-default-nodes",
WP_EVENT_HOOK_DEFAULT_PRIORITY_RESCAN_DEFAULT_NODES,
WP_EVENT_HOOK_EXEC_TYPE_AFTER_EVENTS,
g_cclosure_new ((GCallback) schedule_rescan, self, NULL));
/* default metadata changed */
wp_interest_event_hook_add_interest (WP_INTEREST_EVENT_HOOK (hook),
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.type", "=s", "object-changed",
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.subject.type", "=s", "metadata",
WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "metadata.name", "=s", "default",
NULL);
/* device changed */
wp_interest_event_hook_add_interest (WP_INTEREST_EVENT_HOOK (hook),
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.type", "=s", "object-changed",
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.subject.type", "=s", "device",
NULL);
/* node changed */
wp_interest_event_hook_add_interest (WP_INTEREST_EVENT_HOOK (hook),
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.type", "=s", "object-changed",
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.subject.type", "=s", "node",
NULL);
/* device parms changed */
wp_interest_event_hook_add_interest (WP_INTEREST_EVENT_HOOK (hook),
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.type", "=s", "params-changed",
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.subject.type", "=s", "device",
NULL);
/* node parms changed */
wp_interest_event_hook_add_interest (WP_INTEREST_EVENT_HOOK (hook),
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.type", "=s", "params-changed",
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.subject.type", "=s", "node",
NULL);
wp_event_dispatcher_register_hook (dispatcher, hook);
g_clear_object (&hook);
/* register state save hook as an after event */
hook = wp_simple_event_hook_new ("default-nodes-state-saver",
WP_EVENT_HOOK_DEFAULT_PRIORITY_AFTER_EVENTS_DEFAULT_NODES_STATE_SAVE,
WP_EVENT_HOOK_EXEC_TYPE_AFTER_EVENTS,
g_cclosure_new ((GCallback) timer_start, self, NULL));
wp_interest_event_hook_add_interest (WP_INTEREST_EVENT_HOOK (hook),
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.type", "=s", "object-changed",
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.subject.type", "=s", "metadata",
WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "metadata.name", "=s", "default",
NULL);
wp_event_dispatcher_register_hook (dispatcher, hook);
g_clear_object (&hook);
/* Create the rescan object manager */
self->rescan_om = wp_object_manager_new ();
wp_object_manager_add_interest (self->rescan_om, WP_TYPE_DEVICE, NULL);
wp_object_manager_add_interest (self->rescan_om, WP_TYPE_NODE, NULL);
wp_object_manager_add_interest (self->rescan_om, WP_TYPE_PORT, NULL);
wp_object_manager_request_object_features (self->rescan_om, WP_TYPE_DEVICE,
WP_OBJECT_FEATURES_ALL);
wp_object_manager_request_object_features (self->rescan_om, WP_TYPE_NODE,
WP_OBJECT_FEATURES_ALL);
wp_object_manager_request_object_features (self->rescan_om, WP_TYPE_PORT,
WP_OBJECT_FEATURES_ALL);
wp_core_install_object_manager (core, self->rescan_om);
if (self->use_persistent_storage) {
self->state = wp_state_new (NAME);
load_state (self);
}
/* Create the metadata object manager */
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", "default",
NULL);
wp_object_manager_request_object_features (self->metadata_om,
WP_TYPE_METADATA, WP_OBJECT_FEATURES_ALL);
wp_core_install_object_manager (core, self->metadata_om);
wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}
static void
wp_default_nodes_disable (WpPlugin * plugin)
{
WpDefaultNodes * self = WP_DEFAULT_NODES (plugin);
/* Clear the current timeout callback */
if (self->timeout_source)
g_source_destroy (self->timeout_source);
g_clear_pointer (&self->timeout_source, g_source_unref);
for (guint i = 0; i < N_DEFAULT_NODES; i++) {
g_clear_pointer (&self->defaults[i].value, g_free);
g_clear_pointer (&self->defaults[i].config_value, g_free);
for (guint j = 0; j < N_PREV_CONFIGS; j++)
g_clear_pointer (&self->defaults[i].prev_config_value[j], g_free);
}
g_clear_object (&self->metadata_om);
g_clear_object (&self->rescan_om);
g_clear_object (&self->state);
}
static void
wp_default_nodes_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpDefaultNodes * self = WP_DEFAULT_NODES (object);
switch (property_id) {
case PROP_SAVE_INTERVAL_MS:
self->save_interval_ms = g_value_get_uint (value);
break;
case PROP_USE_PERSISTENT_STORAGE:
self->use_persistent_storage = g_value_get_boolean (value);
break;
case PROP_AUTO_ECHO_CANCEL:
self->auto_echo_cancel = g_value_get_boolean (value);
break;
case PROP_ECHO_CANCEL_SINK_NAME:
g_clear_pointer (&self->echo_cancel_names[WP_DIRECTION_INPUT], g_free);
self->echo_cancel_names[WP_DIRECTION_INPUT] = g_value_dup_string (value);
break;
case PROP_ECHO_CANCEL_SOURCE_NAME:
g_clear_pointer (&self->echo_cancel_names[WP_DIRECTION_OUTPUT], g_free);
self->echo_cancel_names[WP_DIRECTION_OUTPUT] = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_default_nodes_finalize (GObject * object)
{
WpDefaultNodes * self = WP_DEFAULT_NODES (object);
g_clear_pointer (&self->echo_cancel_names[WP_DIRECTION_INPUT], g_free);
g_clear_pointer (&self->echo_cancel_names[WP_DIRECTION_OUTPUT], g_free);
G_OBJECT_CLASS (wp_default_nodes_parent_class)->finalize (object);
}
static void
wp_default_nodes_class_init (WpDefaultNodesClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpPluginClass *plugin_class = (WpPluginClass *) klass;
object_class->finalize = wp_default_nodes_finalize;
object_class->set_property = wp_default_nodes_set_property;
plugin_class->enable = wp_default_nodes_enable;
plugin_class->disable = wp_default_nodes_disable;
g_object_class_install_property (object_class, PROP_SAVE_INTERVAL_MS,
g_param_spec_uint ("save-interval-ms", "save-interval-ms",
"save-interval-ms", 1, G_MAXUINT32, DEFAULT_SAVE_INTERVAL_MS,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_USE_PERSISTENT_STORAGE,
g_param_spec_boolean ("use-persistent-storage", "use-persistent-storage",
"use-persistent-storage", DEFAULT_USE_PERSISTENT_STORAGE,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_AUTO_ECHO_CANCEL,
g_param_spec_boolean ("auto-echo-cancel", "auto-echo-cancel",
"auto-echo-cancel", DEFAULT_AUTO_ECHO_CANCEL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_ECHO_CANCEL_SINK_NAME,
g_param_spec_string ("echo-cancel-sink-name", "echo-cancel-sink-name",
"echo-cancel-sink-name", DEFAULT_ECHO_CANCEL_SINK_NAME,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_ECHO_CANCEL_SOURCE_NAME,
g_param_spec_string ("echo-cancel-source-name", "echo-cancel-source-name",
"echo-cancel-source-name", DEFAULT_ECHO_CANCEL_SOURCE_NAME,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
gint64 save_interval_ms = DEFAULT_SAVE_INTERVAL_MS;
gboolean use_persistent_storage = DEFAULT_USE_PERSISTENT_STORAGE;
gboolean auto_echo_cancel = DEFAULT_AUTO_ECHO_CANCEL;
const gchar *echo_cancel_sink_name = DEFAULT_ECHO_CANCEL_SINK_NAME;
const gchar *echo_cancel_source_name = DEFAULT_ECHO_CANCEL_SOURCE_NAME;
g_autoptr (WpSettings) settings = wp_settings_get_instance(core, NULL);
wp_settings_get_int (settings, "device.save-interval-ms",
&save_interval_ms);
wp_settings_get_boolean (settings, "device.use-persistent-storage",
&use_persistent_storage);
wp_settings_get_boolean (settings, "device.auto-echo-cancel",
&auto_echo_cancel);
wp_settings_get_string (settings, "device.echo-cancel-sink-name",
&echo_cancel_sink_name);
wp_settings_get_string (settings, "device.echo-cancel-source-name",
&echo_cancel_source_name);
wp_plugin_register (g_object_new (wp_default_nodes_get_type (),
"name", NAME,
"core", core,
"save-interval-ms", save_interval_ms,
"use-persistent-storage", use_persistent_storage,
"auto-echo-cancel", auto_echo_cancel,
"echo-cancel-sink-name", echo_cancel_sink_name,
"echo-cancel-source-name", echo_cancel_source_name,
NULL));
return TRUE;
}