Files
wireplumber/modules/module-standard-event-source.c
George Kiagiadakis 4736d56557 log: implement a log topics system, like pipewire
The intention is to make checks for enabled log topics faster.

Every topic has its own structure that is statically defined in the file
where the logs are printed from. The structure is initialized transparently
when it is first used and it contains all the log level flags for the levels
that this topic should print messages. It is then checked on the wp_log()
macro before printing the message.

Topics from SPA/PipeWire are also handled natively, so messages are printed
directly without checking if the topic is enabled, since the PipeWire and SPA
macros do the checking themselves.

Messages coming from GLib are checked inside the handler.

An internal WpLogFields object is used to manage the state of each log
message, populating all the fields appropriately from the place they
are coming from (wp_log, spa_log, glib log), formatting the message and
then printing it. For printing to the journald, we still use the glib
message handler, converting all the needed fields to GLogField on demand.
That message handler does not do any checks for the topic or the level, so
we can just call it to send the message.
2023-05-16 20:42:28 +03:00

462 lines
15 KiB
C

/* WirePlumber
*
* Copyright © 2022 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <wp/wp.h>
WP_DEFINE_LOCAL_LOG_TOPIC ("m-standard-event-source")
/*
* 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 (&gtype_id)) {
GType new_type = g_enum_register_static (
g_intern_static_string ("WpStandardEventSource_RescanContext"),
values);
g_once_init_leave (&gtype_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));
}