diff options
author | Michal Ratajsky <[email protected]> | 2014-07-18 15:41:59 +0200 |
---|---|---|
committer | Michal Ratajsky <[email protected]> | 2014-07-18 15:41:59 +0200 |
commit | 56c76128b0144a5c61e77d2a7aec07a337cfb66d (patch) | |
tree | f67ce44025881578cf6de3332064c214da176a23 /backends/pulse | |
parent | 85070f3b97a3213d75a7bebf86ad973aaa21c55b (diff) | |
download | libmatemixer-56c76128b0144a5c61e77d2a7aec07a337cfb66d.tar.bz2 libmatemixer-56c76128b0144a5c61e77d2a7aec07a337cfb66d.tar.xz |
PulseAudio fixes and API updates
Diffstat (limited to 'backends/pulse')
24 files changed, 3757 insertions, 2118 deletions
diff --git a/backends/pulse/Makefile.am b/backends/pulse/Makefile.am index 3b632a5..0e5a4d6 100644 --- a/backends/pulse/Makefile.am +++ b/backends/pulse/Makefile.am @@ -22,6 +22,8 @@ libmatemixer_pulse_la_SOURCES = \ pulse-enums.h \ pulse-enum-types.c \ pulse-enum-types.h \ + pulse-ext-stream.c \ + pulse-ext-stream.h \ pulse-helpers.c \ pulse-helpers.h \ pulse-monitor.c \ 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); diff --git a/backends/pulse/pulse-backend.h b/backends/pulse/pulse-backend.h index 48fffc6..bd1face 100644 --- a/backends/pulse/pulse-backend.h +++ b/backends/pulse/pulse-backend.h @@ -21,7 +21,7 @@ #include <glib.h> #include <glib-object.h> -#include <libmatemixer/matemixer-backend.h> +#include <libmatemixer/matemixer-backend-module.h> #define PULSE_TYPE_BACKEND \ (pulse_backend_get_type ()) diff --git a/backends/pulse/pulse-client-stream.c b/backends/pulse/pulse-client-stream.c index 8c9312a..d725146 100644 --- a/backends/pulse/pulse-client-stream.c +++ b/backends/pulse/pulse-client-stream.c @@ -15,11 +15,12 @@ * License along with this library; if not, see <http://www.gnu.org/licenses/>. */ +#include <string.h> #include <glib.h> #include <glib-object.h> -#include <string.h> #include <libmatemixer/matemixer-client-stream.h> +#include <libmatemixer/matemixer-enums.h> #include <libmatemixer/matemixer-stream.h> #include <pulse/pulseaudio.h> @@ -29,24 +30,33 @@ struct _PulseClientStreamPrivate { - gchar *app_name; - gchar *app_id; - gchar *app_version; - gchar *app_icon; - MateMixerStream *parent; + gchar *app_name; + gchar *app_id; + gchar *app_version; + gchar *app_icon; + MateMixerStream *parent; + MateMixerClientStreamFlags flags; + MateMixerClientStreamRole role; }; -enum -{ +enum { PROP_0, + PROP_CLIENT_FLAGS, + PROP_ROLE, PROP_PARENT, PROP_APP_NAME, PROP_APP_ID, PROP_APP_VERSION, - PROP_APP_ICON, - N_PROPERTIES + PROP_APP_ICON }; +enum { + REMOVED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS] = { 0, }; + static void mate_mixer_client_stream_interface_init (MateMixerClientStreamInterface *iface); static void pulse_client_stream_class_init (PulseClientStreamClass *klass); @@ -64,26 +74,32 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (PulseClientStream, pulse_client_stream, PULSE_ G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_CLIENT_STREAM, mate_mixer_client_stream_interface_init)) -static MateMixerStream *client_stream_get_parent (MateMixerClientStream *client); -static gboolean client_stream_set_parent (MateMixerClientStream *client, - MateMixerStream *parent); -static gboolean client_stream_remove (MateMixerClientStream *client); +static MateMixerClientStreamFlags pulse_client_stream_get_flags (MateMixerClientStream *client); +static MateMixerClientStreamRole pulse_client_stream_get_role (MateMixerClientStream *client); + +static MateMixerStream * pulse_client_stream_get_parent (MateMixerClientStream *client); +static gboolean pulse_client_stream_set_parent (MateMixerClientStream *client, + MateMixerStream *parent); -static const gchar * client_stream_get_app_name (MateMixerClientStream *client); -static const gchar * client_stream_get_app_id (MateMixerClientStream *client); -static const gchar * client_stream_get_app_version (MateMixerClientStream *client); -static const gchar * client_stream_get_app_icon (MateMixerClientStream *client); +static gboolean pulse_client_stream_remove (MateMixerClientStream *client); + +static const gchar * pulse_client_stream_get_app_name (MateMixerClientStream *client); +static const gchar * pulse_client_stream_get_app_id (MateMixerClientStream *client); +static const gchar * pulse_client_stream_get_app_version (MateMixerClientStream *client); +static const gchar * pulse_client_stream_get_app_icon (MateMixerClientStream *client); static void mate_mixer_client_stream_interface_init (MateMixerClientStreamInterface *iface) { - iface->get_parent = client_stream_get_parent; - iface->set_parent = client_stream_set_parent; - iface->remove = client_stream_remove; - iface->get_app_name = client_stream_get_app_name; - iface->get_app_id = client_stream_get_app_id; - iface->get_app_version = client_stream_get_app_version; - iface->get_app_icon = client_stream_get_app_icon; + iface->get_flags = pulse_client_stream_get_flags; + iface->get_role = pulse_client_stream_get_role; + iface->get_parent = pulse_client_stream_get_parent; + iface->set_parent = pulse_client_stream_set_parent; + iface->remove = pulse_client_stream_remove; + iface->get_app_name = pulse_client_stream_get_app_name; + iface->get_app_id = pulse_client_stream_get_app_id; + iface->get_app_version = pulse_client_stream_get_app_version; + iface->get_app_icon = pulse_client_stream_get_app_icon; } static void @@ -96,6 +112,20 @@ pulse_client_stream_class_init (PulseClientStreamClass *klass) object_class->finalize = pulse_client_stream_finalize; object_class->get_property = pulse_client_stream_get_property; + signals[REMOVED] = + g_signal_new ("removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseClientStreamClass, removed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0, + G_TYPE_NONE); + + g_object_class_override_property (object_class, PROP_CLIENT_FLAGS, "client-flags"); + g_object_class_override_property (object_class, PROP_ROLE, "role"); g_object_class_override_property (object_class, PROP_PARENT, "parent"); g_object_class_override_property (object_class, PROP_APP_NAME, "app-name"); g_object_class_override_property (object_class, PROP_APP_ID, "app-id"); @@ -116,6 +146,12 @@ pulse_client_stream_get_property (GObject *object, client = PULSE_CLIENT_STREAM (object); switch (param_id) { + case PROP_CLIENT_FLAGS: + g_value_set_flags (value, client->priv->flags); + break; + case PROP_ROLE: + g_value_set_enum (value, client->priv->role); + break; case PROP_PARENT: g_value_set_object (value, client->priv->parent); break; @@ -173,79 +209,123 @@ pulse_client_stream_finalize (GObject *object) } gboolean -pulse_client_stream_update_parent (PulseClientStream *client, MateMixerStream *parent) +pulse_client_stream_update_flags (PulseClientStream *pclient, + MateMixerClientStreamFlags flags) { - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); + + if (pclient->priv->flags != flags) { + pclient->priv->flags = flags; + + g_object_notify (G_OBJECT (pclient), "client-flags"); + } + return TRUE; +} - if (client->priv->parent != parent) { - g_clear_object (&client->priv->parent); +gboolean +pulse_client_stream_update_parent (PulseClientStream *pclient, MateMixerStream *parent) +{ + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); + + if (pclient->priv->parent != parent) { + g_clear_object (&pclient->priv->parent); if (G_LIKELY (parent != NULL)) - client->priv->parent = g_object_ref (parent); + pclient->priv->parent = g_object_ref (parent); - g_object_notify (G_OBJECT (client), "parent"); + g_object_notify (G_OBJECT (pclient), "parent"); } return TRUE; } gboolean -pulse_client_stream_update_app_name (PulseClientStream *client, const gchar *app_name) +pulse_client_stream_update_role (PulseClientStream *pclient, + MateMixerClientStreamRole role) { - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); - if (g_strcmp0 (client->priv->app_name, app_name)) { - g_free (client->priv->app_name); - client->priv->app_name = g_strdup (app_name); + if (pclient->priv->role != role) { + pclient->priv->role = role; - g_object_notify (G_OBJECT (client), "app-name"); + g_object_notify (G_OBJECT (pclient), "role"); } return TRUE; } gboolean -pulse_client_stream_update_app_id (PulseClientStream *client, const gchar *app_id) +pulse_client_stream_update_app_name (PulseClientStream *pclient, const gchar *app_name) { - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); - if (g_strcmp0 (client->priv->app_id, app_id)) { - g_free (client->priv->app_id); - client->priv->app_id = g_strdup (app_id); + if (g_strcmp0 (pclient->priv->app_name, app_name) != 0) { + g_free (pclient->priv->app_name); + pclient->priv->app_name = g_strdup (app_name); - g_object_notify (G_OBJECT (client), "app-id"); + g_object_notify (G_OBJECT (pclient), "app-name"); } return TRUE; } gboolean -pulse_client_stream_update_app_version (PulseClientStream *client, const gchar *app_version) +pulse_client_stream_update_app_id (PulseClientStream *pclient, const gchar *app_id) { - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); - if (g_strcmp0 (client->priv->app_version, app_version)) { - g_free (client->priv->app_version); - client->priv->app_version = g_strdup (app_version); + if (g_strcmp0 (pclient->priv->app_id, app_id) != 0) { + g_free (pclient->priv->app_id); + pclient->priv->app_id = g_strdup (app_id); - g_object_notify (G_OBJECT (client), "app-version"); + g_object_notify (G_OBJECT (pclient), "app-id"); } return TRUE; } gboolean -pulse_client_stream_update_app_icon (PulseClientStream *client, const gchar *app_icon) +pulse_client_stream_update_app_version (PulseClientStream *pclient, const gchar *app_version) { - g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); - if (g_strcmp0 (client->priv->app_icon, app_icon)) { - g_free (client->priv->app_icon); - client->priv->app_icon = g_strdup (app_icon); + if (g_strcmp0 (pclient->priv->app_version, app_version) != 0) { + g_free (pclient->priv->app_version); + pclient->priv->app_version = g_strdup (app_version); - g_object_notify (G_OBJECT (client), "app-icon"); + g_object_notify (G_OBJECT (pclient), "app-version"); } return TRUE; } +gboolean +pulse_client_stream_update_app_icon (PulseClientStream *pclient, const gchar *app_icon) +{ + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (pclient), FALSE); + + if (g_strcmp0 (pclient->priv->app_icon, app_icon) != 0) { + g_free (pclient->priv->app_icon); + pclient->priv->app_icon = g_strdup (app_icon); + + g_object_notify (G_OBJECT (pclient), "app-icon"); + } + return TRUE; +} + +static MateMixerClientStreamFlags +pulse_client_stream_get_flags (MateMixerClientStream *client) +{ + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), MATE_MIXER_CLIENT_STREAM_NO_FLAGS); + + return PULSE_CLIENT_STREAM (client)->priv->flags; +} + +static MateMixerClientStreamRole +pulse_client_stream_get_role (MateMixerClientStream *client) +{ + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), MATE_MIXER_CLIENT_STREAM_ROLE_NONE); + + return PULSE_CLIENT_STREAM (client)->priv->role; +} + static MateMixerStream * -client_stream_get_parent (MateMixerClientStream *client) +pulse_client_stream_get_parent (MateMixerClientStream *client) { g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); @@ -253,23 +333,58 @@ client_stream_get_parent (MateMixerClientStream *client) } static gboolean -client_stream_set_parent (MateMixerClientStream *client, MateMixerStream *parent) +pulse_client_stream_set_parent (MateMixerClientStream *client, MateMixerStream *parent) { + PulseClientStream *pclient; + PulseClientStreamClass *klass; + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (parent), FALSE); + + pclient = PULSE_CLIENT_STREAM (client); + klass = PULSE_CLIENT_STREAM_GET_CLASS (pclient); + + if (pclient->priv->parent == parent) + return TRUE; + + if (klass->set_parent (pclient, PULSE_STREAM (parent)) == FALSE) + return FALSE; + + if (pclient->priv->parent != NULL) + g_object_unref (pclient->priv->parent); + + /* It is allowed for the parent to be NULL when the instance is created, but + * changing the parent requires a valid parent stream */ + pclient->priv->parent = g_object_ref (parent); - return PULSE_CLIENT_STREAM_GET_CLASS (client)->set_parent (client, parent); + g_object_notify (G_OBJECT (client), "parent"); + return TRUE; } static gboolean -client_stream_remove (MateMixerClientStream *client) +pulse_client_stream_remove (MateMixerClientStream *client) { + PulseClientStream *pclient; + PulseClientStreamClass *klass; + g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), FALSE); - return PULSE_CLIENT_STREAM_GET_CLASS (client)->remove (client); + pclient = PULSE_CLIENT_STREAM (client); + klass = PULSE_CLIENT_STREAM_GET_CLASS (pclient); + + if (klass->remove (pclient) == FALSE) + return FALSE; + + // XXX handle this in the backend + g_signal_emit (G_OBJECT (client), + signals[REMOVED], + 0); + + return TRUE; } static const gchar * -client_stream_get_app_name (MateMixerClientStream *client) +pulse_client_stream_get_app_name (MateMixerClientStream *client) { g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); @@ -277,7 +392,7 @@ client_stream_get_app_name (MateMixerClientStream *client) } static const gchar * -client_stream_get_app_id (MateMixerClientStream *client) +pulse_client_stream_get_app_id (MateMixerClientStream *client) { g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); @@ -285,7 +400,7 @@ client_stream_get_app_id (MateMixerClientStream *client) } static const gchar * -client_stream_get_app_version (MateMixerClientStream *client) +pulse_client_stream_get_app_version (MateMixerClientStream *client) { g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); @@ -293,7 +408,7 @@ client_stream_get_app_version (MateMixerClientStream *client) } static const gchar * -client_stream_get_app_icon (MateMixerClientStream *client) +pulse_client_stream_get_app_icon (MateMixerClientStream *client) { g_return_val_if_fail (PULSE_IS_CLIENT_STREAM (client), NULL); diff --git a/backends/pulse/pulse-client-stream.h b/backends/pulse/pulse-client-stream.h index 61e9c4d..fe24dc3 100644 --- a/backends/pulse/pulse-client-stream.h +++ b/backends/pulse/pulse-client-stream.h @@ -22,6 +22,7 @@ #include <glib-object.h> #include <libmatemixer/matemixer-client-stream.h> +#include <libmatemixer/matemixer-enums.h> #include <libmatemixer/matemixer-stream.h> #include "pulse-stream.h" @@ -57,24 +58,36 @@ struct _PulseClientStreamClass { PulseStreamClass parent_class; - gboolean (*set_parent) (MateMixerClientStream *client, - MateMixerStream *stream); - gboolean (*remove) (MateMixerClientStream *client); + /*< private >*/ + /* Virtual table */ + gboolean (*set_parent) (PulseClientStream *pclient, + PulseStream *pstream); + + gboolean (*remove) (PulseClientStream *pclient); + + /* Signals */ + void (*removed) (PulseClientStream *pclient); }; GType pulse_client_stream_get_type (void) G_GNUC_CONST; -gboolean pulse_client_stream_update_parent (PulseClientStream *client, - MateMixerStream *parent); - -gboolean pulse_client_stream_update_app_name (PulseClientStream *client, - const gchar *app_name); -gboolean pulse_client_stream_update_app_id (PulseClientStream *client, - const gchar *app_id); -gboolean pulse_client_stream_update_app_version (PulseClientStream *client, - const gchar *app_version); -gboolean pulse_client_stream_update_app_icon (PulseClientStream *client, - const gchar *app_icon); +gboolean pulse_client_stream_update_flags (PulseClientStream *pclient, + MateMixerClientStreamFlags flags); + +gboolean pulse_client_stream_update_role (PulseClientStream *pclient, + MateMixerClientStreamRole role); + +gboolean pulse_client_stream_update_parent (PulseClientStream *pclient, + MateMixerStream *parent); + +gboolean pulse_client_stream_update_app_name (PulseClientStream *pclient, + const gchar *app_name); +gboolean pulse_client_stream_update_app_id (PulseClientStream *pclient, + const gchar *app_id); +gboolean pulse_client_stream_update_app_version (PulseClientStream *pclient, + const gchar *app_version); +gboolean pulse_client_stream_update_app_icon (PulseClientStream *pclient, + const gchar *app_icon); G_END_DECLS diff --git a/backends/pulse/pulse-connection.c b/backends/pulse/pulse-connection.c index 9002f09..cc39caf 100644 --- a/backends/pulse/pulse-connection.c +++ b/backends/pulse/pulse-connection.c @@ -15,13 +15,14 @@ * License along with this library; if not, see <http://www.gnu.org/licenses/>. */ +#include <unistd.h> +#include <sys/types.h> #include <glib.h> #include <glib-object.h> -#include <sys/types.h> -#include <unistd.h> #include <pulse/pulseaudio.h> #include <pulse/glib-mainloop.h> +#include <pulse/ext-stream-restore.h> #include "pulse-connection.h" #include "pulse-enums.h" @@ -35,6 +36,8 @@ struct _PulseConnectionPrivate pa_context *context; pa_proplist *proplist; pa_glib_mainloop *mainloop; + gboolean ext_streams_loading; + gboolean ext_streams_dirty; PulseConnectionState state; }; @@ -59,6 +62,9 @@ enum { SINK_INPUT_REMOVED, SOURCE_OUTPUT_INFO, SOURCE_OUTPUT_REMOVED, + EXT_STREAM_LOADING, + EXT_STREAM_LOADED, + EXT_STREAM_INFO, N_SIGNALS }; @@ -80,47 +86,53 @@ static void pulse_connection_finalize (GObject *object); G_DEFINE_TYPE (PulseConnection, pulse_connection, G_TYPE_OBJECT); -static gchar *connection_get_app_name (void); - -static gboolean connection_load_lists (PulseConnection *connection); - -static void connection_state_cb (pa_context *c, - void *userdata); -static void connection_subscribe_cb (pa_context *c, - pa_subscription_event_type_t t, - uint32_t idx, - void *userdata); -static void connection_server_info_cb (pa_context *c, - const pa_server_info *info, - void *userdata); -static void connection_card_info_cb (pa_context *c, - const pa_card_info *info, - int eol, - void *userdata); -static void connection_sink_info_cb (pa_context *c, - const pa_sink_info *info, - int eol, - void *userdata); -static void connection_source_info_cb (pa_context *c, - const pa_source_info *info, - int eol, - void *userdata); -static void connection_sink_input_info_cb (pa_context *c, - const pa_sink_input_info *info, - int eol, - void *userdata); -static void connection_source_output_info_cb (pa_context *c, - const pa_source_output_info *info, - int eol, - void *userdata); - -static void connection_change_state (PulseConnection *connection, - PulseConnectionState state); - -static void connection_list_loaded (PulseConnection *connection); - -static gboolean connection_process_operation (PulseConnection *connection, - pa_operation *op); +static gchar *create_app_name (void); + +static gboolean load_lists (PulseConnection *connection); +static gboolean load_list_finished (PulseConnection *connection); + +static void pulse_state_cb (pa_context *c, + void *userdata); +static void pulse_subscribe_cb (pa_context *c, + pa_subscription_event_type_t t, + uint32_t idx, + void *userdata); + +static void pulse_restore_subscribe_cb (pa_context *c, + void *userdata); +static void pulse_server_info_cb (pa_context *c, + const pa_server_info *info, + void *userdata); +static void pulse_card_info_cb (pa_context *c, + const pa_card_info *info, + int eol, + void *userdata); +static void pulse_sink_info_cb (pa_context *c, + const pa_sink_info *info, + int eol, + void *userdata); +static void pulse_source_info_cb (pa_context *c, + const pa_source_info *info, + int eol, + void *userdata); +static void pulse_sink_input_info_cb (pa_context *c, + const pa_sink_input_info *info, + int eol, + void *userdata); +static void pulse_source_output_info_cb (pa_context *c, + const pa_source_output_info *info, + int eol, + void *userdata); +static void pulse_ext_stream_restore_cb (pa_context *c, + const pa_ext_stream_restore_info *info, + int eol, + void *userdata); + +static void change_state (PulseConnection *connection, + PulseConnectionState state); + +static gboolean process_pulse_operation (PulseConnection *connection, + pa_operation *op); static void pulse_connection_class_init (PulseConnectionClass *klass) @@ -150,6 +162,8 @@ pulse_connection_class_init (PulseConnectionClass *klass) G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + signals[SERVER_INFO] = g_signal_new ("server-info", G_TYPE_FROM_CLASS (object_class), @@ -282,7 +296,41 @@ pulse_connection_class_init (PulseConnectionClass *klass) 1, G_TYPE_UINT); - g_object_class_install_properties (object_class, N_PROPERTIES, properties); + signals[EXT_STREAM_LOADING] = + g_signal_new ("ext-stream-loading", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, ext_stream_loading), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0, + G_TYPE_NONE); + + signals[EXT_STREAM_LOADED] = + g_signal_new ("ext-stream-loaded", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, ext_stream_loaded), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0, + G_TYPE_NONE); + + signals[EXT_STREAM_INFO] = + g_signal_new ("ext-stream-info", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseConnectionClass, ext_stream_info), + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); g_type_class_add_private (object_class, sizeof (PulseConnectionPrivate)); } @@ -382,7 +430,7 @@ pulse_connection_new (const gchar *app_name, pa_proplist_sets (proplist, PA_PROP_APPLICATION_NAME, app_name); } else { /* Set a sensible default name when application does not provide one */ - gchar *name = connection_get_app_name (); + gchar *name = create_app_name (); pa_proplist_sets (proplist, PA_PROP_APPLICATION_NAME, name); g_free (name); @@ -427,9 +475,9 @@ pulse_connection_connect (PulseConnection *connection, gboolean wait_for_daemon) /* Set function to monitor status changes */ pa_context_set_state_callback (context, - connection_state_cb, + pulse_state_cb, connection); - if (wait_for_daemon) + if (wait_for_daemon == TRUE) flags = PA_CONTEXT_NOFAIL; /* Initiate a connection, state changes will be delivered asynchronously */ @@ -438,7 +486,7 @@ pulse_connection_connect (PulseConnection *connection, gboolean wait_for_daemon) flags, NULL) == 0) { connection->priv->context = context; - connection_change_state (connection, PULSE_CONNECTION_CONNECTING); + change_state (connection, PULSE_CONNECTION_CONNECTING); return TRUE; } @@ -458,8 +506,10 @@ pulse_connection_disconnect (PulseConnection *connection) connection->priv->context = NULL; connection->priv->outstanding = 0; + connection->priv->ext_streams_loading = FALSE; + connection->priv->ext_streams_dirty = FALSE; - connection_change_state (connection, PULSE_CONNECTION_DISCONNECTED); + change_state (connection, PULSE_CONNECTION_DISCONNECTED); } PulseConnectionState @@ -470,6 +520,248 @@ pulse_connection_get_state (PulseConnection *connection) return connection->priv->state; } +gboolean +pulse_connection_load_server_info (PulseConnection *connection) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + + if (connection->priv->state != PULSE_CONNECTION_LOADING && + connection->priv->state != PULSE_CONNECTION_CONNECTED) + return FALSE; + + op = pa_context_get_server_info (connection->priv->context, + pulse_server_info_cb, + connection); + + return process_pulse_operation (connection, op); +} + +gboolean +pulse_connection_load_card_info (PulseConnection *connection, guint32 index) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + + if (connection->priv->state != PULSE_CONNECTION_LOADING && + connection->priv->state != PULSE_CONNECTION_CONNECTED) + return FALSE; + + if (index == PA_INVALID_INDEX) + op = pa_context_get_card_info_by_index (connection->priv->context, + index, + pulse_card_info_cb, + connection); + else + op = pa_context_get_card_info_list (connection->priv->context, + pulse_card_info_cb, + connection); + + return process_pulse_operation (connection, op); +} + +gboolean +pulse_connection_load_card_info_name (PulseConnection *connection, const gchar *name) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + if (connection->priv->state != PULSE_CONNECTION_LOADING && + connection->priv->state != PULSE_CONNECTION_CONNECTED) + return FALSE; + + op = pa_context_get_card_info_by_name (connection->priv->context, + name, + pulse_card_info_cb, + connection); + + return process_pulse_operation (connection, op); +} + +gboolean +pulse_connection_load_sink_info (PulseConnection *connection, guint32 index) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + + if (connection->priv->state != PULSE_CONNECTION_LOADING && + connection->priv->state != PULSE_CONNECTION_CONNECTED) + return FALSE; + + if (index == PA_INVALID_INDEX) + op = pa_context_get_sink_info_by_index (connection->priv->context, + index, + pulse_sink_info_cb, + connection); + else + op = pa_context_get_sink_info_list (connection->priv->context, + pulse_sink_info_cb, + connection); + + return process_pulse_operation (connection, op); +} + +gboolean +pulse_connection_load_sink_info_name (PulseConnection *connection, const gchar *name) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + if (connection->priv->state != PULSE_CONNECTION_LOADING && + connection->priv->state != PULSE_CONNECTION_CONNECTED) + return FALSE; + + op = pa_context_get_sink_info_by_name (connection->priv->context, + name, + pulse_sink_info_cb, + connection); + + return process_pulse_operation (connection, op); +} + +gboolean +pulse_connection_load_sink_input_info (PulseConnection *connection, guint32 index) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + + if (connection->priv->state != PULSE_CONNECTION_LOADING && + connection->priv->state != PULSE_CONNECTION_CONNECTED) + return FALSE; + + if (index == PA_INVALID_INDEX) + op = pa_context_get_sink_input_info (connection->priv->context, + index, + pulse_sink_input_info_cb, + connection); + else + op = pa_context_get_sink_input_info_list (connection->priv->context, + pulse_sink_input_info_cb, + connection); + + return process_pulse_operation (connection, op); +} + +gboolean +pulse_connection_load_source_info (PulseConnection *connection, guint32 index) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + + if (connection->priv->state != PULSE_CONNECTION_LOADING && + connection->priv->state != PULSE_CONNECTION_CONNECTED) + return FALSE; + + if (index == PA_INVALID_INDEX) + op = pa_context_get_source_info_by_index (connection->priv->context, + index, + pulse_source_info_cb, + connection); + else + op = pa_context_get_source_info_list (connection->priv->context, + pulse_source_info_cb, + connection); + + return process_pulse_operation (connection, op); +} + +gboolean +pulse_connection_load_source_info_name (PulseConnection *connection, const gchar *name) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + if (connection->priv->state != PULSE_CONNECTION_LOADING && + connection->priv->state != PULSE_CONNECTION_CONNECTED) + return FALSE; + + op = pa_context_get_source_info_by_name (connection->priv->context, + name, + pulse_source_info_cb, + connection); + + return process_pulse_operation (connection, op); +} + +gboolean +pulse_connection_load_source_output_info (PulseConnection *connection, guint32 index) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + + if (connection->priv->state != PULSE_CONNECTION_LOADING && + connection->priv->state != PULSE_CONNECTION_CONNECTED) + return FALSE; + + if (index == PA_INVALID_INDEX) + op = pa_context_get_source_output_info (connection->priv->context, + index, + pulse_source_output_info_cb, + connection); + else + op = pa_context_get_source_output_info_list (connection->priv->context, + pulse_source_output_info_cb, + connection); + + return process_pulse_operation (connection, op); +} + +gboolean +pulse_connection_load_ext_stream_info (PulseConnection *connection) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + + if (connection->priv->state != PULSE_CONNECTION_LOADING && + connection->priv->state != PULSE_CONNECTION_CONNECTED) + return FALSE; + + /* When we receive a request to load the list of ext-streams, see if + * loading is already in progress and if it is, wait until the current + * loading finishes. + * The PulseBackend class relies on this behaviour to ensure it always + * contains a correct list of ext-streams, also PulseAudio always sends + * a list of all streams in the database and these requests may arrive + * very often, so this also optimizaes the amount of traffic. */ + if (connection->priv->ext_streams_loading == TRUE) { + connection->priv->ext_streams_dirty = TRUE; + return TRUE; + } + + connection->priv->ext_streams_dirty = FALSE; + connection->priv->ext_streams_loading = TRUE; + g_signal_emit (G_OBJECT (connection), + signals[EXT_STREAM_LOADING], + 0); + + op = pa_ext_stream_restore_read (connection->priv->context, + pulse_ext_stream_restore_cb, + connection); + + if (process_pulse_operation (connection, op) == FALSE) { + connection->priv->ext_streams_loading = FALSE; + + g_signal_emit (G_OBJECT (connection), + signals[EXT_STREAM_LOADED], + 0); + return FALSE; + } + return TRUE; +} + PulseMonitor * pulse_connection_create_monitor (PulseConnection *connection, guint32 index_source, @@ -482,13 +774,11 @@ pulse_connection_create_monitor (PulseConnection *connection, return pulse_monitor_new (connection->priv->context, connection->priv->proplist, + NULL, index_source, index_sink_input); } -// XXX watch for some operation failures and eventually reload data -// to restore the previous state - gboolean pulse_connection_set_default_sink (PulseConnection *connection, const gchar *name) @@ -504,7 +794,7 @@ pulse_connection_set_default_sink (PulseConnection *connection, name, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -522,7 +812,7 @@ pulse_connection_set_default_source (PulseConnection *connection, name, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -542,7 +832,7 @@ pulse_connection_set_card_profile (PulseConnection *connection, profile, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -562,7 +852,7 @@ pulse_connection_set_sink_mute (PulseConnection *connection, (int) mute, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -582,7 +872,7 @@ pulse_connection_set_sink_volume (PulseConnection *connection, volume, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -602,7 +892,7 @@ pulse_connection_set_sink_port (PulseConnection *connection, port, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -622,7 +912,7 @@ pulse_connection_set_sink_input_mute (PulseConnection *connection, (int) mute, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -642,7 +932,7 @@ pulse_connection_set_sink_input_volume (PulseConnection *connection, volume, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -662,7 +952,7 @@ pulse_connection_set_source_mute (PulseConnection *connection, (int) mute, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -682,7 +972,7 @@ pulse_connection_set_source_volume (PulseConnection *connection, volume, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -702,7 +992,7 @@ pulse_connection_set_source_port (PulseConnection *connection, port, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -723,7 +1013,7 @@ pulse_connection_set_source_output_mute (PulseConnection *connection, (int) mute, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); #else return FALSE; #endif @@ -747,7 +1037,7 @@ pulse_connection_set_source_output_volume (PulseConnection *connection, volume, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); #else return FALSE; #endif @@ -770,7 +1060,7 @@ pulse_connection_suspend_sink (PulseConnection *connection, (int) suspend, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -790,7 +1080,7 @@ pulse_connection_suspend_source (PulseConnection *connection, (int) suspend, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -810,7 +1100,7 @@ pulse_connection_move_sink_input (PulseConnection *connection, sink_index, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -830,7 +1120,7 @@ pulse_connection_move_source_output (PulseConnection *connection, source_index, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -848,7 +1138,7 @@ pulse_connection_kill_sink_input (PulseConnection *connection, index, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); } gboolean @@ -866,14 +1156,59 @@ pulse_connection_kill_source_output (PulseConnection *connection, index, NULL, NULL); - return connection_process_operation (connection, op); + return process_pulse_operation (connection, op); +} + +gboolean +pulse_connection_write_ext_stream (PulseConnection *connection, + const pa_ext_stream_restore_info *info) +{ + pa_operation *op; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + + if (connection->priv->state != PULSE_CONNECTION_CONNECTED) + return FALSE; + + op = pa_ext_stream_restore_write (connection->priv->context, + PA_UPDATE_REPLACE, + info, 1, + TRUE, + NULL, NULL); + + return process_pulse_operation (connection, op); +} + +gboolean +pulse_connection_delete_ext_stream (PulseConnection *connection, + const gchar *name) +{ + pa_operation *op; + gchar **names; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), FALSE); + + if (connection->priv->state != PULSE_CONNECTION_CONNECTED) + return FALSE; + + names = g_new (gchar *, 2); + names[0] = (gchar *) name; + names[1] = NULL; + + op = pa_ext_stream_restore_delete (connection->priv->context, + (const char * const *) names, + NULL, NULL); + + g_strfreev (names); + + return process_pulse_operation (connection, op); } static gchar * -connection_get_app_name (void) +create_app_name (void) { - const char *name_app; - char name_buf[256]; + const gchar *name_app; + char name_buf[256]; /* Inspired by GStreamer's pulse plugin */ name_app = g_get_application_name (); @@ -887,9 +1222,9 @@ connection_get_app_name (void) } static gboolean -connection_load_lists (PulseConnection *connection) +load_lists (PulseConnection *connection) { - GList *ops = NULL; + GSList *ops = NULL; pa_operation *op; if (G_UNLIKELY (connection->priv->outstanding > 0)) { @@ -898,60 +1233,95 @@ connection_load_lists (PulseConnection *connection) } op = pa_context_get_card_info_list (connection->priv->context, - connection_card_info_cb, + pulse_card_info_cb, connection); if (G_UNLIKELY (op == NULL)) goto error; - ops = g_list_prepend (ops, op); + ops = g_slist_prepend (ops, op); op = pa_context_get_sink_info_list (connection->priv->context, - connection_sink_info_cb, + pulse_sink_info_cb, connection); if (G_UNLIKELY (op == NULL)) goto error; - ops = g_list_prepend (ops, op); + ops = g_slist_prepend (ops, op); op = pa_context_get_sink_input_info_list (connection->priv->context, - connection_sink_input_info_cb, + pulse_sink_input_info_cb, connection); if (G_UNLIKELY (op == NULL)) goto error; - ops = g_list_prepend (ops, op); + ops = g_slist_prepend (ops, op); op = pa_context_get_source_info_list (connection->priv->context, - connection_source_info_cb, + pulse_source_info_cb, connection); if (G_UNLIKELY (op == NULL)) goto error; - ops = g_list_prepend (ops, op); + ops = g_slist_prepend (ops, op); op = pa_context_get_source_output_info_list (connection->priv->context, - connection_source_output_info_cb, + pulse_source_output_info_cb, connection); if (G_UNLIKELY (op == NULL)) goto error; - ops = g_list_prepend (ops, op); - - g_list_foreach (ops, (GFunc) pa_operation_unref, NULL); - g_list_free (ops); + ops = g_slist_prepend (ops, op); connection->priv->outstanding = 5; + + /* This might not always be supported */ + op = pa_ext_stream_restore_read (connection->priv->context, + pulse_ext_stream_restore_cb, + connection); + if (op != NULL) { + ops = g_slist_prepend (ops, op); + connection->priv->outstanding++; + } + + g_slist_foreach (ops, (GFunc) pa_operation_unref, NULL); + g_slist_free (ops); + return TRUE; error: - g_list_foreach (ops, (GFunc) pa_operation_cancel, NULL); - g_list_foreach (ops, (GFunc) pa_operation_unref, NULL); - g_list_free (ops); + g_slist_foreach (ops, (GFunc) pa_operation_cancel, NULL); + g_slist_foreach (ops, (GFunc) pa_operation_unref, NULL); + g_slist_free (ops); return FALSE; } +static gboolean +load_list_finished (PulseConnection *connection) +{ + /* Decrement the number of outstanding requests as a list has just been + * downloaded; when the number reaches 0, server information is requested + * as the final step in the connection process */ + connection->priv->outstanding--; + + if (G_UNLIKELY (connection->priv->outstanding < 0)) { + g_warn_if_reached (); + connection->priv->outstanding = 0; + } + + if (connection->priv->outstanding == 0) { + gboolean ret = pulse_connection_load_server_info (connection); + + if (G_UNLIKELY (ret == FALSE)) { + pulse_connection_disconnect (connection); + return FALSE; + } + } + + return TRUE; +} + static void -connection_state_cb (pa_context *c, void *userdata) +pulse_state_cb (pa_context *c, void *userdata) { PulseConnection *connection; pa_context_state_t state; @@ -971,6 +1341,20 @@ connection_state_cb (pa_context *c, void *userdata) /* We are connected, let's subscribe to notifications and load the * initial lists */ + pa_context_set_subscribe_callback (connection->priv->context, + pulse_subscribe_cb, + connection); + pa_ext_stream_restore_set_subscribe_cb (connection->priv->context, + pulse_restore_subscribe_cb, + connection); + + op = pa_ext_stream_restore_subscribe (connection->priv->context, + TRUE, + NULL, NULL); + + /* Keep going if this operation fails */ + process_pulse_operation (connection, op); + op = pa_context_subscribe (connection->priv->context, PA_SUBSCRIPTION_MASK_SERVER | PA_SUBSCRIPTION_MASK_CARD | @@ -979,26 +1363,14 @@ connection_state_cb (pa_context *c, void *userdata) PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, NULL, NULL); - if (op != NULL) { - pa_context_set_subscribe_callback (connection->priv->context, - connection_subscribe_cb, - connection); - pa_operation_unref (op); - - if (connection_load_lists (connection) == TRUE) { - connection_change_state (connection, PULSE_CONNECTION_LOADING); - return; - } - /* Treat as a connection failure */ - state = PA_CONTEXT_FAILED; - } else { - g_warning ("Failed to subscribe to PulseAudio notifications: %s", - pa_strerror (pa_context_errno (connection->priv->context))); + if (process_pulse_operation (connection, op) == TRUE) { + change_state (connection, PULSE_CONNECTION_LOADING); - /* Treat as a connection failure */ + if (load_lists (connection) == FALSE) + state = PA_CONTEXT_FAILED; + } else state = PA_CONTEXT_FAILED; - } } if (state == PA_CONTEXT_TERMINATED || state == PA_CONTEXT_FAILED) { @@ -1008,105 +1380,93 @@ connection_state_cb (pa_context *c, void *userdata) } if (state == PA_CONTEXT_CONNECTING) - connection_change_state (connection, PULSE_CONNECTION_CONNECTING); + change_state (connection, PULSE_CONNECTION_CONNECTING); else if (state == PA_CONTEXT_AUTHORIZING || state == PA_CONTEXT_SETTING_NAME) - connection_change_state (connection, PULSE_CONNECTION_AUTHORIZING); + change_state (connection, PULSE_CONNECTION_AUTHORIZING); } static void -connection_subscribe_cb (pa_context *c, - pa_subscription_event_type_t t, - uint32_t idx, - void *userdata) +pulse_subscribe_cb (pa_context *c, + pa_subscription_event_type_t t, + uint32_t idx, + void *userdata) { PulseConnection *connection; - pa_operation *op; connection = PULSE_CONNECTION (userdata); switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SERVER: + pulse_connection_load_server_info (connection); + break; + case PA_SUBSCRIPTION_EVENT_CARD: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) g_signal_emit (G_OBJECT (connection), signals[CARD_REMOVED], 0, idx); - } else { - op = pa_context_get_card_info_by_index (connection->priv->context, - idx, - connection_card_info_cb, - connection); - connection_process_operation (connection, op); - } + else + pulse_connection_load_card_info (connection, idx); break; case PA_SUBSCRIPTION_EVENT_SINK: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) g_signal_emit (G_OBJECT (connection), signals[SINK_REMOVED], 0, idx); - } else { - op = pa_context_get_sink_info_by_index (connection->priv->context, - idx, - connection_sink_info_cb, - connection); - connection_process_operation (connection, op); - } + else + pulse_connection_load_sink_info (connection, idx); break; case PA_SUBSCRIPTION_EVENT_SINK_INPUT: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) g_signal_emit (G_OBJECT (connection), signals[SINK_INPUT_REMOVED], 0, idx); - } else { - op = pa_context_get_sink_input_info (connection->priv->context, - idx, - connection_sink_input_info_cb, - connection); - connection_process_operation (connection, op); - } + else + pulse_connection_load_sink_input_info (connection, idx); break; case PA_SUBSCRIPTION_EVENT_SOURCE: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) g_signal_emit (G_OBJECT (connection), signals[SOURCE_REMOVED], 0, idx); - } else { - op = pa_context_get_source_info_by_index (connection->priv->context, - idx, - connection_source_info_cb, - connection); - connection_process_operation (connection, op); - } + else + pulse_connection_load_source_info (connection, idx); break; case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) g_signal_emit (G_OBJECT (connection), signals[SOURCE_OUTPUT_REMOVED], 0, idx); - } else { - op = pa_context_get_source_output_info (connection->priv->context, - idx, - connection_source_output_info_cb, - connection); - connection_process_operation (connection, op); - } + else + pulse_connection_load_source_output_info (connection, idx); break; } } static void -connection_server_info_cb (pa_context *c, - const pa_server_info *info, - void *userdata) +pulse_restore_subscribe_cb (pa_context *c, void *userdata) +{ + PulseConnection *connection; + + connection = PULSE_CONNECTION (userdata); + + pulse_connection_load_ext_stream_info (connection); +} + +static void +pulse_server_info_cb (pa_context *c, + const pa_server_info *info, + void *userdata) { PulseConnection *connection; @@ -1120,14 +1480,14 @@ connection_server_info_cb (pa_context *c, /* This notification may arrive at any time, but it also finalizes the * connection process */ if (connection->priv->state == PULSE_CONNECTION_LOADING) - connection_change_state (connection, PULSE_CONNECTION_CONNECTED); + change_state (connection, PULSE_CONNECTION_CONNECTED); } static void -connection_card_info_cb (pa_context *c, - const pa_card_info *info, - int eol, - void *userdata) +pulse_card_info_cb (pa_context *c, + const pa_card_info *info, + int eol, + void *userdata) { PulseConnection *connection; @@ -1135,7 +1495,7 @@ connection_card_info_cb (pa_context *c, if (eol) { if (connection->priv->state == PULSE_CONNECTION_LOADING) - connection_list_loaded (connection); + load_list_finished (connection); return; } @@ -1146,10 +1506,10 @@ connection_card_info_cb (pa_context *c, } static void -connection_sink_info_cb (pa_context *c, - const pa_sink_info *info, - int eol, - void *userdata) +pulse_sink_info_cb (pa_context *c, + const pa_sink_info *info, + int eol, + void *userdata) { PulseConnection *connection; @@ -1157,7 +1517,7 @@ connection_sink_info_cb (pa_context *c, if (eol) { if (connection->priv->state == PULSE_CONNECTION_LOADING) - connection_list_loaded (connection); + load_list_finished (connection); return; } @@ -1168,10 +1528,10 @@ connection_sink_info_cb (pa_context *c, } static void -connection_sink_input_info_cb (pa_context *c, - const pa_sink_input_info *info, - int eol, - void *userdata) +pulse_sink_input_info_cb (pa_context *c, + const pa_sink_input_info *info, + int eol, + void *userdata) { PulseConnection *connection; @@ -1179,7 +1539,7 @@ connection_sink_input_info_cb (pa_context *c, if (eol) { if (connection->priv->state == PULSE_CONNECTION_LOADING) - connection_list_loaded (connection); + load_list_finished (connection); return; } @@ -1190,10 +1550,10 @@ connection_sink_input_info_cb (pa_context *c, } static void -connection_source_info_cb (pa_context *c, - const pa_source_info *info, - int eol, - void *userdata) +pulse_source_info_cb (pa_context *c, + const pa_source_info *info, + int eol, + void *userdata) { PulseConnection *connection; @@ -1201,7 +1561,7 @@ connection_source_info_cb (pa_context *c, if (eol) { if (connection->priv->state == PULSE_CONNECTION_LOADING) - connection_list_loaded (connection); + load_list_finished (connection); return; } @@ -1212,10 +1572,10 @@ connection_source_info_cb (pa_context *c, } static void -connection_source_output_info_cb (pa_context *c, - const pa_source_output_info *info, - int eol, - void *userdata) +pulse_source_output_info_cb (pa_context *c, + const pa_source_output_info *info, + int eol, + void *userdata) { PulseConnection *connection; @@ -1223,7 +1583,7 @@ connection_source_output_info_cb (pa_context *c, if (eol) { if (connection->priv->state == PULSE_CONNECTION_LOADING) - connection_list_loaded (connection); + load_list_finished (connection); return; } @@ -1234,43 +1594,51 @@ connection_source_output_info_cb (pa_context *c, } static void -connection_change_state (PulseConnection *connection, PulseConnectionState state) +pulse_ext_stream_restore_cb (pa_context *c, + const pa_ext_stream_restore_info *info, + int eol, + void *userdata) { - if (connection->priv->state == state) - return; + PulseConnection *connection; - connection->priv->state = state; + connection = PULSE_CONNECTION (userdata); - g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_STATE]); -} + if (eol) { + connection->priv->ext_streams_loading = FALSE; + g_signal_emit (G_OBJECT (connection), + signals[EXT_STREAM_LOADED], + 0); -static void -connection_list_loaded (PulseConnection *connection) -{ - /* Decrement the number of outstanding requests as a list has just been - * downloaded; when the number reaches 0, server information is requested - * as the final step in the connection process */ - connection->priv->outstanding--; + if (connection->priv->state == PULSE_CONNECTION_LOADING) { + if (load_list_finished (connection) == FALSE) + return; + } - if (G_UNLIKELY (connection->priv->outstanding < 0)) { - g_warn_if_reached (); - connection->priv->outstanding = 0; + if (connection->priv->ext_streams_dirty == TRUE) + pulse_connection_load_ext_stream_info (connection); + + return; } - if (connection->priv->outstanding == 0) { - pa_operation *op; + g_signal_emit (G_OBJECT (connection), + signals[EXT_STREAM_INFO], + 0, + info); +} + +static void +change_state (PulseConnection *connection, PulseConnectionState state) +{ + if (connection->priv->state == state) + return; - op = pa_context_get_server_info (connection->priv->context, - connection_server_info_cb, - connection); + connection->priv->state = state; - if (G_UNLIKELY (connection_process_operation (connection, op) == FALSE)) - pulse_connection_disconnect (connection); - } + g_object_notify_by_pspec (G_OBJECT (connection), properties[PROP_STATE]); } static gboolean -connection_process_operation (PulseConnection *connection, pa_operation *op) +process_pulse_operation (PulseConnection *connection, pa_operation *op) { if (G_UNLIKELY (op == NULL)) { g_warning ("PulseAudio operation failed: %s", diff --git a/backends/pulse/pulse-connection.h b/backends/pulse/pulse-connection.h index ae7b3d3..b9119fd 100644 --- a/backends/pulse/pulse-connection.h +++ b/backends/pulse/pulse-connection.h @@ -22,6 +22,7 @@ #include <glib-object.h> #include <pulse/pulseaudio.h> +#include <pulse/ext-stream-restore.h> #include "pulse-enums.h" #include "pulse-monitor.h" @@ -57,110 +58,152 @@ struct _PulseConnectionClass { GObjectClass parent_class; + /*< private >*/ /* Signals */ - void (*server_info) (PulseConnection *connection, - const pa_server_info *info); - void (*card_info) (PulseConnection *connection, - const pa_card_info *info); - void (*card_removed) (PulseConnection *connection, - guint32 index); - void (*sink_info) (PulseConnection *connection, - const pa_sink_info *info); - void (*sink_removed) (PulseConnection *connection, - guint32 index); - void (*sink_input_info) (PulseConnection *connection, - const pa_sink_input_info *info); - void (*sink_input_removed) (PulseConnection *connection, - guint32 index); - void (*source_info) (PulseConnection *connection, - const pa_source_info *info); - void (*source_removed) (PulseConnection *connection, - guint32 index); - void (*source_output_info) (PulseConnection *connection, - const pa_source_output_info *info); - void (*source_output_removed) (PulseConnection *connection, - guint32 index); + void (*server_info) (PulseConnection *connection, + const pa_server_info *info); + + void (*card_info) (PulseConnection *connection, + const pa_card_info *info); + void (*card_removed) (PulseConnection *connection, + guint32 index); + + void (*sink_info) (PulseConnection *connection, + const pa_sink_info *info); + void (*sink_removed) (PulseConnection *connection, + guint32 index); + + void (*sink_input_info) (PulseConnection *connection, + const pa_sink_input_info *info); + void (*sink_input_removed) (PulseConnection *connection, + guint32 index); + + void (*source_info) (PulseConnection *connection, + const pa_source_info *info); + void (*source_removed) (PulseConnection *connection, + guint32 index); + + void (*source_output_info) (PulseConnection *connection, + const pa_source_output_info *info); + void (*source_output_removed) (PulseConnection *connection, + guint32 index); + + void (*ext_stream_loading) (PulseConnection *connection); + void (*ext_stream_loaded) (PulseConnection *connection); + void (*ext_stream_info) (PulseConnection *connection, + const pa_ext_stream_restore_info *info); }; GType pulse_connection_get_type (void) G_GNUC_CONST; -PulseConnection * pulse_connection_new (const gchar *app_name, - const gchar *app_id, - const gchar *app_version, - const gchar *app_icon, - const gchar *server_address); - -gboolean pulse_connection_connect (PulseConnection *connection, - gboolean wait_for_daemon); -void pulse_connection_disconnect (PulseConnection *connection); - -PulseConnectionState pulse_connection_get_state (PulseConnection *connection); - -PulseMonitor * pulse_connection_create_monitor (PulseConnection *connection, - guint32 index_source, - guint32 index_sink_input); - -gboolean pulse_connection_set_default_sink (PulseConnection *connection, - const gchar *name); -gboolean pulse_connection_set_default_source (PulseConnection *connection, - const gchar *name); - -gboolean pulse_connection_set_card_profile (PulseConnection *connection, - const gchar *device, - const gchar *profile); - -gboolean pulse_connection_set_sink_mute (PulseConnection *connection, - guint32 index, - gboolean mute); -gboolean pulse_connection_set_sink_volume (PulseConnection *connection, - guint32 index, - const pa_cvolume *volume); -gboolean pulse_connection_set_sink_port (PulseConnection *connection, - guint32 index, - const gchar *port); - -gboolean pulse_connection_set_sink_input_mute (PulseConnection *connection, - guint32 index, - gboolean mute); -gboolean pulse_connection_set_sink_input_volume (PulseConnection *connection, - guint32 index, - const pa_cvolume *volume); - -gboolean pulse_connection_set_source_mute (PulseConnection *connection, - guint32 index, - gboolean mute); -gboolean pulse_connection_set_source_volume (PulseConnection *connection, - guint32 index, - const pa_cvolume *volume); -gboolean pulse_connection_set_source_port (PulseConnection *connection, - guint32 index, - const gchar *port); - -gboolean pulse_connection_set_source_output_mute (PulseConnection *connection, - guint32 index, - gboolean mute); -gboolean pulse_connection_set_source_output_volume (PulseConnection *connection, - guint32 index, - const pa_cvolume *volume); - -gboolean pulse_connection_suspend_sink (PulseConnection *connection, - guint32 index, - gboolean suspend); -gboolean pulse_connection_suspend_source (PulseConnection *connection, - guint32 index, - gboolean suspend); - -gboolean pulse_connection_move_sink_input (PulseConnection *connection, - guint32 index, - guint32 sink_index); -gboolean pulse_connection_move_source_output (PulseConnection *connection, - guint32 index, - guint32 source_index); - -gboolean pulse_connection_kill_sink_input (PulseConnection *connection, - guint32 index); -gboolean pulse_connection_kill_source_output (PulseConnection *connection, - guint32 index); +PulseConnection * pulse_connection_new (const gchar *app_name, + const gchar *app_id, + const gchar *app_version, + const gchar *app_icon, + const gchar *server_address); + +gboolean pulse_connection_connect (PulseConnection *connection, + gboolean wait_for_daemon); +void pulse_connection_disconnect (PulseConnection *connection); + +PulseConnectionState pulse_connection_get_state (PulseConnection *connection); + +gboolean pulse_connection_load_server_info (PulseConnection *connection); + +gboolean pulse_connection_load_card_info (PulseConnection *connection, + guint32 index); +gboolean pulse_connection_load_card_info_name (PulseConnection *connection, + const gchar *name); + +gboolean pulse_connection_load_sink_info (PulseConnection *connection, + guint32 index); +gboolean pulse_connection_load_sink_info_name (PulseConnection *connection, + const gchar *name); + +gboolean pulse_connection_load_sink_input_info (PulseConnection *connection, + guint32 index); + +gboolean pulse_connection_load_source_info (PulseConnection *connection, + guint32 index); +gboolean pulse_connection_load_source_info_name (PulseConnection *connection, + const gchar *name); + +gboolean pulse_connection_load_source_output_info (PulseConnection *connection, + guint32 index); + +gboolean pulse_connection_load_ext_stream_info (PulseConnection *connection); + +PulseMonitor * pulse_connection_create_monitor (PulseConnection *connection, + guint32 index_source, + guint32 index_sink_input); + +gboolean pulse_connection_set_default_sink (PulseConnection *connection, + const gchar *name); +gboolean pulse_connection_set_default_source (PulseConnection *connection, + const gchar *name); + +gboolean pulse_connection_set_card_profile (PulseConnection *connection, + const gchar *device, + const gchar *profile); + +gboolean pulse_connection_set_sink_mute (PulseConnection *connection, + guint32 index, + gboolean mute); +gboolean pulse_connection_set_sink_volume (PulseConnection *connection, + guint32 index, + const pa_cvolume *volume); +gboolean pulse_connection_set_sink_port (PulseConnection *connection, + guint32 index, + const gchar *port); + +gboolean pulse_connection_set_sink_input_mute (PulseConnection *connection, + guint32 index, + gboolean mute); +gboolean pulse_connection_set_sink_input_volume (PulseConnection *connection, + guint32 index, + const pa_cvolume *volume); + +gboolean pulse_connection_set_source_mute (PulseConnection *connection, + guint32 index, + gboolean mute); +gboolean pulse_connection_set_source_volume (PulseConnection *connection, + guint32 index, + const pa_cvolume *volume); +gboolean pulse_connection_set_source_port (PulseConnection *connection, + guint32 index, + const gchar *port); + +gboolean pulse_connection_set_source_output_mute (PulseConnection *connection, + guint32 index, + gboolean mute); +gboolean pulse_connection_set_source_output_volume (PulseConnection *connection, + guint32 index, + const pa_cvolume *volume); + +gboolean pulse_connection_suspend_sink (PulseConnection *connection, + guint32 index, + gboolean suspend); +gboolean pulse_connection_suspend_source (PulseConnection *connection, + guint32 index, + gboolean suspend); + +gboolean pulse_connection_move_sink_input (PulseConnection *connection, + guint32 index, + guint32 sink_index); +gboolean pulse_connection_move_source_output (PulseConnection *connection, + guint32 index, + guint32 source_index); + +gboolean pulse_connection_kill_sink_input (PulseConnection *connection, + guint32 index); +gboolean pulse_connection_kill_source_output (PulseConnection *connection, + guint32 index); + +gboolean pulse_connection_write_ext_stream (PulseConnection *connection, + const pa_ext_stream_restore_info *info); + +gboolean pulse_connection_delete_ext_stream (PulseConnection *connection, + const gchar *name); G_END_DECLS diff --git a/backends/pulse/pulse-device.c b/backends/pulse/pulse-device.c index 368d85b..96e06c8 100644 --- a/backends/pulse/pulse-device.c +++ b/backends/pulse/pulse-device.c @@ -15,14 +15,16 @@ * License along with this library; if not, see <http://www.gnu.org/licenses/>. */ +#include <string.h> #include <glib.h> #include <glib-object.h> -#include <string.h> #include <libmatemixer/matemixer-device.h> #include <libmatemixer/matemixer-device-profile.h> +#include <libmatemixer/matemixer-device-profile-private.h> #include <libmatemixer/matemixer-enums.h> #include <libmatemixer/matemixer-port.h> +#include <libmatemixer/matemixer-port-private.h> #include <pulse/pulseaudio.h> @@ -34,27 +36,23 @@ struct _PulseDevicePrivate guint32 index; gchar *name; gchar *description; - GList *profiles; - GList *ports; - GList *streams; - gboolean streams_sorted; gchar *icon; + GHashTable *ports; + GList *ports_list; + GHashTable *profiles; + GList *profiles_list; PulseConnection *connection; MateMixerDeviceProfile *profile; }; -enum -{ +enum { PROP_0, PROP_NAME, PROP_DESCRIPTION, PROP_ICON, - PROP_PORTS, - PROP_PROFILES, PROP_ACTIVE_PROFILE, PROP_INDEX, - PROP_CONNECTION, - N_PROPERTIES + PROP_CONNECTION }; static void mate_mixer_device_interface_init (MateMixerDeviceInterface *iface); @@ -78,35 +76,50 @@ G_DEFINE_TYPE_WITH_CODE (PulseDevice, pulse_device, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_DEVICE, mate_mixer_device_interface_init)) -static const gchar * device_get_name (MateMixerDevice *device); -static const gchar * device_get_description (MateMixerDevice *device); -static const gchar * device_get_icon (MateMixerDevice *device); +#if PA_CHECK_VERSION (5, 0, 0) +typedef pa_card_profile_info2 _pa_card_profile_info; +#else +typedef pa_card_profile_info _pa_card_profile_info; +#endif + +static const gchar * pulse_device_get_name (MateMixerDevice *device); +static const gchar * pulse_device_get_description (MateMixerDevice *device); +static const gchar * pulse_device_get_icon (MateMixerDevice *device); -static const GList * device_list_ports (MateMixerDevice *device); -static const GList * device_list_profiles (MateMixerDevice *device); +static MateMixerPort * pulse_device_get_port (MateMixerDevice *device, + const gchar *name); +static MateMixerDeviceProfile *pulse_device_get_profile (MateMixerDevice *device, + const gchar *name); -static MateMixerDeviceProfile *device_get_active_profile (MateMixerDevice *device); -static gboolean device_set_active_profile (MateMixerDevice *device, - const gchar *profile); +static const GList * pulse_device_list_ports (MateMixerDevice *device); +static const GList * pulse_device_list_profiles (MateMixerDevice *device); -static gint device_compare_ports (gconstpointer a, - gconstpointer b); -static gint device_compare_profiles (gconstpointer a, - gconstpointer b); +static MateMixerDeviceProfile *pulse_device_get_active_profile (MateMixerDevice *device); +static gboolean pulse_device_set_active_profile (MateMixerDevice *device, + MateMixerDeviceProfile *profile); -static void device_free_ports (PulseDevice *device); -static void device_free_profiles (PulseDevice *device); +static void update_port (PulseDevice *device, + pa_card_port_info *p_info); +static void update_profile (PulseDevice *device, + _pa_card_profile_info *p_info); + +static gint compare_ports (gconstpointer a, + gconstpointer b); +static gint compare_profiles (gconstpointer a, + gconstpointer b); static void mate_mixer_device_interface_init (MateMixerDeviceInterface *iface) { - iface->get_name = device_get_name; - iface->get_description = device_get_description; - iface->get_icon = device_get_icon; - iface->list_ports = device_list_ports; - iface->list_profiles = device_list_profiles; - iface->get_active_profile = device_get_active_profile; - iface->set_active_profile = device_set_active_profile; + iface->get_name = pulse_device_get_name; + iface->get_description = pulse_device_get_description; + iface->get_icon = pulse_device_get_icon; + iface->get_port = pulse_device_get_port; + iface->get_profile = pulse_device_get_profile; + iface->list_ports = pulse_device_list_ports; + iface->list_profiles = pulse_device_list_profiles; + iface->get_active_profile = pulse_device_get_active_profile; + iface->set_active_profile = pulse_device_set_active_profile; } static void @@ -144,8 +157,6 @@ pulse_device_class_init (PulseDeviceClass *klass) g_object_class_override_property (object_class, PROP_NAME, "name"); g_object_class_override_property (object_class, PROP_DESCRIPTION, "description"); g_object_class_override_property (object_class, PROP_ICON, "icon"); - g_object_class_override_property (object_class, PROP_PORTS, "ports"); - g_object_class_override_property (object_class, PROP_PROFILES, "profiles"); g_object_class_override_property (object_class, PROP_ACTIVE_PROFILE, "active-profile"); g_type_class_add_private (object_class, sizeof (PulseDevicePrivate)); @@ -171,12 +182,6 @@ pulse_device_get_property (GObject *object, case PROP_ICON: g_value_set_string (value, device->priv->icon); break; - case PROP_PORTS: - g_value_set_pointer (value, device->priv->ports); - break; - case PROP_PROFILES: - g_value_set_pointer (value, device->priv->profiles); - break; case PROP_ACTIVE_PROFILE: g_value_set_object (value, device->priv->profile); break; @@ -222,6 +227,16 @@ pulse_device_init (PulseDevice *device) device->priv = G_TYPE_INSTANCE_GET_PRIVATE (device, PULSE_TYPE_DEVICE, PulseDevicePrivate); + + device->priv->ports = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + device->priv->profiles = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); } static void @@ -231,9 +246,19 @@ pulse_device_dispose (GObject *object) device = PULSE_DEVICE (object); - device_free_ports (device); - device_free_profiles (device); + if (device->priv->ports_list != NULL) { + g_list_free_full (device->priv->ports_list, g_object_unref); + device->priv->ports_list = NULL; + } + g_hash_table_remove_all (device->priv->ports); + if (device->priv->profiles_list != NULL) { + g_list_free_full (device->priv->profiles_list, g_object_unref); + device->priv->profiles_list = NULL; + } + g_hash_table_remove_all (device->priv->profiles); + + g_clear_object (&device->priv->profile); g_clear_object (&device->priv->connection); G_OBJECT_CLASS (pulse_device_parent_class)->dispose (object); @@ -250,6 +275,9 @@ pulse_device_finalize (GObject *object) g_free (device->priv->description); g_free (device->priv->icon); + g_hash_table_destroy (device->priv->ports); + g_hash_table_destroy (device->priv->profiles); + G_OBJECT_CLASS (pulse_device_parent_class)->finalize (object); } @@ -276,6 +304,7 @@ pulse_device_new (PulseConnection *connection, const pa_card_info *info) gboolean pulse_device_update (PulseDevice *device, const pa_card_info *info) { + MateMixerDeviceProfile *profile = NULL; const gchar *prop; guint32 i; @@ -286,7 +315,7 @@ pulse_device_update (PulseDevice *device, const pa_card_info *info) g_object_freeze_notify (G_OBJECT (device)); /* Name */ - if (g_strcmp0 (device->priv->name, info->name)) { + if (g_strcmp0 (device->priv->name, info->name) != 0) { g_free (device->priv->name); device->priv->name = g_strdup (info->name); @@ -299,7 +328,7 @@ pulse_device_update (PulseDevice *device, const pa_card_info *info) if (G_UNLIKELY (prop == NULL)) prop = info->name; - if (g_strcmp0 (device->priv->description, prop)) { + if (g_strcmp0 (device->priv->description, prop) != 0) { g_free (device->priv->description); device->priv->description = g_strdup (prop); @@ -312,49 +341,23 @@ pulse_device_update (PulseDevice *device, const pa_card_info *info) if (G_UNLIKELY (prop == NULL)) prop = "audio-card"; - if (g_strcmp0 (device->priv->icon, prop)) { + if (g_strcmp0 (device->priv->icon, prop) != 0) { g_free (device->priv->icon); device->priv->icon = g_strdup (prop); g_object_notify (G_OBJECT (device), "icon"); } +#if PA_CHECK_VERSION (2, 0, 0) /* List of ports */ - device_free_ports (device); - for (i = 0; i < info->n_ports; i++) { - MateMixerPortFlags flags = MATE_MIXER_PORT_NO_FLAGS; - - prop = pa_proplist_gets (info->ports[i]->proplist, "device.icon_name"); - -#if PA_CHECK_VERSION(2, 0, 0) - if (info->ports[i]->available == PA_PORT_AVAILABLE_YES) - flags |= MATE_MIXER_PORT_AVAILABLE; - - if (info->ports[i]->direction & PA_DIRECTION_INPUT) - flags |= MATE_MIXER_PORT_INPUT; - if (info->ports[i]->direction & PA_DIRECTION_OUTPUT) - flags |= MATE_MIXER_PORT_OUTPUT; -#endif - device->priv->ports = - g_list_prepend (device->priv->ports, - mate_mixer_port_new (info->ports[i]->name, - info->ports[i]->description, - prop, - info->ports[i]->priority, - flags)); + update_port (device, info->ports[i]); } - device->priv->ports = g_list_sort (device->priv->ports, device_compare_ports); - - g_object_notify (G_OBJECT (device), "ports"); +#endif /* List of profiles */ - device_free_profiles (device); - for (i = 0; i < info->n_profiles; i++) { - MateMixerDeviceProfile *profile; - -#if PA_CHECK_VERSION(5, 0, 0) +#if PA_CHECK_VERSION (5, 0, 0) pa_card_profile_info2 *p_info = info->profiles2[i]; /* PulseAudio 5.0 includes a new pa_card_profile_info2 which only @@ -366,28 +369,28 @@ pulse_device_update (PulseDevice *device, const pa_card_info *info) /* The old profile list is an array of structs, not pointers */ pa_card_profile_info *p_info = &info->profiles[i]; #endif - profile = mate_mixer_device_profile_new ( - p_info->name, - p_info->description, - p_info->priority, - p_info->n_sources, - p_info->n_sinks); - - if (device->priv->profile == NULL) { -#if PA_CHECK_VERSION(5, 0, 0) - if (!g_strcmp0 (p_info->name, info->active_profile2->name)) - device->priv->profile = g_object_ref (profile); + update_profile (device, p_info); + } + + /* Figure out whether the currently active profile has changed */ + profile = NULL; + +#if PA_CHECK_VERSION (5, 0, 0) + if (G_LIKELY (info->active_profile2 != NULL)) + profile = g_hash_table_lookup (device->priv->profiles, info->active_profile2->name); #else - if (!g_strcmp0 (p_info->name, info->active_profile->name)) - device->priv->profile = g_object_ref (profile); + if (G_LIKELY (info->active_profile != NULL)) + profile = g_hash_table_lookup (device->priv->profiles, info->active_profile->name); #endif - } - device->priv->profiles = g_list_prepend (device->priv->profiles, profile); - } - device->priv->profiles = g_list_sort (device->priv->profiles, - device_compare_profiles); - g_object_notify (G_OBJECT (device), "profiles"); + if (profile != device->priv->profile) { + g_clear_object (&device->priv->profile); + + if (G_LIKELY (profile != NULL)) + device->priv->profile = g_object_ref (profile); + + g_object_notify (G_OBJECT (device), "active-profile"); + } g_object_thaw_notify (G_OBJECT (device)); return TRUE; @@ -410,7 +413,7 @@ pulse_device_get_index (PulseDevice *device) } static const gchar * -device_get_name (MateMixerDevice *device) +pulse_device_get_name (MateMixerDevice *device) { g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); @@ -418,7 +421,7 @@ device_get_name (MateMixerDevice *device) } static const gchar * -device_get_description (MateMixerDevice *device) +pulse_device_get_description (MateMixerDevice *device) { g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); @@ -426,31 +429,77 @@ device_get_description (MateMixerDevice *device) } static const gchar * -device_get_icon (MateMixerDevice *device) +pulse_device_get_icon (MateMixerDevice *device) { g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); return PULSE_DEVICE (device)->priv->icon; } +static MateMixerPort * +pulse_device_get_port (MateMixerDevice *device, const gchar *name) +{ + g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); + g_return_val_if_fail (name != NULL, NULL); + + return g_hash_table_lookup (PULSE_DEVICE (device)->priv->ports, name); +} + +static MateMixerDeviceProfile * +pulse_device_get_profile (MateMixerDevice *device, const gchar *name) +{ + g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); + g_return_val_if_fail (name != NULL, NULL); + + return g_hash_table_lookup (PULSE_DEVICE (device)->priv->profiles, name); +} + static const GList * -device_list_ports (MateMixerDevice *device) +pulse_device_list_ports (MateMixerDevice *device) { + PulseDevice *pulse; + g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); - return (const GList *) PULSE_DEVICE (device)->priv->ports; + pulse = PULSE_DEVICE (device); + + if (pulse->priv->ports_list == NULL) { + GList *list = g_hash_table_get_values (pulse->priv->ports); + + if (list != NULL) { + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + pulse->priv->ports_list = g_list_sort (list, compare_ports); + } + } + + return (const GList *) pulse->priv->ports_list; } static const GList * -device_list_profiles (MateMixerDevice *device) +pulse_device_list_profiles (MateMixerDevice *device) { + PulseDevice *pulse; + g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); - return (const GList *) PULSE_DEVICE (device)->priv->profiles; + pulse = PULSE_DEVICE (device); + + if (pulse->priv->profiles_list == NULL) { + GList *list = g_hash_table_get_values (pulse->priv->profiles); + + if (list != NULL) { + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + pulse->priv->profiles_list = g_list_sort (list, compare_profiles); + } + } + + return (const GList *) pulse->priv->profiles_list; } static MateMixerDeviceProfile * -device_get_active_profile (MateMixerDevice *device) +pulse_device_get_active_profile (MateMixerDevice *device) { g_return_val_if_fail (PULSE_IS_DEVICE (device), NULL); @@ -458,18 +507,103 @@ device_get_active_profile (MateMixerDevice *device) } static gboolean -device_set_active_profile (MateMixerDevice *device, const gchar *profile) +pulse_device_set_active_profile (MateMixerDevice *device, MateMixerDeviceProfile *profile) { + PulseDevice *pulse; + const gchar *name; + gboolean ret; + g_return_val_if_fail (PULSE_IS_DEVICE (device), FALSE); - g_return_val_if_fail (profile != NULL, FALSE); + g_return_val_if_fail (MATE_MIXER_IS_DEVICE_PROFILE (profile), FALSE); + + pulse = PULSE_DEVICE (device); + + name = mate_mixer_device_profile_get_name (profile); + + /* Make sure the profile belongs to the device */ + if (g_hash_table_lookup (pulse->priv->profiles, name) == NULL) { + g_warning ("Profile %s does not belong to device %s", name, pulse->priv->name); + return FALSE; + } + + ret = pulse_connection_set_card_profile (pulse->priv->connection, + pulse->priv->name, + name); + if (ret == TRUE) { + if (pulse->priv->profile != NULL) + g_object_unref (pulse->priv->profile); + + pulse->priv->profile = g_object_ref (profile); + + g_object_notify (G_OBJECT (device), "active-profile"); + } + return ret; +} + +static void +update_port (PulseDevice *device, pa_card_port_info *p_info) +{ + MateMixerPort *port; + MateMixerPortFlags flags = MATE_MIXER_PORT_NO_FLAGS; + const gchar *icon; + + icon = pa_proplist_gets (p_info->proplist, "device.icon_name"); + + if (p_info->available == PA_PORT_AVAILABLE_YES) + flags |= MATE_MIXER_PORT_AVAILABLE; + + if (p_info->direction & PA_DIRECTION_INPUT) + flags |= MATE_MIXER_PORT_INPUT; + if (p_info->direction & PA_DIRECTION_OUTPUT) + flags |= MATE_MIXER_PORT_OUTPUT; + + port = g_hash_table_lookup (device->priv->ports, p_info->name); + + if (port != NULL) { + /* Update existing port */ + _mate_mixer_port_update_description (port, p_info->description); + _mate_mixer_port_update_icon (port, icon); + _mate_mixer_port_update_priority (port, p_info->priority); + _mate_mixer_port_update_flags (port, flags); + } else { + /* Add previously unknown port to the hash table */ + port = _mate_mixer_port_new (p_info->name, + p_info->description, + icon, + p_info->priority, + flags); + + g_hash_table_insert (device->priv->ports, g_strdup (p_info->name), port); + } +} + +static void +update_profile (PulseDevice *device, _pa_card_profile_info *p_info) +{ + MateMixerDeviceProfile *profile; - return pulse_connection_set_card_profile (PULSE_DEVICE (device)->priv->connection, - PULSE_DEVICE (device)->priv->name, - profile); + profile = g_hash_table_lookup (device->priv->profiles, p_info->name); + + if (profile != NULL) { + /* Update existing profile */ + _mate_mixer_device_profile_update_description (profile, p_info->description); + _mate_mixer_device_profile_update_priority (profile, p_info->priority); + _mate_mixer_device_profile_update_num_input_streams (profile, p_info->n_sources); + _mate_mixer_device_profile_update_num_output_streams (profile, p_info->n_sinks); + } else { + /* Add previously unknown profile to the hash table */ + profile = _mate_mixer_device_profile_new (p_info->name, + p_info->description, + p_info->priority, + p_info->n_sources, + p_info->n_sinks); + + g_hash_table_insert (device->priv->profiles, g_strdup (p_info->name), profile); + } } static gint -device_compare_ports (gconstpointer a, gconstpointer b) +compare_ports (gconstpointer a, gconstpointer b) { MateMixerPort *p1 = MATE_MIXER_PORT (a); MateMixerPort *p2 = MATE_MIXER_PORT (b); @@ -484,7 +618,7 @@ device_compare_ports (gconstpointer a, gconstpointer b) } static gint -device_compare_profiles (gconstpointer a, gconstpointer b) +compare_profiles (gconstpointer a, gconstpointer b) { MateMixerDeviceProfile *p1 = MATE_MIXER_DEVICE_PROFILE (a); MateMixerDeviceProfile *p2 = MATE_MIXER_DEVICE_PROFILE (b); @@ -497,27 +631,3 @@ device_compare_profiles (gconstpointer a, gconstpointer b) return strcmp (mate_mixer_device_profile_get_name (p1), mate_mixer_device_profile_get_name (p2)); } - -static void -device_free_ports (PulseDevice *device) -{ - if (device->priv->ports == NULL) - return; - - g_list_free_full (device->priv->ports, g_object_unref); - - device->priv->ports = NULL; -} - -static void -device_free_profiles (PulseDevice *device) -{ - if (device->priv->profiles == NULL) - return; - - g_list_free_full (device->priv->profiles, g_object_unref); - - g_clear_object (&device->priv->profile); - - device->priv->profiles = NULL; -} diff --git a/backends/pulse/pulse-ext-stream.c b/backends/pulse/pulse-ext-stream.c new file mode 100644 index 0000000..96164e8 --- /dev/null +++ b/backends/pulse/pulse-ext-stream.c @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer-client-stream.h> +#include <libmatemixer/matemixer-enums.h> +#include <libmatemixer/matemixer-stream.h> + +#include <pulse/pulseaudio.h> +#include <pulse/ext-stream-restore.h> + +#include "pulse-connection.h" +#include "pulse-client-stream.h" +#include "pulse-ext-stream.h" +#include "pulse-helpers.h" +#include "pulse-sink.h" +#include "pulse-source.h" +#include "pulse-stream.h" + +static void pulse_ext_stream_class_init (PulseExtStreamClass *klass); +static void pulse_ext_stream_init (PulseExtStream *ext); + +G_DEFINE_TYPE (PulseExtStream, pulse_ext_stream, PULSE_TYPE_CLIENT_STREAM); + +static void pulse_ext_stream_reload (PulseStream *pstream); + +static gboolean pulse_ext_stream_set_mute (PulseStream *pstream, + gboolean mute); +static gboolean pulse_ext_stream_set_volume (PulseStream *pstream, + pa_cvolume *cvolume); +static gboolean pulse_ext_stream_set_parent (PulseClientStream *pclient, + PulseStream *parent); +static gboolean pulse_ext_stream_remove (PulseClientStream *pclient); + +static void +pulse_ext_stream_class_init (PulseExtStreamClass *klass) +{ + PulseStreamClass *stream_class; + PulseClientStreamClass *client_class; + + stream_class = PULSE_STREAM_CLASS (klass); + + stream_class->reload = pulse_ext_stream_reload; + stream_class->set_mute = pulse_ext_stream_set_mute; + stream_class->set_volume = pulse_ext_stream_set_volume; + + client_class = PULSE_CLIENT_STREAM_CLASS (klass); + + client_class->set_parent = pulse_ext_stream_set_parent; + client_class->remove = pulse_ext_stream_remove; +} + +static void +pulse_ext_stream_init (PulseExtStream *ext) +{ +} + +PulseStream * +pulse_ext_stream_new (PulseConnection *connection, + const pa_ext_stream_restore_info *info, + PulseStream *parent) +{ + PulseStream *ext; + + g_return_val_if_fail (PULSE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (info != NULL, NULL); + + ext = g_object_new (PULSE_TYPE_EXT_STREAM, + "connection", connection, + NULL); + + /* Consider the stream name as unchanging parameter */ + pulse_stream_update_name (ext, info->name); + + /* Other data may change at any time, so let's make a use of our update function */ + pulse_ext_stream_update (ext, info, parent); + + return ext; +} + +gboolean +pulse_ext_stream_update (PulseStream *pstream, + const pa_ext_stream_restore_info *info, + PulseStream *parent) +{ + MateMixerClientStreamRole role = MATE_MIXER_CLIENT_STREAM_ROLE_NONE; + MateMixerStreamFlags flags = MATE_MIXER_STREAM_CLIENT | + MATE_MIXER_STREAM_HAS_VOLUME | + MATE_MIXER_STREAM_HAS_MUTE | + MATE_MIXER_STREAM_CAN_SET_VOLUME; + MateMixerClientStreamFlags client_flags = + MATE_MIXER_CLIENT_STREAM_CACHED; + + PulseClientStream *pclient; + gchar *suffix; + + g_return_val_if_fail (PULSE_IS_EXT_STREAM (pstream), FALSE); + g_return_val_if_fail (info != NULL, FALSE); + + pclient = PULSE_CLIENT_STREAM (pstream); + + suffix = strchr (info->name, ':'); + if (suffix != NULL) + suffix++; + + /* Let all the information update before emitting notify signals */ + g_object_freeze_notify (G_OBJECT (pstream)); + + if (g_str_has_prefix (info->name, "sink-input")) + flags |= MATE_MIXER_STREAM_OUTPUT; + else if (g_str_has_prefix (info->name, "source-output")) + flags |= MATE_MIXER_STREAM_INPUT; + else + g_debug ("Unknown ext-stream %s", info->name); + + if (strstr (info->name, "-by-media-role:")) { + if (G_LIKELY (suffix != NULL)) + role = pulse_convert_media_role_name (suffix); + } + else if (strstr (info->name, "-by-application-name:")) { + client_flags |= MATE_MIXER_CLIENT_STREAM_APPLICATION; + + if (G_LIKELY (suffix != NULL)) + pulse_client_stream_update_app_name (pclient, suffix); + } + else if (strstr (info->name, "-by-application-id:")) { + client_flags |= MATE_MIXER_CLIENT_STREAM_APPLICATION; + + if (G_LIKELY (suffix != NULL)) + pulse_client_stream_update_app_id (pclient, suffix); + } + + /* Flags needed before volume */ + pulse_stream_update_flags (pstream, flags); + + pulse_stream_update_channel_map (pstream, &info->channel_map); + pulse_stream_update_volume (pstream, &info->volume, 0); + + pulse_stream_update_mute (pstream, info->mute ? TRUE : FALSE); + + pulse_client_stream_update_flags (pclient, client_flags); + pulse_client_stream_update_role (pclient, role); + + if (parent != NULL) + pulse_client_stream_update_parent (pclient, MATE_MIXER_STREAM (parent)); + else + pulse_client_stream_update_parent (pclient, NULL); + + g_object_thaw_notify (G_OBJECT (pstream)); + return TRUE; +} + +static void +pulse_ext_stream_reload (PulseStream *pstream) +{ + g_return_if_fail (PULSE_IS_EXT_STREAM (pstream)); + + pulse_connection_load_ext_stream_info (pulse_stream_get_connection (pstream)); +} + +static gboolean +pulse_ext_stream_set_mute (PulseStream *pstream, gboolean mute) +{ + MateMixerStream *parent; + const pa_channel_map *map; + const pa_cvolume *cvolume; + pa_ext_stream_restore_info info; + + g_return_val_if_fail (PULSE_IS_EXT_STREAM (pstream), FALSE); + + info.name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream)); + info.mute = mute; + + map = pulse_stream_get_channel_map (pstream); + if (map != NULL) + info.channel_map = *map; + else + pa_channel_map_init (&info.channel_map); + + cvolume = pulse_stream_get_cvolume (pstream); + if (cvolume != NULL) + info.volume = *cvolume; + else + pa_cvolume_init (&info.volume); + + parent = mate_mixer_client_stream_get_parent (MATE_MIXER_CLIENT_STREAM (pstream)); + if (parent != NULL) + info.device = mate_mixer_stream_get_name (parent); + else + info.device = NULL; + + return pulse_connection_write_ext_stream (pulse_stream_get_connection (pstream), &info); +} + +static gboolean +pulse_ext_stream_set_volume (PulseStream *pstream, pa_cvolume *cvolume) +{ + MateMixerStream *parent; + const pa_channel_map *map; + pa_ext_stream_restore_info info; + + g_return_val_if_fail (PULSE_IS_EXT_STREAM (pstream), FALSE); + g_return_val_if_fail (cvolume != NULL, FALSE); + + info.name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream)); + info.mute = mate_mixer_stream_get_mute (MATE_MIXER_STREAM (pstream)); + + map = pulse_stream_get_channel_map (pstream); + if (map != NULL) + info.channel_map = *map; + else + pa_channel_map_init (&info.channel_map); + + parent = mate_mixer_client_stream_get_parent (MATE_MIXER_CLIENT_STREAM (pstream)); + if (parent != NULL) + info.device = mate_mixer_stream_get_name (parent); + else + info.device = NULL; + + info.volume = *cvolume; + + return pulse_connection_write_ext_stream (pulse_stream_get_connection (pstream), &info); +} + +static gboolean +pulse_ext_stream_set_parent (PulseClientStream *pclient, PulseStream *parent) +{ + MateMixerStreamFlags flags; + PulseStream *pstream; + const pa_channel_map *map; + const pa_cvolume *cvolume; + pa_ext_stream_restore_info info; + + g_return_val_if_fail (PULSE_IS_EXT_STREAM (pclient), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (parent), FALSE); + + flags = mate_mixer_stream_get_flags (MATE_MIXER_STREAM (pclient)); + + /* Validate the parent stream */ + if (flags & MATE_MIXER_STREAM_INPUT && !PULSE_IS_SOURCE (parent)) { + g_warning ("Could not change stream parent to %s: not a parent input stream", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (parent))); + return FALSE; + } else if (!PULSE_IS_SINK (parent)) { + g_warning ("Could not change stream parent to %s: not a parent output stream", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (parent))); + return FALSE; + } + + pstream = PULSE_STREAM (pclient); + + info.name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream)); + info.mute = mate_mixer_stream_get_mute (MATE_MIXER_STREAM (pstream)); + + map = pulse_stream_get_channel_map (pstream); + if (map != NULL) + info.channel_map = *map; + else + pa_channel_map_init (&info.channel_map); + + cvolume = pulse_stream_get_cvolume (pstream); + if (cvolume != NULL) + info.volume = *cvolume; + else + pa_cvolume_init (&info.volume); + + info.device = mate_mixer_stream_get_name (MATE_MIXER_STREAM (parent)); + + return pulse_connection_write_ext_stream (pulse_stream_get_connection (pstream), &info); +} + +static gboolean +pulse_ext_stream_remove (PulseClientStream *pclient) +{ + PulseStream *pstream; + const gchar *name; + + g_return_val_if_fail (PULSE_IS_EXT_STREAM (pclient), FALSE); + + pstream = PULSE_STREAM (pclient); + name = mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream)); + + return pulse_connection_delete_ext_stream (pulse_stream_get_connection (pstream), name); +} diff --git a/backends/pulse/pulse-ext-stream.h b/backends/pulse/pulse-ext-stream.h new file mode 100644 index 0000000..e8dabb6 --- /dev/null +++ b/backends/pulse/pulse-ext-stream.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PULSE_EXT_STREAM_H +#define PULSE_EXT_STREAM_H + +#include <glib.h> +#include <glib-object.h> + +#include <pulse/pulseaudio.h> +#include <pulse/ext-stream-restore.h> + +#include "pulse-client-stream.h" +#include "pulse-connection.h" +#include "pulse-stream.h" + +G_BEGIN_DECLS + +#define PULSE_TYPE_EXT_STREAM \ + (pulse_ext_stream_get_type ()) +#define PULSE_EXT_STREAM(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), PULSE_TYPE_EXT_STREAM, PulseExtStream)) +#define PULSE_IS_EXT_STREAM(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), PULSE_TYPE_EXT_STREAM)) +#define PULSE_EXT_STREAM_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), PULSE_TYPE_EXT_STREAM, PulseExtStreamClass)) +#define PULSE_IS_EXT_STREAM_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), PULSE_TYPE_EXT_STREAM)) +#define PULSE_EXT_STREAM_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), PULSE_TYPE_EXT_STREAM, PulseExtStreamClass)) + +typedef struct _PulseExtStream PulseExtStream; +typedef struct _PulseExtStreamClass PulseExtStreamClass; + +struct _PulseExtStream +{ + PulseClientStream parent; +}; + +struct _PulseExtStreamClass +{ + PulseClientStreamClass parent_class; +}; + +GType pulse_ext_stream_get_type (void) G_GNUC_CONST; + +PulseStream *pulse_ext_stream_new (PulseConnection *connection, + const pa_ext_stream_restore_info *info, + PulseStream *parent); + +gboolean pulse_ext_stream_update (PulseStream *pstream, + const pa_ext_stream_restore_info *info, + PulseStream *parent); + +G_END_DECLS + +#endif /* PULSE_EXT_STREAM_H */ diff --git a/backends/pulse/pulse-helpers.c b/backends/pulse/pulse-helpers.c index ca39d8f..577f2c6 100644 --- a/backends/pulse/pulse-helpers.c +++ b/backends/pulse/pulse-helpers.c @@ -15,9 +15,11 @@ * License along with this library; if not, see <http://www.gnu.org/licenses/>. */ +#include <string.h> #include <glib.h> #include <libmatemixer/matemixer-enums.h> + #include <pulse/pulseaudio.h> #include "pulse-helpers.h" @@ -28,44 +30,44 @@ typedef struct { } PositionMap; static PositionMap const position_map[] = { - { MATE_MIXER_CHANNEL_UNKNOWN_POSITION, PA_CHANNEL_POSITION_INVALID }, - { MATE_MIXER_CHANNEL_MONO, PA_CHANNEL_POSITION_MONO }, - { MATE_MIXER_CHANNEL_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_LEFT }, - { MATE_MIXER_CHANNEL_FRONT_RIGHT, PA_CHANNEL_POSITION_FRONT_RIGHT }, - { MATE_MIXER_CHANNEL_FRONT_CENTER, PA_CHANNEL_POSITION_FRONT_CENTER }, - { MATE_MIXER_CHANNEL_LFE, PA_CHANNEL_POSITION_LFE }, - { MATE_MIXER_CHANNEL_BACK_LEFT, PA_CHANNEL_POSITION_REAR_LEFT }, - { MATE_MIXER_CHANNEL_BACK_RIGHT, PA_CHANNEL_POSITION_REAR_RIGHT }, - { MATE_MIXER_CHANNEL_FRONT_LEFT_CENTER, PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER }, - { MATE_MIXER_CHANNEL_FRONT_RIGHT_CENTER, PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER }, - { MATE_MIXER_CHANNEL_BACK_CENTER, PA_CHANNEL_POSITION_REAR_CENTER }, - { MATE_MIXER_CHANNEL_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_LEFT }, - { MATE_MIXER_CHANNEL_SIDE_RIGHT, PA_CHANNEL_POSITION_SIDE_RIGHT }, - { MATE_MIXER_CHANNEL_TOP_FRONT_LEFT, PA_CHANNEL_POSITION_TOP_FRONT_LEFT }, - { MATE_MIXER_CHANNEL_TOP_FRONT_RIGHT, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT }, - { MATE_MIXER_CHANNEL_TOP_FRONT_CENTER, PA_CHANNEL_POSITION_TOP_FRONT_CENTER }, - { MATE_MIXER_CHANNEL_TOP_CENTER, PA_CHANNEL_POSITION_TOP_CENTER }, - { MATE_MIXER_CHANNEL_TOP_BACK_LEFT, PA_CHANNEL_POSITION_TOP_REAR_LEFT }, - { MATE_MIXER_CHANNEL_TOP_BACK_RIGHT, PA_CHANNEL_POSITION_TOP_REAR_RIGHT }, - { MATE_MIXER_CHANNEL_TOP_BACK_CENTER, PA_CHANNEL_POSITION_TOP_REAR_CENTER }, + { MATE_MIXER_CHANNEL_UNKNOWN, PA_CHANNEL_POSITION_INVALID }, + { MATE_MIXER_CHANNEL_MONO, PA_CHANNEL_POSITION_MONO }, + { MATE_MIXER_CHANNEL_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_LEFT }, + { MATE_MIXER_CHANNEL_FRONT_RIGHT, PA_CHANNEL_POSITION_FRONT_RIGHT }, + { MATE_MIXER_CHANNEL_FRONT_CENTER, PA_CHANNEL_POSITION_FRONT_CENTER }, + { MATE_MIXER_CHANNEL_LFE, PA_CHANNEL_POSITION_LFE }, + { MATE_MIXER_CHANNEL_BACK_LEFT, PA_CHANNEL_POSITION_REAR_LEFT }, + { MATE_MIXER_CHANNEL_BACK_RIGHT, PA_CHANNEL_POSITION_REAR_RIGHT }, + { MATE_MIXER_CHANNEL_BACK_CENTER, PA_CHANNEL_POSITION_REAR_CENTER }, + { MATE_MIXER_CHANNEL_FRONT_LEFT_CENTER, PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER }, + { MATE_MIXER_CHANNEL_FRONT_RIGHT_CENTER, PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER }, + { MATE_MIXER_CHANNEL_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_LEFT }, + { MATE_MIXER_CHANNEL_SIDE_RIGHT, PA_CHANNEL_POSITION_SIDE_RIGHT }, + { MATE_MIXER_CHANNEL_TOP_FRONT_LEFT, PA_CHANNEL_POSITION_TOP_FRONT_LEFT }, + { MATE_MIXER_CHANNEL_TOP_FRONT_RIGHT, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT }, + { MATE_MIXER_CHANNEL_TOP_FRONT_CENTER, PA_CHANNEL_POSITION_TOP_FRONT_CENTER }, + { MATE_MIXER_CHANNEL_TOP_CENTER, PA_CHANNEL_POSITION_TOP_CENTER }, + { MATE_MIXER_CHANNEL_TOP_BACK_LEFT, PA_CHANNEL_POSITION_TOP_REAR_LEFT }, + { MATE_MIXER_CHANNEL_TOP_BACK_RIGHT, PA_CHANNEL_POSITION_TOP_REAR_RIGHT }, + { MATE_MIXER_CHANNEL_TOP_BACK_CENTER, PA_CHANNEL_POSITION_TOP_REAR_CENTER }, }; MateMixerChannelPosition pulse_convert_position_from_pulse (pa_channel_position_t position) { - int i; + guint i; for (i = 0; i < G_N_ELEMENTS (position_map); i++) { if (position == position_map[i].pa_position) return position_map[i].mm_position; } - return MATE_MIXER_CHANNEL_UNKNOWN_POSITION; + return MATE_MIXER_CHANNEL_UNKNOWN; } pa_channel_position_t pulse_convert_position_to_pulse (MateMixerChannelPosition position) { - int i; + guint i; for (i = 0; i < G_N_ELEMENTS (position_map); i++) { if (position == position_map[i].mm_position) @@ -73,3 +75,43 @@ pulse_convert_position_to_pulse (MateMixerChannelPosition position) } return PA_CHANNEL_POSITION_INVALID; } + +MateMixerClientStreamRole +pulse_convert_media_role_name (const gchar *name) +{ + if (!strcmp (name, "video")) { + return MATE_MIXER_CLIENT_STREAM_ROLE_VIDEO; + } + else if (!strcmp (name, "music")) { + return MATE_MIXER_CLIENT_STREAM_ROLE_MUSIC; + } + else if (!strcmp (name, "game")) { + return MATE_MIXER_CLIENT_STREAM_ROLE_GAME; + } + else if (!strcmp (name, "event")) { + return MATE_MIXER_CLIENT_STREAM_ROLE_EVENT; + } + else if (!strcmp (name, "phone")) { + return MATE_MIXER_CLIENT_STREAM_ROLE_PHONE; + } + else if (!strcmp (name, "animation")) { + return MATE_MIXER_CLIENT_STREAM_ROLE_ANIMATION; + } + else if (!strcmp (name, "production")) { + return MATE_MIXER_CLIENT_STREAM_ROLE_PRODUCTION; + } + else if (!strcmp (name, "a11y")) { + return MATE_MIXER_CLIENT_STREAM_ROLE_A11Y; + } + else if (!strcmp (name, "test")) { + return MATE_MIXER_CLIENT_STREAM_ROLE_TEST; + } + else if (!strcmp (name, "abstract")) { + return MATE_MIXER_CLIENT_STREAM_ROLE_ABSTRACT; + } + else if (!strcmp (name, "filter")) { + return MATE_MIXER_CLIENT_STREAM_ROLE_FILTER; + } + + return MATE_MIXER_CLIENT_STREAM_ROLE_NONE; +} diff --git a/backends/pulse/pulse-helpers.h b/backends/pulse/pulse-helpers.h index efc8fc9..7ccd753 100644 --- a/backends/pulse/pulse-helpers.h +++ b/backends/pulse/pulse-helpers.h @@ -26,8 +26,10 @@ G_BEGIN_DECLS -MateMixerChannelPosition pulse_convert_position_from_pulse (pa_channel_position_t position); -pa_channel_position_t pulse_convert_position_to_pulse (MateMixerChannelPosition position); +MateMixerChannelPosition pulse_convert_position_from_pulse (pa_channel_position_t position); +pa_channel_position_t pulse_convert_position_to_pulse (MateMixerChannelPosition position); + +MateMixerClientStreamRole pulse_convert_media_role_name (const gchar *name); G_END_DECLS diff --git a/backends/pulse/pulse-monitor.c b/backends/pulse/pulse-monitor.c index 041f903..3d5b4a8 100644 --- a/backends/pulse/pulse-monitor.c +++ b/backends/pulse/pulse-monitor.c @@ -34,23 +34,44 @@ struct _PulseMonitorPrivate }; enum { + PROP_0, + PROP_ENABLED, + PROP_NAME, + PROP_INDEX_SOURCE, + PROP_INDEX_SINK_INPUT, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES] = { NULL, }; + +enum { VALUE, N_SIGNALS }; static guint signals[N_SIGNALS] = { 0, }; -static void pulse_monitor_class_init (PulseMonitorClass *klass); -static void pulse_monitor_init (PulseMonitor *port); -static void pulse_monitor_finalize (GObject *object); +static void pulse_monitor_class_init (PulseMonitorClass *klass); + +static void pulse_monitor_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void pulse_monitor_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); + +static void pulse_monitor_init (PulseMonitor *monitor); +static void pulse_monitor_finalize (GObject *object); G_DEFINE_TYPE (PulseMonitor, pulse_monitor, G_TYPE_OBJECT); -static gboolean monitor_prepare (PulseMonitor *monitor); -static gboolean monitor_connect_record (PulseMonitor *monitor); -static void monitor_read_cb (pa_stream *stream, - size_t length, - void *userdata); +static gboolean stream_connect (PulseMonitor *monitor); + +static void stream_read_cb (pa_stream *stream, + size_t length, + void *userdata); static void pulse_monitor_class_init (PulseMonitorClass *klass) @@ -58,7 +79,50 @@ pulse_monitor_class_init (PulseMonitorClass *klass) GObjectClass *object_class; object_class = G_OBJECT_CLASS (klass); - object_class->finalize = pulse_monitor_finalize; + object_class->finalize = pulse_monitor_finalize; + object_class->get_property = pulse_monitor_get_property; + object_class->set_property = pulse_monitor_set_property; + + properties[PROP_ENABLED] = + g_param_spec_boolean ("enabled", + "Enabled", + "Monitor enabled", + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_NAME] = + g_param_spec_string ("name", + "Name", + "Name of the monitor", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS); + + properties[PROP_INDEX_SOURCE] = + g_param_spec_uint ("index-source", + "Index of source", + "Index of the PulseAudio source", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_INDEX_SINK_INPUT] = + g_param_spec_uint ("index-sink-input", + "Index of sink input", + "Index of the PulseAudio sink input", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); signals[VALUE] = g_signal_new ("value", @@ -76,6 +140,61 @@ pulse_monitor_class_init (PulseMonitorClass *klass) } static void +pulse_monitor_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + PulseMonitor *monitor; + + monitor = PULSE_MONITOR (object); + + switch (param_id) { + case PROP_ENABLED: + g_value_set_boolean (value, monitor->priv->enabled); + break; + case PROP_NAME: + g_value_set_string (value, monitor->priv->name); + break; + case PROP_INDEX_SOURCE: + g_value_set_uint (value, monitor->priv->index_source); + break; + case PROP_INDEX_SINK_INPUT: + g_value_set_uint (value, monitor->priv->index_sink_input); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +pulse_monitor_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + PulseMonitor *monitor; + + monitor = PULSE_MONITOR (object); + + switch (param_id) { + case PROP_NAME: + pulse_monitor_set_name (monitor, g_value_get_string (value)); + break; + case PROP_INDEX_SOURCE: + monitor->priv->index_source = g_value_get_uint (value); + break; + case PROP_INDEX_SINK_INPUT: + monitor->priv->index_sink_input = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void pulse_monitor_init (PulseMonitor *monitor) { monitor->priv = G_TYPE_INSTANCE_GET_PRIVATE (monitor, @@ -90,70 +209,46 @@ pulse_monitor_finalize (GObject *object) monitor = PULSE_MONITOR (object); - if (monitor->priv->stream) + /* The pulse stream may exist if the monitor is running */ + if (monitor->priv->stream != NULL) { + pa_stream_disconnect (monitor->priv->stream); pa_stream_unref (monitor->priv->stream); + } pa_context_unref (monitor->priv->context); pa_proplist_free (monitor->priv->proplist); + g_free (monitor->priv->name); + G_OBJECT_CLASS (pulse_monitor_parent_class)->finalize (object); } PulseMonitor * pulse_monitor_new (pa_context *context, pa_proplist *proplist, + const gchar *name, guint32 index_source, guint32 index_sink_input) { PulseMonitor *monitor; - monitor = g_object_new (PULSE_TYPE_MONITOR, NULL); + g_return_val_if_fail (context != NULL, NULL); + g_return_val_if_fail (proplist != NULL, NULL); + + monitor = g_object_new (PULSE_TYPE_MONITOR, + "name", name, + "index-source", index_source, + "index-sink-input", index_sink_input, + NULL); monitor->priv->context = pa_context_ref (context); monitor->priv->proplist = pa_proplist_copy (proplist); - monitor->priv->index_source = index_source; - monitor->priv->index_sink_input = index_sink_input; - return monitor; } gboolean -pulse_monitor_enable (PulseMonitor *monitor) -{ - g_return_val_if_fail (PULSE_IS_MONITOR (monitor), FALSE); - - if (!monitor->priv->enabled) { - if (monitor->priv->stream == NULL) - monitor_prepare (monitor); - - if (G_LIKELY (monitor->priv->stream != NULL)) - monitor->priv->enabled = monitor_connect_record (monitor); - } - - return monitor->priv->enabled; -} - -void -pulse_monitor_disable (PulseMonitor *monitor) -{ - g_return_if_fail (PULSE_IS_MONITOR (monitor)); - - if (!monitor->priv->enabled) - return; - - pa_stream_disconnect (monitor->priv->stream); - - // XXX stream must be destroyed on disable, re-enabling does not work, this - // is just a quick temporary solution - pa_stream_unref (monitor->priv->stream); - monitor->priv->stream = NULL; - - monitor->priv->enabled = FALSE; -} - -gboolean -pulse_monitor_is_enabled (PulseMonitor *monitor) +pulse_monitor_get_enabled (PulseMonitor *monitor) { g_return_val_if_fail (PULSE_IS_MONITOR (monitor), FALSE); @@ -161,30 +256,27 @@ pulse_monitor_is_enabled (PulseMonitor *monitor) } gboolean -pulse_monitor_update_index (PulseMonitor *monitor, - guint32 index_source, - guint32 index_sink_input) +pulse_monitor_set_enabled (PulseMonitor *monitor, gboolean enabled) { g_return_val_if_fail (PULSE_IS_MONITOR (monitor), FALSE); - if (monitor->priv->index_source == index_source && - monitor->priv->index_sink_input == index_sink_input) + if (enabled == monitor->priv->enabled) return TRUE; - monitor->priv->index_source = index_source; - monitor->priv->index_sink_input = index_sink_input; - - if (pulse_monitor_is_enabled (monitor)) { - pulse_monitor_disable (monitor); + if (enabled) { + monitor->priv->enabled = stream_connect (monitor); - /* Unset the Pulse stream to let enabling recreate it */ - g_clear_pointer (&monitor->priv->stream, pa_stream_unref); + if (monitor->priv->enabled == FALSE) + return FALSE; + } else { + pa_stream_disconnect (monitor->priv->stream); + pa_stream_unref (monitor->priv->stream); - pulse_monitor_enable (monitor); - } else if (monitor->priv->stream) { - /* Disabled now but was enabled before and still holds source index */ - g_clear_pointer (&monitor->priv->stream, pa_stream_unref); + monitor->priv->stream = NULL; + monitor->priv->enabled = FALSE; } + g_object_notify_by_pspec (G_OBJECT (monitor), properties[PROP_ENABLED]); + return TRUE; } @@ -201,21 +293,32 @@ pulse_monitor_set_name (PulseMonitor *monitor, const gchar *name) { g_return_val_if_fail (PULSE_IS_MONITOR (monitor), FALSE); - g_free (monitor->priv->name); + if (g_strcmp0 (name, monitor->priv->name) != 0) { + g_free (monitor->priv->name); + monitor->priv->name = g_strdup (name); - monitor->priv->name = g_strdup (name); + g_object_notify_by_pspec (G_OBJECT (monitor), properties[PROP_NAME]); + } return TRUE; } static gboolean -monitor_prepare (PulseMonitor *monitor) +stream_connect (PulseMonitor *monitor) { pa_sample_spec spec; + pa_buffer_attr attr; const gchar *name; + gchar *idx; + int ret; - spec.channels = 1; - spec.format = PA_SAMPLE_FLOAT32; - spec.rate = 25; + attr.maxlength = (guint32) -1; + attr.tlength = 0; + attr.prebuf = 0; + attr.minreq = 0; + attr.fragsize = sizeof (gfloat); + spec.channels = 1; + spec.format = PA_SAMPLE_FLOAT32; + spec.rate = 25; if (monitor->priv->name != NULL) name = monitor->priv->name; @@ -230,60 +333,58 @@ monitor_prepare (PulseMonitor *monitor) monitor->priv->proplist); if (G_UNLIKELY (monitor->priv->stream == NULL)) { - g_warning ("Failed to create PulseAudio monitor: %s", + g_warning ("Failed to create peak monitor: %s", pa_strerror (pa_context_errno (monitor->priv->context))); return FALSE; } + /* Set sink input index for the stream, source outputs are not supported */ if (monitor->priv->index_sink_input != PA_INVALID_INDEX) pa_stream_set_monitor_stream (monitor->priv->stream, monitor->priv->index_sink_input); pa_stream_set_read_callback (monitor->priv->stream, - monitor_read_cb, + stream_read_cb, monitor); - return TRUE; -} - -static gboolean -monitor_connect_record (PulseMonitor *monitor) -{ - pa_buffer_attr attr; - gchar *name; - int ret; - attr.maxlength = (guint32) -1; - attr.tlength = 0; - attr.prebuf = 0; - attr.minreq = 0; - attr.fragsize = sizeof (gfloat); - - name = g_strdup_printf ("%u", monitor->priv->index_source); - ret = pa_stream_connect_record (monitor->priv->stream, - name, - &attr, - PA_STREAM_DONT_MOVE | - PA_STREAM_PEAK_DETECT | - PA_STREAM_ADJUST_LATENCY | - PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND); - g_free (name); + /* Source index must be passed as a string */ + idx = g_strdup_printf ("%u", monitor->priv->index_source); + ret = pa_stream_connect_record (monitor->priv->stream, + idx, + &attr, + PA_STREAM_DONT_MOVE | + PA_STREAM_PEAK_DETECT | + PA_STREAM_ADJUST_LATENCY); + g_free (idx); if (ret < 0) { - g_warning ("Failed to connect PulseAudio monitor: %s", pa_strerror (ret)); + g_warning ("Failed to connect peak monitor: %s", pa_strerror (ret)); return FALSE; } return TRUE; } static void -monitor_read_cb (pa_stream *stream, size_t length, void *userdata) +stream_read_cb (pa_stream *stream, size_t length, void *userdata) { const void *data; + /* Read the next fragment from the buffer (for recording streams). + * + * If there is data at the current read index, data will point to the + * actual data and length will contain the size of the data in bytes + * (which can be less or more than a complete fragment). + * + * If there is no data at the current read index, it means that either + * the buffer is empty or it contains a hole (that is, the write index + * is ahead of the read index but there's no data where the read index + * points at). If the buffer is empty, data will be NULL and length will + * be 0. If there is a hole, data will be NULL and length will contain + * the length of the hole. */ if (pa_stream_peek (stream, &data, &length) < 0) return; - if (data) { + if (data != NULL) { gdouble v = ((const gfloat *) data)[length / sizeof (gfloat) - 1]; g_signal_emit (G_OBJECT (userdata), @@ -294,6 +395,6 @@ monitor_read_cb (pa_stream *stream, size_t length, void *userdata) /* pa_stream_drop() should not be called if the buffer is empty, but it * should be called if there is a hole */ - if (length) + if (length > 0) pa_stream_drop (stream); } diff --git a/backends/pulse/pulse-monitor.h b/backends/pulse/pulse-monitor.h index b3f6f89..41147f5 100644 --- a/backends/pulse/pulse-monitor.h +++ b/backends/pulse/pulse-monitor.h @@ -63,21 +63,18 @@ GType pulse_monitor_get_type (void) G_GNUC_CONST; PulseMonitor *pulse_monitor_new (pa_context *context, pa_proplist *proplist, + const gchar *name, guint32 index_source, guint32 index_sink_input); -gboolean pulse_monitor_enable (PulseMonitor *monitor); -void pulse_monitor_disable (PulseMonitor *monitor); -gboolean pulse_monitor_is_enabled (PulseMonitor *monitor); +gboolean pulse_monitor_get_enabled (PulseMonitor *monitor); +gboolean pulse_monitor_set_enabled (PulseMonitor *monitor, + gboolean enabled); const gchar * pulse_monitor_get_name (PulseMonitor *monitor); gboolean pulse_monitor_set_name (PulseMonitor *monitor, const gchar *name); -gboolean pulse_monitor_update_index (PulseMonitor *monitor, - guint32 index_source, - guint32 index_sink_input); - G_END_DECLS #endif /* PULSE_MONITOR_H */ diff --git a/backends/pulse/pulse-sink-input.c b/backends/pulse/pulse-sink-input.c index 051b351..54cd3b3 100644 --- a/backends/pulse/pulse-sink-input.c +++ b/backends/pulse/pulse-sink-input.c @@ -20,12 +20,14 @@ #include <glib-object.h> #include <libmatemixer/matemixer-client-stream.h> +#include <libmatemixer/matemixer-enums.h> #include <libmatemixer/matemixer-stream.h> #include <pulse/pulseaudio.h> #include "pulse-connection.h" #include "pulse-client-stream.h" +#include "pulse-helpers.h" #include "pulse-monitor.h" #include "pulse-sink.h" #include "pulse-sink-input.h" @@ -36,14 +38,16 @@ static void pulse_sink_input_init (PulseSinkInput *input); G_DEFINE_TYPE (PulseSinkInput, pulse_sink_input, PULSE_TYPE_CLIENT_STREAM); -static gboolean sink_input_set_mute (MateMixerStream *stream, - gboolean mute); -static gboolean sink_input_set_volume (MateMixerStream *stream, - pa_cvolume *volume); -static gboolean sink_input_set_parent (MateMixerClientStream *stream, - MateMixerStream *parent); -static gboolean sink_input_remove (MateMixerClientStream *stream); -static PulseMonitor *sink_input_create_monitor (MateMixerStream *stream); +static void pulse_sink_input_reload (PulseStream *pstream); + +static gboolean pulse_sink_input_set_mute (PulseStream *pstream, + gboolean mute); +static gboolean pulse_sink_input_set_volume (PulseStream *pstream, + pa_cvolume *cvolume); +static gboolean pulse_sink_input_set_parent (PulseClientStream *pclient, + PulseStream *parent); +static gboolean pulse_sink_input_remove (PulseClientStream *pclient); +static PulseMonitor *pulse_sink_input_create_monitor (PulseStream *pstream); static void pulse_sink_input_class_init (PulseSinkInputClass *klass) @@ -53,14 +57,15 @@ pulse_sink_input_class_init (PulseSinkInputClass *klass) stream_class = PULSE_STREAM_CLASS (klass); - stream_class->set_mute = sink_input_set_mute; - stream_class->set_volume = sink_input_set_volume; - stream_class->create_monitor = sink_input_create_monitor; + stream_class->reload = pulse_sink_input_reload; + stream_class->set_mute = pulse_sink_input_set_mute; + stream_class->set_volume = pulse_sink_input_set_volume; + stream_class->create_monitor = pulse_sink_input_create_monitor; client_class = PULSE_CLIENT_STREAM_CLASS (klass); - client_class->set_parent = sink_input_set_parent; - client_class->remove = sink_input_remove; + client_class->set_parent = pulse_sink_input_set_parent; + client_class->remove = pulse_sink_input_remove; } static void @@ -91,7 +96,7 @@ pulse_sink_input_new (PulseConnection *connection, } gboolean -pulse_sink_input_update (PulseStream *stream, +pulse_sink_input_update (PulseStream *pstream, const pa_sink_input_info *info, PulseStream *parent) { @@ -99,66 +104,81 @@ pulse_sink_input_update (PulseStream *stream, MATE_MIXER_STREAM_CLIENT | MATE_MIXER_STREAM_HAS_MUTE | MATE_MIXER_STREAM_HAS_MONITOR; - gchar *name; + PulseClientStream *pclient; + const gchar *prop; + const gchar *description = NULL; + gchar *name; - const gchar *prop; - const gchar *description = NULL; + g_return_val_if_fail (PULSE_IS_SINK_INPUT (pstream), FALSE); + g_return_val_if_fail (info != NULL, FALSE); - g_return_val_if_fail (PULSE_IS_SINK_INPUT (stream), FALSE); + pclient = PULSE_CLIENT_STREAM (pstream); /* Let all the information update before emitting notify signals */ - g_object_freeze_notify (G_OBJECT (stream)); + g_object_freeze_notify (G_OBJECT (pstream)); /* Many mixer applications query the Pulse client list and use the client * name here, but we use the name only as an identifier, so let's avoid * this unnecessary overhead and use a custom name. - * Also make sure to make the name unique by including the Pulse index. */ + * Also make sure to make the name unique by including the PulseAudio index. */ name = g_strdup_printf ("pulse-stream-client-output-%lu", (gulong) info->index); - pulse_stream_update_name (stream, name); + pulse_stream_update_name (pstream, name); g_free (name); prop = pa_proplist_gets (info->proplist, PA_PROP_MEDIA_ROLE); + if (prop != NULL) { + MateMixerClientStreamRole role = pulse_convert_media_role_name (prop); - if (prop != NULL && !strcmp (prop, "event")) { - /* The event description seems to provide much better readable - * description for event streams */ - prop = pa_proplist_gets (info->proplist, PA_PROP_EVENT_DESCRIPTION); + if (role == MATE_MIXER_CLIENT_STREAM_ROLE_EVENT) { + /* The event description seems to provide much better readable + * description for event streams */ + prop = pa_proplist_gets (info->proplist, PA_PROP_EVENT_DESCRIPTION); - if (G_LIKELY (prop != NULL)) - description = prop; + if (G_LIKELY (prop != NULL)) + description = prop; + } + pulse_client_stream_update_role (pclient, role); + } else + pulse_client_stream_update_role (pclient, MATE_MIXER_CLIENT_STREAM_ROLE_NONE); - flags |= MATE_MIXER_STREAM_EVENT; - } if (description == NULL) description = info->name; - pulse_stream_update_description (stream, description); - pulse_stream_update_mute (stream, info->mute ? TRUE : FALSE); + pulse_stream_update_description (pstream, description); + pulse_stream_update_mute (pstream, info->mute ? TRUE : FALSE); if (info->client != PA_INVALID_INDEX) - flags |= MATE_MIXER_STREAM_APPLICATION; + pulse_client_stream_update_flags (pclient, MATE_MIXER_CLIENT_STREAM_APPLICATION); + else + pulse_client_stream_update_flags (pclient, MATE_MIXER_CLIENT_STREAM_NO_FLAGS); + + if (G_LIKELY (parent != NULL)) { + if (pulse_sink_get_monitor_index (parent) != PA_INVALID_INDEX) + flags |= MATE_MIXER_STREAM_HAS_MONITOR; - if (pa_channel_map_can_balance (&info->channel_map)) - flags |= MATE_MIXER_STREAM_CAN_BALANCE; - if (pa_channel_map_can_fade (&info->channel_map)) - flags |= MATE_MIXER_STREAM_CAN_FADE; + pulse_client_stream_update_parent (pclient, MATE_MIXER_STREAM (parent)); + } else + pulse_client_stream_update_parent (pclient, NULL); #if PA_CHECK_VERSION(1, 0, 0) - if (info->has_volume) + if (info->has_volume) { flags |= MATE_MIXER_STREAM_HAS_VOLUME | MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME; - if (info->volume_writable) - flags |= MATE_MIXER_STREAM_CAN_SET_VOLUME; + + if (info->volume_writable) + flags |= MATE_MIXER_STREAM_CAN_SET_VOLUME; + } /* Flags needed before volume */ - pulse_stream_update_flags (stream, flags); + pulse_stream_update_flags (pstream, flags); + pulse_stream_update_channel_map (pstream, &info->channel_map); if (info->has_volume) - pulse_stream_update_volume (stream, &info->volume, &info->channel_map, 0); + pulse_stream_update_volume (pstream, &info->volume, 0); else - pulse_stream_update_volume (stream, NULL, &info->channel_map, 0); + pulse_stream_update_volume (pstream, NULL, 0); #else /* Pre-1.0 PulseAudio does not include the has_volume and volume_writable * fields, but does include the volume info, so let's give it a try */ @@ -168,127 +188,121 @@ pulse_sink_input_update (PulseStream *stream, MATE_MIXER_STREAM_CAN_SET_VOLUME; /* Flags needed before volume */ - pulse_stream_update_flags (stream, flags); + pulse_stream_update_flags (pstream, flags); + pulse_stream_update_channel_map (pstream, &info->channel_map); - pulse_stream_update_volume (stream, &info->volume, &info->channel_map, 0); + pulse_stream_update_volume (pstream, &info->volume, 0); #endif prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_NAME); if (prop != NULL) - pulse_client_stream_update_app_name (PULSE_CLIENT_STREAM (stream), prop); + pulse_client_stream_update_app_name (pclient, prop); prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ID); if (prop != NULL) - pulse_client_stream_update_app_id (PULSE_CLIENT_STREAM (stream), prop); + pulse_client_stream_update_app_id (pclient, prop); prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_VERSION); if (prop != NULL) - pulse_client_stream_update_app_version (PULSE_CLIENT_STREAM (stream), prop); + pulse_client_stream_update_app_version (pclient, prop); prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ICON_NAME); if (prop != NULL) - pulse_client_stream_update_app_icon (PULSE_CLIENT_STREAM (stream), prop); - - if (G_LIKELY (parent != NULL)) - pulse_client_stream_update_parent (PULSE_CLIENT_STREAM (stream), - MATE_MIXER_STREAM (parent)); - else - pulse_client_stream_update_parent (PULSE_CLIENT_STREAM (stream), NULL); + pulse_client_stream_update_app_icon (pclient, prop); // XXX needs to fix monitor if parent changes - g_object_thaw_notify (G_OBJECT (stream)); + g_object_thaw_notify (G_OBJECT (pstream)); return TRUE; } -static gboolean -sink_input_set_mute (MateMixerStream *stream, gboolean mute) +static void +pulse_sink_input_reload (PulseStream *pstream) { - PulseStream *pulse; + g_return_if_fail (PULSE_IS_SINK_INPUT (pstream)); - g_return_val_if_fail (PULSE_IS_SINK_INPUT (stream), FALSE); + pulse_connection_load_sink_input_info (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream)); +} - pulse = PULSE_STREAM (stream); +static gboolean +pulse_sink_input_set_mute (PulseStream *pstream, gboolean mute) +{ + g_return_val_if_fail (PULSE_IS_SINK_INPUT (pstream), FALSE); - return pulse_connection_set_sink_input_mute (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), + return pulse_connection_set_sink_input_mute (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), mute); } static gboolean -sink_input_set_volume (MateMixerStream *stream, pa_cvolume *volume) +pulse_sink_input_set_volume (PulseStream *pstream, pa_cvolume *cvolume) { - PulseStream *pulse; - - g_return_val_if_fail (PULSE_IS_SINK_INPUT (stream), FALSE); - g_return_val_if_fail (volume != NULL, FALSE); - - pulse = PULSE_STREAM (stream); + g_return_val_if_fail (PULSE_IS_SINK_INPUT (pstream), FALSE); + g_return_val_if_fail (cvolume != NULL, FALSE); - return pulse_connection_set_sink_input_volume (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), - volume); + return pulse_connection_set_sink_input_volume (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), + cvolume); } static gboolean -sink_input_set_parent (MateMixerClientStream *stream, MateMixerStream *parent) +pulse_sink_input_set_parent (PulseClientStream *pclient, PulseStream *parent) { - PulseStream *pulse; + PulseStream *pstream; - g_return_val_if_fail (PULSE_IS_SINK_INPUT (stream), FALSE); + g_return_val_if_fail (PULSE_IS_SINK_INPUT (pclient), FALSE); - if (G_UNLIKELY (!PULSE_IS_SINK (parent))) { + if (!PULSE_IS_SINK (parent)) { g_warning ("Could not change stream parent to %s: not a parent output stream", - mate_mixer_stream_get_name (parent)); + mate_mixer_stream_get_name (MATE_MIXER_STREAM (parent))); return FALSE; } - pulse = PULSE_STREAM (stream); + pstream = PULSE_STREAM (pclient); - return pulse_connection_move_sink_input (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), - pulse_stream_get_index (PULSE_STREAM (parent))); + return pulse_connection_move_sink_input (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), + pulse_stream_get_index (parent)); } static gboolean -sink_input_remove (MateMixerClientStream *stream) +pulse_sink_input_remove (PulseClientStream *pclient) { - PulseStream *pulse; + PulseStream *pstream; - g_return_val_if_fail (PULSE_IS_SINK_INPUT (stream), FALSE); + g_return_val_if_fail (PULSE_IS_SINK_INPUT (pclient), FALSE); - pulse = PULSE_STREAM (stream); + pstream = PULSE_STREAM (pclient); - return pulse_connection_kill_sink_input (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse)); + return pulse_connection_kill_sink_input (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream)); } static PulseMonitor * -sink_input_create_monitor (MateMixerStream *stream) +pulse_sink_input_create_monitor (PulseStream *pstream) { MateMixerStream *parent; - PulseStream *pulse; guint32 index; - g_return_val_if_fail (PULSE_IS_SINK_INPUT (stream), NULL); + g_return_val_if_fail (PULSE_IS_SINK_INPUT (pstream), NULL); - parent = mate_mixer_client_stream_get_parent (MATE_MIXER_CLIENT_STREAM (stream)); + parent = mate_mixer_client_stream_get_parent (MATE_MIXER_CLIENT_STREAM (pstream)); if (G_UNLIKELY (parent == NULL)) { g_debug ("Not creating monitor for client stream %s as it is not available", - mate_mixer_stream_get_name (stream)); + mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream))); return NULL; } - pulse = PULSE_STREAM (stream); index = pulse_sink_get_monitor_index (PULSE_STREAM (parent)); if (G_UNLIKELY (index == PA_INVALID_INDEX)) { g_debug ("Not creating monitor for client stream %s as it is not available", - mate_mixer_stream_get_name (stream)); + mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream))); return NULL; } - return pulse_connection_create_monitor (pulse_stream_get_connection (pulse), + return pulse_connection_create_monitor (pulse_stream_get_connection (pstream), index, - pulse_stream_get_index (pulse)); + pulse_stream_get_index (pstream)); } diff --git a/backends/pulse/pulse-sink-input.h b/backends/pulse/pulse-sink-input.h index a498999..1e5004b 100644 --- a/backends/pulse/pulse-sink-input.h +++ b/backends/pulse/pulse-sink-input.h @@ -61,7 +61,7 @@ PulseStream *pulse_sink_input_new (PulseConnection *connection, const pa_sink_input_info *info, PulseStream *parent); -gboolean pulse_sink_input_update (PulseStream *stream, +gboolean pulse_sink_input_update (PulseStream *pstream, const pa_sink_input_info *info, PulseStream *parent); diff --git a/backends/pulse/pulse-sink.c b/backends/pulse/pulse-sink.c index 27d1466..0f828b1 100644 --- a/backends/pulse/pulse-sink.c +++ b/backends/pulse/pulse-sink.c @@ -18,6 +18,8 @@ #include <glib.h> #include <glib-object.h> +#include <libmatemixer/matemixer-port.h> +#include <libmatemixer/matemixer-port-private.h> #include <libmatemixer/matemixer-stream.h> #include <pulse/pulseaudio.h> @@ -38,15 +40,23 @@ static void pulse_sink_init (PulseSink *sink); G_DEFINE_TYPE (PulseSink, pulse_sink, PULSE_TYPE_STREAM); -static gboolean sink_set_mute (MateMixerStream *stream, - gboolean mute); -static gboolean sink_set_volume (MateMixerStream *stream, - pa_cvolume *volume); -static gboolean sink_set_active_port (MateMixerStream *stream, - const gchar *port); -static gboolean sink_suspend (MateMixerStream *stream); -static gboolean sink_resume (MateMixerStream *stream); -static PulseMonitor *sink_create_monitor (MateMixerStream *stream); +static void pulse_sink_reload (PulseStream *pstream); + +static gboolean pulse_sink_set_mute (PulseStream *pstream, + gboolean mute); +static gboolean pulse_sink_set_volume (PulseStream *pstream, + pa_cvolume *cvolume); +static gboolean pulse_sink_set_active_port (PulseStream *pstream, + MateMixerPort *port); + +static gboolean pulse_sink_suspend (PulseStream *pstream); +static gboolean pulse_sink_resume (PulseStream *pstream); + +static PulseMonitor *pulse_sink_create_monitor (PulseStream *pstream); + +static void update_ports (PulseStream *pstream, + pa_sink_port_info **ports, + pa_sink_port_info *active); static void pulse_sink_class_init (PulseSinkClass *klass) @@ -55,12 +65,13 @@ pulse_sink_class_init (PulseSinkClass *klass) stream_class = PULSE_STREAM_CLASS (klass); - stream_class->set_mute = sink_set_mute; - stream_class->set_volume = sink_set_volume; - stream_class->set_active_port = sink_set_active_port; - stream_class->suspend = sink_suspend; - stream_class->resume = sink_resume; - stream_class->create_monitor = sink_create_monitor; + stream_class->reload = pulse_sink_reload; + stream_class->set_mute = pulse_sink_set_mute; + stream_class->set_volume = pulse_sink_set_volume; + stream_class->set_active_port = pulse_sink_set_active_port; + stream_class->suspend = pulse_sink_suspend; + stream_class->resume = pulse_sink_resume; + stream_class->create_monitor = pulse_sink_create_monitor; g_type_class_add_private (klass, sizeof (PulseSinkPrivate)); } @@ -98,71 +109,46 @@ pulse_sink_new (PulseConnection *connection, } guint32 -pulse_sink_get_monitor_index (PulseStream *stream) +pulse_sink_get_monitor_index (PulseStream *pstream) { - g_return_val_if_fail (PULSE_IS_SINK (stream), PA_INVALID_INDEX); + g_return_val_if_fail (PULSE_IS_SINK (pstream), PA_INVALID_INDEX); - return PULSE_SINK (stream)->priv->index_monitor; + return PULSE_SINK (pstream)->priv->index_monitor; } gboolean -pulse_sink_update (PulseStream *stream, const pa_sink_info *info, PulseDevice *device) +pulse_sink_update (PulseStream *pstream, const pa_sink_info *info, PulseDevice *device) { MateMixerStreamFlags flags = MATE_MIXER_STREAM_OUTPUT | MATE_MIXER_STREAM_HAS_MUTE | MATE_MIXER_STREAM_HAS_VOLUME | - MATE_MIXER_STREAM_HAS_MONITOR | MATE_MIXER_STREAM_CAN_SET_VOLUME | MATE_MIXER_STREAM_CAN_SUSPEND; PulseSink *sink; - GList *ports = NULL; - guint32 i; - g_return_val_if_fail (PULSE_IS_SINK (stream), FALSE); + g_return_val_if_fail (PULSE_IS_SINK (pstream), FALSE); + g_return_val_if_fail (info != NULL, FALSE); /* Let all the information update before emitting notify signals */ - g_object_freeze_notify (G_OBJECT (stream)); + g_object_freeze_notify (G_OBJECT (pstream)); - pulse_stream_update_name (stream, info->name); - pulse_stream_update_description (stream, info->description); - pulse_stream_update_mute (stream, info->mute ? TRUE : FALSE); - - /* List of ports */ - for (i = 0; i < info->n_ports; i++) { - MateMixerPortFlags flags = MATE_MIXER_PORT_NO_FLAGS; - -#if PA_CHECK_VERSION(2, 0, 0) - if (info->ports[i]->available == PA_PORT_AVAILABLE_YES) - flags |= MATE_MIXER_PORT_AVAILABLE; -#endif - ports = g_list_prepend (ports, - mate_mixer_port_new (info->ports[i]->name, - info->ports[i]->description, - NULL, - info->ports[i]->priority, - flags)); - } - pulse_stream_update_ports (stream, ports); - - /* Active port */ - if (info->active_port) - pulse_stream_update_active_port (stream, info->active_port->name); - else - pulse_stream_update_active_port (stream, NULL); + pulse_stream_update_name (pstream, info->name); + pulse_stream_update_description (pstream, info->description); + pulse_stream_update_mute (pstream, info->mute ? TRUE : FALSE); /* Stream state */ switch (info->state) { case PA_SINK_RUNNING: - pulse_stream_update_state (stream, MATE_MIXER_STREAM_RUNNING); + pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_RUNNING); break; case PA_SINK_IDLE: - pulse_stream_update_state (stream, MATE_MIXER_STREAM_IDLE); + pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_IDLE); break; case PA_SINK_SUSPENDED: - pulse_stream_update_state (stream, MATE_MIXER_STREAM_SUSPENDED); + pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_SUSPENDED); break; default: - pulse_stream_update_state (stream, MATE_MIXER_STREAM_UNKNOWN_STATE); + pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_UNKNOWN); break; } @@ -172,132 +158,172 @@ pulse_sink_update (PulseStream *stream, const pa_sink_info *info, PulseDevice *d if (info->flags & PA_SINK_FLAT_VOLUME) flags |= MATE_MIXER_STREAM_HAS_FLAT_VOLUME; - if (pa_channel_map_can_balance (&info->channel_map)) - flags |= MATE_MIXER_STREAM_CAN_BALANCE; - if (pa_channel_map_can_fade (&info->channel_map)) - flags |= MATE_MIXER_STREAM_CAN_FADE; + sink = PULSE_SINK (pstream); - /* Flags must be updated before volume */ - pulse_stream_update_flags (stream, flags); - - pulse_stream_update_volume (stream, - &info->volume, - &info->channel_map, - info->base_volume); - - pulse_stream_update_device (stream, MATE_MIXER_DEVICE (device)); + if (sink->priv->index_monitor == PA_INVALID_INDEX) + sink->priv->index_monitor = info->monitor_source; - sink = PULSE_SINK (stream); + if (sink->priv->index_monitor != PA_INVALID_INDEX) + flags |= MATE_MIXER_STREAM_HAS_MONITOR; - /* Handle change of monitoring source index */ - // XXX probably call this each time to validate - if (sink->priv->index_monitor != info->monitor_source) { - PulseMonitor *monitor; + /* Flags must be updated before volume */ + pulse_stream_update_flags (pstream, flags); - monitor = pulse_stream_get_monitor (PULSE_STREAM (stream)); + pulse_stream_update_channel_map (pstream, &info->channel_map); + pulse_stream_update_volume (pstream, &info->volume, info->base_volume); - if (monitor) - pulse_monitor_update_index (monitor, - info->monitor_source, - PA_INVALID_INDEX); + pulse_stream_update_device (pstream, MATE_MIXER_DEVICE (device)); - sink->priv->index_monitor = info->monitor_source; + /* Ports must be updated after device */ + if (info->ports != NULL) { + update_ports (pstream, info->ports, info->active_port); } - g_object_thaw_notify (G_OBJECT (stream)); + g_object_thaw_notify (G_OBJECT (pstream)); return TRUE; } -static gboolean -sink_set_mute (MateMixerStream *stream, gboolean mute) +static void +pulse_sink_reload (PulseStream *pstream) { - PulseStream *pulse; + g_return_if_fail (PULSE_IS_SINK (pstream)); - g_return_val_if_fail (PULSE_IS_SINK (stream), FALSE); + pulse_connection_load_sink_info (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream)); +} - pulse = PULSE_STREAM (stream); +static gboolean +pulse_sink_set_mute (PulseStream *pstream, gboolean mute) +{ + g_return_val_if_fail (PULSE_IS_SINK (pstream), FALSE); - return pulse_connection_set_sink_mute (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), + return pulse_connection_set_sink_mute (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), mute); } static gboolean -sink_set_volume (MateMixerStream *stream, pa_cvolume *volume) +pulse_sink_set_volume (PulseStream *pstream, pa_cvolume *cvolume) { - PulseStream *pulse; - - g_return_val_if_fail (PULSE_IS_SINK (stream), FALSE); - g_return_val_if_fail (volume != NULL, FALSE); + g_return_val_if_fail (PULSE_IS_SINK (pstream), FALSE); + g_return_val_if_fail (cvolume != NULL, FALSE); - pulse = PULSE_STREAM (stream); - - return pulse_connection_set_sink_volume (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), - volume); + return pulse_connection_set_sink_volume (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), + cvolume); } static gboolean -sink_set_active_port (MateMixerStream *stream, const gchar *port) +pulse_sink_set_active_port (PulseStream *pstream, MateMixerPort *port) { - PulseStream *pulse; - - g_return_val_if_fail (PULSE_IS_SINK (stream), FALSE); - g_return_val_if_fail (port != NULL, FALSE); + g_return_val_if_fail (PULSE_IS_SINK (pstream), FALSE); + g_return_val_if_fail (MATE_MIXER_IS_PORT (port), FALSE); - pulse = PULSE_STREAM (stream); - - return pulse_connection_set_sink_port (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), - port); + return pulse_connection_set_sink_port (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), + mate_mixer_port_get_name (port)); } static gboolean -sink_suspend (MateMixerStream *stream) +pulse_sink_suspend (PulseStream *pstream) { - PulseStream *pulse; - - g_return_val_if_fail (PULSE_IS_SINK (stream), FALSE); + g_return_val_if_fail (PULSE_IS_SINK (pstream), FALSE); - pulse = PULSE_STREAM (stream); - - return pulse_connection_suspend_sink (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), + return pulse_connection_suspend_sink (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), TRUE); } static gboolean -sink_resume (MateMixerStream *stream) +pulse_sink_resume (PulseStream *pstream) { - PulseStream *pulse; - - g_return_val_if_fail (PULSE_IS_SINK (stream), FALSE); - - pulse = PULSE_STREAM (stream); + g_return_val_if_fail (PULSE_IS_SINK (pstream), FALSE); - return pulse_connection_suspend_sink (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), + return pulse_connection_suspend_sink (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), FALSE); } static PulseMonitor * -sink_create_monitor (MateMixerStream *stream) +pulse_sink_create_monitor (PulseStream *pstream) { - PulseStream *pulse; - guint32 index; + guint32 index; - g_return_val_if_fail (PULSE_IS_SINK (stream), NULL); + g_return_val_if_fail (PULSE_IS_SINK (pstream), NULL); - pulse = PULSE_STREAM (stream); - index = pulse_sink_get_monitor_index (pulse); + index = pulse_sink_get_monitor_index (pstream); if (G_UNLIKELY (index == PA_INVALID_INDEX)) { - g_debug ("Not creating monitor for stream %s as it is not available", - mate_mixer_stream_get_name (stream)); + g_debug ("Not creating monitor for stream %s: not available", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream))); return NULL; } - return pulse_connection_create_monitor (pulse_stream_get_connection (pulse), + return pulse_connection_create_monitor (pulse_stream_get_connection (pstream), index, PA_INVALID_INDEX); } + +static void +update_ports (PulseStream *pstream, + pa_sink_port_info **ports, + pa_sink_port_info *active) +{ + MateMixerPort *port; + MateMixerDevice *device; + GHashTable *hash; + + hash = pulse_stream_get_ports (pstream); + + while (*ports != NULL) { + MateMixerPortFlags flags = MATE_MIXER_PORT_NO_FLAGS; + pa_sink_port_info *info = *ports; + const gchar *icon = NULL; + + device = mate_mixer_stream_get_device (MATE_MIXER_STREAM (pstream)); + if (device != NULL) { + port = mate_mixer_device_get_port (device, info->name); + + if (port != NULL) { + flags = mate_mixer_port_get_flags (port); + icon = mate_mixer_port_get_icon (port); + } + } + +#if PA_CHECK_VERSION(2, 0, 0) + if (info->available == PA_PORT_AVAILABLE_YES) + flags |= MATE_MIXER_PORT_AVAILABLE; + else + flags &= ~MATE_MIXER_PORT_AVAILABLE; +#endif + + port = g_hash_table_lookup (hash, info->name); + + if (port != NULL) { + /* Update existing port */ + _mate_mixer_port_update_description (port, info->description); + _mate_mixer_port_update_icon (port, icon); + _mate_mixer_port_update_priority (port, info->priority); + _mate_mixer_port_update_flags (port, flags); + } else { + /* Add previously unknown port to the hash table */ + port = _mate_mixer_port_new (info->name, + info->description, + icon, + info->priority, + flags); + + g_hash_table_insert (hash, g_strdup (info->name), port); + } + + ports++; + } + + /* Active port */ + if (G_LIKELY (active != NULL)) + port = g_hash_table_lookup (hash, active->name); + else + port = NULL; + + pulse_stream_update_active_port (pstream, port); +} diff --git a/backends/pulse/pulse-sink.h b/backends/pulse/pulse-sink.h index 7265a7c..c0631ca 100644 --- a/backends/pulse/pulse-sink.h +++ b/backends/pulse/pulse-sink.h @@ -65,9 +65,9 @@ PulseStream *pulse_sink_new (PulseConnection *connection, const pa_sink_info *info, PulseDevice *device); -guint32 pulse_sink_get_monitor_index (PulseStream *stream); +guint32 pulse_sink_get_monitor_index (PulseStream *pstream); -gboolean pulse_sink_update (PulseStream *stream, +gboolean pulse_sink_update (PulseStream *pstream, const pa_sink_info *info, PulseDevice *device); diff --git a/backends/pulse/pulse-source-output.c b/backends/pulse/pulse-source-output.c index 3f3c3ae..c46b65b 100644 --- a/backends/pulse/pulse-source-output.c +++ b/backends/pulse/pulse-source-output.c @@ -26,6 +26,7 @@ #include "pulse-connection.h" #include "pulse-client-stream.h" +#include "pulse-helpers.h" #include "pulse-monitor.h" #include "pulse-stream.h" #include "pulse-source.h" @@ -36,14 +37,18 @@ static void pulse_source_output_init (PulseSourceOutput *output); G_DEFINE_TYPE (PulseSourceOutput, pulse_source_output, PULSE_TYPE_CLIENT_STREAM); -static gboolean source_output_set_mute (MateMixerStream *stream, - gboolean mute); -static gboolean source_output_set_volume (MateMixerStream *stream, - pa_cvolume *volume); -static gboolean source_output_set_parent (MateMixerClientStream *stream, - MateMixerStream *parent); -static gboolean source_output_remove (MateMixerClientStream *stream); -static PulseMonitor *source_output_create_monitor (MateMixerStream *stream); +static void pulse_source_output_reload (PulseStream *pstream); + +static gboolean pulse_source_output_set_mute (PulseStream *pstream, + gboolean mute); +static gboolean pulse_source_output_set_volume (PulseStream *pstream, + pa_cvolume *cvolume); + +static gboolean pulse_source_output_set_parent (PulseClientStream *pclient, + PulseStream *parent); +static gboolean pulse_source_output_remove (PulseClientStream *pclient); + +static PulseMonitor *pulse_source_output_create_monitor (PulseStream *pstream); static void pulse_source_output_class_init (PulseSourceOutputClass *klass) @@ -53,14 +58,15 @@ pulse_source_output_class_init (PulseSourceOutputClass *klass) stream_class = PULSE_STREAM_CLASS (klass); - stream_class->set_mute = source_output_set_mute; - stream_class->set_volume = source_output_set_volume; - stream_class->create_monitor = source_output_create_monitor; + stream_class->reload = pulse_source_output_reload; + stream_class->set_mute = pulse_source_output_set_mute; + stream_class->set_volume = pulse_source_output_set_volume; + stream_class->create_monitor = pulse_source_output_create_monitor; client_class = PULSE_CLIENT_STREAM_CLASS (klass); - client_class->set_parent = source_output_set_parent; - client_class->remove = source_output_remove; + client_class->set_parent = pulse_source_output_set_parent; + client_class->remove = pulse_source_output_remove; } static void @@ -91,21 +97,24 @@ pulse_source_output_new (PulseConnection *connection, } gboolean -pulse_source_output_update (PulseStream *stream, +pulse_source_output_update (PulseStream *pstream, const pa_source_output_info *info, PulseStream *parent) { MateMixerStreamFlags flags = MATE_MIXER_STREAM_INPUT | MATE_MIXER_STREAM_CLIENT; - gchar *name; + PulseClientStream *pclient; + const gchar *prop; + const gchar *description = NULL; + gchar *name; - const gchar *prop; - const gchar *description = NULL; + g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (pstream), FALSE); + g_return_val_if_fail (info != NULL, FALSE); - g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (stream), FALSE); + pclient = PULSE_CLIENT_STREAM (pstream); /* Let all the information update before emitting notify signals */ - g_object_freeze_notify (G_OBJECT (stream)); + g_object_freeze_notify (G_OBJECT (pstream)); /* Many other mixer applications query the Pulse client list and use the * client name here, but we use the name only as an identifier, so let's avoid @@ -113,159 +122,168 @@ pulse_source_output_update (PulseStream *stream, * Also make sure to make the name unique by including the Pulse index. */ name = g_strdup_printf ("pulse-stream-client-input-%lu", (gulong) info->index); - pulse_stream_update_name (stream, name); + pulse_stream_update_name (pstream, name); g_free (name); prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_NAME); if (prop != NULL) - pulse_client_stream_update_app_name (PULSE_CLIENT_STREAM (stream), prop); + pulse_client_stream_update_app_name (pclient, prop); prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ID); if (prop != NULL) - pulse_client_stream_update_app_id (PULSE_CLIENT_STREAM (stream), prop); + pulse_client_stream_update_app_id (pclient, prop); prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_VERSION); if (prop != NULL) - pulse_client_stream_update_app_version (PULSE_CLIENT_STREAM (stream), prop); + pulse_client_stream_update_app_version (pclient, prop); prop = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_ICON_NAME); if (prop != NULL) - pulse_client_stream_update_app_icon (PULSE_CLIENT_STREAM (stream), prop); + pulse_client_stream_update_app_icon (pclient, prop); prop = pa_proplist_gets (info->proplist, PA_PROP_MEDIA_ROLE); + if (prop != NULL) { + MateMixerClientStreamRole role = pulse_convert_media_role_name (prop); - if (prop != NULL && !strcmp (prop, "event")) { - /* The event description seems to provide much better readable - * description for event streams */ - prop = pa_proplist_gets (info->proplist, PA_PROP_EVENT_DESCRIPTION); + if (role == MATE_MIXER_CLIENT_STREAM_ROLE_EVENT) { + /* The event description seems to provide much better readable + * description for event streams */ + prop = pa_proplist_gets (info->proplist, PA_PROP_EVENT_DESCRIPTION); - if (G_LIKELY (prop != NULL)) - description = prop; + if (G_LIKELY (prop != NULL)) + description = prop; + } + pulse_client_stream_update_role (pclient, role); + } else + pulse_client_stream_update_role (pclient, MATE_MIXER_CLIENT_STREAM_ROLE_NONE); - flags |= MATE_MIXER_STREAM_EVENT; - } if (description == NULL) description = info->name; - pulse_stream_update_description (stream, description); + pulse_stream_update_description (pstream, description); if (info->client != PA_INVALID_INDEX) - flags |= MATE_MIXER_STREAM_APPLICATION; + pulse_client_stream_update_flags (pclient, MATE_MIXER_CLIENT_STREAM_APPLICATION); + else + pulse_client_stream_update_flags (pclient, MATE_MIXER_CLIENT_STREAM_NO_FLAGS); - if (pa_channel_map_can_balance (&info->channel_map)) - flags |= MATE_MIXER_STREAM_CAN_BALANCE; - if (pa_channel_map_can_fade (&info->channel_map)) - flags |= MATE_MIXER_STREAM_CAN_FADE; + if (G_LIKELY (parent != NULL)) { + pulse_client_stream_update_parent (pclient, MATE_MIXER_STREAM (parent)); + flags |= MATE_MIXER_STREAM_HAS_MONITOR; + } else + pulse_client_stream_update_parent (pclient, NULL); #if PA_CHECK_VERSION(1, 0, 0) if (info->has_volume) { flags |= MATE_MIXER_STREAM_HAS_VOLUME; + if (info->volume_writable) flags |= MATE_MIXER_STREAM_CAN_SET_VOLUME; } + flags |= MATE_MIXER_STREAM_HAS_MUTE; - pulse_stream_update_flags (stream, flags); - pulse_stream_update_mute (stream, info->mute ? TRUE : FALSE); + /* Flags needed before volume */ + pulse_stream_update_flags (pstream, flags); + pulse_stream_update_channel_map (pstream, &info->channel_map); + pulse_stream_update_mute (pstream, info->mute ? TRUE : FALSE); if (info->has_volume) - pulse_stream_update_volume (stream, &info->volume, &info->channel_map, 0); + pulse_stream_update_volume (pstream, &info->volume, 0); else - pulse_stream_update_volume (stream, NULL, &info->channel_map, 0); + pulse_stream_update_volume (pstream, NULL, 0); #else - pulse_stream_update_flags (stream, flags); - pulse_stream_update_volume (stream, NULL, &info->channel_map, 0); -#endif + /* Flags needed before volume */ + pulse_stream_update_flags (pstream, flags); - if (G_LIKELY (parent != NULL)) - pulse_client_stream_update_parent (PULSE_CLIENT_STREAM (stream), - MATE_MIXER_STREAM (parent)); - else - pulse_client_stream_update_parent (PULSE_CLIENT_STREAM (stream), NULL); + pulse_stream_update_channel_map (pstream, &info->channel_map); + pulse_stream_update_volume (pstream, NULL, 0); +#endif // XXX needs to fix monitor if parent changes - g_object_thaw_notify (G_OBJECT (stream)); + g_object_thaw_notify (G_OBJECT (pstream)); return TRUE; } -static gboolean -source_output_set_mute (MateMixerStream *stream, gboolean mute) +static void +pulse_source_output_reload (PulseStream *pstream) { - PulseStream *pulse; + g_return_if_fail (PULSE_IS_SOURCE_OUTPUT (pstream)); - g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (stream), FALSE); + pulse_connection_load_source_output_info (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream)); +} - pulse = PULSE_STREAM (stream); +static gboolean +pulse_source_output_set_mute (PulseStream *pstream, gboolean mute) +{ + g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (pstream), FALSE); - return pulse_connection_set_source_output_mute (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), + return pulse_connection_set_source_output_mute (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), mute); } static gboolean -source_output_set_volume (MateMixerStream *stream, pa_cvolume *volume) +pulse_source_output_set_volume (PulseStream *pstream, pa_cvolume *cvolume) { - PulseStream *pulse; - - g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (stream), FALSE); - g_return_val_if_fail (volume != NULL, FALSE); - - pulse = PULSE_STREAM (stream); + g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (pstream), FALSE); + g_return_val_if_fail (cvolume != NULL, FALSE); - return pulse_connection_set_source_output_volume (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), - volume); + return pulse_connection_set_source_output_volume (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), + cvolume); } static gboolean -source_output_set_parent (MateMixerClientStream *stream, MateMixerStream *parent) +pulse_source_output_set_parent (PulseClientStream *pclient, PulseStream *parent) { - PulseStream *pulse; + PulseStream *pstream; - g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (stream), FALSE); + g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (pclient), FALSE); - if (G_UNLIKELY (!PULSE_IS_SOURCE (parent))) { + if (!PULSE_IS_SOURCE (parent)) { g_warning ("Could not change stream parent to %s: not a parent input stream", - mate_mixer_stream_get_name (parent)); + mate_mixer_stream_get_name (MATE_MIXER_STREAM (parent))); return FALSE; } - pulse = PULSE_STREAM (stream); + pstream = PULSE_STREAM (pclient); - return pulse_connection_move_sink_input (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), - pulse_stream_get_index (PULSE_STREAM (parent))); + return pulse_connection_move_sink_input (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), + pulse_stream_get_index (parent)); } static gboolean -source_output_remove (MateMixerClientStream *stream) +pulse_source_output_remove (PulseClientStream *pclient) { - PulseStream *pulse; + PulseStream *pstream; - g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (stream), FALSE); + g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (pclient), FALSE); - pulse = PULSE_STREAM (stream); + pstream = PULSE_STREAM (pclient); - return pulse_connection_kill_source_output (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse)); + return pulse_connection_kill_source_output (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream)); } static PulseMonitor * -source_output_create_monitor (MateMixerStream *stream) +pulse_source_output_create_monitor (PulseStream *pstream) { MateMixerStream *parent; - g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (stream), NULL); + g_return_val_if_fail (PULSE_IS_SOURCE_OUTPUT (pstream), NULL); - parent = mate_mixer_client_stream_get_parent (MATE_MIXER_CLIENT_STREAM (stream)); + parent = mate_mixer_client_stream_get_parent (MATE_MIXER_CLIENT_STREAM (pstream)); if (G_UNLIKELY (parent == NULL)) { - g_debug ("Not creating monitor for client stream %s as it is not available", - mate_mixer_stream_get_name (stream)); + g_debug ("Not creating monitor for client stream %s: not available", + mate_mixer_stream_get_name (MATE_MIXER_STREAM (pstream))); return NULL; } - return pulse_connection_create_monitor (pulse_stream_get_connection (PULSE_STREAM (stream)), + return pulse_connection_create_monitor (pulse_stream_get_connection (pstream), pulse_stream_get_index (PULSE_STREAM (parent)), PA_INVALID_INDEX); } diff --git a/backends/pulse/pulse-source-output.h b/backends/pulse/pulse-source-output.h index 69efae0..845d439 100644 --- a/backends/pulse/pulse-source-output.h +++ b/backends/pulse/pulse-source-output.h @@ -62,7 +62,7 @@ PulseStream *pulse_source_output_new (PulseConnection *connec const pa_source_output_info *info, PulseStream *parent); -gboolean pulse_source_output_update (PulseStream *stream, +gboolean pulse_source_output_update (PulseStream *pstream, const pa_source_output_info *info, PulseStream *parent); diff --git a/backends/pulse/pulse-source.c b/backends/pulse/pulse-source.c index b443402..e7dce6f 100644 --- a/backends/pulse/pulse-source.c +++ b/backends/pulse/pulse-source.c @@ -18,8 +18,9 @@ #include <glib.h> #include <glib-object.h> -#include <libmatemixer/matemixer-stream.h> #include <libmatemixer/matemixer-port.h> +#include <libmatemixer/matemixer-port-private.h> +#include <libmatemixer/matemixer-stream.h> #include <pulse/pulseaudio.h> @@ -34,13 +35,20 @@ static void pulse_source_init (PulseSource *source); G_DEFINE_TYPE (PulseSource, pulse_source, PULSE_TYPE_STREAM); -static gboolean source_set_mute (MateMixerStream *stream, - gboolean mute); -static gboolean source_set_volume (MateMixerStream *stream, - pa_cvolume *volume); -static gboolean source_set_active_port (MateMixerStream *stream, - const gchar *port_name); -static PulseMonitor *source_create_monitor (MateMixerStream *stream); +static void pulse_source_reload (PulseStream *pstream); + +static gboolean pulse_source_set_mute (PulseStream *pstream, + gboolean mute); +static gboolean pulse_source_set_volume (PulseStream *pstream, + pa_cvolume *cvolume); +static gboolean pulse_source_set_active_port (PulseStream *pstream, + MateMixerPort *port); + +static PulseMonitor *pulse_source_create_monitor (PulseStream *pstream); + +static void update_ports (PulseStream *pstream, + pa_source_port_info **ports, + pa_source_port_info *active); static void pulse_source_class_init (PulseSourceClass *klass) @@ -49,10 +57,11 @@ pulse_source_class_init (PulseSourceClass *klass) stream_class = PULSE_STREAM_CLASS (klass); - stream_class->set_mute = source_set_mute; - stream_class->set_volume = source_set_volume; - stream_class->set_active_port = source_set_active_port; - stream_class->create_monitor = source_create_monitor; + stream_class->reload = pulse_source_reload; + stream_class->set_mute = pulse_source_set_mute; + stream_class->set_volume = pulse_source_set_volume; + stream_class->set_active_port = pulse_source_set_active_port; + stream_class->create_monitor = pulse_source_create_monitor; } static void @@ -83,63 +92,40 @@ pulse_source_new (PulseConnection *connection, } gboolean -pulse_source_update (PulseStream *stream, +pulse_source_update (PulseStream *pstream, const pa_source_info *info, PulseDevice *device) { MateMixerStreamFlags flags = MATE_MIXER_STREAM_INPUT | MATE_MIXER_STREAM_HAS_MUTE | + MATE_MIXER_STREAM_HAS_MONITOR | MATE_MIXER_STREAM_HAS_VOLUME | MATE_MIXER_STREAM_CAN_SET_VOLUME | MATE_MIXER_STREAM_CAN_SUSPEND; - GList *ports = NULL; - guint32 i; - g_return_val_if_fail (PULSE_IS_SOURCE (stream), FALSE); + g_return_val_if_fail (PULSE_IS_SOURCE (pstream), FALSE); + g_return_val_if_fail (info != NULL, FALSE); /* Let all the information update before emitting notify signals */ - g_object_freeze_notify (G_OBJECT (stream)); - - pulse_stream_update_name (stream, info->name); - pulse_stream_update_description (stream, info->description); - pulse_stream_update_mute (stream, info->mute ? TRUE : FALSE); + g_object_freeze_notify (G_OBJECT (pstream)); - /* List of ports */ - for (i = 0; i < info->n_ports; i++) { - MateMixerPortFlags flags = MATE_MIXER_PORT_NO_FLAGS; - -#if PA_CHECK_VERSION(2, 0, 0) - if (info->ports[i]->available == PA_PORT_AVAILABLE_YES) - flags |= MATE_MIXER_PORT_AVAILABLE; -#endif - ports = g_list_prepend (ports, - mate_mixer_port_new (info->ports[i]->name, - info->ports[i]->description, - NULL, - info->ports[i]->priority, - flags)); - } - pulse_stream_update_ports (stream, ports); - - /* Active port */ - if (info->active_port) - pulse_stream_update_active_port (stream, info->active_port->name); - else - pulse_stream_update_active_port (stream, NULL); + pulse_stream_update_name (pstream, info->name); + pulse_stream_update_description (pstream, info->description); + pulse_stream_update_mute (pstream, info->mute ? TRUE : FALSE); /* Stream state */ switch (info->state) { case PA_SOURCE_RUNNING: - pulse_stream_update_state (stream, MATE_MIXER_STREAM_RUNNING); + pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_RUNNING); break; case PA_SOURCE_IDLE: - pulse_stream_update_state (stream, MATE_MIXER_STREAM_IDLE); + pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_IDLE); break; case PA_SOURCE_SUSPENDED: - pulse_stream_update_state (stream, MATE_MIXER_STREAM_SUSPENDED); + pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_SUSPENDED); break; default: - pulse_stream_update_state (stream, MATE_MIXER_STREAM_UNKNOWN_STATE); + pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_UNKNOWN); break; } @@ -149,79 +135,134 @@ pulse_source_update (PulseStream *stream, if (info->flags & PA_SINK_FLAT_VOLUME) flags |= MATE_MIXER_STREAM_HAS_FLAT_VOLUME; - if (pa_channel_map_can_balance (&info->channel_map)) - flags |= MATE_MIXER_STREAM_CAN_BALANCE; - if (pa_channel_map_can_fade (&info->channel_map)) - flags |= MATE_MIXER_STREAM_CAN_FADE; - /* Flags must be updated before volume */ - pulse_stream_update_flags (stream, flags); + pulse_stream_update_flags (pstream, flags); + + pulse_stream_update_channel_map (pstream, &info->channel_map); + pulse_stream_update_volume (pstream, &info->volume, info->base_volume); - pulse_stream_update_volume (stream, - &info->volume, - &info->channel_map, - info->base_volume); + pulse_stream_update_device (pstream, MATE_MIXER_DEVICE (device)); - pulse_stream_update_device (stream, MATE_MIXER_DEVICE (device)); + /* Ports must be updated after device */ + if (info->ports != NULL) { + update_ports (pstream, info->ports, info->active_port); + } - g_object_thaw_notify (G_OBJECT (stream)); + g_object_thaw_notify (G_OBJECT (pstream)); return TRUE; } -static gboolean -source_set_mute (MateMixerStream *stream, gboolean mute) +static void +pulse_source_reload (PulseStream *pstream) { - PulseStream *pulse; + g_return_if_fail (PULSE_IS_SOURCE (pstream)); - g_return_val_if_fail (PULSE_IS_SOURCE (stream), FALSE); + pulse_connection_load_source_info (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream)); +} - pulse = PULSE_STREAM (stream); +static gboolean +pulse_source_set_mute (PulseStream *pstream, gboolean mute) +{ + g_return_val_if_fail (PULSE_IS_SOURCE (pstream), FALSE); - return pulse_connection_set_source_mute (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), + return pulse_connection_set_source_mute (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), mute); } static gboolean -source_set_volume (MateMixerStream *stream, pa_cvolume *volume) +pulse_source_set_volume (PulseStream *pstream, pa_cvolume *cvolume) { - PulseStream *pulse; - - g_return_val_if_fail (PULSE_IS_SOURCE (stream), FALSE); - g_return_val_if_fail (volume != NULL, FALSE); - - pulse = PULSE_STREAM (stream); + g_return_val_if_fail (PULSE_IS_SOURCE (pstream), FALSE); + g_return_val_if_fail (cvolume != NULL, FALSE); - return pulse_connection_set_source_volume (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), - volume); + return pulse_connection_set_source_volume (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), + cvolume); } static gboolean -source_set_active_port (MateMixerStream *stream, const gchar *port_name) +pulse_source_set_active_port (PulseStream *pstream, MateMixerPort *port) { - PulseStream *pulse; + g_return_val_if_fail (PULSE_IS_SOURCE (pstream), FALSE); + g_return_val_if_fail (MATE_MIXER_IS_PORT (port), FALSE); - g_return_val_if_fail (PULSE_IS_SOURCE (stream), FALSE); - g_return_val_if_fail (port_name != NULL, FALSE); + return pulse_connection_set_source_port (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), + mate_mixer_port_get_name (port)); +} - pulse = PULSE_STREAM (stream); +static PulseMonitor * +pulse_source_create_monitor (PulseStream *pstream) +{ + g_return_val_if_fail (PULSE_IS_SOURCE (pstream), NULL); - return pulse_connection_set_source_port (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), - port_name); + return pulse_connection_create_monitor (pulse_stream_get_connection (pstream), + pulse_stream_get_index (pstream), + PA_INVALID_INDEX); } -static PulseMonitor * -source_create_monitor (MateMixerStream *stream) +static void +update_ports (PulseStream *pstream, + pa_source_port_info **ports, + pa_source_port_info *active) { - PulseStream *pulse; + MateMixerPort *port; + MateMixerDevice *device; + GHashTable *hash; - g_return_val_if_fail (PULSE_IS_SOURCE (stream), NULL); + hash = pulse_stream_get_ports (pstream); - pulse = PULSE_STREAM (stream); + while (*ports != NULL) { + MateMixerPortFlags flags = MATE_MIXER_PORT_NO_FLAGS; + pa_source_port_info *info = *ports; + const gchar *icon = NULL; - return pulse_connection_create_monitor (pulse_stream_get_connection (pulse), - pulse_stream_get_index (pulse), - PA_INVALID_INDEX); + device = mate_mixer_stream_get_device (MATE_MIXER_STREAM (pstream)); + if (device != NULL) { + port = mate_mixer_device_get_port (device, info->name); + + if (port != NULL) { + flags = mate_mixer_port_get_flags (port); + icon = mate_mixer_port_get_icon (port); + } + } + +#if PA_CHECK_VERSION(2, 0, 0) + if (info->available == PA_PORT_AVAILABLE_YES) + flags |= MATE_MIXER_PORT_AVAILABLE; + else + flags &= ~MATE_MIXER_PORT_AVAILABLE; +#endif + + port = g_hash_table_lookup (hash, info->name); + + if (port != NULL) { + /* Update existing port */ + _mate_mixer_port_update_description (port, info->description); + _mate_mixer_port_update_icon (port, icon); + _mate_mixer_port_update_priority (port, info->priority); + _mate_mixer_port_update_flags (port, flags); + } else { + /* Add previously unknown port to the hash table */ + port = _mate_mixer_port_new (info->name, + info->description, + icon, + info->priority, + flags); + + g_hash_table_insert (hash, g_strdup (info->name), port); + } + + ports++; + } + + /* Active port */ + if (G_LIKELY (active != NULL)) + port = g_hash_table_lookup (hash, active->name); + else + port = NULL; + + pulse_stream_update_active_port (pstream, port); } diff --git a/backends/pulse/pulse-source.h b/backends/pulse/pulse-source.h index fc64fe7..9abf6d8 100644 --- a/backends/pulse/pulse-source.h +++ b/backends/pulse/pulse-source.h @@ -61,7 +61,7 @@ PulseStream *pulse_source_new (PulseConnection *connection, const pa_source_info *info, PulseDevice *device); -gboolean pulse_source_update (PulseStream *stream, +gboolean pulse_source_update (PulseStream *pstream, const pa_source_info *info, PulseDevice *device); diff --git a/backends/pulse/pulse-stream.c b/backends/pulse/pulse-stream.c index c7ac52a..fb738ad 100644 --- a/backends/pulse/pulse-stream.c +++ b/backends/pulse/pulse-stream.c @@ -15,12 +15,6 @@ * License along with this library; if not, see <http://www.gnu.org/licenses/>. */ -// XXX -// - make sure all functions work correctly with flags -// - make sure all functions notify -// - figure out whether functions should g_warning on errors -// - distinguish MateMixer and Pulse variable names - #include <string.h> #include <glib.h> #include <glib-object.h> @@ -40,19 +34,20 @@ struct _PulseStreamPrivate { guint32 index; - guint32 index_device; gchar *name; gchar *description; MateMixerDevice *device; MateMixerStreamFlags flags; MateMixerStreamState state; gboolean mute; - pa_cvolume volume; + guint volume; + pa_cvolume cvolume; pa_volume_t base_volume; pa_channel_map channel_map; gfloat balance; gfloat fade; - GList *ports; + GHashTable *ports; + GList *ports_list; MateMixerPort *port; PulseConnection *connection; PulseMonitor *monitor; @@ -67,142 +62,197 @@ enum { PROP_FLAGS, PROP_STATE, PROP_MUTE, - PROP_NUM_CHANNELS, PROP_VOLUME, PROP_BALANCE, PROP_FADE, - PROP_PORTS, PROP_ACTIVE_PORT, PROP_INDEX, - PROP_CONNECTION, - N_PROPERTIES + PROP_CONNECTION }; -static void mate_mixer_stream_interface_init (MateMixerStreamInterface *iface); -static void pulse_stream_class_init (PulseStreamClass *klass); -static void pulse_stream_init (PulseStream *stream); -static void pulse_stream_dispose (GObject *object); -static void pulse_stream_finalize (GObject *object); +static void mate_mixer_stream_interface_init (MateMixerStreamInterface *iface); + +static void pulse_stream_class_init (PulseStreamClass *klass); + +static void pulse_stream_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void pulse_stream_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); + +static void pulse_stream_init (PulseStream *pstream); +static void pulse_stream_dispose (GObject *object); +static void pulse_stream_finalize (GObject *object); G_DEFINE_ABSTRACT_TYPE_WITH_CODE (PulseStream, pulse_stream, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_STREAM, mate_mixer_stream_interface_init)) -/* Interface implementation */ -static const gchar * stream_get_name (MateMixerStream *stream); -static const gchar * stream_get_description (MateMixerStream *stream); -static MateMixerDevice * stream_get_device (MateMixerStream *stream); -static MateMixerStreamFlags stream_get_flags (MateMixerStream *stream); -static MateMixerStreamState stream_get_state (MateMixerStream *stream); -static gboolean stream_get_mute (MateMixerStream *stream); -static gboolean stream_set_mute (MateMixerStream *stream, - gboolean mute); -static guint stream_get_num_channels (MateMixerStream *stream); -static guint stream_get_volume (MateMixerStream *stream); -static gboolean stream_set_volume (MateMixerStream *stream, - guint volume); -static gdouble stream_get_decibel (MateMixerStream *stream); -static gboolean stream_set_decibel (MateMixerStream *stream, - gdouble decibel); -static MateMixerChannelPosition stream_get_channel_position (MateMixerStream *stream, - guint channel); -static guint stream_get_channel_volume (MateMixerStream *stream, - guint channel); -static gboolean stream_set_channel_volume (MateMixerStream *stream, - guint channel, - guint volume); -static gdouble stream_get_channel_decibel (MateMixerStream *stream, - guint channel); -static gboolean stream_set_channel_decibel (MateMixerStream *stream, - guint channel, - gdouble decibel); -static gboolean stream_has_position (MateMixerStream *stream, - MateMixerChannelPosition position); -static guint stream_get_position_volume (MateMixerStream *stream, - MateMixerChannelPosition position); -static gboolean stream_set_position_volume (MateMixerStream *stream, - MateMixerChannelPosition position, - guint volume); -static gdouble stream_get_position_decibel (MateMixerStream *stream, - MateMixerChannelPosition position); -static gboolean stream_set_position_decibel (MateMixerStream *stream, - MateMixerChannelPosition position, - gdouble decibel); -static gfloat stream_get_balance (MateMixerStream *stream); -static gboolean stream_set_balance (MateMixerStream *stream, - gfloat balance); -static gfloat stream_get_fade (MateMixerStream *stream); -static gboolean stream_set_fade (MateMixerStream *stream, - gfloat fade); -static gboolean stream_suspend (MateMixerStream *stream); -static gboolean stream_resume (MateMixerStream *stream); - -static gboolean stream_monitor_start (MateMixerStream *stream); -static void stream_monitor_stop (MateMixerStream *stream); -static gboolean stream_monitor_is_running (MateMixerStream *stream); -static gboolean stream_monitor_set_name (MateMixerStream *stream, - const gchar *name); -static void stream_monitor_value (PulseMonitor *monitor, - gdouble value, - MateMixerStream *stream); - -static const GList * stream_list_ports (MateMixerStream *stream); -static MateMixerPort * stream_get_active_port (MateMixerStream *stream); -static gboolean stream_set_active_port (MateMixerStream *stream, - const gchar *port_name); - -static guint stream_get_min_volume (MateMixerStream *stream); -static guint stream_get_max_volume (MateMixerStream *stream); -static guint stream_get_normal_volume (MateMixerStream *stream); -static guint stream_get_base_volume (MateMixerStream *stream); - -static gboolean stream_set_cvolume (MateMixerStream *stream, - pa_cvolume *volume); -static gint stream_compare_ports (gconstpointer a, - gconstpointer b); +static const gchar * pulse_stream_get_name (MateMixerStream *stream); +static const gchar * pulse_stream_get_description (MateMixerStream *stream); +static MateMixerDevice * pulse_stream_get_device (MateMixerStream *stream); +static MateMixerStreamFlags pulse_stream_get_flags (MateMixerStream *stream); +static MateMixerStreamState pulse_stream_get_state (MateMixerStream *stream); + +static gboolean pulse_stream_get_mute (MateMixerStream *stream); +static gboolean pulse_stream_set_mute (MateMixerStream *stream, + gboolean mute); + +static guint pulse_stream_get_num_channels (MateMixerStream *stream); + +static guint pulse_stream_get_volume (MateMixerStream *stream); +static gboolean pulse_stream_set_volume (MateMixerStream *stream, + guint volume); + +static gdouble pulse_stream_get_decibel (MateMixerStream *stream); +static gboolean pulse_stream_set_decibel (MateMixerStream *stream, + gdouble decibel); + +static guint pulse_stream_get_channel_volume (MateMixerStream *stream, + guint channel); +static gboolean pulse_stream_set_channel_volume (MateMixerStream *stream, + guint channel, + guint volume); + +static gdouble pulse_stream_get_channel_decibel (MateMixerStream *stream, + guint channel); +static gboolean pulse_stream_set_channel_decibel (MateMixerStream *stream, + guint channel, + gdouble decibel); + +static MateMixerChannelPosition pulse_stream_get_channel_position (MateMixerStream *stream, + guint channel); +static gboolean pulse_stream_has_channel_position (MateMixerStream *stream, + MateMixerChannelPosition position); + +static gfloat pulse_stream_get_balance (MateMixerStream *stream); +static gboolean pulse_stream_set_balance (MateMixerStream *stream, + gfloat balance); + +static gfloat pulse_stream_get_fade (MateMixerStream *stream); +static gboolean pulse_stream_set_fade (MateMixerStream *stream, + gfloat fade); + +static gboolean pulse_stream_suspend (MateMixerStream *stream); +static gboolean pulse_stream_resume (MateMixerStream *stream); + +static gboolean pulse_stream_monitor_start (MateMixerStream *stream); +static void pulse_stream_monitor_stop (MateMixerStream *stream); +static gboolean pulse_stream_monitor_is_running (MateMixerStream *stream); +static gboolean pulse_stream_monitor_set_name (MateMixerStream *stream, + const gchar *name); + +static const GList * pulse_stream_list_ports (MateMixerStream *stream); + +static MateMixerPort * pulse_stream_get_active_port (MateMixerStream *stream); +static gboolean pulse_stream_set_active_port (MateMixerStream *stream, + MateMixerPort *port); + +static guint pulse_stream_get_min_volume (MateMixerStream *stream); +static guint pulse_stream_get_max_volume (MateMixerStream *stream); +static guint pulse_stream_get_normal_volume (MateMixerStream *stream); +static guint pulse_stream_get_base_volume (MateMixerStream *stream); + +static void on_monitor_value (PulseMonitor *monitor, + gdouble value, + PulseStream *pstream); + +static gboolean update_balance_fade (PulseStream *pstream); + +static gboolean set_cvolume (PulseStream *pstream, + pa_cvolume *cvolume); + +static gint compare_ports (gconstpointer a, + gconstpointer b); static void mate_mixer_stream_interface_init (MateMixerStreamInterface *iface) { - iface->get_name = stream_get_name; - iface->get_description = stream_get_description; - iface->get_device = stream_get_device; - iface->get_flags = stream_get_flags; - iface->get_state = stream_get_state; - iface->get_mute = stream_get_mute; - iface->set_mute = stream_set_mute; - iface->get_num_channels = stream_get_num_channels; - iface->get_volume = stream_get_volume; - iface->set_volume = stream_set_volume; - iface->get_decibel = stream_get_decibel; - iface->set_decibel = stream_set_decibel; - iface->get_channel_position = stream_get_channel_position; - iface->get_channel_volume = stream_get_channel_volume; - iface->set_channel_volume = stream_set_channel_volume; - iface->get_channel_decibel = stream_get_channel_decibel; - iface->set_channel_decibel = stream_set_channel_decibel; - iface->has_position = stream_has_position; - iface->get_position_volume = stream_get_position_volume; - iface->set_position_volume = stream_set_position_volume; - iface->get_position_decibel = stream_get_position_decibel; - iface->set_position_decibel = stream_set_position_decibel; - iface->get_balance = stream_get_balance; - iface->set_balance = stream_set_balance; - iface->get_fade = stream_get_fade; - iface->set_fade = stream_set_fade; - iface->suspend = stream_suspend; - iface->resume = stream_resume; - iface->monitor_start = stream_monitor_start; - iface->monitor_stop = stream_monitor_stop; - iface->monitor_is_running = stream_monitor_is_running; - iface->monitor_set_name = stream_monitor_set_name; - iface->list_ports = stream_list_ports; - iface->get_active_port = stream_get_active_port; - iface->set_active_port = stream_set_active_port; - iface->get_min_volume = stream_get_min_volume; - iface->get_max_volume = stream_get_max_volume; - iface->get_normal_volume = stream_get_normal_volume; - iface->get_base_volume = stream_get_base_volume; + iface->get_name = pulse_stream_get_name; + iface->get_description = pulse_stream_get_description; + iface->get_device = pulse_stream_get_device; + iface->get_flags = pulse_stream_get_flags; + iface->get_state = pulse_stream_get_state; + iface->get_mute = pulse_stream_get_mute; + iface->set_mute = pulse_stream_set_mute; + iface->get_num_channels = pulse_stream_get_num_channels; + iface->get_volume = pulse_stream_get_volume; + iface->set_volume = pulse_stream_set_volume; + iface->get_decibel = pulse_stream_get_decibel; + iface->set_decibel = pulse_stream_set_decibel; + iface->get_channel_volume = pulse_stream_get_channel_volume; + iface->set_channel_volume = pulse_stream_set_channel_volume; + iface->get_channel_decibel = pulse_stream_get_channel_decibel; + iface->set_channel_decibel = pulse_stream_set_channel_decibel; + iface->get_channel_position = pulse_stream_get_channel_position; + iface->has_channel_position = pulse_stream_has_channel_position; + iface->get_balance = pulse_stream_get_balance; + iface->set_balance = pulse_stream_set_balance; + iface->get_fade = pulse_stream_get_fade; + iface->set_fade = pulse_stream_set_fade; + iface->suspend = pulse_stream_suspend; + iface->resume = pulse_stream_resume; + iface->monitor_start = pulse_stream_monitor_start; + iface->monitor_stop = pulse_stream_monitor_stop; + iface->monitor_is_running = pulse_stream_monitor_is_running; + iface->monitor_set_name = pulse_stream_monitor_set_name; + iface->list_ports = pulse_stream_list_ports; + iface->get_active_port = pulse_stream_get_active_port; + iface->set_active_port = pulse_stream_set_active_port; + iface->get_min_volume = pulse_stream_get_min_volume; + iface->get_max_volume = pulse_stream_get_max_volume; + iface->get_normal_volume = pulse_stream_get_normal_volume; + iface->get_base_volume = pulse_stream_get_base_volume; +} + +static void +pulse_stream_class_init (PulseStreamClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = pulse_stream_dispose; + object_class->finalize = pulse_stream_finalize; + object_class->get_property = pulse_stream_get_property; + object_class->set_property = pulse_stream_set_property; + + g_object_class_install_property (object_class, + PROP_INDEX, + g_param_spec_uint ("index", + "Index", + "Stream index", + 0, + G_MAXUINT, + 0, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_CONNECTION, + g_param_spec_object ("connection", + "Connection", + "PulseAudio connection", + PULSE_TYPE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_override_property (object_class, PROP_NAME, "name"); + g_object_class_override_property (object_class, PROP_DESCRIPTION, "description"); + g_object_class_override_property (object_class, PROP_DEVICE, "device"); + g_object_class_override_property (object_class, PROP_FLAGS, "flags"); + g_object_class_override_property (object_class, PROP_STATE, "state"); + g_object_class_override_property (object_class, PROP_MUTE, "mute"); + g_object_class_override_property (object_class, PROP_VOLUME, "volume"); + g_object_class_override_property (object_class, PROP_BALANCE, "balance"); + g_object_class_override_property (object_class, PROP_FADE, "fade"); + g_object_class_override_property (object_class, PROP_ACTIVE_PORT, "active-port"); + + g_type_class_add_private (object_class, sizeof (PulseStreamPrivate)); } static void @@ -211,52 +261,46 @@ pulse_stream_get_property (GObject *object, GValue *value, GParamSpec *pspec) { - PulseStream *stream; + PulseStream *pstream; - stream = PULSE_STREAM (object); + pstream = PULSE_STREAM (object); switch (param_id) { case PROP_NAME: - g_value_set_string (value, stream->priv->name); + g_value_set_string (value, pstream->priv->name); break; case PROP_DESCRIPTION: - g_value_set_string (value, stream->priv->description); + g_value_set_string (value, pstream->priv->description); break; case PROP_DEVICE: - g_value_set_object (value, stream->priv->device); + g_value_set_object (value, pstream->priv->device); break; case PROP_FLAGS: - g_value_set_flags (value, stream->priv->flags); + g_value_set_flags (value, pstream->priv->flags); break; case PROP_STATE: - g_value_set_enum (value, stream->priv->state); + g_value_set_enum (value, pstream->priv->state); break; case PROP_MUTE: - g_value_set_boolean (value, stream->priv->mute); - break; - case PROP_NUM_CHANNELS: - g_value_set_uint (value, stream_get_num_channels (MATE_MIXER_STREAM (stream))); + g_value_set_boolean (value, pstream->priv->mute); break; case PROP_VOLUME: - g_value_set_int64 (value, stream_get_volume (MATE_MIXER_STREAM (stream))); + g_value_set_uint (value, pstream->priv->volume); break; case PROP_BALANCE: - g_value_set_float (value, stream->priv->balance); + g_value_set_float (value, pstream->priv->balance); break; case PROP_FADE: - g_value_set_float (value, stream->priv->fade); - break; - case PROP_PORTS: - g_value_set_pointer (value, stream->priv->ports); + g_value_set_float (value, pstream->priv->fade); break; case PROP_ACTIVE_PORT: - g_value_set_object (value, stream->priv->port); + g_value_set_object (value, pstream->priv->port); break; case PROP_INDEX: - g_value_set_uint (value, stream->priv->index); + g_value_set_uint (value, pstream->priv->index); break; case PROP_CONNECTION: - g_value_set_object (value, stream->priv->connection); + g_value_set_object (value, pstream->priv->connection); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); @@ -270,17 +314,17 @@ pulse_stream_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - PulseStream *stream; + PulseStream *pstream; - stream = PULSE_STREAM (object); + pstream = PULSE_STREAM (object); switch (param_id) { case PROP_INDEX: - stream->priv->index = g_value_get_uint (value); + pstream->priv->index = g_value_get_uint (value); break; case PROP_CONNECTION: /* Construct-only object */ - stream->priv->connection = g_value_dup_object (value); + pstream->priv->connection = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); @@ -289,78 +333,41 @@ pulse_stream_set_property (GObject *object, } static void -pulse_stream_class_init (PulseStreamClass *klass) +pulse_stream_init (PulseStream *pstream) { - GObjectClass *object_class; - - object_class = G_OBJECT_CLASS (klass); - object_class->dispose = pulse_stream_dispose; - object_class->finalize = pulse_stream_finalize; - object_class->get_property = pulse_stream_get_property; - object_class->set_property = pulse_stream_set_property; - - g_object_class_install_property (object_class, - PROP_INDEX, - g_param_spec_uint ("index", - "Index", - "Stream index", - 0, - G_MAXUINT, - 0, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS)); + pstream->priv = G_TYPE_INSTANCE_GET_PRIVATE (pstream, + PULSE_TYPE_STREAM, + PulseStreamPrivate); - g_object_class_install_property (object_class, - PROP_CONNECTION, - g_param_spec_object ("connection", - "Connection", - "PulseAudio connection", - PULSE_TYPE_CONNECTION, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS)); + pstream->priv->ports = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); - g_object_class_override_property (object_class, PROP_NAME, "name"); - g_object_class_override_property (object_class, PROP_DESCRIPTION, "description"); - g_object_class_override_property (object_class, PROP_DEVICE, "device"); - g_object_class_override_property (object_class, PROP_FLAGS, "flags"); - g_object_class_override_property (object_class, PROP_STATE, "state"); - g_object_class_override_property (object_class, PROP_MUTE, "mute"); - g_object_class_override_property (object_class, PROP_NUM_CHANNELS, "num-channels"); - g_object_class_override_property (object_class, PROP_VOLUME, "volume"); - g_object_class_override_property (object_class, PROP_BALANCE, "balance"); - g_object_class_override_property (object_class, PROP_FADE, "fade"); - g_object_class_override_property (object_class, PROP_PORTS, "ports"); - g_object_class_override_property (object_class, PROP_ACTIVE_PORT, "active-port"); - - g_type_class_add_private (object_class, sizeof (PulseStreamPrivate)); -} + /* Initialize empty volume and channel map structures, they will be used + * if the stream does not support volume */ + pa_cvolume_init (&pstream->priv->cvolume); -static void -pulse_stream_init (PulseStream *stream) -{ - stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, - PULSE_TYPE_STREAM, - PulseStreamPrivate); + pa_channel_map_init (&pstream->priv->channel_map); } static void pulse_stream_dispose (GObject *object) { - PulseStream *stream; + PulseStream *pstream; - stream = PULSE_STREAM (object); + pstream = PULSE_STREAM (object); - if (stream->priv->ports) { - g_list_free_full (stream->priv->ports, g_object_unref); - stream->priv->ports = NULL; + if (pstream->priv->ports_list != NULL) { + g_list_free_full (pstream->priv->ports_list, g_object_unref); + pstream->priv->ports_list = NULL; } + g_hash_table_remove_all (pstream->priv->ports); - g_clear_object (&stream->priv->port); - g_clear_object (&stream->priv->device); - g_clear_object (&stream->priv->monitor); - g_clear_object (&stream->priv->connection); + g_clear_object (&pstream->priv->port); + g_clear_object (&pstream->priv->device); + g_clear_object (&pstream->priv->monitor); + g_clear_object (&pstream->priv->connection); G_OBJECT_CLASS (pulse_stream_parent_class)->dispose (object); } @@ -368,220 +375,261 @@ pulse_stream_dispose (GObject *object) static void pulse_stream_finalize (GObject *object) { - PulseStream *stream; + PulseStream *pstream; - stream = PULSE_STREAM (object); + pstream = PULSE_STREAM (object); - g_free (stream->priv->name); - g_free (stream->priv->description); + g_free (pstream->priv->name); + g_free (pstream->priv->description); + g_free (pstream->priv->monitor_name); + + g_hash_table_destroy (pstream->priv->ports); G_OBJECT_CLASS (pulse_stream_parent_class)->finalize (object); } guint32 -pulse_stream_get_index (PulseStream *stream) +pulse_stream_get_index (PulseStream *pstream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); + g_return_val_if_fail (PULSE_IS_STREAM (pstream), 0); - return stream->priv->index; + return pstream->priv->index; } PulseConnection * -pulse_stream_get_connection (PulseStream *stream) +pulse_stream_get_connection (PulseStream *pstream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); + g_return_val_if_fail (PULSE_IS_STREAM (pstream), NULL); - return stream->priv->connection; + return pstream->priv->connection; } PulseMonitor * -pulse_stream_get_monitor (PulseStream *stream) +pulse_stream_get_monitor (PulseStream *pstream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); + g_return_val_if_fail (PULSE_IS_STREAM (pstream), NULL); - return stream->priv->monitor; + return pstream->priv->monitor; +} + +const pa_cvolume * +pulse_stream_get_cvolume (PulseStream *pstream) +{ + g_return_val_if_fail (PULSE_IS_STREAM (pstream), NULL); + + return &pstream->priv->cvolume; +} + +const pa_channel_map * +pulse_stream_get_channel_map (PulseStream *pstream) +{ + g_return_val_if_fail (PULSE_IS_STREAM (pstream), NULL); + + return &pstream->priv->channel_map; +} + +GHashTable * +pulse_stream_get_ports (PulseStream *pstream) +{ + g_return_val_if_fail (PULSE_IS_STREAM (pstream), NULL); + + return pstream->priv->ports; } gboolean -pulse_stream_update_name (PulseStream *stream, const gchar *name) +pulse_stream_update_name (PulseStream *pstream, const gchar *name) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); /* Allow the name to be NULL */ - if (g_strcmp0 (name, stream->priv->name)) { - g_free (stream->priv->name); - stream->priv->name = g_strdup (name); + if (g_strcmp0 (name, pstream->priv->name) != 0) { + g_free (pstream->priv->name); + pstream->priv->name = g_strdup (name); - g_object_notify (G_OBJECT (stream), "name"); + g_object_notify (G_OBJECT (pstream), "name"); + return TRUE; } - return TRUE; + return FALSE; } gboolean -pulse_stream_update_description (PulseStream *stream, const gchar *description) +pulse_stream_update_description (PulseStream *pstream, const gchar *description) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); /* Allow the description to be NULL */ - if (g_strcmp0 (description, stream->priv->description)) { - g_free (stream->priv->description); - stream->priv->description = g_strdup (description); + if (g_strcmp0 (description, pstream->priv->description) != 0) { + g_free (pstream->priv->description); + pstream->priv->description = g_strdup (description); - g_object_notify (G_OBJECT (stream), "description"); + g_object_notify (G_OBJECT (pstream), "description"); + return TRUE; } - return TRUE; + return FALSE; } gboolean -pulse_stream_update_device (PulseStream *stream, MateMixerDevice *device) +pulse_stream_update_device (PulseStream *pstream, MateMixerDevice *device) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - if (stream->priv->device != device) { - g_clear_object (&stream->priv->device); + if (pstream->priv->device != device) { + g_clear_object (&pstream->priv->device); if (G_LIKELY (device != NULL)) - stream->priv->device = g_object_ref (device); + pstream->priv->device = g_object_ref (device); - g_object_notify (G_OBJECT (stream), "device"); + g_object_notify (G_OBJECT (pstream), "device"); + return TRUE; } - return TRUE; + return FALSE; } gboolean -pulse_stream_update_flags (PulseStream *stream, MateMixerStreamFlags flags) +pulse_stream_update_flags (PulseStream *pstream, MateMixerStreamFlags flags) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); + + if (pstream->priv->flags != flags) { + pstream->priv->flags = flags; - if (stream->priv->flags != flags) { - stream->priv->flags = flags; - g_object_notify (G_OBJECT (stream), "flags"); + g_object_notify (G_OBJECT (pstream), "flags"); + return TRUE; } - return TRUE; + return FALSE; } gboolean -pulse_stream_update_state (PulseStream *stream, MateMixerStreamState state) +pulse_stream_update_state (PulseStream *pstream, MateMixerStreamState state) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - if (stream->priv->state != state) { - stream->priv->state = state; - g_object_notify (G_OBJECT (stream), "state"); + if (pstream->priv->state != state) { + pstream->priv->state = state; + + g_object_notify (G_OBJECT (pstream), "state"); + return TRUE; } - return TRUE; + return FALSE; } gboolean -pulse_stream_update_mute (PulseStream *stream, gboolean mute) +pulse_stream_update_channel_map (PulseStream *pstream, const pa_channel_map *map) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + MateMixerStreamFlags flags; - if (stream->priv->mute != mute) { - stream->priv->mute = mute; - g_object_notify (G_OBJECT (stream), "mute"); + g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); + g_return_val_if_fail (map != NULL, FALSE); + + flags = pstream->priv->flags; + + if (pa_channel_map_valid (map)) { + if (pa_channel_map_can_balance (map)) + flags |= MATE_MIXER_STREAM_CAN_BALANCE; + else + flags &= ~MATE_MIXER_STREAM_CAN_BALANCE; + + if (pa_channel_map_can_fade (map)) + flags |= MATE_MIXER_STREAM_CAN_FADE; + else + flags &= ~MATE_MIXER_STREAM_CAN_FADE; + + pstream->priv->channel_map = *map; + } else { + flags &= ~(MATE_MIXER_STREAM_CAN_BALANCE | MATE_MIXER_STREAM_CAN_FADE); + + /* If the channel map is not valid, create an empty channel map, which + * also won't validate, but at least we know what it is */ + pa_channel_map_init (&pstream->priv->channel_map); } + + pulse_stream_update_flags (pstream, flags); return TRUE; } gboolean -pulse_stream_update_volume (PulseStream *stream, - const pa_cvolume *volume, - const pa_channel_map *map, - pa_volume_t base_volume) +pulse_stream_update_volume (PulseStream *pstream, + const pa_cvolume *cvolume, + pa_volume_t base_volume) { - gfloat fade = 0.0f; - gfloat balance = 0.0f; + MateMixerStreamFlags flags; - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - /* The channel map should be always present, but volume is not always - * supported and might be NULL */ - if (G_LIKELY (map != NULL)) { - if (!pa_channel_map_equal (&stream->priv->channel_map, map)) - stream->priv->channel_map = *map; - } + /* The base volume is not a property */ + pstream->priv->base_volume = base_volume; - if (volume != NULL) { - if (!pa_cvolume_equal (&stream->priv->volume, volume)) { - stream->priv->volume = *volume; + flags = pstream->priv->flags; - g_object_notify (G_OBJECT (stream), "volume"); - } + if (cvolume != NULL && pa_cvolume_valid (cvolume)) { + /* Decibel volume and volume settability flags must be provided by + * the implementation */ + flags |= MATE_MIXER_STREAM_HAS_VOLUME; - stream->priv->base_volume = (base_volume > 0) - ? base_volume - : PA_VOLUME_NORM; + if (pa_cvolume_equal (&pstream->priv->cvolume, cvolume) == 0) { + pstream->priv->cvolume = *cvolume; + pstream->priv->volume = (guint) pa_cvolume_max (&pstream->priv->cvolume); - /* Fade and balance need a valid channel map and volume, otherwise - * compare against the default values */ - fade = pa_cvolume_get_fade (volume, &stream->priv->channel_map); - balance = pa_cvolume_get_balance (volume, &stream->priv->channel_map); + g_object_notify (G_OBJECT (pstream), "volume"); + } } else { - stream->priv->base_volume = PA_VOLUME_NORM; - } + flags &= ~(MATE_MIXER_STREAM_HAS_VOLUME | + MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME | + MATE_MIXER_STREAM_CAN_SET_VOLUME); - if (stream->priv->balance != balance) { - stream->priv->balance = balance; - g_object_notify (G_OBJECT (stream), "balance"); - } + /* If the cvolume is not valid, create an empty cvolume, which also + * won't validate, but at least we know what it is */ + pa_cvolume_init (&pstream->priv->cvolume); + + if (pstream->priv->volume != (guint) PA_VOLUME_MUTED) { + pstream->priv->volume = (guint) PA_VOLUME_MUTED; - if (stream->priv->fade != fade) { - stream->priv->fade = fade; - g_object_notify (G_OBJECT (stream), "fade"); + g_object_notify (G_OBJECT (pstream), "volume"); + } } + + pulse_stream_update_flags (pstream, flags); + + /* Changing volume may change the balance and fade values as well */ + update_balance_fade (pstream); return TRUE; } gboolean -pulse_stream_update_ports (PulseStream *stream, GList *ports) +pulse_stream_update_mute (PulseStream *pstream, gboolean mute) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); - if (stream->priv->ports) - g_list_free_full (stream->priv->ports, g_object_unref); + if (pstream->priv->mute != mute) { + pstream->priv->mute = mute; - if (ports) - stream->priv->ports = g_list_sort (ports, stream_compare_ports); - else - stream->priv->ports = NULL; - - g_object_notify (G_OBJECT (stream), "ports"); - return TRUE; + g_object_notify (G_OBJECT (pstream), "mute"); + return TRUE; + } + return FALSE; } gboolean -pulse_stream_update_active_port (PulseStream *stream, const gchar *port_name) +pulse_stream_update_active_port (PulseStream *pstream, MateMixerPort *port) { - GList *list; - MateMixerPort *port = NULL; + g_return_val_if_fail (PULSE_IS_STREAM (pstream), FALSE); + g_return_val_if_fail (MATE_MIXER_IS_PORT (port), FALSE); - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - list = stream->priv->ports; - while (list) { - port = MATE_MIXER_PORT (list->data); - - if (!g_strcmp0 (mate_mixer_port_get_name (port), port_name)) - break; - - port = NULL; - list = list->next; - } + if (pstream->priv->port != port) { + if (pstream->priv->port != NULL) + g_clear_object (&pstream->priv->port); - if (stream->priv->port != port) { - if (stream->priv->port) - g_clear_object (&stream->priv->port); - if (port) - stream->priv->port = g_object_ref (port); + if (port != NULL) + pstream->priv->port = g_object_ref (port); - g_object_notify (G_OBJECT (stream), "active-port"); + g_object_notify (G_OBJECT (pstream), "active-port"); + return TRUE; } - return TRUE; + return FALSE; } static const gchar * -stream_get_name (MateMixerStream *stream) +pulse_stream_get_name (MateMixerStream *stream) { g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); @@ -589,7 +637,7 @@ stream_get_name (MateMixerStream *stream) } static const gchar * -stream_get_description (MateMixerStream *stream) +pulse_stream_get_description (MateMixerStream *stream) { g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); @@ -597,7 +645,7 @@ stream_get_description (MateMixerStream *stream) } static MateMixerDevice * -stream_get_device (MateMixerStream *stream) +pulse_stream_get_device (MateMixerStream *stream) { g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); @@ -605,7 +653,7 @@ stream_get_device (MateMixerStream *stream) } static MateMixerStreamFlags -stream_get_flags (MateMixerStream *stream) +pulse_stream_get_flags (MateMixerStream *stream) { g_return_val_if_fail (PULSE_IS_STREAM (stream), MATE_MIXER_STREAM_NO_FLAGS); @@ -613,269 +661,253 @@ stream_get_flags (MateMixerStream *stream) } static MateMixerStreamState -stream_get_state (MateMixerStream *stream) +pulse_stream_get_state (MateMixerStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), MATE_MIXER_STREAM_UNKNOWN_STATE); + g_return_val_if_fail (PULSE_IS_STREAM (stream), MATE_MIXER_STREAM_STATE_UNKNOWN); return PULSE_STREAM (stream)->priv->state; } static gboolean -stream_get_mute (MateMixerStream *stream) +pulse_stream_get_mute (MateMixerStream *stream) { + PulseStream *pstream; + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - return PULSE_STREAM (stream)->priv->mute; + pstream = PULSE_STREAM (stream); + + if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_MUTE)) + return FALSE; + + return pstream->priv->mute; } static gboolean -stream_set_mute (MateMixerStream *stream, gboolean mute) +pulse_stream_set_mute (MateMixerStream *stream, gboolean mute) { - PulseStream *pulse; + PulseStream *pstream; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - pulse = PULSE_STREAM (stream); + pstream = PULSE_STREAM (stream); + + if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_MUTE)) + return FALSE; - if (pulse->priv->mute != mute) { - if (PULSE_STREAM_GET_CLASS (stream)->set_mute (stream, mute) == FALSE) + if (pstream->priv->mute != mute) { + PulseStreamClass *klass = PULSE_STREAM_GET_CLASS (pstream); + + if (klass->set_mute (pstream, mute) == FALSE) return FALSE; - pulse->priv->mute = mute; + pstream->priv->mute = mute; + g_object_notify (G_OBJECT (stream), "mute"); } return TRUE; } static guint -stream_get_num_channels (MateMixerStream *stream) +pulse_stream_get_num_channels (MateMixerStream *stream) { g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); - return PULSE_STREAM (stream)->priv->volume.channels; + return PULSE_STREAM (stream)->priv->channel_map.channels; } static guint -stream_get_volume (MateMixerStream *stream) +pulse_stream_get_volume (MateMixerStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); + PulseStream *pstream; + + g_return_val_if_fail (PULSE_IS_STREAM (stream), (guint) PA_VOLUME_MUTED); + + pstream = PULSE_STREAM (stream); - return (guint) pa_cvolume_max (&PULSE_STREAM (stream)->priv->volume); + if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_VOLUME)) + return (guint) PA_VOLUME_MUTED; + + return pstream->priv->volume; } static gboolean -stream_set_volume (MateMixerStream *stream, guint volume) +pulse_stream_set_volume (MateMixerStream *stream, guint volume) { - PulseStream *pulse; - pa_cvolume cvolume; + PulseStream *pstream; + pa_cvolume cvolume; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_CAN_SET_VOLUME)) + pstream = PULSE_STREAM (stream); + + if (!(pstream->priv->flags & MATE_MIXER_STREAM_CAN_SET_VOLUME)) return FALSE; - pulse = PULSE_STREAM (stream); - cvolume = pulse->priv->volume; + cvolume = pstream->priv->cvolume; if (pa_cvolume_scale (&cvolume, (pa_volume_t) volume) == NULL) return FALSE; - return stream_set_cvolume (stream, &cvolume); + return set_cvolume (pstream, &cvolume); } static gdouble -stream_get_decibel (MateMixerStream *stream) +pulse_stream_get_decibel (MateMixerStream *stream) { - gdouble value; + PulseStream *pstream; + gdouble value; g_return_val_if_fail (PULSE_IS_STREAM (stream), -MATE_MIXER_INFINITY); - if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME)) + pstream = PULSE_STREAM (stream); + + if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME)) return -MATE_MIXER_INFINITY; - value = pa_sw_volume_to_dB (stream_get_volume (stream)); + value = pa_sw_volume_to_dB (pulse_stream_get_volume (stream)); + /* PA_VOLUME_MUTED is converted to PA_DECIBEL_MININFTY */ return (value == PA_DECIBEL_MININFTY) ? -MATE_MIXER_INFINITY : value; } static gboolean -stream_set_decibel (MateMixerStream *stream, gdouble decibel) +pulse_stream_set_decibel (MateMixerStream *stream, gdouble decibel) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME) || - !(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_CAN_SET_VOLUME)) - return FALSE; - - return stream_set_volume (stream, pa_sw_volume_from_dB (decibel)); -} + PulseStream *pstream; -static MateMixerChannelPosition -stream_get_channel_position (MateMixerStream *stream, guint channel) -{ - PulseStream *pulse; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), MATE_MIXER_CHANNEL_UNKNOWN_POSITION); + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - pulse = PULSE_STREAM (stream); + pstream = PULSE_STREAM (stream); - if (channel >= pulse->priv->channel_map.channels) - return MATE_MIXER_CHANNEL_UNKNOWN_POSITION; + if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME) || + !(pstream->priv->flags & MATE_MIXER_STREAM_CAN_SET_VOLUME)) + return FALSE; - return pulse_convert_position_to_pulse (pulse->priv->channel_map.map[channel]); + return pulse_stream_set_volume (stream, pa_sw_volume_from_dB (decibel)); } static guint -stream_get_channel_volume (MateMixerStream *stream, guint channel) +pulse_stream_get_channel_volume (MateMixerStream *stream, guint channel) { - PulseStream *pulse; + PulseStream *pstream; - g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); + g_return_val_if_fail (PULSE_IS_STREAM (stream), (guint) PA_VOLUME_MUTED); + + pstream = PULSE_STREAM (stream); - pulse = PULSE_STREAM (stream); + if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_VOLUME)) + return (guint) PA_VOLUME_MUTED; - if (channel >= pulse->priv->volume.channels) - return stream_get_min_volume (stream); + if (channel >= pstream->priv->cvolume.channels) + return (guint) PA_VOLUME_MUTED; - return (guint) pulse->priv->volume.values[channel]; + return (guint) pstream->priv->cvolume.values[channel]; } static gboolean -stream_set_channel_volume (MateMixerStream *stream, guint channel, guint volume) +pulse_stream_set_channel_volume (MateMixerStream *stream, guint channel, guint volume) { - PulseStream *pulse; - pa_cvolume cvolume; + PulseStream *pstream; + pa_cvolume cvolume; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - pulse = PULSE_STREAM (stream); - cvolume = pulse->priv->volume; + pstream = PULSE_STREAM (stream); + + if (!(pstream->priv->flags & MATE_MIXER_STREAM_CAN_SET_VOLUME)) + return FALSE; - if (channel >= pulse->priv->volume.channels) + if (channel >= pstream->priv->cvolume.channels) return FALSE; + /* This is safe, because the cvolume is validated by set_cvolume() */ + cvolume = pstream->priv->cvolume; cvolume.values[channel] = (pa_volume_t) volume; - return stream_set_cvolume (stream, &cvolume); + return set_cvolume (pstream, &cvolume); } static gdouble -stream_get_channel_decibel (MateMixerStream *stream, guint channel) +pulse_stream_get_channel_decibel (MateMixerStream *stream, guint channel) { - PulseStream *pulse; + PulseStream *pstream; gdouble value; g_return_val_if_fail (PULSE_IS_STREAM (stream), -MATE_MIXER_INFINITY); - if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME)) - return -MATE_MIXER_INFINITY; + pstream = PULSE_STREAM (stream); - pulse = PULSE_STREAM (stream); + if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME)) + return -MATE_MIXER_INFINITY; - if (channel >= pulse->priv->volume.channels) + if (channel >= pstream->priv->cvolume.channels) return -MATE_MIXER_INFINITY; - value = pa_sw_volume_to_dB (pulse->priv->volume.values[channel]); + value = pa_sw_volume_to_dB (pstream->priv->cvolume.values[channel]); return (value == PA_DECIBEL_MININFTY) ? -MATE_MIXER_INFINITY : value; } static gboolean -stream_set_channel_decibel (MateMixerStream *stream, - guint channel, - gdouble decibel) +pulse_stream_set_channel_decibel (MateMixerStream *stream, + guint channel, + gdouble decibel) { + PulseStream *pstream; + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME) || - !(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_CAN_SET_VOLUME)) + pstream = PULSE_STREAM (stream); + + if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME) || + !(pstream->priv->flags & MATE_MIXER_STREAM_CAN_SET_VOLUME)) return FALSE; - return stream_set_channel_volume (stream, channel, pa_sw_volume_from_dB (decibel)); + return pulse_stream_set_channel_volume (stream, channel, pa_sw_volume_from_dB (decibel)); } -static gboolean -stream_has_position (MateMixerStream *stream, MateMixerChannelPosition position) +static MateMixerChannelPosition +pulse_stream_get_channel_position (MateMixerStream *stream, guint channel) { - PulseStream *pulse; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - - pulse = PULSE_STREAM (stream); + PulseStream *pstream; - return pa_channel_map_has_position (&pulse->priv->channel_map, - pulse_convert_position_to_pulse (position)); -} - -static guint -stream_get_position_volume (MateMixerStream *stream, - MateMixerChannelPosition position) -{ - PulseStream *pulse; + g_return_val_if_fail (PULSE_IS_STREAM (stream), MATE_MIXER_CHANNEL_UNKNOWN); - g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); + pstream = PULSE_STREAM (stream); - pulse = PULSE_STREAM (stream); + if (channel >= pstream->priv->channel_map.channels) + return MATE_MIXER_CHANNEL_UNKNOWN; - return pa_cvolume_get_position (&pulse->priv->volume, - &pulse->priv->channel_map, - pulse_convert_position_to_pulse (position)); + return pulse_convert_position_to_pulse (pstream->priv->channel_map.map[channel]); } static gboolean -stream_set_position_volume (MateMixerStream *stream, - MateMixerChannelPosition position, - guint volume) +pulse_stream_has_channel_position (MateMixerStream *stream, + MateMixerChannelPosition position) { - PulseStream *pulse; - pa_cvolume cvolume; + PulseStream *pstream; + pa_channel_position_t p; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - pulse = PULSE_STREAM (stream); - cvolume = pulse->priv->volume; - - if (!pa_cvolume_set_position (&cvolume, - &pulse->priv->channel_map, - pulse_convert_position_to_pulse (position), - (pa_volume_t) volume)) - return FALSE; - - return stream_set_cvolume (stream, &cvolume); -} - -static gdouble -stream_get_position_decibel (MateMixerStream *stream, - MateMixerChannelPosition position) -{ - gdouble value; - - g_return_val_if_fail (PULSE_IS_STREAM (stream), -MATE_MIXER_INFINITY); - - if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME)) - return -MATE_MIXER_INFINITY; - - value = pa_sw_volume_to_dB (stream_get_position_volume (stream, position)); + pstream = PULSE_STREAM (stream); - return (value == PA_DECIBEL_MININFTY) ? -MATE_MIXER_INFINITY : value; -} - -static gboolean -stream_set_position_decibel (MateMixerStream *stream, - MateMixerChannelPosition position, - gdouble decibel) -{ - g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); + /* Handle invalid position as a special case, otherwise this function would + * return TRUE for e.g. unknown index in a default channel map */ + p = pulse_convert_position_to_pulse (position); - if (!(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_HAS_DECIBEL_VOLUME) || - !(mate_mixer_stream_get_flags (stream) & MATE_MIXER_STREAM_CAN_SET_VOLUME)) + if (p == PA_CHANNEL_POSITION_INVALID) return FALSE; - return stream_set_position_volume (stream, position, pa_sw_volume_from_dB (decibel)); + if (pa_channel_map_has_position (&pstream->priv->channel_map, p) != 0) + return TRUE; + else + return FALSE; } static gfloat -stream_get_balance (MateMixerStream *stream) +pulse_stream_get_balance (MateMixerStream *stream) { g_return_val_if_fail (PULSE_IS_STREAM (stream), 0.0f); @@ -883,24 +915,28 @@ stream_get_balance (MateMixerStream *stream) } static gboolean -stream_set_balance (MateMixerStream *stream, gfloat balance) +pulse_stream_set_balance (MateMixerStream *stream, gfloat balance) { - PulseStream *pulse; + PulseStream *pstream; pa_cvolume cvolume; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - pulse = PULSE_STREAM (stream); - cvolume = pulse->priv->volume; + pstream = PULSE_STREAM (stream); + + if (!(pstream->priv->flags & MATE_MIXER_STREAM_CAN_BALANCE)) + return FALSE; + + cvolume = pstream->priv->cvolume; - if (pa_cvolume_set_balance (&cvolume, &pulse->priv->channel_map, balance) == NULL) + if (pa_cvolume_set_balance (&cvolume, &pstream->priv->channel_map, balance) == NULL) return FALSE; - return stream_set_cvolume (stream, &cvolume); + return set_cvolume (pstream, &cvolume); } static gfloat -stream_get_fade (MateMixerStream *stream) +pulse_stream_get_fade (MateMixerStream *stream) { g_return_val_if_fail (PULSE_IS_STREAM (stream), 0.0f); @@ -908,138 +944,178 @@ stream_get_fade (MateMixerStream *stream) } static gboolean -stream_set_fade (MateMixerStream *stream, gfloat fade) +pulse_stream_set_fade (MateMixerStream *stream, gfloat fade) { - PulseStream *pulse; + PulseStream *pstream; pa_cvolume cvolume; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - pulse = PULSE_STREAM (stream); - cvolume = pulse->priv->volume; + pstream = PULSE_STREAM (stream); - if (pa_cvolume_set_fade (&cvolume, &pulse->priv->channel_map, fade) == NULL) + if (!(pstream->priv->flags & MATE_MIXER_STREAM_CAN_FADE)) return FALSE; - return stream_set_cvolume (stream, &cvolume); + cvolume = pstream->priv->cvolume; + + if (pa_cvolume_set_fade (&cvolume, &pstream->priv->channel_map, fade) == NULL) + return FALSE; + + return set_cvolume (pstream, &cvolume); } static gboolean -stream_suspend (MateMixerStream *stream) +pulse_stream_suspend (MateMixerStream *stream) { + PulseStream *pstream; + PulseStreamClass *klass; + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - if (!(PULSE_STREAM (stream)->priv->flags & MATE_MIXER_STREAM_CAN_SUSPEND)) + pstream = PULSE_STREAM (stream); + + if (!(pstream->priv->flags & MATE_MIXER_STREAM_CAN_SUSPEND)) return FALSE; - return PULSE_STREAM_GET_CLASS (stream)->suspend (stream); + if (pstream->priv->state == MATE_MIXER_STREAM_STATE_SUSPENDED) + return FALSE; + + klass = PULSE_STREAM_GET_CLASS (pstream); + + if (klass->suspend (pstream) == FALSE) + return FALSE; + + pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_SUSPENDED); + return TRUE; } static gboolean -stream_resume (MateMixerStream *stream) +pulse_stream_resume (MateMixerStream *stream) { + PulseStream *pstream; + PulseStreamClass *klass; + g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - if (!(PULSE_STREAM (stream)->priv->flags & MATE_MIXER_STREAM_CAN_SUSPEND)) + pstream = PULSE_STREAM (stream); + + if (!(pstream->priv->flags & MATE_MIXER_STREAM_CAN_SUSPEND)) + return FALSE; + + if (pstream->priv->state != MATE_MIXER_STREAM_STATE_SUSPENDED) return FALSE; - return PULSE_STREAM_GET_CLASS (stream)->resume (stream); + klass = PULSE_STREAM_GET_CLASS (pstream); + + if (klass->resume (pstream) == FALSE) + return FALSE; + + /* The state when resumed should be either RUNNING or IDLE, let's assume + * IDLE for now and request an immediate update */ + pulse_stream_update_state (pstream, MATE_MIXER_STREAM_STATE_IDLE); + + klass->reload (pstream); + return TRUE; } static gboolean -stream_monitor_start (MateMixerStream *stream) +pulse_stream_monitor_start (MateMixerStream *stream) { - PulseStream *pulse; + PulseStream *pstream; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - pulse = PULSE_STREAM (stream); + pstream = PULSE_STREAM (stream); - if (!pulse->priv->monitor) { - pulse->priv->monitor = PULSE_STREAM_GET_CLASS (stream)->create_monitor (stream); + if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_MONITOR)) + return FALSE; + + if (pstream->priv->monitor == NULL) { + pstream->priv->monitor = PULSE_STREAM_GET_CLASS (pstream)->create_monitor (pstream); - if (G_UNLIKELY (pulse->priv->monitor == NULL)) + if (G_UNLIKELY (pstream->priv->monitor == NULL)) return FALSE; - pulse_monitor_set_name (pulse->priv->monitor, - pulse->priv->monitor_name); + pulse_monitor_set_name (pstream->priv->monitor, + pstream->priv->monitor_name); - g_signal_connect (G_OBJECT (pulse->priv->monitor), + g_signal_connect (G_OBJECT (pstream->priv->monitor), "value", - G_CALLBACK (stream_monitor_value), - stream); + G_CALLBACK (on_monitor_value), + pstream); } - g_debug ("Enabling monitor for stream %s", pulse->priv->name); - return pulse_monitor_enable (pulse->priv->monitor); + return pulse_monitor_set_enabled (pstream->priv->monitor, TRUE); } static void -stream_monitor_stop (MateMixerStream *stream) +pulse_stream_monitor_stop (MateMixerStream *stream) { - PulseStream *pulse; + PulseStream *pstream; g_return_if_fail (PULSE_IS_STREAM (stream)); - pulse = PULSE_STREAM (stream); - - if (pulse->priv->monitor && - pulse_monitor_is_enabled (pulse->priv->monitor)) { - g_debug ("Disabling monitor for stream %s", pulse->priv->name); + pstream = PULSE_STREAM (stream); - pulse_monitor_disable (pulse->priv->monitor); - } + if (pstream->priv->monitor != NULL) + pulse_monitor_set_enabled (pstream->priv->monitor, FALSE); } static gboolean -stream_monitor_is_running (MateMixerStream *stream) +pulse_stream_monitor_is_running (MateMixerStream *stream) { - PulseStream *pulse; + PulseStream *pstream; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - pulse = PULSE_STREAM (stream); + pstream = PULSE_STREAM (stream); - if (pulse->priv->monitor) - return pulse_monitor_is_enabled (pulse->priv->monitor); + if (pstream->priv->monitor != NULL) + return pulse_monitor_get_enabled (pstream->priv->monitor); return FALSE; } static gboolean -stream_monitor_set_name (MateMixerStream *stream, const gchar *name) +pulse_stream_monitor_set_name (MateMixerStream *stream, const gchar *name) { - PulseStream *pulse; + PulseStream *pstream; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - pulse = PULSE_STREAM (stream); + pstream = PULSE_STREAM (stream); - if (pulse->priv->monitor) - pulse_monitor_set_name (pulse->priv->monitor, name); + if (pstream->priv->monitor != NULL) + pulse_monitor_set_name (pstream->priv->monitor, name); - pulse->priv->monitor_name = g_strdup (name); + pstream->priv->monitor_name = g_strdup (name); return TRUE; } -static void -stream_monitor_value (PulseMonitor *monitor, gdouble value, MateMixerStream *stream) -{ - g_signal_emit_by_name (G_OBJECT (stream), - "monitor-value", - value); -} - static const GList * -stream_list_ports (MateMixerStream *stream) +pulse_stream_list_ports (MateMixerStream *stream) { + PulseStream *pstream; + g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); - return (const GList *) PULSE_STREAM (stream)->priv->ports; + pstream = PULSE_STREAM (stream); + + if (pstream->priv->ports_list == NULL) { + GList *list = g_hash_table_get_values (pstream->priv->ports); + + if (list != NULL) { + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + pstream->priv->ports_list = g_list_sort (list, compare_ports); + } + } + + return (const GList *) pstream->priv->ports_list; } static MateMixerPort * -stream_get_active_port (MateMixerStream *stream) +pulse_stream_get_active_port (MateMixerStream *stream) { g_return_val_if_fail (PULSE_IS_STREAM (stream), NULL); @@ -1047,90 +1123,163 @@ stream_get_active_port (MateMixerStream *stream) } static gboolean -stream_set_active_port (MateMixerStream *stream, const gchar *port_name) +pulse_stream_set_active_port (MateMixerStream *stream, MateMixerPort *port) { - PulseStream *pulse; - GList *list; - MateMixerPort *port = NULL; + PulseStream *pstream; + PulseStreamClass *klass; + const gchar *name; g_return_val_if_fail (PULSE_IS_STREAM (stream), FALSE); - g_return_val_if_fail (port_name != NULL, FALSE); + g_return_val_if_fail (MATE_MIXER_IS_PORT (port), FALSE); - pulse = PULSE_STREAM (stream); - list = pulse->priv->ports; - while (list) { - port = MATE_MIXER_PORT (list->data); + pstream = PULSE_STREAM (stream); - if (!g_strcmp0 (mate_mixer_port_get_name (port), port_name)) - break; + /* Make sure the port comes from this stream */ + name = mate_mixer_port_get_name (port); - port = NULL; - list = list->next; + if (g_hash_table_lookup (pstream->priv->ports, name) == NULL) { + g_warning ("Port %s does not belong to stream %s", + mate_mixer_port_get_name (port), + mate_mixer_stream_get_name (stream)); + return FALSE; } - if (port == NULL || - PULSE_STREAM_GET_CLASS (stream)->set_active_port (stream, port_name) == FALSE) + klass = PULSE_STREAM_GET_CLASS (pstream); + + /* Change the port */ + if (klass->set_active_port (pstream, port) == FALSE) return FALSE; - if (pulse->priv->port) - g_object_unref (pulse->priv->port); + if (pstream->priv->port != NULL) + g_object_unref (pstream->priv->port); - pulse->priv->port = g_object_ref (port); + pstream->priv->port = g_object_ref (port); g_object_notify (G_OBJECT (stream), "active-port"); return TRUE; } static guint -stream_get_min_volume (MateMixerStream *stream) +pulse_stream_get_min_volume (MateMixerStream *stream) { return (guint) PA_VOLUME_MUTED; } static guint -stream_get_max_volume (MateMixerStream *stream) +pulse_stream_get_max_volume (MateMixerStream *stream) { + PulseStream *pstream; + + g_return_val_if_fail (PULSE_IS_STREAM (stream), (guint) PA_VOLUME_MUTED); + + pstream = PULSE_STREAM (stream); + + if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_VOLUME)) + return (guint) PA_VOLUME_MUTED; + return (guint) PA_VOLUME_UI_MAX; } static guint -stream_get_normal_volume (MateMixerStream *stream) +pulse_stream_get_normal_volume (MateMixerStream *stream) { + PulseStream *pstream; + + g_return_val_if_fail (PULSE_IS_STREAM (stream), (guint) PA_VOLUME_MUTED); + + pstream = PULSE_STREAM (stream); + + if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_VOLUME)) + return (guint) PA_VOLUME_MUTED; + return (guint) PA_VOLUME_NORM; } static guint -stream_get_base_volume (MateMixerStream *stream) +pulse_stream_get_base_volume (MateMixerStream *stream) { - g_return_val_if_fail (PULSE_IS_STREAM (stream), 0); + PulseStream *pstream; + + g_return_val_if_fail (PULSE_IS_STREAM (stream), (guint) PA_VOLUME_MUTED); + + pstream = PULSE_STREAM (stream); + + if (!(pstream->priv->flags & MATE_MIXER_STREAM_HAS_VOLUME)) + return (guint) PA_VOLUME_MUTED; + + if (pstream->priv->base_volume > 0) + return pstream->priv->base_volume; + else + return (guint) PA_VOLUME_NORM; +} + +static void +on_monitor_value (PulseMonitor *monitor, gdouble value, PulseStream *pstream) +{ + g_signal_emit_by_name (G_OBJECT (pstream), + "monitor-value", + value); +} + +static gboolean +update_balance_fade (PulseStream *pstream) +{ + gfloat fade; + gfloat balance; + gboolean changed = FALSE; + + /* The PulseAudio return the default 0.0f values on errors, so skip checking + * validity of the channel map and volume */ + balance = pa_cvolume_get_balance (&pstream->priv->cvolume, + &pstream->priv->channel_map); + + if (pstream->priv->balance != balance) { + pstream->priv->balance = balance; - return PULSE_STREAM (stream)->priv->base_volume; + g_object_notify (G_OBJECT (pstream), "balance"); + changed = TRUE; + } + + fade = pa_cvolume_get_fade (&pstream->priv->cvolume, + &pstream->priv->channel_map); + + if (pstream->priv->fade != fade) { + pstream->priv->fade = fade; + + g_object_notify (G_OBJECT (pstream), "fade"); + changed = TRUE; + } + + return changed; } static gboolean -stream_set_cvolume (MateMixerStream *stream, pa_cvolume *volume) +set_cvolume (PulseStream *pstream, pa_cvolume *cvolume) { - PulseStream *pulse; + PulseStreamClass *klass; - if (!pa_cvolume_valid (volume)) + if (pa_cvolume_valid (cvolume) == 0) return FALSE; + if (pa_cvolume_equal (cvolume, &pstream->priv->cvolume) != 0) + return TRUE; - pulse = PULSE_STREAM (stream); + klass = PULSE_STREAM_GET_CLASS (pstream); - if (!pa_cvolume_equal (volume, &pulse->priv->volume)) { - if (PULSE_STREAM_GET_CLASS (stream)->set_volume (stream, volume) == FALSE) - return FALSE; + if (klass->set_volume (pstream, cvolume) == FALSE) + return FALSE; - pulse->priv->volume = *volume; - g_object_notify (G_OBJECT (stream), "volume"); + pstream->priv->cvolume = *cvolume; + pstream->priv->volume = (guint) pa_cvolume_max (cvolume); - // XXX notify fade and balance - } + g_object_notify (G_OBJECT (pstream), "volume"); + + /* Changing volume may change the balance and fade values as well */ + update_balance_fade (pstream); return TRUE; } static gint -stream_compare_ports (gconstpointer a, gconstpointer b) +compare_ports (gconstpointer a, gconstpointer b) { MateMixerPort *p1 = MATE_MIXER_PORT (a); MateMixerPort *p2 = MATE_MIXER_PORT (b); diff --git a/backends/pulse/pulse-stream.h b/backends/pulse/pulse-stream.h index aa296ea..e4c6a00 100644 --- a/backends/pulse/pulse-stream.h +++ b/backends/pulse/pulse-stream.h @@ -22,6 +22,7 @@ #include <glib-object.h> #include <libmatemixer/matemixer-device.h> +#include <libmatemixer/matemixer-port.h> #include <libmatemixer/matemixer-stream.h> #include <pulse/pulseaudio.h> @@ -60,51 +61,56 @@ struct _PulseStreamClass { GObjectClass parent_class; - gboolean (*set_mute) (MateMixerStream *stream, - gboolean mute); - gboolean (*set_volume) (MateMixerStream *stream, - pa_cvolume *volume); + /*< private >*/ + /* Virtual table */ + void (*reload) (PulseStream *stream); + + gboolean (*set_mute) (PulseStream *stream, + gboolean mute); + gboolean (*set_volume) (PulseStream *stream, + pa_cvolume *volume); - gboolean (*set_active_port) (MateMixerStream *stream, - const gchar *port_name); + gboolean (*set_active_port) (PulseStream *stream, + MateMixerPort *port); - gboolean (*suspend) (MateMixerStream *stream); - gboolean (*resume) (MateMixerStream *stream); + gboolean (*suspend) (PulseStream *stream); + gboolean (*resume) (PulseStream *stream); - PulseMonitor *(*create_monitor) (MateMixerStream *stream); + PulseMonitor *(*create_monitor) (PulseStream *stream); }; -GType pulse_stream_get_type (void) G_GNUC_CONST; - -guint32 pulse_stream_get_index (PulseStream *stream); -PulseConnection *pulse_stream_get_connection (PulseStream *stream); -PulseMonitor * pulse_stream_get_monitor (PulseStream *stream); - -gboolean pulse_stream_update_name (PulseStream *stream, - const gchar *name); -gboolean pulse_stream_update_description (PulseStream *stream, - const gchar *description); -gboolean pulse_stream_update_device (PulseStream *stream, - MateMixerDevice *device); -gboolean pulse_stream_update_flags (PulseStream *stream, - MateMixerStreamFlags flags); -gboolean pulse_stream_update_state (PulseStream *stream, - MateMixerStreamState state); -gboolean pulse_stream_update_mute (PulseStream *stream, - gboolean mute); - -gboolean pulse_stream_update_volume (PulseStream *stream, - const pa_cvolume *volume, - const pa_channel_map *map, - pa_volume_t base_volume); - -gboolean pulse_stream_update_channel_map (PulseStream *stream, - const pa_channel_map *map); - -gboolean pulse_stream_update_ports (PulseStream *stream, - GList *ports); -gboolean pulse_stream_update_active_port (PulseStream *stream, - const gchar *port_name); +GType pulse_stream_get_type (void) G_GNUC_CONST; + +guint32 pulse_stream_get_index (PulseStream *pstream); +PulseConnection * pulse_stream_get_connection (PulseStream *pstream); +PulseMonitor * pulse_stream_get_monitor (PulseStream *pstream); +GHashTable * pulse_stream_get_ports (PulseStream *pstream); + +const pa_cvolume * pulse_stream_get_cvolume (PulseStream *pstream); +const pa_channel_map *pulse_stream_get_channel_map (PulseStream *pstream); + +gboolean pulse_stream_update_name (PulseStream *pstream, + const gchar *name); +gboolean pulse_stream_update_description (PulseStream *pstream, + const gchar *description); +gboolean pulse_stream_update_device (PulseStream *pstream, + MateMixerDevice *device); +gboolean pulse_stream_update_flags (PulseStream *pstream, + MateMixerStreamFlags flags); +gboolean pulse_stream_update_state (PulseStream *pstream, + MateMixerStreamState state); + +gboolean pulse_stream_update_channel_map (PulseStream *pstream, + const pa_channel_map *map); +gboolean pulse_stream_update_volume (PulseStream *pstream, + const pa_cvolume *volume, + pa_volume_t base_volume); + +gboolean pulse_stream_update_mute (PulseStream *pstream, + gboolean mute); + +gboolean pulse_stream_update_active_port (PulseStream *pstream, + MateMixerPort *port); G_END_DECLS |