Files
wireplumber/lib/wp/dbus.c

377 lines
9.2 KiB
C

/* WirePlumber
*
* Copyright © 2022 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#define G_LOG_DOMAIN "wp-dbus"
#include "private/registry.h"
#include "log.h"
#include "wpenums.h"
#include "dbus.h"
enum {
STEP_DBUS_ENABLE = WP_TRANSITION_STEP_CUSTOM_START,
};
enum
{
PROP_DBUS_0,
PROP_DBUS_BUS_TYPE,
PROP_DBUS_STATE,
};
/**
* \since 0.4.11
*/
struct _WpDbus
{
WpObject parent;
/* Props */
GBusType bus_type;
WpDBusState state;
GCancellable *cancellable;
GDBusConnection *connection;
};
static void on_connection_closed (GDBusConnection *connection, gboolean
remote_peer_vanished, GError *error, gpointer data);
G_DEFINE_TYPE (WpDbus, wp_dbus, WP_TYPE_OBJECT)
static void
wp_dbus_init (WpDbus * self)
{
}
static void
wp_dbus_set_state (WpDbus *self, WpDBusState new_state)
{
if (self->state != new_state) {
self->state = new_state;
g_object_notify (G_OBJECT (self), "state");
}
}
static void
on_got_bus (GObject * obj, GAsyncResult * res, gpointer data)
{
WpTransition *transition;
WpDbus *self;
g_autoptr (GError) error = NULL;
if (WP_IS_TRANSITION (data)) {
// coming from wp_dbus_enable
transition = WP_TRANSITION (data);
self = wp_transition_get_source_object (transition);
} else {
// coming from on_sync_reconnect
transition = NULL;
self = WP_DBUS (data);
}
self->connection = g_dbus_connection_new_for_address_finish (res, &error);
if (!self->connection) {
if (transition) {
g_prefix_error (&error, "Failed to connect to bus: ");
wp_transition_return_error (transition, g_steal_pointer (&error));
}
return;
}
wp_debug_object (self, "Connected to bus");
/* set up connection */
g_signal_connect_object (self->connection, "closed",
G_CALLBACK (on_connection_closed), self, 0);
g_dbus_connection_set_exit_on_close (self->connection, FALSE);
wp_dbus_set_state (self, WP_DBUS_STATE_CONNECTED);
wp_object_update_features (WP_OBJECT (self), WP_DBUS_FEATURE_ENABLED, 0);
}
static gboolean
do_connect (WpDbus *self, GAsyncReadyCallback callback, gpointer data,
GError **error)
{
g_autofree gchar *address = NULL;
address = g_dbus_address_get_for_bus_sync (self->bus_type, NULL, error);
if (!address) {
g_prefix_error (error, "Error acquiring bus address: ");
return FALSE;
}
wp_dbus_set_state (self, WP_DBUS_STATE_CONNECTING);
wp_debug_object (self, "Connecting to bus: %s", address);
g_dbus_connection_new_for_address (address,
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
NULL, self->cancellable, callback, data);
return TRUE;
}
static void
on_sync_reconnect (WpCore * core, GAsyncResult * res, WpDbus * self)
{
g_autoptr (GError) error = NULL;
if (!wp_core_sync_finish (core, res, &error)) {
wp_warning_object (self, "core sync error: %s", error->message);
return;
}
if (!do_connect (self, on_got_bus, self, &error))
wp_info_object (self, "Cannot reconnect on sync: %s", error->message);
}
static void
on_connection_closed (GDBusConnection *connection,
gboolean remote_peer_vanished, GError *error, gpointer data)
{
WpDbus *self = WP_DBUS (data);
g_autoptr (WpCore) core = NULL;
wp_info_object (self, "DBus connection closed: %s", error->message);
g_clear_object (&self->connection);
wp_dbus_set_state (self, WP_DBUS_STATE_CLOSED);
/* try to reconnect on idle if connection was closed */
core = wp_object_get_core (WP_OBJECT (self));
if (core && wp_core_is_connected (core)) {
wp_info_object (self, "Trying to reconnect on sync");
wp_core_sync_closure (core, NULL, g_cclosure_new_object (
G_CALLBACK (on_sync_reconnect), G_OBJECT (self)));
}
}
static void
wp_dbus_enable (WpDbus *self, WpTransition *transition)
{
g_autoptr (GError) error = NULL;
if (!do_connect (self, on_got_bus, transition, &error)) {
wp_transition_return_error (transition, g_steal_pointer (&error));
return;
}
}
static void
wp_dbus_disable (WpDbus *self)
{
g_cancellable_cancel (self->cancellable);
g_clear_object (&self->connection);
wp_dbus_set_state (self, WP_DBUS_STATE_CLOSED);
g_clear_object (&self->cancellable);
self->cancellable = g_cancellable_new ();
wp_object_update_features (WP_OBJECT (self), 0, WP_DBUS_FEATURE_ENABLED);
}
static WpObjectFeatures
wp_dbus_get_supported_features (WpObject * self)
{
return WP_DBUS_FEATURE_ENABLED;
}
static guint
wp_dbus_activate_get_next_step (WpObject * object,
WpFeatureActivationTransition * t, guint step, WpObjectFeatures missing)
{
switch (step) {
case WP_TRANSITION_STEP_NONE:
return STEP_DBUS_ENABLE;
case STEP_DBUS_ENABLE:
return WP_TRANSITION_STEP_NONE;
default:
return WP_TRANSITION_STEP_ERROR;
}
}
static void
wp_dbus_activate_execute_step (WpObject * object,
WpFeatureActivationTransition * t, guint step, WpObjectFeatures missing)
{
WpDbus *self = WP_DBUS (object);
WpTransition *transition = WP_TRANSITION (t);
switch (step) {
case STEP_DBUS_ENABLE:
wp_dbus_enable (self, transition);
break;
case WP_TRANSITION_STEP_ERROR:
break;
default:
g_return_if_reached ();
}
}
static void
wp_dbus_deactivate (WpObject * object, WpObjectFeatures features)
{
WpDbus *self = WP_DBUS (object);
guint current = wp_object_get_active_features (object);
if (features & current & WP_DBUS_FEATURE_ENABLED)
wp_dbus_disable (self);
}
static void
wp_dbus_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpDbus *self = WP_DBUS (object);
switch (property_id) {
case PROP_DBUS_BUS_TYPE:
g_value_set_enum (value, self->bus_type);
break;
case PROP_DBUS_STATE:
g_value_set_enum (value, self->state);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_dbus_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpDbus *self = WP_DBUS (object);
switch (property_id) {
case PROP_DBUS_BUS_TYPE:
self->bus_type = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_dbus_class_init (WpDbusClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpObjectClass *wpobject_class = (WpObjectClass *) klass;
object_class->set_property = wp_dbus_set_property;
object_class->get_property = wp_dbus_get_property;
wpobject_class->get_supported_features = wp_dbus_get_supported_features;
wpobject_class->activate_get_next_step = wp_dbus_activate_get_next_step;
wpobject_class->activate_execute_step = wp_dbus_activate_execute_step;
wpobject_class->deactivate = wp_dbus_deactivate;
g_object_class_install_property (object_class, PROP_DBUS_BUS_TYPE,
g_param_spec_enum ("bus-type", "bus-type", "The bus type",
G_TYPE_BUS_TYPE, G_BUS_TYPE_NONE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_DBUS_STATE,
g_param_spec_enum ("state", "state", "The dbus connection state",
WP_TYPE_DBUS_STATE, WP_DBUS_STATE_CLOSED,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}
static gboolean
find_dbus_func (gpointer object, gpointer p)
{
GBusType *bus_type = p;
if (!WP_IS_DBUS (object) || !bus_type)
return FALSE;
return wp_dbus_get_bus_type (WP_DBUS (object)) == *bus_type;
}
/*!
* \brief Returns the dbus instance that is associated with the given core and
* bus type.
*
* This method will also create the instance and register it with the core
* if it had not been created before.
*
* \param core the core
* \param bus_type the bus type
* \return (transfer full): the dbus instance
*/
WpDbus *
wp_dbus_get_instance (WpCore *core, GBusType bus_type)
{
WpRegistry *registry;
WpDbus *dbus;
g_return_val_if_fail (core, NULL);
g_return_val_if_fail (
bus_type != G_BUS_TYPE_NONE && bus_type != G_BUS_TYPE_STARTER, NULL);
registry = wp_core_get_registry (core);
dbus = wp_registry_find_object (registry, (GEqualFunc) find_dbus_func,
&bus_type);
if (G_UNLIKELY (!dbus)) {
dbus = g_object_new (WP_TYPE_DBUS,
"core", core,
"bus-type", bus_type,
NULL);
wp_registry_register_object (registry, g_object_ref (dbus));
}
return dbus;
}
/*!
* \brief Returns the bus type of the dbus object.
*
* \param self the DBus object
* \returns the bus type
*/
GBusType
wp_dbus_get_bus_type (WpDbus *self)
{
g_return_val_if_fail (WP_IS_DBUS (self), G_BUS_TYPE_NONE);
return self->bus_type;
}
/*!
* \brief Returns the dbus connection state of the dbus object.
*
* \param self the DBus object
* \returns the dbus connection state
*/
WpDBusState
wp_dbus_get_state (WpDbus *self)
{
g_return_val_if_fail (WP_IS_DBUS (self), WP_DBUS_STATE_CLOSED);
return self->state;
}
/*!
* \brief Returns the DBus connection object of the dbus object.
*
* \param self the DBus object
* \return (transfer full): the DBus connection object
*/
GDBusConnection *
wp_dbus_get_connection (WpDbus *self)
{
g_return_val_if_fail (WP_IS_DBUS (self), NULL);
return self->connection ? g_object_ref (self->connection) : NULL;
}