Files
wireplumber/lib/wp/global-proxy.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

409 lines
12 KiB
C

/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "global-proxy.h"
#include "private/registry.h"
#include "core.h"
#include "error.h"
#include "log.h"
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-global-proxy")
/*! \defgroup wpglobalproxy WpGlobalProxy */
/*!
* \struct WpGlobalProxy
*
* A proxy that represents a PipeWire global object, i.e. an
* object that is made available through the PipeWire registry.
*
* \gproperties
*
* \gproperty{global, WpGlobal *, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY,
* Internal WpGlobal object}
*
* \gproperty{factory-name, gchar *, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY,
* The factory name}
*
* \gproperty{global-properties, WpProperties *,
* G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
* The pipewire global properties}
*
* \gproperty{permissions, guint, G_PARAM_READABLE,
* The pipewire global permissions}
*/
typedef struct _WpGlobalProxyPrivate WpGlobalProxyPrivate;
struct _WpGlobalProxyPrivate
{
WpGlobal *global;
gchar factory_name[96];
WpProperties *properties;
};
enum {
PROP_0,
PROP_GLOBAL,
PROP_FACTORY_NAME,
PROP_GLOBAL_PROPERTIES,
PROP_PERMISSIONS,
};
G_DEFINE_TYPE_WITH_PRIVATE (WpGlobalProxy, wp_global_proxy, WP_TYPE_PROXY)
static void
wp_global_proxy_init (WpGlobalProxy * self)
{
}
static void
wp_global_proxy_dispose (GObject * object)
{
WpGlobalProxy *self = WP_GLOBAL_PROXY (object);
WpGlobalProxyPrivate *priv =
wp_global_proxy_get_instance_private (self);
if (priv->global)
wp_global_rm_flag (priv->global, WP_GLOBAL_FLAG_OWNED_BY_PROXY);
G_OBJECT_CLASS (wp_global_proxy_parent_class)->dispose (object);
}
static void
wp_global_proxy_finalize (GObject * object)
{
WpGlobalProxy *self = WP_GLOBAL_PROXY (object);
WpGlobalProxyPrivate *priv =
wp_global_proxy_get_instance_private (self);
g_clear_pointer (&priv->properties, wp_properties_unref);
g_clear_pointer (&priv->global, wp_global_unref);
G_OBJECT_CLASS (wp_global_proxy_parent_class)->finalize (object);
}
static void
wp_global_proxy_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpGlobalProxy *self = WP_GLOBAL_PROXY (object);
WpGlobalProxyPrivate *priv =
wp_global_proxy_get_instance_private (self);
switch (property_id) {
case PROP_GLOBAL:
priv->global = g_value_dup_boxed (value);
break;
case PROP_FACTORY_NAME:
priv->factory_name[0] = '\0';
strncpy (priv->factory_name, g_value_get_string (value),
sizeof (priv->factory_name) - 1);
break;
case PROP_GLOBAL_PROPERTIES:
priv->properties = g_value_dup_boxed (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_global_proxy_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpGlobalProxy *self = WP_GLOBAL_PROXY (object);
switch (property_id) {
case PROP_PERMISSIONS:
g_value_set_uint (value, wp_global_proxy_get_permissions (self));
break;
case PROP_GLOBAL_PROPERTIES:
g_value_take_boxed (value, wp_global_proxy_get_global_properties (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static WpObjectFeatures
wp_global_proxy_get_supported_features (WpObject * object)
{
return WP_PROXY_FEATURE_BOUND;
}
enum {
STEP_BIND = WP_TRANSITION_STEP_CUSTOM_START,
};
static guint
wp_global_proxy_activate_get_next_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
/* we only support BOUND, so this is the only
feature that can be in @em missing */
g_return_val_if_fail (missing == WP_PROXY_FEATURE_BOUND,
WP_TRANSITION_STEP_ERROR);
return STEP_BIND;
}
static void
wp_global_proxy_step_bind (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
WpProxyClass *proxy_klass = WP_PROXY_GET_CLASS (WP_PROXY (object));
WpGlobalProxy *self = WP_GLOBAL_PROXY (object);
WpGlobalProxyPrivate *priv = wp_global_proxy_get_instance_private (self);
/* Create the pipewire object if global is NULL */
if (priv->global == NULL && priv->factory_name[0] != '\0') {
g_autoptr (WpCore) core = NULL;
struct pw_core *pw_core = NULL;
struct pw_proxy * p = NULL;
core = wp_object_get_core (WP_OBJECT (self));
if (G_UNLIKELY (!core)) {
wp_transition_return_error (WP_TRANSITION (transition), g_error_new (
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"The WirePlumber core is not valid; object cannot be created"));
return;
}
pw_core = wp_core_get_pw_core (core);
if (G_UNLIKELY (!pw_core)) {
wp_transition_return_error (WP_TRANSITION (transition), g_error_new (
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"The WirePlumber core is not connected; object cannot be created"));
return;
}
p = pw_core_create_object (pw_core, priv->factory_name,
proxy_klass->pw_iface_type, proxy_klass->pw_iface_version,
priv->properties ?
wp_properties_peek_dict (priv->properties) : NULL, 0);
if (G_UNLIKELY (!p)) {
wp_transition_return_error (WP_TRANSITION (transition), g_error_new (
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to create object with given factory name and properties"));
return;
}
wp_proxy_set_pw_proxy (WP_PROXY (self), p);
}
/* Bind */
if (wp_proxy_get_pw_proxy (WP_PROXY (self)) == NULL) {
if (!wp_global_proxy_bind (self)) {
wp_transition_return_error (WP_TRANSITION (transition), g_error_new (
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
"global not specified or destroyed; cannot bind proxy"));
}
}
}
static void
wp_global_proxy_activate_execute_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
switch (step) {
case STEP_BIND:
wp_global_proxy_step_bind (object, transition, step, missing);
break;
case WP_TRANSITION_STEP_ERROR:
break;
default:
g_assert_not_reached ();
}
}
static void
wp_global_proxy_bound (WpProxy * proxy, guint32 global_id)
{
WpGlobalProxy *self = WP_GLOBAL_PROXY (proxy);
WpGlobalProxyPrivate *priv =
wp_global_proxy_get_instance_private (self);
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
if (!priv->global) {
wp_registry_prepare_new_global (wp_core_get_registry (core),
global_id, PW_PERM_ALL, WP_GLOBAL_FLAG_OWNED_BY_PROXY,
G_TYPE_FROM_INSTANCE (self), self,
priv->properties ? wp_properties_peek_dict (priv->properties) : NULL,
&priv->global);
}
}
static void
wp_global_proxy_destroyed (WpProxy * proxy)
{
WpGlobalProxy *self = WP_GLOBAL_PROXY (proxy);
WpGlobalProxyPrivate *priv =
wp_global_proxy_get_instance_private (self);
g_clear_pointer (&priv->global, wp_global_unref);
}
static void
wp_global_proxy_class_init (WpGlobalProxyClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpObjectClass *wpobject_class = (WpObjectClass *) klass;
WpProxyClass *proxy_class = (WpProxyClass *) klass;
object_class->finalize = wp_global_proxy_finalize;
object_class->dispose = wp_global_proxy_dispose;
object_class->set_property = wp_global_proxy_set_property;
object_class->get_property = wp_global_proxy_get_property;
wpobject_class->get_supported_features =
wp_global_proxy_get_supported_features;
wpobject_class->activate_get_next_step =
wp_global_proxy_activate_get_next_step;
wpobject_class->activate_execute_step =
wp_global_proxy_activate_execute_step;
proxy_class->bound = wp_global_proxy_bound;
proxy_class->pw_proxy_destroyed = wp_global_proxy_destroyed;
g_object_class_install_property (object_class, PROP_GLOBAL,
g_param_spec_boxed ("global", "global", "Internal WpGlobal object",
wp_global_get_type (),
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_FACTORY_NAME,
g_param_spec_string ("factory-name", "factory-name",
"The factory name", "",
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_GLOBAL_PROPERTIES,
g_param_spec_boxed ("global-properties", "global-properties",
"The pipewire global properties", WP_TYPE_PROPERTIES,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_PERMISSIONS,
g_param_spec_uint ("permissions", "permissions",
"The pipewire global permissions", 0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}
/*!
* \brief Requests the PipeWire server to destroy the object represented by
* this proxy.
*
* If the server allows it, the object will be destroyed and the
* WpProxy's `pw-proxy-destroyed` signal will be emitted. If the server does
* not allow it, nothing will happen.
*
* This is mostly useful for destroying WpLink objects.
*
* \ingroup wpglobalproxy
* \param self the pipewire global
*/
void
wp_global_proxy_request_destroy (WpGlobalProxy * self)
{
g_return_if_fail (WP_IS_GLOBAL_PROXY (self));
WpGlobalProxyPrivate *priv =
wp_global_proxy_get_instance_private (self);
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
if (priv->global && core) {
WpRegistry *reg = wp_core_get_registry (core);
if (reg->pw_registry)
pw_registry_destroy (reg->pw_registry, priv->global->id);
}
}
/*!
* \brief Gets the permissions of a pipewire global
* \ingroup wpglobalproxy
* \param self the pipewire global
* \returns the permissions that wireplumber has on this object
*/
guint32
wp_global_proxy_get_permissions (WpGlobalProxy * self)
{
g_return_val_if_fail (WP_IS_GLOBAL_PROXY (self), 0);
WpGlobalProxyPrivate *priv =
wp_global_proxy_get_instance_private (self);
return priv->global ? priv->global->permissions : PW_PERM_ALL;
}
/*!
* \brief Gets the global properties of a pipewire global
* \ingroup wpglobalproxy
* \param self the pipewire global
* \returns (transfer full): the global (immutable) properties of this
* pipewire object
*/
WpProperties *
wp_global_proxy_get_global_properties (WpGlobalProxy * self)
{
g_return_val_if_fail (WP_IS_GLOBAL_PROXY (self), NULL);
WpGlobalProxyPrivate *priv =
wp_global_proxy_get_instance_private (self);
if (priv->global && priv->global->properties)
return wp_properties_ref (priv->global->properties);
return NULL;
}
/*!
* \brief Binds to the global and creates the underlying `pw_proxy`.
*
* This is mostly meant to be called internally. It will create the `pw_proxy`
* and will activate the WP_PROXY_FEATURE_BOUND feature.
*
* This may only be called if there is no `pw_proxy` associated with this
* object yet.
*
* \ingroup wpglobalproxy
* \param self the pipewire global
* \returns TRUE on success, FALSE if there is no global to bind to
*/
gboolean
wp_global_proxy_bind (WpGlobalProxy * self)
{
WpGlobalProxyPrivate *priv;
struct pw_proxy *p;
g_return_val_if_fail (WP_IS_GLOBAL_PROXY (self), FALSE);
g_return_val_if_fail (wp_proxy_get_pw_proxy (WP_PROXY (self)) == NULL, FALSE);
priv = wp_global_proxy_get_instance_private (self);
if (!priv->global || !priv->global->proxy)
return FALSE;
/*
* We can bind only if the WpGlobal will unbind this proxy on removal, so
* assert here that this is so. Why: pw_registry_bind can race with the global
* id being replaced with a different object on server side, so we must rely
* on the registry_global messages to know if the object was replaced. If the
* race occurs, and a wrong object is going to be bound here, what will then
* happen is that WpGlobal will dispose this proxy and no problem arises.
*/
g_return_val_if_fail (priv->global->proxy == self, FALSE);
p = wp_global_bind (priv->global);
if (!p)
return FALSE;
wp_proxy_set_pw_proxy (WP_PROXY (self), p);
return TRUE;
}