From 6be9a89195e0d3bf8408cea661f22cb97b638f24 Mon Sep 17 00:00:00 2001 From: Michal Ratajsky Date: Fri, 20 Jun 2014 00:12:40 +0200 Subject: Pulse and API updates, fixes --- backends/pulse/pulse-backend.c | 354 +++++++++++++++++++++++++++++------------ 1 file changed, 253 insertions(+), 101 deletions(-) (limited to 'backends/pulse/pulse-backend.c') diff --git a/backends/pulse/pulse-backend.c b/backends/pulse/pulse-backend.c index 79a69a0..8ed7342 100644 --- a/backends/pulse/pulse-backend.c +++ b/backends/pulse/pulse-backend.c @@ -21,6 +21,7 @@ #include #include +#include #include @@ -44,10 +45,11 @@ struct _PulseBackendPrivate gchar *app_version; gchar *app_icon; gchar *server_address; - gchar *default_sink; - gchar *default_source; + gboolean connected_once; + GSource *connect_source; + MateMixerStream *default_sink; + MateMixerStream *default_source; GHashTable *devices; - GHashTable *cards; GHashTable *sinks; GHashTable *sink_inputs; GHashTable *sources; @@ -59,11 +61,25 @@ struct _PulseBackendPrivate enum { PROP_0, PROP_STATE, + PROP_DEFAULT_INPUT, + PROP_DEFAULT_OUTPUT, N_PROPERTIES }; static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface); +static void pulse_backend_class_init (PulseBackendClass *klass); +static void pulse_backend_class_finalize (PulseBackendClass *klass); + +static void pulse_backend_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void pulse_backend_init (PulseBackend *pulse); +static void pulse_backend_dispose (GObject *object); +static void pulse_backend_finalize (GObject *object); + G_DEFINE_DYNAMIC_TYPE_EXTENDED (PulseBackend, pulse_backend, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND, @@ -127,10 +143,18 @@ static void backend_source_output_removed_cb (PulseConnection guint index, PulseBackend *pulse); +static gboolean backend_try_reconnect (PulseBackend *pulse); +static void backend_remove_connect_source (PulseBackend *pulse); +static void backend_change_state (PulseBackend *backend, + MateMixerState state); + static gint backend_compare_devices (gconstpointer a, gconstpointer b); static gint backend_compare_streams (gconstpointer a, gconstpointer b); +static gboolean backend_compare_stream_name (gpointer key, + gpointer value, + gpointer user_data); static MateMixerBackendInfo info; @@ -166,6 +190,29 @@ mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) iface->set_default_output_stream = backend_set_default_output_stream; } +static void +pulse_backend_class_init (PulseBackendClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = pulse_backend_dispose; + object_class->finalize = pulse_backend_finalize; + object_class->get_property = pulse_backend_get_property; + + g_object_class_override_property (object_class, PROP_STATE, "state"); + g_object_class_override_property (object_class, PROP_DEFAULT_INPUT, "default-input"); + g_object_class_override_property (object_class, PROP_DEFAULT_OUTPUT, "default-output"); + + g_type_class_add_private (object_class, sizeof (PulseBackendPrivate)); +} + +/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ +static void +pulse_backend_class_finalize (PulseBackendClass *klass) +{ +} + static void pulse_backend_get_property (GObject *object, guint param_id, @@ -180,6 +227,12 @@ pulse_backend_get_property (GObject *object, case PROP_STATE: g_value_set_enum (value, pulse->priv->state); break; + case PROP_DEFAULT_INPUT: + g_value_set_object (value, pulse->priv->default_source); + break; + case PROP_DEFAULT_OUTPUT: + g_value_set_object (value, pulse->priv->default_sink); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; @@ -192,16 +245,12 @@ pulse_backend_init (PulseBackend *pulse) pulse->priv = G_TYPE_INSTANCE_GET_PRIVATE (pulse, PULSE_TYPE_BACKEND, PulseBackendPrivate); + pulse->priv->devices = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); - pulse->priv->cards = - g_hash_table_new_full (g_direct_hash, - g_direct_equal, - NULL, - g_object_unref); pulse->priv->sinks = g_hash_table_new_full (g_direct_hash, g_direct_equal, @@ -227,41 +276,7 @@ pulse_backend_init (PulseBackend *pulse) static void pulse_backend_dispose (GObject *object) { - PulseBackend *pulse; - - pulse = PULSE_BACKEND (object); - - if (pulse->priv->devices) { - g_hash_table_destroy (pulse->priv->devices); - pulse->priv->devices = NULL; - } - - if (pulse->priv->cards) { - g_hash_table_destroy (pulse->priv->cards); - pulse->priv->cards = NULL; - } - - if (pulse->priv->sinks) { - g_hash_table_destroy (pulse->priv->sinks); - pulse->priv->devices = NULL; - } - - if (pulse->priv->sink_inputs) { - g_hash_table_destroy (pulse->priv->sink_inputs); - pulse->priv->devices = NULL; - } - - if (pulse->priv->sources) { - g_hash_table_destroy (pulse->priv->sources); - pulse->priv->devices = NULL; - } - - if (pulse->priv->source_outputs) { - g_hash_table_destroy (pulse->priv->source_outputs); - pulse->priv->source_outputs = NULL; - } - - g_clear_object (&pulse->priv->connection); + backend_close (MATE_MIXER_BACKEND (object)); G_OBJECT_CLASS (pulse_backend_parent_class)->dispose (object); } @@ -282,27 +297,6 @@ pulse_backend_finalize (GObject *object) G_OBJECT_CLASS (pulse_backend_parent_class)->finalize (object); } -static void -pulse_backend_class_init (PulseBackendClass *klass) -{ - GObjectClass *object_class; - - object_class = G_OBJECT_CLASS (klass); - object_class->dispose = pulse_backend_dispose; - object_class->finalize = pulse_backend_finalize; - object_class->get_property = pulse_backend_get_property; - - g_object_class_override_property (object_class, PROP_STATE, "state"); - - g_type_class_add_private (klass, sizeof (PulseBackendPrivate)); -} - -/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ -static void -pulse_backend_class_finalize (PulseBackendClass *klass) -{ -} - static gboolean backend_open (MateMixerBackend *backend) { @@ -321,6 +315,10 @@ backend_open (MateMixerBackend *backend) pulse->priv->app_version, pulse->priv->app_icon, pulse->priv->server_address); + + /* No connection attempt is made during the construction of the connection, + * but it sets up the PulseAudio structures, which might fail in an + * unlikely case */ if (G_UNLIKELY (connection == NULL)) { pulse->priv->state = MATE_MIXER_STATE_FAILED; return FALSE; @@ -375,6 +373,8 @@ backend_open (MateMixerBackend *backend) G_CALLBACK (backend_source_output_removed_cb), pulse); + /* Connect to the PulseAudio server, this might fail either instantly or + * asynchronously, for example when remote connection timeouts */ if (!pulse_connection_connect (connection)) { pulse->priv->state = MATE_MIXER_STATE_FAILED; g_object_unref (connection); @@ -388,9 +388,48 @@ backend_open (MateMixerBackend *backend) static void backend_close (MateMixerBackend *backend) { + PulseBackend *pulse; + g_return_if_fail (PULSE_IS_BACKEND (backend)); - g_clear_object (&PULSE_BACKEND (backend)->priv->connection); + pulse = PULSE_BACKEND (backend); + + if (pulse->priv->connection) { + g_signal_handlers_disconnect_by_data (pulse->priv->connection, pulse); + + pulse_connection_disconnect (pulse->priv->connection); + g_clear_object (&pulse->priv->connection); + } + + if (pulse->priv->devices) { + g_hash_table_destroy (pulse->priv->devices); + pulse->priv->devices = NULL; + } + + if (pulse->priv->sinks) { + g_hash_table_destroy (pulse->priv->sinks); + pulse->priv->sinks = NULL; + } + + if (pulse->priv->sink_inputs) { + g_hash_table_destroy (pulse->priv->sink_inputs); + pulse->priv->sink_inputs = NULL; + } + + if (pulse->priv->sources) { + g_hash_table_destroy (pulse->priv->sources); + pulse->priv->sources = NULL; + } + + if (pulse->priv->source_outputs) { + g_hash_table_destroy (pulse->priv->source_outputs); + pulse->priv->source_outputs = NULL; + } + + g_clear_object (&pulse->priv->default_sink); + g_clear_object (&pulse->priv->default_source); + + backend_change_state (pulse, MATE_MIXER_STATE_IDLE); } static MateMixerState @@ -406,18 +445,19 @@ backend_set_data (MateMixerBackend *backend, const MateMixerBackendData *data) { PulseBackend *pulse; - if (data == NULL) - return; - g_return_if_fail (PULSE_IS_BACKEND (backend)); pulse = PULSE_BACKEND (backend); - g_free (data->app_name); - g_free (data->app_id); - g_free (data->app_version); - g_free (data->app_icon); - g_free (data->server_address); + g_clear_pointer (&pulse->priv->app_name, g_free); + g_clear_pointer (&pulse->priv->app_id, g_free); + g_clear_pointer (&pulse->priv->app_version, g_free); + g_clear_pointer (&pulse->priv->app_icon, g_free); + g_clear_pointer (&pulse->priv->server_address, g_free); + + /* Allow to unset the details by passing NULL data */ + if (G_UNLIKELY (data == NULL)) + return; pulse->priv->app_name = g_strdup (data->app_name); pulse->priv->app_id = g_strdup (data->app_id); @@ -433,6 +473,7 @@ backend_list_devices (MateMixerBackend *backend) g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); + /* Always create a new current list, caching is done in the main library */ list = g_hash_table_get_values (PULSE_BACKEND (backend)->priv->devices); g_list_foreach (list, (GFunc) g_object_ref, NULL); @@ -450,6 +491,7 @@ backend_list_streams (MateMixerBackend *backend) pulse = PULSE_BACKEND (backend); + /* Always create a new current list, caching is done in the main library */ list = g_list_concat (g_hash_table_get_values (pulse->priv->sinks), g_hash_table_get_values (pulse->priv->sink_inputs)); list = g_list_concat (list, @@ -465,7 +507,9 @@ backend_list_streams (MateMixerBackend *backend) static MateMixerStream * backend_get_default_input_stream (MateMixerBackend *backend) { - return NULL; + g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); + + return PULSE_BACKEND (backend)->priv->default_source; } static gboolean @@ -473,8 +517,8 @@ backend_set_default_input_stream (MateMixerBackend *backend, MateMixerStream *st { PulseBackend *pulse; - g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); - g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); + g_return_val_if_fail (PULSE_IS_BACKEND (backend), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); pulse = PULSE_BACKEND (backend); @@ -485,7 +529,9 @@ backend_set_default_input_stream (MateMixerBackend *backend, MateMixerStream *st static MateMixerStream * backend_get_default_output_stream (MateMixerBackend *backend) { - return NULL; + g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); + + return PULSE_BACKEND (backend)->priv->default_sink; } static gboolean @@ -493,8 +539,8 @@ backend_set_default_output_stream (MateMixerBackend *backend, MateMixerStream *s { PulseBackend *pulse; - g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); - g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); + g_return_val_if_fail (PULSE_IS_BACKEND (backend), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); pulse = PULSE_BACKEND (backend); @@ -511,17 +557,39 @@ backend_connection_state_cb (PulseConnection *connection, switch (state) { case PULSE_CONNECTION_DISCONNECTED: + if (pulse->priv->connected_once) { + /* We managed to connect once before, try to reconnect and if it + * fails immediately, use an idle source; + * in case the idle source already exists, just let it try again */ + if (!pulse->priv->connect_source && + !pulse_connection_connect (connection)) { + pulse->priv->connect_source = g_idle_source_new (); + + g_source_set_callback (pulse->priv->connect_source, + (GSourceFunc) backend_try_reconnect, + pulse, + (GDestroyNotify) backend_remove_connect_source); + + g_source_attach (pulse->priv->connect_source, + g_main_context_get_thread_default ()); + } + break; + } + + /* First connection attempt has failed */ + backend_change_state (pulse, MATE_MIXER_STATE_FAILED); break; + case PULSE_CONNECTION_CONNECTING: - break; case PULSE_CONNECTION_AUTHORIZING: - break; case PULSE_CONNECTION_LOADING: + backend_change_state (pulse, MATE_MIXER_STATE_CONNECTING); break; + case PULSE_CONNECTION_CONNECTED: - pulse->priv->state = MATE_MIXER_STATE_READY; + pulse->priv->connected_once = TRUE; - g_object_notify (G_OBJECT (pulse), "state"); + backend_change_state (pulse, MATE_MIXER_STATE_READY); break; } } @@ -531,21 +599,64 @@ backend_server_info_cb (PulseConnection *connection, const pa_server_info *info, PulseBackend *pulse) { - // XXX add property - - if (g_strcmp0 (pulse->priv->default_sink, info->default_sink_name)) { - g_free (pulse->priv->default_sink); - - pulse->priv->default_sink = g_strdup (info->default_sink_name); - // g_object_notify (G_OBJECT (pulse), "default-output"); + const gchar *name_source = NULL; + const gchar *name_sink = NULL; + + if (pulse->priv->default_source) + name_source = mate_mixer_stream_get_name (pulse->priv->default_source); + if (pulse->priv->default_sink) + name_sink = mate_mixer_stream_get_name (pulse->priv->default_sink); + + if (g_strcmp0 (name_source, info->default_source_name)) { + if (pulse->priv->default_source) + g_clear_object (&pulse->priv->default_source); + + if (info->default_source_name != NULL) { + MateMixerStream *stream = g_hash_table_find (pulse->priv->sources, + backend_compare_stream_name, + (gpointer) info->default_source_name); + + /* It is theoretically possible to receive a server info notification + * before the stream lists are fully downloaded, this should not be + * a problem as a newer notification will arrive later after the + * streams are read. + * Of course this will only work if Pulse delivers notifications in + * the correct order, but it seems it does. */ + if (G_LIKELY (stream != NULL)) { + pulse->priv->default_source = g_object_ref (stream); + g_debug ("Default input stream changed to %s", info->default_source_name); + + g_object_notify (G_OBJECT (pulse), "default-output"); + } else + g_debug ("Default input stream %s not yet known", + info->default_source_name); + } } - if (g_strcmp0 (pulse->priv->default_source, info->default_source_name)) { - g_free (pulse->priv->default_source); - - pulse->priv->default_source = g_strdup (info->default_source_name); - // g_object_notify (G_OBJECT (pulse), "default-input"); + if (g_strcmp0 (name_sink, info->default_sink_name)) { + if (pulse->priv->default_sink) + g_clear_object (&pulse->priv->default_sink); + + if (info->default_sink_name != NULL) { + MateMixerStream *stream = g_hash_table_find (pulse->priv->sinks, + backend_compare_stream_name, + (gpointer) info->default_sink_name); + if (G_LIKELY (stream != NULL)) { + pulse->priv->default_sink = g_object_ref (stream); + g_debug ("Default output stream changed to %s", info->default_sink_name); + + g_object_notify (G_OBJECT (pulse), "default-output"); + } else + g_debug ("Default output stream %s not yet known", + info->default_sink_name); + } } + + if (pulse->priv->state != MATE_MIXER_STATE_READY) + g_debug ("Sound server is %s version %s, running on %s", + info->server_name, + info->server_version, + info->host_name); } static void @@ -557,12 +668,12 @@ backend_card_info_cb (PulseConnection *connection, p = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (info->index)); if (!p) { - PulseDevice *device; + PulseDevice *device = pulse_device_new (connection, info); - device = pulse_device_new (connection, info); g_hash_table_insert (pulse->priv->devices, GINT_TO_POINTER (pulse_device_get_index (device)), device); + g_signal_emit_by_name (G_OBJECT (pulse), "device-added", mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); @@ -606,9 +717,8 @@ backend_sink_info_cb (PulseConnection *connection, p = g_hash_table_lookup (pulse->priv->sinks, GINT_TO_POINTER (info->index)); if (!p) { - PulseStream *stream; + PulseStream *stream = pulse_sink_new (connection, info); - stream = pulse_sink_new (connection, info); g_hash_table_insert (pulse->priv->sinks, GINT_TO_POINTER (pulse_stream_get_index (stream)), stream); @@ -653,12 +763,15 @@ backend_sink_input_info_cb (PulseConnection *connection, PulseBackend *pulse) { gpointer p; + gpointer parent; + + parent = g_hash_table_lookup (pulse->priv->sinks, GINT_TO_POINTER (info->sink)); p = g_hash_table_lookup (pulse->priv->sink_inputs, GINT_TO_POINTER (info->index)); if (!p) { PulseStream *stream; - stream = pulse_sink_input_new (connection, info); + stream = pulse_sink_input_new (connection, info, parent); g_hash_table_insert (pulse->priv->sink_inputs, GINT_TO_POINTER (pulse_stream_get_index (stream)), stream); @@ -667,7 +780,7 @@ backend_sink_input_info_cb (PulseConnection *connection, "stream-added", mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); } else { - pulse_sink_input_update (p, info); + pulse_sink_input_update (p, info, parent); g_signal_emit_by_name (G_OBJECT (pulse), "stream-changed", @@ -708,6 +821,9 @@ backend_source_info_cb (PulseConnection *connection, if (!p) { PulseStream *stream; + if (info->monitor_of_sink != PA_INVALID_INDEX) + return; + stream = pulse_source_new (connection, info); g_hash_table_insert (pulse->priv->sources, GINT_TO_POINTER (pulse_stream_get_index (stream)), @@ -733,6 +849,8 @@ backend_source_removed_cb (PulseConnection *connection, gpointer p; gchar *name; + // XXX set parent + p = g_hash_table_lookup (pulse->priv->sources, GINT_TO_POINTER (index)); if (G_UNLIKELY (p == NULL)) return; @@ -797,6 +915,32 @@ backend_source_output_removed_cb (PulseConnection *connection, g_free (name); } +static gboolean +backend_try_reconnect (PulseBackend *pulse) +{ + /* When the connect call succeeds, return FALSE to remove the idle source + * and wait for the connection state notifications, otherwise this function + * will be called again */ + return !pulse_connection_connect (pulse->priv->connection); +} + +static void +backend_remove_connect_source (PulseBackend *pulse) +{ + g_clear_pointer (&pulse->priv->connect_source, g_source_unref); +} + +static void +backend_change_state (PulseBackend *backend, MateMixerState state) +{ + if (backend->priv->state == state) + return; + + backend->priv->state = state; + + g_object_notify (G_OBJECT (backend), "state"); +} + static gint backend_compare_devices (gconstpointer a, gconstpointer b) { @@ -810,3 +954,11 @@ backend_compare_streams (gconstpointer a, gconstpointer b) return strcmp (mate_mixer_stream_get_name (MATE_MIXER_STREAM (a)), mate_mixer_stream_get_name (MATE_MIXER_STREAM (b))); } + +static gboolean +backend_compare_stream_name (gpointer key, gpointer value, gpointer user_data) +{ + MateMixerStream *stream = MATE_MIXER_STREAM (value); + + return !strcmp (mate_mixer_stream_get_name (stream), (const gchar *) user_data); +} -- cgit v1.2.1