diff options
Diffstat (limited to 'backends/pulse/pulse-backend.c')
-rw-r--r-- | backends/pulse/pulse-backend.c | 1104 |
1 files changed, 662 insertions, 442 deletions
diff --git a/backends/pulse/pulse-backend.c b/backends/pulse/pulse-backend.c index da73396..0494545 100644 --- a/backends/pulse/pulse-backend.c +++ b/backends/pulse/pulse-backend.c @@ -24,11 +24,13 @@ #include <libmatemixer/matemixer-stream.h> #include <pulse/pulseaudio.h> +#include <pulse/ext-stream-restore.h> #include "pulse-backend.h" #include "pulse-connection.h" #include "pulse-device.h" #include "pulse-enums.h" +#include "pulse-ext-stream.h" #include "pulse-stream.h" #include "pulse-sink.h" #include "pulse-sink-input.h" @@ -50,10 +52,8 @@ struct _PulseBackendPrivate MateMixerStream *default_sink; MateMixerStream *default_source; GHashTable *devices; - GHashTable *sinks; - GHashTable *sink_inputs; - GHashTable *sources; - GHashTable *source_outputs; + GHashTable *streams; + GHashTable *ext_streams; MateMixerState state; PulseConnection *connection; }; @@ -85,90 +85,102 @@ G_DEFINE_DYNAMIC_TYPE_EXTENDED (PulseBackend, pulse_backend, G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND, mate_mixer_backend_interface_init)) -static gboolean backend_open (MateMixerBackend *backend); -static void backend_close (MateMixerBackend *backend); - -static MateMixerState backend_get_state (MateMixerBackend *backend); - -static void backend_set_data (MateMixerBackend *backend, - const MateMixerBackendData *data); - -static GList * backend_list_devices (MateMixerBackend *backend); -static GList * backend_list_streams (MateMixerBackend *backend); - -static MateMixerStream *backend_get_default_input_stream (MateMixerBackend *backend); -static gboolean backend_set_default_input_stream (MateMixerBackend *backend, - MateMixerStream *stream); - -static MateMixerStream *backend_get_default_output_stream (MateMixerBackend *backend); -static gboolean backend_set_default_output_stream (MateMixerBackend *backend, - MateMixerStream *stream); - -static void backend_connection_state_cb (PulseConnection *connection, - GParamSpec *pspec, - PulseBackend *pulse); - -static void backend_server_info_cb (PulseConnection *connection, - const pa_server_info *info, - PulseBackend *pulse); - -static void backend_card_info_cb (PulseConnection *connection, - const pa_card_info *info, - PulseBackend *pulse); -static void backend_card_removed_cb (PulseConnection *connection, - guint index, - PulseBackend *pulse); -static void backend_sink_info_cb (PulseConnection *connection, - const pa_sink_info *info, - PulseBackend *pulse); -static void backend_sink_removed_cb (PulseConnection *connection, - guint index, - PulseBackend *pulse); -static void backend_sink_input_info_cb (PulseConnection *connection, - const pa_sink_input_info *info, - PulseBackend *pulse); -static void backend_sink_input_removed_cb (PulseConnection *connection, - guint index, - PulseBackend *pulse); -static void backend_source_info_cb (PulseConnection *connection, - const pa_source_info *info, - PulseBackend *pulse); -static void backend_source_removed_cb (PulseConnection *connection, - guint index, - PulseBackend *pulse); -static void backend_source_output_info_cb (PulseConnection *connection, - const pa_source_output_info *info, - PulseBackend *pulse); -static void backend_source_output_removed_cb (PulseConnection *connection, - guint index, - PulseBackend *pulse); - -static gboolean backend_try_reconnect (PulseBackend *pulse); -static void backend_remove_connect_source (PulseBackend *pulse); - -static void backend_mark_hanging (PulseBackend *pulse); -static void backend_mark_hanging_hash (GHashTable *hash); - -static void backend_remove_hanging (PulseBackend *pulse); -static void backend_remove_hanging_hash (PulseBackend *pulse, - GHashTable *hash); - -static void backend_remove_device (PulseBackend *pulse, - PulseDevice *device); -static void backend_remove_stream (PulseBackend *pulse, - GHashTable *hash, - PulseStream *stream); - -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_names (gpointer key, - gpointer value, - gpointer user_data); +static gboolean pulse_backend_open (MateMixerBackend *backend); +static void pulse_backend_close (MateMixerBackend *backend); + +static MateMixerState pulse_backend_get_state (MateMixerBackend *backend); + +static void pulse_backend_set_data (MateMixerBackend *backend, + const MateMixerBackendData *data); + +static GList * pulse_backend_list_devices (MateMixerBackend *backend); +static GList * pulse_backend_list_streams (MateMixerBackend *backend); +static GList * pulse_backend_list_cached_streams (MateMixerBackend *backend); + +static MateMixerStream *pulse_backend_get_default_input_stream (MateMixerBackend *backend); +static gboolean pulse_backend_set_default_input_stream (MateMixerBackend *backend, + MateMixerStream *stream); + +static MateMixerStream *pulse_backend_get_default_output_stream (MateMixerBackend *backend); +static gboolean pulse_backend_set_default_output_stream (MateMixerBackend *backend, + MateMixerStream *stream); + +static void on_connection_state_notify (PulseConnection *connection, + GParamSpec *pspec, + PulseBackend *pulse); + +static void on_connection_server_info (PulseConnection *connection, + const pa_server_info *info, + PulseBackend *pulse); + +static void on_connection_card_info (PulseConnection *connection, + const pa_card_info *info, + PulseBackend *pulse); +static void on_connection_card_removed (PulseConnection *connection, + guint index, + PulseBackend *pulse); +static void on_connection_sink_info (PulseConnection *connection, + const pa_sink_info *info, + PulseBackend *pulse); +static void on_connection_sink_removed (PulseConnection *connection, + guint index, + PulseBackend *pulse); +static void on_connection_sink_input_info (PulseConnection *connection, + const pa_sink_input_info *info, + PulseBackend *pulse); +static void on_connection_sink_input_removed (PulseConnection *connection, + guint index, + PulseBackend *pulse); +static void on_connection_source_info (PulseConnection *connection, + const pa_source_info *info, + PulseBackend *pulse); +static void on_connection_source_removed (PulseConnection *connection, + guint index, + PulseBackend *pulse); +static void on_connection_source_output_info (PulseConnection *connection, + const pa_source_output_info *info, + PulseBackend *pulse); +static void on_connection_source_output_removed (PulseConnection *connection, + guint index, + PulseBackend *pulse); +static void on_connection_ext_stream_loading (PulseConnection *connection, + PulseBackend *pulse); +static void on_connection_ext_stream_loaded (PulseConnection *connection, + PulseBackend *pulse); +static void on_connection_ext_stream_info (PulseConnection *connection, + const pa_ext_stream_restore_info *info, + PulseBackend *pulse); + +static gboolean connect_source_reconnect (PulseBackend *pulse); +static void connect_source_remove (PulseBackend *pulse); + +static void check_pending_sink (PulseBackend *pulse, + PulseStream *stream); +static void check_pending_source (PulseBackend *pulse, + PulseStream *stream); + +static void mark_hanging (PulseBackend *pulse); +static void mark_hanging_hash (GHashTable *hash); + +static void unmark_hanging (PulseBackend *pulse, + GObject *object); + +static void remove_hanging (PulseBackend *pulse); +static void remove_device (PulseBackend *pulse, + PulseDevice *device); +static void remove_stream (PulseBackend *pulse, + PulseStream *stream); + +static void change_state (PulseBackend *backend, + MateMixerState state); + +static gint compare_devices (gconstpointer a, + gconstpointer b); +static gint compare_streams (gconstpointer a, + gconstpointer b); +static gboolean compare_stream_names (gpointer key, + gpointer value, + gpointer user_data); static MateMixerBackendInfo info; @@ -192,16 +204,17 @@ backend_module_get_info (void) static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) { - iface->open = backend_open; - iface->close = backend_close; - iface->get_state = backend_get_state; - iface->set_data = backend_set_data; - iface->list_devices = backend_list_devices; - iface->list_streams = backend_list_streams; - iface->get_default_input_stream = backend_get_default_input_stream; - iface->set_default_input_stream = backend_set_default_input_stream; - iface->get_default_output_stream = backend_get_default_output_stream; - iface->set_default_output_stream = backend_set_default_output_stream; + iface->open = pulse_backend_open; + iface->close = pulse_backend_close; + iface->get_state = pulse_backend_get_state; + iface->set_data = pulse_backend_set_data; + iface->list_devices = pulse_backend_list_devices; + iface->list_streams = pulse_backend_list_streams; + iface->list_cached_streams = pulse_backend_list_cached_streams; + iface->get_default_input_stream = pulse_backend_get_default_input_stream; + iface->set_default_input_stream = pulse_backend_set_default_input_stream; + iface->get_default_output_stream = pulse_backend_get_default_output_stream; + iface->set_default_output_stream = pulse_backend_set_default_output_stream; } static void @@ -260,40 +273,28 @@ pulse_backend_init (PulseBackend *pulse) PULSE_TYPE_BACKEND, PulseBackendPrivate); - /* These hash tables store PulseDevice and PulseStream instances, the key - * is the PulseAudio index which is not unique across different stream - * types, hence the separate hash tables */ + /* These hash tables store PulseDevice and PulseStream instances */ pulse->priv->devices = 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, - NULL, + pulse->priv->streams = + g_hash_table_new_full (g_int64_hash, + g_int64_equal, + g_free, g_object_unref); - pulse->priv->sink_inputs = - g_hash_table_new_full (g_direct_hash, - g_direct_equal, - NULL, - g_object_unref); - pulse->priv->sources = - g_hash_table_new_full (g_direct_hash, - g_direct_equal, - NULL, - g_object_unref); - pulse->priv->source_outputs = - g_hash_table_new_full (g_direct_hash, - g_direct_equal, - NULL, + pulse->priv->ext_streams = + g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, g_object_unref); } static void pulse_backend_dispose (GObject *object) { - backend_close (MATE_MIXER_BACKEND (object)); + pulse_backend_close (MATE_MIXER_BACKEND (object)); G_OBJECT_CLASS (pulse_backend_parent_class)->dispose (object); } @@ -312,16 +313,14 @@ pulse_backend_finalize (GObject *object) g_free (pulse->priv->server_address); g_hash_table_destroy (pulse->priv->devices); - g_hash_table_destroy (pulse->priv->sinks); - g_hash_table_destroy (pulse->priv->sink_inputs); - g_hash_table_destroy (pulse->priv->sources); - g_hash_table_destroy (pulse->priv->source_outputs); + g_hash_table_destroy (pulse->priv->streams); + g_hash_table_destroy (pulse->priv->ext_streams); G_OBJECT_CLASS (pulse_backend_parent_class)->finalize (object); } static gboolean -backend_open (MateMixerBackend *backend) +pulse_backend_open (MateMixerBackend *backend) { PulseBackend *pulse; PulseConnection *connection; @@ -345,75 +344,87 @@ backend_open (MateMixerBackend *backend) * but it sets up the PulseAudio structures, which might fail in an * unlikely case */ if (G_UNLIKELY (connection == NULL)) { - backend_change_state (pulse, MATE_MIXER_STATE_FAILED); + change_state (pulse, MATE_MIXER_STATE_FAILED); return FALSE; } - g_signal_connect (connection, + g_signal_connect (G_OBJECT (connection), "notify::state", - G_CALLBACK (backend_connection_state_cb), + G_CALLBACK (on_connection_state_notify), pulse); - g_signal_connect (connection, + g_signal_connect (G_OBJECT (connection), "server-info", - G_CALLBACK (backend_server_info_cb), + G_CALLBACK (on_connection_server_info), pulse); - g_signal_connect (connection, + g_signal_connect (G_OBJECT (connection), "card-info", - G_CALLBACK (backend_card_info_cb), + G_CALLBACK (on_connection_card_info), pulse); - g_signal_connect (connection, + g_signal_connect (G_OBJECT (connection), "card-removed", - G_CALLBACK (backend_card_removed_cb), + G_CALLBACK (on_connection_card_removed), pulse); - g_signal_connect (connection, + g_signal_connect (G_OBJECT (connection), "sink-info", - G_CALLBACK (backend_sink_info_cb), + G_CALLBACK (on_connection_sink_info), pulse); - g_signal_connect (connection, + g_signal_connect (G_OBJECT (connection), "sink-removed", - G_CALLBACK (backend_sink_removed_cb), + G_CALLBACK (on_connection_sink_removed), pulse); - g_signal_connect (connection, + g_signal_connect (G_OBJECT (connection), "sink-input-info", - G_CALLBACK (backend_sink_input_info_cb), + G_CALLBACK (on_connection_sink_input_info), pulse); - g_signal_connect (connection, + g_signal_connect (G_OBJECT (connection), "sink-input-removed", - G_CALLBACK (backend_sink_input_removed_cb), + G_CALLBACK (on_connection_sink_input_removed), pulse); - g_signal_connect (connection, + g_signal_connect (G_OBJECT (connection), "source-info", - G_CALLBACK (backend_source_info_cb), + G_CALLBACK (on_connection_source_info), pulse); - g_signal_connect (connection, + g_signal_connect (G_OBJECT (connection), "source-removed", - G_CALLBACK (backend_source_removed_cb), + G_CALLBACK (on_connection_source_removed), pulse); - g_signal_connect (connection, + g_signal_connect (G_OBJECT (connection), "source-output-info", - G_CALLBACK (backend_source_output_info_cb), + G_CALLBACK (on_connection_source_output_info), pulse); - g_signal_connect (connection, + g_signal_connect (G_OBJECT (connection), "source-output-removed", - G_CALLBACK (backend_source_output_removed_cb), + G_CALLBACK (on_connection_source_output_removed), + pulse); + g_signal_connect (G_OBJECT (connection), + "ext-stream-loading", + G_CALLBACK (on_connection_ext_stream_loading), + pulse); + g_signal_connect (G_OBJECT (connection), + "ext-stream-loaded", + G_CALLBACK (on_connection_ext_stream_loaded), pulse); + g_signal_connect (G_OBJECT (connection), + "ext-stream-info", + G_CALLBACK (on_connection_ext_stream_info), + pulse); + + change_state (pulse, MATE_MIXER_STATE_CONNECTING); /* Connect to the PulseAudio server, this might fail either instantly or * asynchronously, for example when remote connection timeouts */ - if (!pulse_connection_connect (connection, FALSE)) { + if (pulse_connection_connect (connection, FALSE) == FALSE) { g_object_unref (connection); - backend_change_state (pulse, MATE_MIXER_STATE_FAILED); + change_state (pulse, MATE_MIXER_STATE_FAILED); return FALSE; } pulse->priv->connection = connection; - - backend_change_state (pulse, MATE_MIXER_STATE_CONNECTING); return TRUE; } static void -backend_close (MateMixerBackend *backend) +pulse_backend_close (MateMixerBackend *backend) { PulseBackend *pulse; @@ -421,24 +432,29 @@ backend_close (MateMixerBackend *backend) pulse = PULSE_BACKEND (backend); - backend_remove_connect_source (pulse); + connect_source_remove (pulse); + + if (pulse->priv->connection != NULL) { + g_signal_handlers_disconnect_by_data (G_OBJECT (pulse->priv->connection), + pulse); + + g_clear_object (&pulse->priv->connection); + } - // XXX disconnect from notifies - g_clear_object (&pulse->priv->connection); g_clear_object (&pulse->priv->default_sink); g_clear_object (&pulse->priv->default_source); g_hash_table_remove_all (pulse->priv->devices); - g_hash_table_remove_all (pulse->priv->sinks); - g_hash_table_remove_all (pulse->priv->sink_inputs); - g_hash_table_remove_all (pulse->priv->sources); - g_hash_table_remove_all (pulse->priv->source_outputs); + g_hash_table_remove_all (pulse->priv->streams); + g_hash_table_remove_all (pulse->priv->ext_streams); - backend_change_state (pulse, MATE_MIXER_STATE_IDLE); + pulse->priv->connected_once = FALSE; + + change_state (pulse, MATE_MIXER_STATE_IDLE); } static MateMixerState -backend_get_state (MateMixerBackend *backend) +pulse_backend_get_state (MateMixerBackend *backend) { g_return_val_if_fail (PULSE_IS_BACKEND (backend), MATE_MIXER_STATE_UNKNOWN); @@ -446,7 +462,7 @@ backend_get_state (MateMixerBackend *backend) } static void -backend_set_data (MateMixerBackend *backend, const MateMixerBackendData *data) +pulse_backend_set_data (MateMixerBackend *backend, const MateMixerBackendData *data) { PulseBackend *pulse; @@ -469,45 +485,61 @@ backend_set_data (MateMixerBackend *backend, const MateMixerBackendData *data) } static GList * -backend_list_devices (MateMixerBackend *backend) +pulse_backend_list_devices (MateMixerBackend *backend) { GList *list; g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); - /* Always create a new current list, caching is done in the main library */ + /* Convert the hash table to a sorted linked list, this list is expected + * to be cached in the main library */ list = g_hash_table_get_values (PULSE_BACKEND (backend)->priv->devices); + if (list != NULL) { + g_list_foreach (list, (GFunc) g_object_ref, NULL); - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return g_list_sort (list, backend_compare_devices); + return g_list_sort (list, compare_devices); + } + return NULL; } static GList * -backend_list_streams (MateMixerBackend *backend) +pulse_backend_list_streams (MateMixerBackend *backend) { - GList *list; - PulseBackend *pulse; + GList *list; g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); - pulse = PULSE_BACKEND (backend); + /* Convert the hash table to a sorted linked list, this list is expected + * to be cached in the main library */ + list = g_hash_table_get_values (PULSE_BACKEND (backend)->priv->streams); + if (list != NULL) { + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return g_list_sort (list, compare_streams); + } + return NULL; +} - /* 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, - g_hash_table_get_values (pulse->priv->sources)); - list = g_list_concat (list, - g_hash_table_get_values (pulse->priv->source_outputs)); +static GList * +pulse_backend_list_cached_streams (MateMixerBackend *backend) +{ + GList *list; - g_list_foreach (list, (GFunc) g_object_ref, NULL); + g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); + + /* Convert the hash table to a sorted linked list, this list is expected + * to be cached in the main library */ + list = g_hash_table_get_values (PULSE_BACKEND (backend)->priv->ext_streams); + if (list != NULL) { + g_list_foreach (list, (GFunc) g_object_ref, NULL); - return g_list_sort (list, backend_compare_streams); + return g_list_sort (list, compare_streams); + } + return NULL; } static MateMixerStream * -backend_get_default_input_stream (MateMixerBackend *backend) +pulse_backend_get_default_input_stream (MateMixerBackend *backend) { g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); @@ -515,30 +547,38 @@ backend_get_default_input_stream (MateMixerBackend *backend) } static gboolean -backend_set_default_input_stream (MateMixerBackend *backend, MateMixerStream *stream) +pulse_backend_set_default_input_stream (MateMixerBackend *backend, + MateMixerStream *stream) { PulseBackend *pulse; + const gchar *name; g_return_val_if_fail (PULSE_IS_BACKEND (backend), FALSE); - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_SOURCE (stream), FALSE); pulse = PULSE_BACKEND (backend); - if (G_UNLIKELY (!PULSE_IS_SOURCE (stream))) { - g_warn_if_reached (); + name = mate_mixer_stream_get_name (stream); + if (pulse_connection_set_default_source (pulse->priv->connection, name) == FALSE) return FALSE; - } - if (!pulse_connection_set_default_source (pulse->priv->connection, - mate_mixer_stream_get_name (stream))) - return FALSE; + if (pulse->priv->default_source != NULL) + g_object_unref (pulse->priv->default_source); + + pulse->priv->default_source = g_object_ref (stream); + + /* We might be in the process of setting a default source for which the details + * are not yet known, make sure the change does not happen */ + g_object_set_data (G_OBJECT (pulse), + "backend-pending-source", + NULL); g_object_notify (G_OBJECT (pulse), "default-input"); return TRUE; } static MateMixerStream * -backend_get_default_output_stream (MateMixerBackend *backend) +pulse_backend_get_default_output_stream (MateMixerBackend *backend) { g_return_val_if_fail (PULSE_IS_BACKEND (backend), NULL); @@ -546,55 +586,64 @@ backend_get_default_output_stream (MateMixerBackend *backend) } static gboolean -backend_set_default_output_stream (MateMixerBackend *backend, MateMixerStream *stream) +pulse_backend_set_default_output_stream (MateMixerBackend *backend, + MateMixerStream *stream) { PulseBackend *pulse; + const gchar *name; g_return_val_if_fail (PULSE_IS_BACKEND (backend), FALSE); - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_SINK (stream), FALSE); pulse = PULSE_BACKEND (backend); - if (G_UNLIKELY (!PULSE_IS_SINK (stream))) { - g_warn_if_reached (); + name = mate_mixer_stream_get_name (stream); + if (pulse_connection_set_default_sink (pulse->priv->connection, name) == FALSE) return FALSE; - } - if (!pulse_connection_set_default_sink (pulse->priv->connection, - mate_mixer_stream_get_name (stream))) - return FALSE; + if (pulse->priv->default_sink != NULL) + g_object_unref (pulse->priv->default_sink); + + pulse->priv->default_sink = g_object_ref (stream); + + /* We might be in the process of setting a default sink for which the details + * are not yet known, make sure the change does not happen */ + g_object_set_data (G_OBJECT (pulse), + "backend-pending-sink", + NULL); g_object_notify (G_OBJECT (pulse), "default-output"); return TRUE; } static void -backend_connection_state_cb (PulseConnection *connection, - GParamSpec *pspec, - PulseBackend *pulse) +on_connection_state_notify (PulseConnection *connection, + GParamSpec *pspec, + PulseBackend *pulse) { PulseConnectionState state = pulse_connection_get_state (connection); switch (state) { case PULSE_CONNECTION_DISCONNECTED: - if (pulse->priv->connected_once) { + if (pulse->priv->connected_once == TRUE) { /* We managed to connect once before, try to reconnect and if it * fails immediately, use a timeout source. * All current devices and streams are marked as hanging as it is - * unknown whether they are still available, stream callbacks will - * unmark them and remaining unavailable streams will be removed - * when the CONNECTED state is reached. */ - backend_mark_hanging (pulse); - backend_change_state (pulse, MATE_MIXER_STATE_CONNECTING); - - if (!pulse->priv->connect_source && - !pulse_connection_connect (connection, TRUE)) { + * unknown whether they are still available. + * Stream callbacks will unmark available streams and remaining + * unavailable streams will be removed when the CONNECTED state + * is reached. */ + mark_hanging (pulse); + change_state (pulse, MATE_MIXER_STATE_CONNECTING); + + if (pulse->priv->connect_source == NULL && + pulse_connection_connect (connection, TRUE) == FALSE) { pulse->priv->connect_source = g_timeout_source_new (200); g_source_set_callback (pulse->priv->connect_source, - (GSourceFunc) backend_try_reconnect, + (GSourceFunc) connect_source_reconnect, pulse, - (GDestroyNotify) backend_remove_connect_source); + (GDestroyNotify) connect_source_remove); g_source_attach (pulse->priv->connect_source, g_main_context_get_thread_default ()); @@ -603,30 +652,30 @@ backend_connection_state_cb (PulseConnection *connection, } /* First connection attempt has failed */ - backend_change_state (pulse, MATE_MIXER_STATE_FAILED); + change_state (pulse, MATE_MIXER_STATE_FAILED); break; case PULSE_CONNECTION_CONNECTING: case PULSE_CONNECTION_AUTHORIZING: case PULSE_CONNECTION_LOADING: - backend_change_state (pulse, MATE_MIXER_STATE_CONNECTING); + change_state (pulse, MATE_MIXER_STATE_CONNECTING); break; case PULSE_CONNECTION_CONNECTED: - if (pulse->priv->connected_once) - backend_remove_hanging (pulse); + if (pulse->priv->connected_once == TRUE) + remove_hanging (pulse); else pulse->priv->connected_once = TRUE; - backend_change_state (pulse, MATE_MIXER_STATE_READY); + change_state (pulse, MATE_MIXER_STATE_READY); break; } } static void -backend_server_info_cb (PulseConnection *connection, - const pa_server_info *info, - PulseBackend *pulse) +on_connection_server_info (PulseConnection *connection, + const pa_server_info *info, + PulseBackend *pulse) { const gchar *name_source = NULL; const gchar *name_sink = NULL; @@ -634,55 +683,94 @@ backend_server_info_cb (PulseConnection *connection, if (pulse->priv->default_source != NULL) name_source = mate_mixer_stream_get_name (pulse->priv->default_source); - // XXX - // default input might be monitor !!! - - if (g_strcmp0 (name_source, info->default_source_name)) { + if (g_strcmp0 (name_source, info->default_source_name) != 0) { if (pulse->priv->default_source != NULL) 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_names, + MateMixerStream *stream = g_hash_table_find (pulse->priv->streams, + compare_stream_names, (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)) { + /* It is possible that we are unaware of the default stream, either + * because the stream details have not arrived yet, or because we chose + * to ignore the stream. + * When this happens, remember the name of the stream and wait for the + * stream info callback. */ + if (stream != NULL) { pulse->priv->default_source = g_object_ref (stream); - g_debug ("Default input stream changed to %s", info->default_source_name); + g_object_set_data (G_OBJECT (pulse), + "backend-pending-source", + NULL); - g_object_notify (G_OBJECT (pulse), "default-input"); - } else - g_debug ("Default input stream %s not yet known", + g_debug ("Default input stream changed to %s", info->default_source_name); + } else { + g_debug ("Default input stream changed to unknown stream %s", info->default_source_name); - } + + g_object_set_data_full (G_OBJECT (pulse), + "backend-pending-source", + g_strdup (info->default_source_name), + g_free); + + /* In most cases (for example changing profile) the stream info + * arrives by itself, but do not rely on it and request it explicitely. + * In the meantime, keep the default stream set to NULL, which is + * important as we cannot guarantee that the info arrives and we use it. */ + pulse_connection_load_source_info_name (pulse->priv->connection, + info->default_source_name); + } + } else + g_debug ("Default input stream unset"); + + g_object_notify (G_OBJECT (pulse), "default-input"); } if (pulse->priv->default_sink != NULL) name_sink = mate_mixer_stream_get_name (pulse->priv->default_sink); - if (g_strcmp0 (name_sink, info->default_sink_name)) { + if (g_strcmp0 (name_sink, info->default_sink_name) != 0) { if (pulse->priv->default_sink != NULL) 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_names, + MateMixerStream *stream = g_hash_table_find (pulse->priv->streams, + compare_stream_names, (gpointer) info->default_sink_name); - if (G_LIKELY (stream != NULL)) { + + /* It is possible that we are unaware of the default stream, either + * because the stream details have not arrived yet, or because we chose + * to ignore the stream. + * When this happens, remember the name of the stream and wait for the + * stream info callback. */ + if (stream != NULL) { pulse->priv->default_sink = g_object_ref (stream); + g_object_set_data (G_OBJECT (pulse), + "backend-pending-sink", + NULL); + 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", + } else { + g_debug ("Default output stream changed to unknown stream %s", info->default_sink_name); - } + + g_object_set_data_full (G_OBJECT (pulse), + "backend-pending-sink", + g_strdup (info->default_sink_name), + g_free); + + /* In most cases (for example changing profile) the stream info + * arrives by itself, but do not rely on it and request it explicitely. + * In the meantime, keep the default stream set to NULL, which is + * important as we cannot guarantee that the info arrives and we use it. */ + pulse_connection_load_sink_info_name (pulse->priv->connection, + info->default_sink_name); + } + } else + g_debug ("Default output stream unset"); + + g_object_notify (G_OBJECT (pulse), "default-output"); } if (pulse->priv->state != MATE_MIXER_STATE_READY) @@ -693,401 +781,533 @@ backend_server_info_cb (PulseConnection *connection, } static void -backend_card_info_cb (PulseConnection *connection, - const pa_card_info *info, - PulseBackend *pulse) +on_connection_card_info (PulseConnection *connection, + const pa_card_info *info, + PulseBackend *pulse) { - gpointer p; PulseDevice *device; - p = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (info->index)); - if (p == NULL) { + device = g_hash_table_lookup (pulse->priv->devices, GUINT_TO_POINTER (info->index)); + if (device == NULL) { device = pulse_device_new (connection, info); - if (G_UNLIKELY (device == NULL)) return; - g_hash_table_insert (pulse->priv->devices, - GINT_TO_POINTER (info->index), - device); + g_hash_table_insert (pulse->priv->devices, GUINT_TO_POINTER (info->index), device); + + g_signal_emit_by_name (G_OBJECT (pulse), + "device-added", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); } else { - device = PULSE_DEVICE (p); pulse_device_update (device, info); - } - if (pulse->priv->connected_once) { /* The object might be hanging if reconnecting is in progress, remove the * hanging flag to prevent it from being removed when connected */ - if (pulse->priv->state != MATE_MIXER_STATE_READY) - g_object_steal_data (G_OBJECT (device), "hanging"); - - g_signal_emit_by_name (G_OBJECT (pulse), - (p == NULL) - ? "device-added" - : "device-changed", - mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + unmark_hanging (pulse, G_OBJECT (device)); } } static void -backend_card_removed_cb (PulseConnection *connection, - guint index, - PulseBackend *pulse) +on_connection_card_removed (PulseConnection *connection, + guint index, + PulseBackend *pulse) { - gpointer p; + PulseDevice *device; - p = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (index)); - if (G_UNLIKELY (p == NULL)) + device = g_hash_table_lookup (pulse->priv->devices, GUINT_TO_POINTER (index)); + if (G_UNLIKELY (device == NULL)) return; - backend_remove_device (pulse, PULSE_DEVICE (p)); + remove_device (pulse, device); } +/* PulseAudio uses 32-bit integers as indices for sinks, sink inputs, sources and + * source inputs, these indices are not unique among the different kinds of streams, + * but we want to keep all of them in a single hash table. Allow this by using 64-bit + * hash table keys, use the lower 32 bits for the PulseAudio index and some of the + * higher bits to indicate what kind of stream it is. */ +enum { + HASH_BIT_SINK = (1ULL << 63), + HASH_BIT_SINK_INPUT = (1ULL << 62), + HASH_BIT_SOURCE = (1ULL << 61), + HASH_BIT_SOURCE_OUTPUT = (1ULL << 60) +}; +#define HASH_ID_SINK(idx) (((idx) & 0xffffffff) | HASH_BIT_SINK) +#define HASH_ID_SINK_INPUT(idx) (((idx) & 0xffffffff) | HASH_BIT_SINK_INPUT) +#define HASH_ID_SOURCE(idx) (((idx) & 0xffffffff) | HASH_BIT_SOURCE) +#define HASH_ID_SOURCE_OUTPUT(idx) (((idx) & 0xffffffff) | HASH_BIT_SOURCE_OUTPUT) + static void -backend_sink_info_cb (PulseConnection *connection, - const pa_sink_info *info, - PulseBackend *pulse) +on_connection_sink_info (PulseConnection *connection, + const pa_sink_info *info, + PulseBackend *pulse) { PulseDevice *device = NULL; PulseStream *stream; - gpointer p = NULL; + gint64 index = HASH_ID_SINK (info->index); if (info->card != PA_INVALID_INDEX) - p = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (info->card)); - if (p) - device = PULSE_DEVICE (p); + device = g_hash_table_lookup (pulse->priv->devices, GUINT_TO_POINTER (info->card)); - p = g_hash_table_lookup (pulse->priv->sinks, GINT_TO_POINTER (info->index)); - if (p == NULL) { + stream = g_hash_table_lookup (pulse->priv->streams, &index); + if (stream == NULL) { stream = pulse_sink_new (connection, info, device); - if (G_UNLIKELY (stream == NULL)) return; - g_hash_table_insert (pulse->priv->sinks, - GINT_TO_POINTER (info->index), - stream); + g_hash_table_insert (pulse->priv->streams, g_memdup (&index, 8), stream); + + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-added", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + + /* We might be waiting for this sink to set it as the default */ + check_pending_sink (pulse, stream); } else { - stream = PULSE_STREAM (p); pulse_sink_update (stream, info, device); - } - if (pulse->priv->connected_once) { /* The object might be hanging if reconnecting is in progress, remove the * hanging flag to prevent it from being removed when connected */ - if (pulse->priv->state != MATE_MIXER_STATE_READY) - g_object_steal_data (G_OBJECT (stream), "hanging"); - - g_signal_emit_by_name (G_OBJECT (pulse), - (p == NULL) - ? "stream-added" - : "stream-changed", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + unmark_hanging (pulse, G_OBJECT (stream)); } } static void -backend_sink_removed_cb (PulseConnection *connection, - guint index, - PulseBackend *pulse) +on_connection_sink_removed (PulseConnection *connection, + guint idx, + PulseBackend *pulse) { - gpointer p; + PulseStream *stream; + gint64 index = HASH_ID_SINK (idx); - p = g_hash_table_lookup (pulse->priv->sinks, GINT_TO_POINTER (index)); - if (G_UNLIKELY (p == NULL)) + stream = g_hash_table_lookup (pulse->priv->streams, &index); + if (G_UNLIKELY (stream == NULL)) return; - backend_remove_stream (pulse, pulse->priv->sinks, PULSE_STREAM (p)); + remove_stream (pulse, stream); } static void -backend_sink_input_info_cb (PulseConnection *connection, - const pa_sink_input_info *info, - PulseBackend *pulse) +on_connection_sink_input_info (PulseConnection *connection, + const pa_sink_input_info *info, + PulseBackend *pulse) { PulseStream *stream; - gpointer p; - gpointer parent = NULL; + PulseStream *parent = NULL; + gint64 index; + + if (G_LIKELY (info->sink != PA_INVALID_INDEX)) { + index = HASH_ID_SINK (info->sink); - if (G_LIKELY (info->sink)) { - parent = g_hash_table_lookup (pulse->priv->sinks, GINT_TO_POINTER (info->sink)); + parent = g_hash_table_lookup (pulse->priv->streams, &index); if (G_UNLIKELY (parent == NULL)) g_debug ("Unknown parent %d of PulseAudio sink input %s", info->sink, info->name); } - p = g_hash_table_lookup (pulse->priv->sink_inputs, GINT_TO_POINTER (info->index)); - if (p == NULL) { - stream = pulse_sink_input_new (connection, info, parent); + index = HASH_ID_SINK_INPUT (info->index); + stream = g_hash_table_lookup (pulse->priv->streams, &index); + if (stream == NULL) { + stream = pulse_sink_input_new (connection, info, parent); if (G_UNLIKELY (stream == NULL)) return; - g_hash_table_insert (pulse->priv->sink_inputs, - GINT_TO_POINTER (info->index), - stream); + g_hash_table_insert (pulse->priv->streams, g_memdup (&index, 8), stream); + + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-added", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); } else { - stream = PULSE_STREAM (p); pulse_sink_input_update (stream, info, parent); - } - if (pulse->priv->connected_once) { /* The object might be hanging if reconnecting is in progress, remove the * hanging flag to prevent it from being removed when connected */ - if (pulse->priv->state != MATE_MIXER_STATE_READY) - g_object_steal_data (G_OBJECT (stream), "hanging"); - - g_signal_emit_by_name (G_OBJECT (pulse), - (p == NULL) - ? "stream-added" - : "stream-changed", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + unmark_hanging (pulse, G_OBJECT (stream)); } } static void -backend_sink_input_removed_cb (PulseConnection *connection, - guint index, - PulseBackend *pulse) +on_connection_sink_input_removed (PulseConnection *connection, + guint idx, + PulseBackend *pulse) { - gpointer p; + PulseStream *stream; + gint64 index = HASH_ID_SINK_INPUT (idx); - p = g_hash_table_lookup (pulse->priv->sink_inputs, GINT_TO_POINTER (index)); - if (G_UNLIKELY (p == NULL)) + stream = g_hash_table_lookup (pulse->priv->streams, &index); + if (G_UNLIKELY (stream == NULL)) return; - backend_remove_stream (pulse, pulse->priv->sink_inputs, PULSE_STREAM (p)); + remove_stream (pulse, stream); } static void -backend_source_info_cb (PulseConnection *connection, - const pa_source_info *info, - PulseBackend *pulse) +on_connection_source_info (PulseConnection *connection, + const pa_source_info *info, + PulseBackend *pulse) { PulseDevice *device = NULL; PulseStream *stream; - gpointer p = NULL; + gint64 index = HASH_ID_SOURCE (info->index); /* Skip monitor streams */ if (info->monitor_of_sink != PA_INVALID_INDEX) return; if (info->card != PA_INVALID_INDEX) - p = g_hash_table_lookup (pulse->priv->devices, GINT_TO_POINTER (info->card)); - if (p) - device = PULSE_DEVICE (p); + device = g_hash_table_lookup (pulse->priv->devices, GUINT_TO_POINTER (info->card)); - p = g_hash_table_lookup (pulse->priv->sources, GINT_TO_POINTER (info->index)); - if (p == NULL) { + stream = g_hash_table_lookup (pulse->priv->streams, &index); + if (stream == NULL) { stream = pulse_source_new (connection, info, device); - if (G_UNLIKELY (stream == NULL)) return; - g_hash_table_insert (pulse->priv->sources, - GINT_TO_POINTER (info->index), - stream); + g_hash_table_insert (pulse->priv->streams, g_memdup (&index, 8), stream); + + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-added", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + + /* We might be waiting for this source to set it as the default */ + check_pending_source (pulse, stream); } else { - stream = PULSE_STREAM (p); pulse_source_update (stream, info, device); - } - if (pulse->priv->connected_once) { /* The object might be hanging if reconnecting is in progress, remove the * hanging flag to prevent it from being removed when connected */ - if (pulse->priv->state != MATE_MIXER_STATE_READY) - g_object_steal_data (G_OBJECT (stream), "hanging"); - - g_signal_emit_by_name (G_OBJECT (pulse), - (p == NULL) - ? "stream-added" - : "stream-changed", - mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + unmark_hanging (pulse, G_OBJECT (stream)); } } static void -backend_source_removed_cb (PulseConnection *connection, - guint index, - PulseBackend *pulse) +on_connection_source_removed (PulseConnection *connection, + guint idx, + PulseBackend *pulse) { - gpointer p; + PulseStream *stream; + gint64 index = HASH_ID_SOURCE (idx); - p = g_hash_table_lookup (pulse->priv->sources, GINT_TO_POINTER (index)); - if (G_UNLIKELY (p == NULL)) + stream = g_hash_table_lookup (pulse->priv->streams, &index); + if (G_UNLIKELY (stream == NULL)) return; - backend_remove_stream (pulse, pulse->priv->sources, PULSE_STREAM (p)); + remove_stream (pulse, stream); } static void -backend_source_output_info_cb (PulseConnection *connection, - const pa_source_output_info *info, - PulseBackend *pulse) +on_connection_source_output_info (PulseConnection *connection, + const pa_source_output_info *info, + PulseBackend *pulse) { PulseStream *stream; - gpointer p; - gpointer parent = NULL; + PulseStream *parent = NULL; + gint64 index; - if (G_LIKELY (info->source)) { - parent = g_hash_table_lookup (pulse->priv->sources, GINT_TO_POINTER (info->source)); + if (G_LIKELY (info->source != PA_INVALID_INDEX)) { + index = HASH_ID_SOURCE (info->source); - /* Probably a monitor source that we have skipped */ + /* Most likely a monitor source that we have skipped */ + parent = g_hash_table_lookup (pulse->priv->streams, &index); if (parent == NULL) return; } - p = g_hash_table_lookup (pulse->priv->source_outputs, GINT_TO_POINTER (info->index)); - if (p == NULL) { + index = HASH_ID_SOURCE_OUTPUT (info->index); + + stream = g_hash_table_lookup (pulse->priv->streams, &index); + if (stream == NULL) { stream = pulse_source_output_new (connection, info, parent); if (G_UNLIKELY (stream == NULL)) return; - g_hash_table_insert (pulse->priv->source_outputs, - GINT_TO_POINTER (info->index), - stream); + g_hash_table_insert (pulse->priv->streams, g_memdup (&index, 8), stream); + + g_signal_emit_by_name (G_OBJECT (pulse), + "stream-added", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); } else { - stream = PULSE_STREAM (p); pulse_source_output_update (stream, info, parent); - } - if (pulse->priv->connected_once) { /* The object might be hanging if reconnecting is in progress, remove the * hanging flag to prevent it from being removed when connected */ - if (pulse->priv->state != MATE_MIXER_STATE_READY) - g_object_steal_data (G_OBJECT (stream), "hanging"); + unmark_hanging (pulse, G_OBJECT (stream)); + } +} + +static void +on_connection_source_output_removed (PulseConnection *connection, + guint idx, + PulseBackend *pulse) +{ + PulseStream *stream; + gint64 index = HASH_ID_SOURCE_OUTPUT (idx); + + stream = g_hash_table_lookup (pulse->priv->streams, &index); + if (G_UNLIKELY (stream == NULL)) + return; + + remove_stream (pulse, stream); +} + +static void +on_connection_ext_stream_info (PulseConnection *connection, + const pa_ext_stream_restore_info *info, + PulseBackend *pulse) +{ + PulseStream *stream; + PulseStream *parent = NULL; + + if (G_LIKELY (info->device != NULL)) + parent = g_hash_table_find (pulse->priv->streams, compare_stream_names, + (gpointer) info->device); + + stream = g_hash_table_lookup (pulse->priv->ext_streams, info->name); + if (stream == NULL) { + stream = pulse_ext_stream_new (connection, info, parent); + if (G_UNLIKELY (stream == NULL)) + return; + + g_hash_table_insert (pulse->priv->ext_streams, g_strdup (info->name), stream); g_signal_emit_by_name (G_OBJECT (pulse), - (p == NULL) - ? "stream-added" - : "stream-changed", + "cached-stream-added", mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); + } else { + pulse_ext_stream_update (stream, info, parent); + + /* The object might be hanging if ext-streams are being loaded, remove + * the hanging flag to prevent it from being removed */ + unmark_hanging (pulse, G_OBJECT (stream)); } } static void -backend_source_output_removed_cb (PulseConnection *connection, - guint index, - PulseBackend *pulse) +on_connection_ext_stream_loading (PulseConnection *connection, PulseBackend *pulse) { - gpointer p; + mark_hanging_hash (pulse->priv->ext_streams); +} - p = g_hash_table_lookup (pulse->priv->source_outputs, GINT_TO_POINTER (index)); - if (G_UNLIKELY (p == NULL)) - return; +static void +on_connection_ext_stream_loaded (PulseConnection *connection, PulseBackend *pulse) +{ + GHashTableIter iter; + gpointer value; + + g_hash_table_iter_init (&iter, pulse->priv->ext_streams); - backend_remove_stream (pulse, pulse->priv->source_outputs, PULSE_STREAM (p)); + while (g_hash_table_iter_next (&iter, NULL, &value) == TRUE) { + guint hanging = + GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (value), "backend-hanging")); + + if (hanging == 1) { + gchar *name = g_strdup ((const gchar *) value); + + g_hash_table_remove (pulse->priv->ext_streams, (gconstpointer) name); + g_signal_emit_by_name (G_OBJECT (pulse), + "cached-stream-removed", + name); + g_free (name); + } + } } static gboolean -backend_try_reconnect (PulseBackend *pulse) +connect_source_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, TRUE); + if (pulse_connection_connect (pulse->priv->connection, TRUE) == TRUE) { + connect_source_remove (pulse); + return FALSE; + } + return TRUE; } static void -backend_remove_connect_source (PulseBackend *pulse) +connect_source_remove (PulseBackend *pulse) { g_clear_pointer (&pulse->priv->connect_source, g_source_unref); } static void -backend_mark_hanging (PulseBackend *pulse) +check_pending_sink (PulseBackend *pulse, PulseStream *stream) { - backend_mark_hanging_hash (pulse->priv->devices); - backend_mark_hanging_hash (pulse->priv->sinks); - backend_mark_hanging_hash (pulse->priv->sink_inputs); - backend_mark_hanging_hash (pulse->priv->sources); - backend_mark_hanging_hash (pulse->priv->source_outputs); + const gchar *pending; + const gchar *name; + + /* See if the currently added sream matches the default input stream + * we are waiting for */ + pending = g_object_get_data (G_OBJECT (pulse), "backend-pending-sink"); + if (pending == NULL) + return; + + name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)); + if (g_strcmp0 (pending, name) != 0) + return; + + pulse->priv->default_sink = g_object_ref (stream); + g_object_set_data (G_OBJECT (pulse), + "backend-pending-sink", + NULL); + + g_debug ("Default output stream changed to pending stream %s", name); + + g_object_notify (G_OBJECT (pulse), "default-output"); } static void -backend_mark_hanging_hash (GHashTable *hash) +check_pending_source (PulseBackend *pulse, PulseStream *stream) { - GHashTableIter iter; - gpointer value; + const gchar *pending; + const gchar *name; - g_hash_table_iter_init (&iter, hash); + /* See if the currently added sream matches the default input stream + * we are waiting for */ + pending = g_object_get_data (G_OBJECT (pulse), "backend-pending-source"); + if (pending == NULL) + return; + + name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)); + if (g_strcmp0 (pending, name) != 0) + return; - while (g_hash_table_iter_next (&iter, NULL, &value)) - g_object_set_data (G_OBJECT (value), "hanging", GINT_TO_POINTER (1)); + pulse->priv->default_source = g_object_ref (stream); + g_object_set_data (G_OBJECT (pulse), + "backend-pending-source", + NULL); + + g_debug ("Default input stream changed to pending stream %s", name); + + g_object_notify (G_OBJECT (pulse), "default-input"); +} + +static void +mark_hanging (PulseBackend *pulse) +{ + /* Mark devices and streams as hanging, ext-streams are handled separately */ + mark_hanging_hash (pulse->priv->devices); + mark_hanging_hash (pulse->priv->streams); } static void -backend_remove_hanging (PulseBackend *pulse) +mark_hanging_hash (GHashTable *hash) { GHashTableIter iter; gpointer value; - g_hash_table_iter_init (&iter, pulse->priv->devices); + g_hash_table_iter_init (&iter, hash); - while (g_hash_table_iter_next (&iter, NULL, &value)) - if (g_object_get_data (G_OBJECT (value), "hanging")) - backend_remove_device (pulse, PULSE_DEVICE (value)); + while (g_hash_table_iter_next (&iter, NULL, &value) == TRUE) + g_object_set_data (G_OBJECT (value), "backend-hanging", GUINT_TO_POINTER (1)); +} + +static void +unmark_hanging (PulseBackend *pulse, GObject *object) +{ + if (pulse->priv->connected_once == FALSE) + return; + if (pulse->priv->state == MATE_MIXER_STATE_READY) + return; - backend_remove_hanging_hash (pulse, pulse->priv->sinks); - backend_remove_hanging_hash (pulse, pulse->priv->sink_inputs); - backend_remove_hanging_hash (pulse, pulse->priv->sources); - backend_remove_hanging_hash (pulse, pulse->priv->source_outputs); + g_object_steal_data (object, "backend-hanging"); } static void -backend_remove_hanging_hash (PulseBackend *pulse, GHashTable *hash) +remove_hanging (PulseBackend *pulse) { GHashTableIter iter; gpointer value; - g_hash_table_iter_init (&iter, hash); + g_hash_table_iter_init (&iter, pulse->priv->devices); + + while (g_hash_table_iter_next (&iter, NULL, &value) == TRUE) { + guint hanging = + GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (value), "backend-hanging")); - while (g_hash_table_iter_next (&iter, NULL, &value)) - if (g_object_get_data (G_OBJECT (value), "hanging")) - backend_remove_stream (pulse, hash, PULSE_STREAM (value)); + if (hanging == 1) + remove_device (pulse, PULSE_DEVICE (value)); + } + + g_hash_table_iter_init (&iter, pulse->priv->streams); + + while (g_hash_table_iter_next (&iter, NULL, &value) == TRUE) { + guint hanging = + GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (value), "backend-hanging")); + + if (hanging == 1) + remove_stream (pulse, PULSE_STREAM (value)); + } } static void -backend_remove_device (PulseBackend *pulse, PulseDevice *device) +remove_device (PulseBackend *pulse, PulseDevice *device) { gchar *name; name = g_strdup (mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); g_hash_table_remove (pulse->priv->devices, - GINT_TO_POINTER (pulse_device_get_index (device))); + GUINT_TO_POINTER (pulse_device_get_index (device))); g_signal_emit_by_name (G_OBJECT (pulse), "device-removed", name); g_free (name); } static void -backend_remove_stream (PulseBackend *pulse, GHashTable *hash, PulseStream *stream) +remove_stream (PulseBackend *pulse, PulseStream *stream) { - gchar *name; - - /* Make sure we do not end up with invalid default streams, but this is - * very unlikely to happen */ - if (G_UNLIKELY (MATE_MIXER_STREAM (stream) == pulse->priv->default_sink)) { + gchar *name; + guint32 idx; + gint64 index; + gboolean reload = FALSE; + + /* The removed stream might be one of the default streams, this happens + * especially when switching profiles, after which PulseAudio removes the + * old streams and creates new ones with different names */ + if (MATE_MIXER_STREAM (stream) == pulse->priv->default_sink) { g_clear_object (&pulse->priv->default_sink); + g_object_notify (G_OBJECT (pulse), "default-output"); + reload = TRUE; } - else if (G_UNLIKELY (MATE_MIXER_STREAM (stream) == pulse->priv->default_source)) { + else if (MATE_MIXER_STREAM (stream) == pulse->priv->default_source) { g_clear_object (&pulse->priv->default_source); + g_object_notify (G_OBJECT (pulse), "default-input"); + reload = TRUE; } + idx = pulse_stream_get_index (stream); + + if (PULSE_IS_SINK (stream)) + index = HASH_ID_SINK (idx); + else if (PULSE_IS_SINK_INPUT (stream)) + index = HASH_ID_SINK_INPUT (idx); + else if (PULSE_IS_SOURCE (stream)) + index = HASH_ID_SOURCE (idx); + else + index = HASH_ID_SOURCE_OUTPUT (idx); + name = g_strdup (mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream))); - g_hash_table_remove (hash, GINT_TO_POINTER (pulse_stream_get_index (stream))); + g_hash_table_remove (pulse->priv->streams, &index); + + /* PulseAudio usually sends a server info update by itself when default + * stream changes, but there is at least one case when it does not - setting + * a card profile to off, so to be sure request an update explicitely */ + if (reload == TRUE) + pulse_connection_load_server_info (pulse->priv->connection); g_signal_emit_by_name (G_OBJECT (pulse), "stream-removed", name); g_free (name); } static void -backend_change_state (PulseBackend *backend, MateMixerState state) +change_state (PulseBackend *backend, MateMixerState state) { if (backend->priv->state == state) return; @@ -1098,21 +1318,21 @@ backend_change_state (PulseBackend *backend, MateMixerState state) } static gint -backend_compare_devices (gconstpointer a, gconstpointer b) +compare_devices (gconstpointer a, gconstpointer b) { return strcmp (mate_mixer_device_get_name (MATE_MIXER_DEVICE (a)), mate_mixer_device_get_name (MATE_MIXER_DEVICE (b))); } static gint -backend_compare_streams (gconstpointer a, gconstpointer b) +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_names (gpointer key, gpointer value, gpointer user_data) +compare_stream_names (gpointer key, gpointer value, gpointer user_data) { MateMixerStream *stream = MATE_MIXER_STREAM (value); |