
This change completely refactors the way components are loaded in wireplumber: - The module_init() function must return a GObject now. This object is either a WpPlugin or a WpSiFactory in the current modules. - When the component loader initializes a module, it automatically registers the WpPlugin or WpSiFactory with their respective methods. There is no need to register the WpPlugin or WpSiFactory in the module now. - The wp_core_load_component() API has been refactored to be asynchronows. This allows the component loader to automatically activate WpPlugin objects, and therefore allows the application to directly get the WpPlugin without having to find it. This simplifies a lot of things. - The 'ifexists' and 'nofail' component flags now work even if the respective WpPlugin could not be activated. - The code that loads components in main.c has also been simplified a lot, and the option to load dangling components has also been removed.
460 lines
15 KiB
C
460 lines
15 KiB
C
/* WirePlumber
|
|
*
|
|
* Copyright © 2022 Collabora Ltd.
|
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include <wp/wp.h>
|
|
|
|
/*
|
|
* Module subscribes for certain object manager events to and pushes them as
|
|
* events on to the Event Stack.
|
|
*/
|
|
|
|
enum {
|
|
ACTION_GET_OBJECT_MANAGER,
|
|
ACTION_CREATE_EVENT,
|
|
ACTION_PUSH_EVENT,
|
|
ACTION_SCHEDULE_RESCAN,
|
|
N_SIGNALS
|
|
};
|
|
|
|
typedef enum {
|
|
OBJECT_TYPE_PORT,
|
|
OBJECT_TYPE_LINK,
|
|
OBJECT_TYPE_NODE,
|
|
OBJECT_TYPE_SESSION_ITEM,
|
|
OBJECT_TYPE_CLIENT,
|
|
OBJECT_TYPE_DEVICE,
|
|
OBJECT_TYPE_METADATA,
|
|
N_OBJECT_TYPES,
|
|
OBJECT_TYPE_INVALID = N_OBJECT_TYPES
|
|
} ObjectType;
|
|
|
|
typedef enum {
|
|
RESCAN_CONTEXT_LINKING,
|
|
RESCAN_CONTEXT_DEFAULT_NODES,
|
|
N_RESCAN_CONTEXTS,
|
|
} RescanContext;
|
|
|
|
static GType
|
|
rescan_context_get_type (void)
|
|
{
|
|
static gsize gtype_id = 0;
|
|
static const GEnumValue values[] = {
|
|
{ RESCAN_CONTEXT_LINKING, "RESCAN_CONTEXT_LINKING", "linking" },
|
|
{ RESCAN_CONTEXT_DEFAULT_NODES, "RESCAN_CONTEXT_DEFAULT_NODES", "default-nodes" },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
if (g_once_init_enter (>ype_id)) {
|
|
GType new_type = g_enum_register_static (
|
|
g_intern_static_string ("WpStandardEventSource_RescanContext"),
|
|
values);
|
|
g_once_init_leave (>ype_id, new_type);
|
|
}
|
|
return (GType) gtype_id;
|
|
}
|
|
#define TYPE_RESCAN_CONTEXT (rescan_context_get_type ())
|
|
|
|
struct _WpStandardEventSource
|
|
{
|
|
WpPlugin parent;
|
|
WpObjectManager *oms[N_OBJECT_TYPES];
|
|
WpEventHook *rescan_done_hook;
|
|
gboolean rescan_scheduled[N_RESCAN_CONTEXTS];
|
|
gint n_oms_installed;
|
|
};
|
|
|
|
static guint signals[N_SIGNALS] = {0};
|
|
|
|
G_DECLARE_FINAL_TYPE (WpStandardEventSource, wp_standard_event_source,
|
|
WP, STANDARD_EVENT_SOURCE, WpPlugin)
|
|
G_DEFINE_TYPE (WpStandardEventSource, wp_standard_event_source, WP_TYPE_PLUGIN)
|
|
|
|
static void
|
|
wp_standard_event_source_init (WpStandardEventSource * self)
|
|
{
|
|
}
|
|
|
|
static GType
|
|
object_type_to_gtype (ObjectType type)
|
|
{
|
|
switch (type) {
|
|
case OBJECT_TYPE_PORT: return WP_TYPE_PORT;
|
|
case OBJECT_TYPE_LINK: return WP_TYPE_LINK;
|
|
case OBJECT_TYPE_NODE: return WP_TYPE_NODE;
|
|
case OBJECT_TYPE_SESSION_ITEM: return WP_TYPE_SESSION_ITEM;
|
|
case OBJECT_TYPE_CLIENT: return WP_TYPE_CLIENT;
|
|
case OBJECT_TYPE_DEVICE: return WP_TYPE_DEVICE;
|
|
case OBJECT_TYPE_METADATA: return WP_TYPE_METADATA;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static ObjectType
|
|
type_str_to_object_type (const gchar * type_str)
|
|
{
|
|
if (!g_strcmp0 (type_str, "port"))
|
|
return OBJECT_TYPE_PORT;
|
|
else if (!g_strcmp0 (type_str, "link"))
|
|
return OBJECT_TYPE_LINK;
|
|
else if (!g_strcmp0 (type_str, "node"))
|
|
return OBJECT_TYPE_NODE;
|
|
else if (!g_strcmp0 (type_str, "session-item"))
|
|
return OBJECT_TYPE_SESSION_ITEM;
|
|
else if (!g_strcmp0 (type_str, "client"))
|
|
return OBJECT_TYPE_CLIENT;
|
|
else if (!g_strcmp0 (type_str, "device"))
|
|
return OBJECT_TYPE_DEVICE;
|
|
else if (!g_strcmp0 (type_str, "metadata"))
|
|
return OBJECT_TYPE_METADATA;
|
|
else
|
|
return OBJECT_TYPE_INVALID;
|
|
}
|
|
|
|
static const gchar *
|
|
get_object_type (gpointer obj, WpProperties **properties)
|
|
{
|
|
/* keeping these sorted by the frequency of events related to these objects */
|
|
if (WP_IS_PORT (obj))
|
|
return "port";
|
|
else if (WP_IS_LINK (obj))
|
|
return "link";
|
|
else if (WP_IS_NODE (obj))
|
|
return "node";
|
|
else if (WP_IS_SESSION_ITEM (obj)) {
|
|
if (*properties == NULL)
|
|
*properties = wp_properties_new_empty();
|
|
|
|
if (WP_IS_SI_LINKABLE (obj)) {
|
|
wp_properties_set (*properties,
|
|
"event.session-item.interface", "linkable");
|
|
} else if (WP_IS_SI_LINK (obj)) {
|
|
wp_properties_set (*properties,
|
|
"event.session-item.interface", "link");
|
|
}
|
|
return "session-item";
|
|
}
|
|
else if (WP_IS_CLIENT (obj))
|
|
return "client";
|
|
else if (WP_IS_DEVICE (obj))
|
|
return "device";
|
|
else if (WP_IS_METADATA (obj))
|
|
return "metadata";
|
|
|
|
wp_debug_object (obj, "Unknown global proxy type");
|
|
return G_OBJECT_TYPE_NAME (obj);
|
|
}
|
|
|
|
static gint
|
|
get_default_event_priority (const gchar *event_type)
|
|
{
|
|
if (g_str_has_prefix (event_type, "select-"))
|
|
return 500;
|
|
else if (!g_strcmp0 (event_type, "rescan-for-default-nodes"))
|
|
return -490;
|
|
else if (!g_strcmp0 (event_type, "rescan-for-linking"))
|
|
return -500;
|
|
else if (!g_strcmp0 (event_type, "node-state-changed"))
|
|
return 50;
|
|
else if (!g_strcmp0 (event_type, "metadata-changed"))
|
|
return 50;
|
|
else if (g_str_has_suffix (event_type, "-params-changed"))
|
|
return 50;
|
|
else if (g_str_has_prefix (event_type, "client-"))
|
|
return 200;
|
|
else if (g_str_has_prefix (event_type, "device-"))
|
|
return 170;
|
|
else if (g_str_has_prefix (event_type, "port-"))
|
|
return 150;
|
|
else if (g_str_has_prefix (event_type, "node-"))
|
|
return 130;
|
|
else if (g_str_has_prefix (event_type, "session-item-"))
|
|
return 110;
|
|
else if (g_str_has_suffix (event_type, "-added") ||
|
|
g_str_has_suffix (event_type, "-removed"))
|
|
return 20;
|
|
|
|
wp_debug ("Unknown event type: %s, using priority 0", event_type);
|
|
return 0;
|
|
}
|
|
|
|
static WpObjectManager *
|
|
wp_standard_event_get_object_manager (WpStandardEventSource *self,
|
|
const gchar * type_str)
|
|
{
|
|
ObjectType type = type_str_to_object_type (type_str);
|
|
|
|
if (G_UNLIKELY (type == OBJECT_TYPE_INVALID)) {
|
|
wp_critical_object (self, "object type '%s' is not valid", type_str);
|
|
return NULL;
|
|
}
|
|
|
|
g_return_val_if_fail (self->oms[type], NULL);
|
|
return g_object_ref (self->oms[type]);
|
|
}
|
|
|
|
static WpEvent *
|
|
wp_standard_event_source_create_event (WpStandardEventSource *self,
|
|
const gchar *event_type, gpointer subject, WpProperties *misc_properties)
|
|
{
|
|
g_autoptr (WpEvent) event = NULL;
|
|
g_autoptr (WpProperties) properties = wp_properties_new_empty ();
|
|
g_autofree gchar *full_event_type = NULL;
|
|
|
|
const gchar *subject_type =
|
|
subject ? get_object_type (subject, &properties) : NULL;
|
|
|
|
if (subject_type) {
|
|
wp_properties_set (properties, "event.subject.type", subject_type);
|
|
|
|
/* prefix the event with the subject type, unless it is a select-* event */
|
|
if (!g_str_has_prefix (event_type, "select-")) {
|
|
full_event_type = g_strdup_printf ("%s-%s", subject_type, event_type);
|
|
event_type = full_event_type;
|
|
}
|
|
}
|
|
if (misc_properties)
|
|
wp_properties_add (properties, misc_properties);
|
|
|
|
gint priority = get_default_event_priority (event_type);
|
|
|
|
wp_debug_object (self,
|
|
"pushing event '%s', prio %d, subject " WP_OBJECT_FORMAT " (%s)",
|
|
event_type, priority, WP_OBJECT_ARGS (subject), subject_type);
|
|
|
|
event = wp_event_new (event_type, priority, g_steal_pointer (&properties),
|
|
G_OBJECT (self), G_OBJECT (subject));
|
|
|
|
/* watch for subject pw-proxy-destroyed and cancel event,
|
|
unless this is a "removed" event, in which case we expect the proxy
|
|
to be destroyed and the event should still go through */
|
|
if (subject && !g_str_has_suffix (event_type, "-removed")
|
|
&& g_type_is_a (G_OBJECT_TYPE (subject), WP_TYPE_PROXY)) {
|
|
g_signal_connect_object (subject, "pw-proxy-destroyed",
|
|
(GCallback) g_cancellable_cancel, wp_event_get_cancellable (event),
|
|
G_CONNECT_SWAPPED);
|
|
}
|
|
|
|
return g_steal_pointer (&event);
|
|
}
|
|
|
|
static void
|
|
wp_standard_event_source_push_event (WpStandardEventSource *self,
|
|
const gchar *event_type, gpointer subject, WpProperties *misc_properties)
|
|
{
|
|
|
|
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
|
|
g_return_if_fail (core);
|
|
g_autoptr (WpEventDispatcher) dispatcher =
|
|
wp_event_dispatcher_get_instance (core);
|
|
g_return_if_fail (dispatcher);
|
|
|
|
wp_event_dispatcher_push_event (dispatcher,
|
|
wp_standard_event_source_create_event (
|
|
self, event_type, subject, misc_properties));
|
|
}
|
|
|
|
static void
|
|
wp_standard_event_source_schedule_rescan (WpStandardEventSource *self,
|
|
RescanContext context)
|
|
{
|
|
if (!self->rescan_scheduled[context]) {
|
|
g_autoptr (GEnumClass) klass = g_type_class_ref (TYPE_RESCAN_CONTEXT);
|
|
GEnumValue *value = g_enum_get_value (klass, context);
|
|
g_autofree gchar *event_type = g_strdup_printf ("rescan-for-%s",
|
|
value->value_nick);
|
|
wp_standard_event_source_push_event (self, event_type, NULL, NULL);
|
|
self->rescan_scheduled[context] = TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_metadata_changed (WpMetadata *obj, guint32 subject,
|
|
const gchar *key, const gchar *spa_type, const gchar *value,
|
|
WpStandardEventSource *self)
|
|
{
|
|
g_autoptr (WpProperties) properties = wp_properties_new_empty ();
|
|
wp_properties_setf (properties, "event.subject.id", "%u", subject);
|
|
wp_properties_set (properties, "event.subject.key", key);
|
|
wp_properties_set (properties, "event.subject.spa_type", spa_type);
|
|
wp_properties_set (properties, "event.subject.value", value);
|
|
|
|
wp_standard_event_source_push_event (self, "changed", obj, properties);
|
|
}
|
|
|
|
static void
|
|
on_params_changed (WpPipewireObject *obj, const gchar *id,
|
|
WpStandardEventSource *self)
|
|
{
|
|
g_autoptr (WpProperties) properties = wp_properties_new_empty ();
|
|
wp_properties_set (properties, "event.subject.param-id", id);
|
|
|
|
wp_standard_event_source_push_event (self, "params-changed", obj, properties);
|
|
}
|
|
|
|
static void
|
|
on_node_state_changed (WpNode *obj, WpNodeState old_state,
|
|
WpNodeState new_state, WpStandardEventSource *self)
|
|
{
|
|
g_autoptr (GEnumClass) klass = g_type_class_ref (WP_TYPE_NODE_STATE);
|
|
GEnumValue *old_value = g_enum_get_value (klass, old_state);
|
|
GEnumValue *new_value = g_enum_get_value (klass, new_state);
|
|
|
|
g_autoptr (WpProperties) properties = wp_properties_new (
|
|
"event.subject.old-state", old_value ? old_value->value_nick : NULL,
|
|
"event.subject.new-state", new_value ? new_value->value_nick : NULL,
|
|
NULL);
|
|
wp_standard_event_source_push_event (self, "state-changed", obj,
|
|
properties);
|
|
}
|
|
|
|
static void
|
|
on_object_added (WpObjectManager *om, WpObject *obj, WpStandardEventSource *self)
|
|
{
|
|
wp_standard_event_source_push_event (self, "added", obj, NULL);
|
|
|
|
if (WP_IS_PIPEWIRE_OBJECT (obj)) {
|
|
g_signal_connect_object (obj, "params-changed",
|
|
G_CALLBACK (on_params_changed), self, 0);
|
|
}
|
|
|
|
if (WP_IS_NODE (obj)) {
|
|
g_signal_connect_object (obj, "state-changed",
|
|
G_CALLBACK (on_node_state_changed), self, 0);
|
|
}
|
|
else if (WP_IS_METADATA (obj)) {
|
|
g_signal_connect_object (obj, "changed",
|
|
G_CALLBACK (on_metadata_changed), self, 0);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_object_removed (WpObjectManager *om, WpObject *obj, WpStandardEventSource *self)
|
|
{
|
|
wp_standard_event_source_push_event (self, "removed", obj, NULL);
|
|
}
|
|
|
|
static void
|
|
on_om_installed (WpObjectManager * om, WpStandardEventSource * self)
|
|
{
|
|
if (++self->n_oms_installed == N_OBJECT_TYPES)
|
|
wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
|
|
}
|
|
|
|
static void
|
|
on_rescan_done (WpEvent * event, WpStandardEventSource * self)
|
|
{
|
|
g_autoptr (WpProperties) properties = wp_event_get_properties (event);
|
|
const gchar *event_type = wp_properties_get (properties, "event.type");
|
|
|
|
/* the event type is "rescan-for-<context>" and the enum nickname is just
|
|
"<context>", so we get the substring from the 12th character onwards */
|
|
g_autoptr (GEnumClass) klass = g_type_class_ref (TYPE_RESCAN_CONTEXT);
|
|
GEnumValue *value = g_enum_get_value_by_nick (klass, &event_type[11]);
|
|
|
|
g_return_if_fail (value != NULL && value->value_nick != NULL);
|
|
self->rescan_scheduled[value->value] = FALSE;
|
|
}
|
|
|
|
static void
|
|
wp_standard_event_source_enable (WpPlugin * plugin, WpTransition * transition)
|
|
{
|
|
WpStandardEventSource * self = WP_STANDARD_EVENT_SOURCE (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_return_if_fail (dispatcher);
|
|
|
|
/* install object managers */
|
|
self->n_oms_installed = 0;
|
|
for (gint i = 0; i < N_OBJECT_TYPES; i++) {
|
|
GType gtype = object_type_to_gtype (i);
|
|
self->oms[i] = wp_object_manager_new ();
|
|
wp_object_manager_add_interest (self->oms[i], gtype, NULL);
|
|
wp_object_manager_request_object_features (self->oms[i],
|
|
gtype, WP_OBJECT_FEATURES_ALL);
|
|
g_signal_connect_object (self->oms[i], "object-added",
|
|
G_CALLBACK (on_object_added), self, 0);
|
|
g_signal_connect_object (self->oms[i], "object-removed",
|
|
G_CALLBACK (on_object_removed), self, 0);
|
|
g_signal_connect_object (self->oms[i], "installed",
|
|
G_CALLBACK (on_om_installed), self, 0);
|
|
wp_core_install_object_manager (core, self->oms[i]);
|
|
}
|
|
|
|
/* install hook to restore the rescan_scheduled state after rescanning */
|
|
self->rescan_done_hook = wp_simple_event_hook_new (
|
|
"m-standard-event-source/rescan-done", NULL, NULL,
|
|
g_cclosure_new_object ((GCallback) on_rescan_done, G_OBJECT (self)));
|
|
wp_interest_event_hook_add_interest (
|
|
WP_INTEREST_EVENT_HOOK (self->rescan_done_hook),
|
|
WP_CONSTRAINT_TYPE_PW_PROPERTY, "event.type", "#s", "rescan-for-*",
|
|
NULL);
|
|
wp_event_dispatcher_register_hook (dispatcher, self->rescan_done_hook);
|
|
}
|
|
|
|
static void
|
|
wp_standard_event_source_disable (WpPlugin * plugin)
|
|
{
|
|
WpStandardEventSource * self = WP_STANDARD_EVENT_SOURCE (plugin);
|
|
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
|
|
g_autoptr (WpEventDispatcher) dispatcher = core ?
|
|
wp_event_dispatcher_get_instance (core) : NULL;
|
|
|
|
for (gint i = 0; i < N_OBJECT_TYPES; i++)
|
|
g_clear_object (&self->oms[i]);
|
|
|
|
if (dispatcher)
|
|
wp_event_dispatcher_unregister_hook (dispatcher, self->rescan_done_hook);
|
|
g_clear_object (&self->rescan_done_hook);
|
|
}
|
|
|
|
static void
|
|
wp_standard_event_source_class_init (WpStandardEventSourceClass * klass)
|
|
{
|
|
WpPluginClass *plugin_class = (WpPluginClass *) klass;
|
|
|
|
plugin_class->enable = wp_standard_event_source_enable;
|
|
plugin_class->disable = wp_standard_event_source_disable;
|
|
|
|
signals[ACTION_GET_OBJECT_MANAGER] = g_signal_new_class_handler (
|
|
"get-object-manager", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
(GCallback) wp_standard_event_get_object_manager,
|
|
NULL, NULL, NULL, WP_TYPE_OBJECT_MANAGER, 1, G_TYPE_STRING);
|
|
|
|
signals[ACTION_CREATE_EVENT] = g_signal_new_class_handler (
|
|
"create-event", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
(GCallback) wp_standard_event_source_create_event,
|
|
NULL, NULL, NULL, WP_TYPE_EVENT, 3,
|
|
G_TYPE_STRING, WP_TYPE_OBJECT, WP_TYPE_PROPERTIES);
|
|
|
|
signals[ACTION_PUSH_EVENT] = g_signal_new_class_handler (
|
|
"push-event", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
(GCallback) wp_standard_event_source_push_event,
|
|
NULL, NULL, NULL, G_TYPE_NONE, 3,
|
|
G_TYPE_STRING, WP_TYPE_OBJECT, WP_TYPE_PROPERTIES);
|
|
|
|
signals[ACTION_SCHEDULE_RESCAN] = g_signal_new_class_handler (
|
|
"schedule-rescan", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
(GCallback) wp_standard_event_source_schedule_rescan,
|
|
NULL, NULL, NULL, G_TYPE_NONE, 1, TYPE_RESCAN_CONTEXT);
|
|
}
|
|
|
|
WP_PLUGIN_EXPORT GObject *
|
|
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
|
|
{
|
|
return G_OBJECT (g_object_new (wp_standard_event_source_get_type (),
|
|
"name", "standard-event-source",
|
|
"core", core,
|
|
NULL));
|
|
}
|