/* WirePlumber * * Copyright © 2022 Collabora Ltd. * @author Julian Bouzas * * 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; }